162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2020-21 Intel Corporation. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/etherdevice.h> 762306a36Sopenharmony_ci#include <linux/if_arp.h> 862306a36Sopenharmony_ci#include <linux/if_link.h> 962306a36Sopenharmony_ci#include <linux/rtnetlink.h> 1062306a36Sopenharmony_ci#include <linux/wwan.h> 1162306a36Sopenharmony_ci#include <net/pkt_sched.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci#include "iosm_ipc_chnl_cfg.h" 1462306a36Sopenharmony_ci#include "iosm_ipc_imem_ops.h" 1562306a36Sopenharmony_ci#include "iosm_ipc_wwan.h" 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define IOSM_IP_TYPE_MASK 0xF0 1862306a36Sopenharmony_ci#define IOSM_IP_TYPE_IPV4 0x40 1962306a36Sopenharmony_ci#define IOSM_IP_TYPE_IPV6 0x60 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/** 2262306a36Sopenharmony_ci * struct iosm_netdev_priv - netdev WWAN driver specific private data 2362306a36Sopenharmony_ci * @ipc_wwan: Pointer to iosm_wwan struct 2462306a36Sopenharmony_ci * @netdev: Pointer to network interface device structure 2562306a36Sopenharmony_ci * @if_id: Interface id for device. 2662306a36Sopenharmony_ci * @ch_id: IPC channel number for which interface device is created. 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_cistruct iosm_netdev_priv { 2962306a36Sopenharmony_ci struct iosm_wwan *ipc_wwan; 3062306a36Sopenharmony_ci struct net_device *netdev; 3162306a36Sopenharmony_ci int if_id; 3262306a36Sopenharmony_ci int ch_id; 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci/** 3662306a36Sopenharmony_ci * struct iosm_wwan - This structure contains information about WWAN root device 3762306a36Sopenharmony_ci * and interface to the IPC layer. 3862306a36Sopenharmony_ci * @ipc_imem: Pointer to imem data-struct 3962306a36Sopenharmony_ci * @sub_netlist: List of active netdevs 4062306a36Sopenharmony_ci * @dev: Pointer device structure 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_cistruct iosm_wwan { 4362306a36Sopenharmony_ci struct iosm_imem *ipc_imem; 4462306a36Sopenharmony_ci struct iosm_netdev_priv __rcu *sub_netlist[IP_MUX_SESSION_END + 1]; 4562306a36Sopenharmony_ci struct device *dev; 4662306a36Sopenharmony_ci}; 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/* Bring-up the wwan net link */ 4962306a36Sopenharmony_cistatic int ipc_wwan_link_open(struct net_device *netdev) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); 5262306a36Sopenharmony_ci struct iosm_wwan *ipc_wwan = priv->ipc_wwan; 5362306a36Sopenharmony_ci int if_id = priv->if_id; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci if (if_id < IP_MUX_SESSION_START || 5662306a36Sopenharmony_ci if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) 5762306a36Sopenharmony_ci return -EINVAL; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci /* get channel id */ 6062306a36Sopenharmony_ci priv->ch_id = ipc_imem_sys_wwan_open(ipc_wwan->ipc_imem, if_id); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci if (priv->ch_id < 0) { 6362306a36Sopenharmony_ci dev_err(ipc_wwan->dev, 6462306a36Sopenharmony_ci "cannot connect wwan0 & id %d to the IPC mem layer", 6562306a36Sopenharmony_ci if_id); 6662306a36Sopenharmony_ci return -ENODEV; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* enable tx path, DL data may follow */ 7062306a36Sopenharmony_ci netif_start_queue(netdev); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci dev_dbg(ipc_wwan->dev, "Channel id %d allocated to if_id %d", 7362306a36Sopenharmony_ci priv->ch_id, priv->if_id); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci return 0; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci/* Bring-down the wwan net link */ 7962306a36Sopenharmony_cistatic int ipc_wwan_link_stop(struct net_device *netdev) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci netif_stop_queue(netdev); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci ipc_imem_sys_wwan_close(priv->ipc_wwan->ipc_imem, priv->if_id, 8662306a36Sopenharmony_ci priv->ch_id); 8762306a36Sopenharmony_ci priv->ch_id = -1; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci return 0; 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci/* Transmit a packet */ 9362306a36Sopenharmony_cistatic netdev_tx_t ipc_wwan_link_transmit(struct sk_buff *skb, 9462306a36Sopenharmony_ci struct net_device *netdev) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); 9762306a36Sopenharmony_ci struct iosm_wwan *ipc_wwan = priv->ipc_wwan; 9862306a36Sopenharmony_ci unsigned int len = skb->len; 9962306a36Sopenharmony_ci int if_id = priv->if_id; 10062306a36Sopenharmony_ci int ret; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci /* Interface IDs from 1 to 8 are for IP data 10362306a36Sopenharmony_ci * & from 257 to 261 are for non-IP data 10462306a36Sopenharmony_ci */ 10562306a36Sopenharmony_ci if (if_id < IP_MUX_SESSION_START || 10662306a36Sopenharmony_ci if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) 10762306a36Sopenharmony_ci return -EINVAL; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* Send the SKB to device for transmission */ 11062306a36Sopenharmony_ci ret = ipc_imem_sys_wwan_transmit(ipc_wwan->ipc_imem, 11162306a36Sopenharmony_ci if_id, priv->ch_id, skb); 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci /* Return code of zero is success */ 11462306a36Sopenharmony_ci if (ret == 0) { 11562306a36Sopenharmony_ci netdev->stats.tx_packets++; 11662306a36Sopenharmony_ci netdev->stats.tx_bytes += len; 11762306a36Sopenharmony_ci ret = NETDEV_TX_OK; 11862306a36Sopenharmony_ci } else if (ret == -EBUSY) { 11962306a36Sopenharmony_ci ret = NETDEV_TX_BUSY; 12062306a36Sopenharmony_ci dev_err(ipc_wwan->dev, "unable to push packets"); 12162306a36Sopenharmony_ci } else { 12262306a36Sopenharmony_ci goto exit; 12362306a36Sopenharmony_ci } 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci return ret; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ciexit: 12862306a36Sopenharmony_ci /* Log any skb drop */ 12962306a36Sopenharmony_ci if (if_id) 13062306a36Sopenharmony_ci dev_dbg(ipc_wwan->dev, "skb dropped. IF_ID: %d, ret: %d", if_id, 13162306a36Sopenharmony_ci ret); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci dev_kfree_skb_any(skb); 13462306a36Sopenharmony_ci netdev->stats.tx_dropped++; 13562306a36Sopenharmony_ci return NETDEV_TX_OK; 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci/* Ops structure for wwan net link */ 13962306a36Sopenharmony_cistatic const struct net_device_ops ipc_inm_ops = { 14062306a36Sopenharmony_ci .ndo_open = ipc_wwan_link_open, 14162306a36Sopenharmony_ci .ndo_stop = ipc_wwan_link_stop, 14262306a36Sopenharmony_ci .ndo_start_xmit = ipc_wwan_link_transmit, 14362306a36Sopenharmony_ci}; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci/* Setup function for creating new net link */ 14662306a36Sopenharmony_cistatic void ipc_wwan_setup(struct net_device *iosm_dev) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci iosm_dev->header_ops = NULL; 14962306a36Sopenharmony_ci iosm_dev->hard_header_len = 0; 15062306a36Sopenharmony_ci iosm_dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci iosm_dev->type = ARPHRD_NONE; 15362306a36Sopenharmony_ci iosm_dev->mtu = ETH_DATA_LEN; 15462306a36Sopenharmony_ci iosm_dev->min_mtu = ETH_MIN_MTU; 15562306a36Sopenharmony_ci iosm_dev->max_mtu = ETH_MAX_MTU; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci iosm_dev->flags = IFF_POINTOPOINT | IFF_NOARP; 15862306a36Sopenharmony_ci iosm_dev->needs_free_netdev = true; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci iosm_dev->netdev_ops = &ipc_inm_ops; 16162306a36Sopenharmony_ci} 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci/* Create new wwan net link */ 16462306a36Sopenharmony_cistatic int ipc_wwan_newlink(void *ctxt, struct net_device *dev, 16562306a36Sopenharmony_ci u32 if_id, struct netlink_ext_ack *extack) 16662306a36Sopenharmony_ci{ 16762306a36Sopenharmony_ci struct iosm_wwan *ipc_wwan = ctxt; 16862306a36Sopenharmony_ci struct iosm_netdev_priv *priv; 16962306a36Sopenharmony_ci int err; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci if (if_id < IP_MUX_SESSION_START || 17262306a36Sopenharmony_ci if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) 17362306a36Sopenharmony_ci return -EINVAL; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci priv = wwan_netdev_drvpriv(dev); 17662306a36Sopenharmony_ci priv->if_id = if_id; 17762306a36Sopenharmony_ci priv->netdev = dev; 17862306a36Sopenharmony_ci priv->ipc_wwan = ipc_wwan; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci if (rcu_access_pointer(ipc_wwan->sub_netlist[if_id])) 18162306a36Sopenharmony_ci return -EBUSY; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci err = register_netdevice(dev); 18462306a36Sopenharmony_ci if (err) 18562306a36Sopenharmony_ci return err; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci rcu_assign_pointer(ipc_wwan->sub_netlist[if_id], priv); 18862306a36Sopenharmony_ci netif_device_attach(dev); 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci return 0; 19162306a36Sopenharmony_ci} 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_cistatic void ipc_wwan_dellink(void *ctxt, struct net_device *dev, 19462306a36Sopenharmony_ci struct list_head *head) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(dev); 19762306a36Sopenharmony_ci struct iosm_wwan *ipc_wwan = ctxt; 19862306a36Sopenharmony_ci int if_id = priv->if_id; 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci if (WARN_ON(if_id < IP_MUX_SESSION_START || 20162306a36Sopenharmony_ci if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))) 20262306a36Sopenharmony_ci return; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci if (WARN_ON(rcu_access_pointer(ipc_wwan->sub_netlist[if_id]) != priv)) 20562306a36Sopenharmony_ci return; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci RCU_INIT_POINTER(ipc_wwan->sub_netlist[if_id], NULL); 20862306a36Sopenharmony_ci /* unregistering includes synchronize_net() */ 20962306a36Sopenharmony_ci unregister_netdevice_queue(dev, head); 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic const struct wwan_ops iosm_wwan_ops = { 21362306a36Sopenharmony_ci .priv_size = sizeof(struct iosm_netdev_priv), 21462306a36Sopenharmony_ci .setup = ipc_wwan_setup, 21562306a36Sopenharmony_ci .newlink = ipc_wwan_newlink, 21662306a36Sopenharmony_ci .dellink = ipc_wwan_dellink, 21762306a36Sopenharmony_ci}; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ciint ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg, 22062306a36Sopenharmony_ci bool dss, int if_id) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci struct sk_buff *skb = skb_arg; 22362306a36Sopenharmony_ci struct net_device_stats *stats; 22462306a36Sopenharmony_ci struct iosm_netdev_priv *priv; 22562306a36Sopenharmony_ci int ret; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if ((skb->data[0] & IOSM_IP_TYPE_MASK) == IOSM_IP_TYPE_IPV4) 22862306a36Sopenharmony_ci skb->protocol = htons(ETH_P_IP); 22962306a36Sopenharmony_ci else if ((skb->data[0] & IOSM_IP_TYPE_MASK) == 23062306a36Sopenharmony_ci IOSM_IP_TYPE_IPV6) 23162306a36Sopenharmony_ci skb->protocol = htons(ETH_P_IPV6); 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci skb->pkt_type = PACKET_HOST; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci if (if_id < IP_MUX_SESSION_START || 23662306a36Sopenharmony_ci if_id > IP_MUX_SESSION_END) { 23762306a36Sopenharmony_ci ret = -EINVAL; 23862306a36Sopenharmony_ci goto free; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci rcu_read_lock(); 24262306a36Sopenharmony_ci priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]); 24362306a36Sopenharmony_ci if (!priv) { 24462306a36Sopenharmony_ci ret = -EINVAL; 24562306a36Sopenharmony_ci goto unlock; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci skb->dev = priv->netdev; 24862306a36Sopenharmony_ci stats = &priv->netdev->stats; 24962306a36Sopenharmony_ci stats->rx_packets++; 25062306a36Sopenharmony_ci stats->rx_bytes += skb->len; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci ret = netif_rx(skb); 25362306a36Sopenharmony_ci skb = NULL; 25462306a36Sopenharmony_ciunlock: 25562306a36Sopenharmony_ci rcu_read_unlock(); 25662306a36Sopenharmony_cifree: 25762306a36Sopenharmony_ci dev_kfree_skb(skb); 25862306a36Sopenharmony_ci return ret; 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_civoid ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int if_id, bool on) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci struct net_device *netdev; 26462306a36Sopenharmony_ci struct iosm_netdev_priv *priv; 26562306a36Sopenharmony_ci bool is_tx_blk; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci rcu_read_lock(); 26862306a36Sopenharmony_ci priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]); 26962306a36Sopenharmony_ci if (!priv) { 27062306a36Sopenharmony_ci rcu_read_unlock(); 27162306a36Sopenharmony_ci return; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci netdev = priv->netdev; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci is_tx_blk = netif_queue_stopped(netdev); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci if (on) 27962306a36Sopenharmony_ci dev_dbg(ipc_wwan->dev, "session id[%d]: flowctrl enable", 28062306a36Sopenharmony_ci if_id); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci if (on && !is_tx_blk) 28362306a36Sopenharmony_ci netif_stop_queue(netdev); 28462306a36Sopenharmony_ci else if (!on && is_tx_blk) 28562306a36Sopenharmony_ci netif_wake_queue(netdev); 28662306a36Sopenharmony_ci rcu_read_unlock(); 28762306a36Sopenharmony_ci} 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_cistruct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev) 29062306a36Sopenharmony_ci{ 29162306a36Sopenharmony_ci struct iosm_wwan *ipc_wwan; 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci ipc_wwan = kzalloc(sizeof(*ipc_wwan), GFP_KERNEL); 29462306a36Sopenharmony_ci if (!ipc_wwan) 29562306a36Sopenharmony_ci return NULL; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci ipc_wwan->dev = dev; 29862306a36Sopenharmony_ci ipc_wwan->ipc_imem = ipc_imem; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci /* WWAN core will create a netdev for the default IP MUX channel */ 30162306a36Sopenharmony_ci if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan, 30262306a36Sopenharmony_ci IP_MUX_SESSION_DEFAULT)) { 30362306a36Sopenharmony_ci kfree(ipc_wwan); 30462306a36Sopenharmony_ci return NULL; 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci return ipc_wwan; 30862306a36Sopenharmony_ci} 30962306a36Sopenharmony_ci 31062306a36Sopenharmony_civoid ipc_wwan_deinit(struct iosm_wwan *ipc_wwan) 31162306a36Sopenharmony_ci{ 31262306a36Sopenharmony_ci /* This call will remove all child netdev(s) */ 31362306a36Sopenharmony_ci wwan_unregister_ops(ipc_wwan->dev); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci kfree(ipc_wwan); 31662306a36Sopenharmony_ci} 317