mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
7.9 KiB
345 lines
7.9 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* Copyright (C) 2020-21 Intel Corporation. |
|
*/ |
|
|
|
#include <linux/etherdevice.h> |
|
#include <linux/if_arp.h> |
|
#include <linux/if_link.h> |
|
#include <linux/rtnetlink.h> |
|
#include <linux/wwan.h> |
|
|
|
#include "iosm_ipc_chnl_cfg.h" |
|
#include "iosm_ipc_imem_ops.h" |
|
#include "iosm_ipc_wwan.h" |
|
|
|
#define IOSM_IP_TYPE_MASK 0xF0 |
|
#define IOSM_IP_TYPE_IPV4 0x40 |
|
#define IOSM_IP_TYPE_IPV6 0x60 |
|
|
|
#define IOSM_IF_ID_PAYLOAD 2 |
|
|
|
/** |
|
* struct iosm_netdev_priv - netdev WWAN driver specific private data |
|
* @ipc_wwan: Pointer to iosm_wwan struct |
|
* @netdev: Pointer to network interface device structure |
|
* @if_id: Interface id for device. |
|
* @ch_id: IPC channel number for which interface device is created. |
|
*/ |
|
struct iosm_netdev_priv { |
|
struct iosm_wwan *ipc_wwan; |
|
struct net_device *netdev; |
|
int if_id; |
|
int ch_id; |
|
}; |
|
|
|
/** |
|
* struct iosm_wwan - This structure contains information about WWAN root device |
|
* and interface to the IPC layer. |
|
* @ipc_imem: Pointer to imem data-struct |
|
* @sub_netlist: List of active netdevs |
|
* @dev: Pointer device structure |
|
* @if_mutex: Mutex used for add and remove interface id |
|
*/ |
|
struct iosm_wwan { |
|
struct iosm_imem *ipc_imem; |
|
struct iosm_netdev_priv __rcu *sub_netlist[IP_MUX_SESSION_END + 1]; |
|
struct device *dev; |
|
struct mutex if_mutex; /* Mutex used for add and remove interface id */ |
|
}; |
|
|
|
/* Bring-up the wwan net link */ |
|
static int ipc_wwan_link_open(struct net_device *netdev) |
|
{ |
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); |
|
struct iosm_wwan *ipc_wwan = priv->ipc_wwan; |
|
int if_id = priv->if_id; |
|
int ret; |
|
|
|
if (if_id < IP_MUX_SESSION_START || |
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) |
|
return -EINVAL; |
|
|
|
mutex_lock(&ipc_wwan->if_mutex); |
|
|
|
/* get channel id */ |
|
priv->ch_id = ipc_imem_sys_wwan_open(ipc_wwan->ipc_imem, if_id); |
|
|
|
if (priv->ch_id < 0) { |
|
dev_err(ipc_wwan->dev, |
|
"cannot connect wwan0 & id %d to the IPC mem layer", |
|
if_id); |
|
ret = -ENODEV; |
|
goto out; |
|
} |
|
|
|
/* enable tx path, DL data may follow */ |
|
netif_start_queue(netdev); |
|
|
|
dev_dbg(ipc_wwan->dev, "Channel id %d allocated to if_id %d", |
|
priv->ch_id, priv->if_id); |
|
|
|
ret = 0; |
|
out: |
|
mutex_unlock(&ipc_wwan->if_mutex); |
|
return ret; |
|
} |
|
|
|
/* Bring-down the wwan net link */ |
|
static int ipc_wwan_link_stop(struct net_device *netdev) |
|
{ |
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); |
|
|
|
netif_stop_queue(netdev); |
|
|
|
mutex_lock(&priv->ipc_wwan->if_mutex); |
|
ipc_imem_sys_wwan_close(priv->ipc_wwan->ipc_imem, priv->if_id, |
|
priv->ch_id); |
|
priv->ch_id = -1; |
|
mutex_unlock(&priv->ipc_wwan->if_mutex); |
|
|
|
return 0; |
|
} |
|
|
|
/* Transmit a packet */ |
|
static int ipc_wwan_link_transmit(struct sk_buff *skb, |
|
struct net_device *netdev) |
|
{ |
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); |
|
struct iosm_wwan *ipc_wwan = priv->ipc_wwan; |
|
unsigned int len = skb->len; |
|
int if_id = priv->if_id; |
|
int ret; |
|
|
|
/* Interface IDs from 1 to 8 are for IP data |
|
* & from 257 to 261 are for non-IP data |
|
*/ |
|
if (if_id < IP_MUX_SESSION_START || |
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) |
|
return -EINVAL; |
|
|
|
/* Send the SKB to device for transmission */ |
|
ret = ipc_imem_sys_wwan_transmit(ipc_wwan->ipc_imem, |
|
if_id, priv->ch_id, skb); |
|
|
|
/* Return code of zero is success */ |
|
if (ret == 0) { |
|
netdev->stats.tx_packets++; |
|
netdev->stats.tx_bytes += len; |
|
ret = NETDEV_TX_OK; |
|
} else if (ret == -EBUSY) { |
|
ret = NETDEV_TX_BUSY; |
|
dev_err(ipc_wwan->dev, "unable to push packets"); |
|
} else { |
|
goto exit; |
|
} |
|
|
|
return ret; |
|
|
|
exit: |
|
/* Log any skb drop */ |
|
if (if_id) |
|
dev_dbg(ipc_wwan->dev, "skb dropped. IF_ID: %d, ret: %d", if_id, |
|
ret); |
|
|
|
dev_kfree_skb_any(skb); |
|
netdev->stats.tx_dropped++; |
|
return NETDEV_TX_OK; |
|
} |
|
|
|
/* Ops structure for wwan net link */ |
|
static const struct net_device_ops ipc_inm_ops = { |
|
.ndo_open = ipc_wwan_link_open, |
|
.ndo_stop = ipc_wwan_link_stop, |
|
.ndo_start_xmit = ipc_wwan_link_transmit, |
|
}; |
|
|
|
/* Setup function for creating new net link */ |
|
static void ipc_wwan_setup(struct net_device *iosm_dev) |
|
{ |
|
iosm_dev->header_ops = NULL; |
|
iosm_dev->hard_header_len = 0; |
|
iosm_dev->priv_flags |= IFF_NO_QUEUE; |
|
|
|
iosm_dev->type = ARPHRD_NONE; |
|
iosm_dev->mtu = ETH_DATA_LEN; |
|
iosm_dev->min_mtu = ETH_MIN_MTU; |
|
iosm_dev->max_mtu = ETH_MAX_MTU; |
|
|
|
iosm_dev->flags = IFF_POINTOPOINT | IFF_NOARP; |
|
|
|
iosm_dev->netdev_ops = &ipc_inm_ops; |
|
} |
|
|
|
/* Create new wwan net link */ |
|
static int ipc_wwan_newlink(void *ctxt, struct net_device *dev, |
|
u32 if_id, struct netlink_ext_ack *extack) |
|
{ |
|
struct iosm_wwan *ipc_wwan = ctxt; |
|
struct iosm_netdev_priv *priv; |
|
int err; |
|
|
|
if (if_id < IP_MUX_SESSION_START || |
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) |
|
return -EINVAL; |
|
|
|
priv = wwan_netdev_drvpriv(dev); |
|
priv->if_id = if_id; |
|
priv->netdev = dev; |
|
priv->ipc_wwan = ipc_wwan; |
|
|
|
mutex_lock(&ipc_wwan->if_mutex); |
|
if (rcu_access_pointer(ipc_wwan->sub_netlist[if_id])) { |
|
err = -EBUSY; |
|
goto out_unlock; |
|
} |
|
|
|
err = register_netdevice(dev); |
|
if (err) |
|
goto out_unlock; |
|
|
|
rcu_assign_pointer(ipc_wwan->sub_netlist[if_id], priv); |
|
mutex_unlock(&ipc_wwan->if_mutex); |
|
|
|
netif_device_attach(dev); |
|
|
|
return 0; |
|
|
|
out_unlock: |
|
mutex_unlock(&ipc_wwan->if_mutex); |
|
return err; |
|
} |
|
|
|
static void ipc_wwan_dellink(void *ctxt, struct net_device *dev, |
|
struct list_head *head) |
|
{ |
|
struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(dev); |
|
struct iosm_wwan *ipc_wwan = ctxt; |
|
int if_id = priv->if_id; |
|
|
|
if (WARN_ON(if_id < IP_MUX_SESSION_START || |
|
if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))) |
|
return; |
|
|
|
mutex_lock(&ipc_wwan->if_mutex); |
|
|
|
if (WARN_ON(rcu_access_pointer(ipc_wwan->sub_netlist[if_id]) != priv)) |
|
goto unlock; |
|
|
|
RCU_INIT_POINTER(ipc_wwan->sub_netlist[if_id], NULL); |
|
/* unregistering includes synchronize_net() */ |
|
unregister_netdevice_queue(dev, head); |
|
|
|
unlock: |
|
mutex_unlock(&ipc_wwan->if_mutex); |
|
} |
|
|
|
static const struct wwan_ops iosm_wwan_ops = { |
|
.priv_size = sizeof(struct iosm_netdev_priv), |
|
.setup = ipc_wwan_setup, |
|
.newlink = ipc_wwan_newlink, |
|
.dellink = ipc_wwan_dellink, |
|
}; |
|
|
|
int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg, |
|
bool dss, int if_id) |
|
{ |
|
struct sk_buff *skb = skb_arg; |
|
struct net_device_stats *stats; |
|
struct iosm_netdev_priv *priv; |
|
int ret; |
|
|
|
if ((skb->data[0] & IOSM_IP_TYPE_MASK) == IOSM_IP_TYPE_IPV4) |
|
skb->protocol = htons(ETH_P_IP); |
|
else if ((skb->data[0] & IOSM_IP_TYPE_MASK) == |
|
IOSM_IP_TYPE_IPV6) |
|
skb->protocol = htons(ETH_P_IPV6); |
|
|
|
skb->pkt_type = PACKET_HOST; |
|
|
|
if (if_id < IP_MUX_SESSION_START || |
|
if_id > IP_MUX_SESSION_END) { |
|
ret = -EINVAL; |
|
goto free; |
|
} |
|
|
|
rcu_read_lock(); |
|
priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]); |
|
if (!priv) { |
|
ret = -EINVAL; |
|
goto unlock; |
|
} |
|
skb->dev = priv->netdev; |
|
stats = &priv->netdev->stats; |
|
stats->rx_packets++; |
|
stats->rx_bytes += skb->len; |
|
|
|
ret = netif_rx(skb); |
|
skb = NULL; |
|
unlock: |
|
rcu_read_unlock(); |
|
free: |
|
dev_kfree_skb(skb); |
|
return ret; |
|
} |
|
|
|
void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int if_id, bool on) |
|
{ |
|
struct net_device *netdev; |
|
struct iosm_netdev_priv *priv; |
|
bool is_tx_blk; |
|
|
|
rcu_read_lock(); |
|
priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]); |
|
if (!priv) { |
|
rcu_read_unlock(); |
|
return; |
|
} |
|
|
|
netdev = priv->netdev; |
|
|
|
is_tx_blk = netif_queue_stopped(netdev); |
|
|
|
if (on) |
|
dev_dbg(ipc_wwan->dev, "session id[%d]: flowctrl enable", |
|
if_id); |
|
|
|
if (on && !is_tx_blk) |
|
netif_stop_queue(netdev); |
|
else if (!on && is_tx_blk) |
|
netif_wake_queue(netdev); |
|
rcu_read_unlock(); |
|
} |
|
|
|
struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev) |
|
{ |
|
struct iosm_wwan *ipc_wwan; |
|
|
|
ipc_wwan = kzalloc(sizeof(*ipc_wwan), GFP_KERNEL); |
|
if (!ipc_wwan) |
|
return NULL; |
|
|
|
ipc_wwan->dev = dev; |
|
ipc_wwan->ipc_imem = ipc_imem; |
|
|
|
/* WWAN core will create a netdev for the default IP MUX channel */ |
|
if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan, |
|
IP_MUX_SESSION_DEFAULT)) { |
|
kfree(ipc_wwan); |
|
return NULL; |
|
} |
|
|
|
mutex_init(&ipc_wwan->if_mutex); |
|
|
|
return ipc_wwan; |
|
} |
|
|
|
void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan) |
|
{ |
|
/* This call will remove all child netdev(s) */ |
|
wwan_unregister_ops(ipc_wwan->dev); |
|
|
|
mutex_destroy(&ipc_wwan->if_mutex); |
|
|
|
kfree(ipc_wwan); |
|
}
|
|
|