12a54f2c7SM Chetan Kumar // SPDX-License-Identifier: GPL-2.0-only 22a54f2c7SM Chetan Kumar /* 32a54f2c7SM Chetan Kumar * Copyright (C) 2020-21 Intel Corporation. 42a54f2c7SM Chetan Kumar */ 52a54f2c7SM Chetan Kumar 62a54f2c7SM Chetan Kumar #include <linux/etherdevice.h> 72a54f2c7SM Chetan Kumar #include <linux/if_arp.h> 82a54f2c7SM Chetan Kumar #include <linux/if_link.h> 92a54f2c7SM Chetan Kumar #include <linux/rtnetlink.h> 102a54f2c7SM Chetan Kumar #include <linux/wwan.h> 115d710dc3SM Chetan Kumar #include <net/pkt_sched.h> 122a54f2c7SM Chetan Kumar 132a54f2c7SM Chetan Kumar #include "iosm_ipc_chnl_cfg.h" 142a54f2c7SM Chetan Kumar #include "iosm_ipc_imem_ops.h" 152a54f2c7SM Chetan Kumar #include "iosm_ipc_wwan.h" 162a54f2c7SM Chetan Kumar 172a54f2c7SM Chetan Kumar #define IOSM_IP_TYPE_MASK 0xF0 182a54f2c7SM Chetan Kumar #define IOSM_IP_TYPE_IPV4 0x40 192a54f2c7SM Chetan Kumar #define IOSM_IP_TYPE_IPV6 0x60 202a54f2c7SM Chetan Kumar 212a54f2c7SM Chetan Kumar /** 2269940924SSergey Ryazanov * struct iosm_netdev_priv - netdev WWAN driver specific private data 232a54f2c7SM Chetan Kumar * @ipc_wwan: Pointer to iosm_wwan struct 242a54f2c7SM Chetan Kumar * @netdev: Pointer to network interface device structure 252a54f2c7SM Chetan Kumar * @if_id: Interface id for device. 262a54f2c7SM Chetan Kumar * @ch_id: IPC channel number for which interface device is created. 272a54f2c7SM Chetan Kumar */ 282a54f2c7SM Chetan Kumar struct iosm_netdev_priv { 292a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan; 302a54f2c7SM Chetan Kumar struct net_device *netdev; 312a54f2c7SM Chetan Kumar int if_id; 322a54f2c7SM Chetan Kumar int ch_id; 332a54f2c7SM Chetan Kumar }; 342a54f2c7SM Chetan Kumar 352a54f2c7SM Chetan Kumar /** 362a54f2c7SM Chetan Kumar * struct iosm_wwan - This structure contains information about WWAN root device 372a54f2c7SM Chetan Kumar * and interface to the IPC layer. 382a54f2c7SM Chetan Kumar * @ipc_imem: Pointer to imem data-struct 392a54f2c7SM Chetan Kumar * @sub_netlist: List of active netdevs 402a54f2c7SM Chetan Kumar * @dev: Pointer device structure 412a54f2c7SM Chetan Kumar */ 422a54f2c7SM Chetan Kumar struct iosm_wwan { 432a54f2c7SM Chetan Kumar struct iosm_imem *ipc_imem; 442a54f2c7SM Chetan Kumar struct iosm_netdev_priv __rcu *sub_netlist[IP_MUX_SESSION_END + 1]; 452a54f2c7SM Chetan Kumar struct device *dev; 462a54f2c7SM Chetan Kumar }; 472a54f2c7SM Chetan Kumar 482a54f2c7SM Chetan Kumar /* Bring-up the wwan net link */ 492a54f2c7SM Chetan Kumar static int ipc_wwan_link_open(struct net_device *netdev) 502a54f2c7SM Chetan Kumar { 5169940924SSergey Ryazanov struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); 522a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan = priv->ipc_wwan; 532a54f2c7SM Chetan Kumar int if_id = priv->if_id; 542a54f2c7SM Chetan Kumar 552a54f2c7SM Chetan Kumar if (if_id < IP_MUX_SESSION_START || 562a54f2c7SM Chetan Kumar if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) 572a54f2c7SM Chetan Kumar return -EINVAL; 582a54f2c7SM Chetan Kumar 592a54f2c7SM Chetan Kumar /* get channel id */ 602a54f2c7SM Chetan Kumar priv->ch_id = ipc_imem_sys_wwan_open(ipc_wwan->ipc_imem, if_id); 612a54f2c7SM Chetan Kumar 622a54f2c7SM Chetan Kumar if (priv->ch_id < 0) { 632a54f2c7SM Chetan Kumar dev_err(ipc_wwan->dev, 642a54f2c7SM Chetan Kumar "cannot connect wwan0 & id %d to the IPC mem layer", 652a54f2c7SM Chetan Kumar if_id); 66*1db34aa5SBagas Sanjaya return -ENODEV; 672a54f2c7SM Chetan Kumar } 682a54f2c7SM Chetan Kumar 692a54f2c7SM Chetan Kumar /* enable tx path, DL data may follow */ 702a54f2c7SM Chetan Kumar netif_start_queue(netdev); 712a54f2c7SM Chetan Kumar 722a54f2c7SM Chetan Kumar dev_dbg(ipc_wwan->dev, "Channel id %d allocated to if_id %d", 732a54f2c7SM Chetan Kumar priv->ch_id, priv->if_id); 742a54f2c7SM Chetan Kumar 75*1db34aa5SBagas Sanjaya return 0; 762a54f2c7SM Chetan Kumar } 772a54f2c7SM Chetan Kumar 782a54f2c7SM Chetan Kumar /* Bring-down the wwan net link */ 792a54f2c7SM Chetan Kumar static int ipc_wwan_link_stop(struct net_device *netdev) 802a54f2c7SM Chetan Kumar { 8169940924SSergey Ryazanov struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); 822a54f2c7SM Chetan Kumar 832a54f2c7SM Chetan Kumar netif_stop_queue(netdev); 842a54f2c7SM Chetan Kumar 852a54f2c7SM Chetan Kumar ipc_imem_sys_wwan_close(priv->ipc_wwan->ipc_imem, priv->if_id, 862a54f2c7SM Chetan Kumar priv->ch_id); 872a54f2c7SM Chetan Kumar priv->ch_id = -1; 882a54f2c7SM Chetan Kumar 892a54f2c7SM Chetan Kumar return 0; 902a54f2c7SM Chetan Kumar } 912a54f2c7SM Chetan Kumar 922a54f2c7SM Chetan Kumar /* Transmit a packet */ 930c9441c4SNathan Huckleberry static netdev_tx_t ipc_wwan_link_transmit(struct sk_buff *skb, 942a54f2c7SM Chetan Kumar struct net_device *netdev) 952a54f2c7SM Chetan Kumar { 9669940924SSergey Ryazanov struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(netdev); 972a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan = priv->ipc_wwan; 98c302e3a1SM Chetan Kumar unsigned int len = skb->len; 992a54f2c7SM Chetan Kumar int if_id = priv->if_id; 1002a54f2c7SM Chetan Kumar int ret; 1012a54f2c7SM Chetan Kumar 1022a54f2c7SM Chetan Kumar /* Interface IDs from 1 to 8 are for IP data 1032a54f2c7SM Chetan Kumar * & from 257 to 261 are for non-IP data 1042a54f2c7SM Chetan Kumar */ 1052a54f2c7SM Chetan Kumar if (if_id < IP_MUX_SESSION_START || 1062a54f2c7SM Chetan Kumar if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) 1072a54f2c7SM Chetan Kumar return -EINVAL; 1082a54f2c7SM Chetan Kumar 1092a54f2c7SM Chetan Kumar /* Send the SKB to device for transmission */ 1102a54f2c7SM Chetan Kumar ret = ipc_imem_sys_wwan_transmit(ipc_wwan->ipc_imem, 1112a54f2c7SM Chetan Kumar if_id, priv->ch_id, skb); 1122a54f2c7SM Chetan Kumar 1132a54f2c7SM Chetan Kumar /* Return code of zero is success */ 1142a54f2c7SM Chetan Kumar if (ret == 0) { 115c302e3a1SM Chetan Kumar netdev->stats.tx_packets++; 116c302e3a1SM Chetan Kumar netdev->stats.tx_bytes += len; 1172a54f2c7SM Chetan Kumar ret = NETDEV_TX_OK; 1182a54f2c7SM Chetan Kumar } else if (ret == -EBUSY) { 1192a54f2c7SM Chetan Kumar ret = NETDEV_TX_BUSY; 1202a54f2c7SM Chetan Kumar dev_err(ipc_wwan->dev, "unable to push packets"); 1212a54f2c7SM Chetan Kumar } else { 1222a54f2c7SM Chetan Kumar goto exit; 1232a54f2c7SM Chetan Kumar } 1242a54f2c7SM Chetan Kumar 1252a54f2c7SM Chetan Kumar return ret; 1262a54f2c7SM Chetan Kumar 1272a54f2c7SM Chetan Kumar exit: 1282a54f2c7SM Chetan Kumar /* Log any skb drop */ 1292a54f2c7SM Chetan Kumar if (if_id) 1302a54f2c7SM Chetan Kumar dev_dbg(ipc_wwan->dev, "skb dropped. IF_ID: %d, ret: %d", if_id, 1312a54f2c7SM Chetan Kumar ret); 1322a54f2c7SM Chetan Kumar 1332a54f2c7SM Chetan Kumar dev_kfree_skb_any(skb); 134c302e3a1SM Chetan Kumar netdev->stats.tx_dropped++; 135c302e3a1SM Chetan Kumar return NETDEV_TX_OK; 1362a54f2c7SM Chetan Kumar } 1372a54f2c7SM Chetan Kumar 1382a54f2c7SM Chetan Kumar /* Ops structure for wwan net link */ 1392a54f2c7SM Chetan Kumar static const struct net_device_ops ipc_inm_ops = { 1402a54f2c7SM Chetan Kumar .ndo_open = ipc_wwan_link_open, 1412a54f2c7SM Chetan Kumar .ndo_stop = ipc_wwan_link_stop, 1422a54f2c7SM Chetan Kumar .ndo_start_xmit = ipc_wwan_link_transmit, 1432a54f2c7SM Chetan Kumar }; 1442a54f2c7SM Chetan Kumar 1452a54f2c7SM Chetan Kumar /* Setup function for creating new net link */ 1462a54f2c7SM Chetan Kumar static void ipc_wwan_setup(struct net_device *iosm_dev) 1472a54f2c7SM Chetan Kumar { 1482a54f2c7SM Chetan Kumar iosm_dev->header_ops = NULL; 1492a54f2c7SM Chetan Kumar iosm_dev->hard_header_len = 0; 1505d710dc3SM Chetan Kumar iosm_dev->tx_queue_len = DEFAULT_TX_QUEUE_LEN; 1512a54f2c7SM Chetan Kumar 1522a54f2c7SM Chetan Kumar iosm_dev->type = ARPHRD_NONE; 153d7340f46SM Chetan Kumar iosm_dev->mtu = ETH_DATA_LEN; 1542a54f2c7SM Chetan Kumar iosm_dev->min_mtu = ETH_MIN_MTU; 1552a54f2c7SM Chetan Kumar iosm_dev->max_mtu = ETH_MAX_MTU; 1562a54f2c7SM Chetan Kumar 1572a54f2c7SM Chetan Kumar iosm_dev->flags = IFF_POINTOPOINT | IFF_NOARP; 158f25caacaSHW He iosm_dev->needs_free_netdev = true; 1592a54f2c7SM Chetan Kumar 1602a54f2c7SM Chetan Kumar iosm_dev->netdev_ops = &ipc_inm_ops; 1612a54f2c7SM Chetan Kumar } 1622a54f2c7SM Chetan Kumar 1632a54f2c7SM Chetan Kumar /* Create new wwan net link */ 1642a54f2c7SM Chetan Kumar static int ipc_wwan_newlink(void *ctxt, struct net_device *dev, 1652a54f2c7SM Chetan Kumar u32 if_id, struct netlink_ext_ack *extack) 1662a54f2c7SM Chetan Kumar { 1672a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan = ctxt; 1682a54f2c7SM Chetan Kumar struct iosm_netdev_priv *priv; 1692a54f2c7SM Chetan Kumar int err; 1702a54f2c7SM Chetan Kumar 1712a54f2c7SM Chetan Kumar if (if_id < IP_MUX_SESSION_START || 1722a54f2c7SM Chetan Kumar if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist)) 1732a54f2c7SM Chetan Kumar return -EINVAL; 1742a54f2c7SM Chetan Kumar 17569940924SSergey Ryazanov priv = wwan_netdev_drvpriv(dev); 1762a54f2c7SM Chetan Kumar priv->if_id = if_id; 1772a54f2c7SM Chetan Kumar priv->netdev = dev; 1782a54f2c7SM Chetan Kumar priv->ipc_wwan = ipc_wwan; 1792a54f2c7SM Chetan Kumar 180f9027f88SZhaoping Shu if (rcu_access_pointer(ipc_wwan->sub_netlist[if_id])) 181f9027f88SZhaoping Shu return -EBUSY; 1822a54f2c7SM Chetan Kumar 1832a54f2c7SM Chetan Kumar err = register_netdevice(dev); 1842a54f2c7SM Chetan Kumar if (err) 185f9027f88SZhaoping Shu return err; 1862a54f2c7SM Chetan Kumar 1872a54f2c7SM Chetan Kumar rcu_assign_pointer(ipc_wwan->sub_netlist[if_id], priv); 1882a54f2c7SM Chetan Kumar netif_device_attach(dev); 1892a54f2c7SM Chetan Kumar 1902a54f2c7SM Chetan Kumar return 0; 1912a54f2c7SM Chetan Kumar } 1922a54f2c7SM Chetan Kumar 1932a54f2c7SM Chetan Kumar static void ipc_wwan_dellink(void *ctxt, struct net_device *dev, 1942a54f2c7SM Chetan Kumar struct list_head *head) 1952a54f2c7SM Chetan Kumar { 19669940924SSergey Ryazanov struct iosm_netdev_priv *priv = wwan_netdev_drvpriv(dev); 1972a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan = ctxt; 1982a54f2c7SM Chetan Kumar int if_id = priv->if_id; 1992a54f2c7SM Chetan Kumar 2002a54f2c7SM Chetan Kumar if (WARN_ON(if_id < IP_MUX_SESSION_START || 2012a54f2c7SM Chetan Kumar if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))) 2022a54f2c7SM Chetan Kumar return; 2032a54f2c7SM Chetan Kumar 2042a54f2c7SM Chetan Kumar if (WARN_ON(rcu_access_pointer(ipc_wwan->sub_netlist[if_id]) != priv)) 205f9027f88SZhaoping Shu return; 2062a54f2c7SM Chetan Kumar 2072a54f2c7SM Chetan Kumar RCU_INIT_POINTER(ipc_wwan->sub_netlist[if_id], NULL); 2082a54f2c7SM Chetan Kumar /* unregistering includes synchronize_net() */ 209679505baSM Chetan Kumar unregister_netdevice_queue(dev, head); 2102a54f2c7SM Chetan Kumar } 2112a54f2c7SM Chetan Kumar 2122a54f2c7SM Chetan Kumar static const struct wwan_ops iosm_wwan_ops = { 2132a54f2c7SM Chetan Kumar .priv_size = sizeof(struct iosm_netdev_priv), 2142a54f2c7SM Chetan Kumar .setup = ipc_wwan_setup, 2152a54f2c7SM Chetan Kumar .newlink = ipc_wwan_newlink, 2162a54f2c7SM Chetan Kumar .dellink = ipc_wwan_dellink, 2172a54f2c7SM Chetan Kumar }; 2182a54f2c7SM Chetan Kumar 2192a54f2c7SM Chetan Kumar int ipc_wwan_receive(struct iosm_wwan *ipc_wwan, struct sk_buff *skb_arg, 2202a54f2c7SM Chetan Kumar bool dss, int if_id) 2212a54f2c7SM Chetan Kumar { 2222a54f2c7SM Chetan Kumar struct sk_buff *skb = skb_arg; 2232a54f2c7SM Chetan Kumar struct net_device_stats *stats; 2242a54f2c7SM Chetan Kumar struct iosm_netdev_priv *priv; 2252a54f2c7SM Chetan Kumar int ret; 2262a54f2c7SM Chetan Kumar 2272a54f2c7SM Chetan Kumar if ((skb->data[0] & IOSM_IP_TYPE_MASK) == IOSM_IP_TYPE_IPV4) 2282a54f2c7SM Chetan Kumar skb->protocol = htons(ETH_P_IP); 2292a54f2c7SM Chetan Kumar else if ((skb->data[0] & IOSM_IP_TYPE_MASK) == 2302a54f2c7SM Chetan Kumar IOSM_IP_TYPE_IPV6) 2312a54f2c7SM Chetan Kumar skb->protocol = htons(ETH_P_IPV6); 2322a54f2c7SM Chetan Kumar 2332a54f2c7SM Chetan Kumar skb->pkt_type = PACKET_HOST; 2342a54f2c7SM Chetan Kumar 2355bb4eea0SM Chetan Kumar if (if_id < IP_MUX_SESSION_START || 2365bb4eea0SM Chetan Kumar if_id > IP_MUX_SESSION_END) { 2372a54f2c7SM Chetan Kumar ret = -EINVAL; 2382a54f2c7SM Chetan Kumar goto free; 2392a54f2c7SM Chetan Kumar } 2402a54f2c7SM Chetan Kumar 2412a54f2c7SM Chetan Kumar rcu_read_lock(); 2422a54f2c7SM Chetan Kumar priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]); 2432a54f2c7SM Chetan Kumar if (!priv) { 2442a54f2c7SM Chetan Kumar ret = -EINVAL; 2452a54f2c7SM Chetan Kumar goto unlock; 2462a54f2c7SM Chetan Kumar } 2472a54f2c7SM Chetan Kumar skb->dev = priv->netdev; 2482a54f2c7SM Chetan Kumar stats = &priv->netdev->stats; 2492a54f2c7SM Chetan Kumar stats->rx_packets++; 2502a54f2c7SM Chetan Kumar stats->rx_bytes += skb->len; 2512a54f2c7SM Chetan Kumar 2522a54f2c7SM Chetan Kumar ret = netif_rx(skb); 2532a54f2c7SM Chetan Kumar skb = NULL; 2542a54f2c7SM Chetan Kumar unlock: 2552a54f2c7SM Chetan Kumar rcu_read_unlock(); 2562a54f2c7SM Chetan Kumar free: 2572a54f2c7SM Chetan Kumar dev_kfree_skb(skb); 2582a54f2c7SM Chetan Kumar return ret; 2592a54f2c7SM Chetan Kumar } 2602a54f2c7SM Chetan Kumar 2612a54f2c7SM Chetan Kumar void ipc_wwan_tx_flowctrl(struct iosm_wwan *ipc_wwan, int if_id, bool on) 2622a54f2c7SM Chetan Kumar { 2632a54f2c7SM Chetan Kumar struct net_device *netdev; 2642a54f2c7SM Chetan Kumar struct iosm_netdev_priv *priv; 2652a54f2c7SM Chetan Kumar bool is_tx_blk; 2662a54f2c7SM Chetan Kumar 2672a54f2c7SM Chetan Kumar rcu_read_lock(); 2682a54f2c7SM Chetan Kumar priv = rcu_dereference(ipc_wwan->sub_netlist[if_id]); 2692a54f2c7SM Chetan Kumar if (!priv) { 2702a54f2c7SM Chetan Kumar rcu_read_unlock(); 2712a54f2c7SM Chetan Kumar return; 2722a54f2c7SM Chetan Kumar } 2732a54f2c7SM Chetan Kumar 2742a54f2c7SM Chetan Kumar netdev = priv->netdev; 2752a54f2c7SM Chetan Kumar 2762a54f2c7SM Chetan Kumar is_tx_blk = netif_queue_stopped(netdev); 2772a54f2c7SM Chetan Kumar 2782a54f2c7SM Chetan Kumar if (on) 2792a54f2c7SM Chetan Kumar dev_dbg(ipc_wwan->dev, "session id[%d]: flowctrl enable", 2802a54f2c7SM Chetan Kumar if_id); 2812a54f2c7SM Chetan Kumar 2822a54f2c7SM Chetan Kumar if (on && !is_tx_blk) 2832a54f2c7SM Chetan Kumar netif_stop_queue(netdev); 2842a54f2c7SM Chetan Kumar else if (!on && is_tx_blk) 2852a54f2c7SM Chetan Kumar netif_wake_queue(netdev); 2862a54f2c7SM Chetan Kumar rcu_read_unlock(); 2872a54f2c7SM Chetan Kumar } 2882a54f2c7SM Chetan Kumar 2892a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan_init(struct iosm_imem *ipc_imem, struct device *dev) 2902a54f2c7SM Chetan Kumar { 2912a54f2c7SM Chetan Kumar struct iosm_wwan *ipc_wwan; 2922a54f2c7SM Chetan Kumar 2932a54f2c7SM Chetan Kumar ipc_wwan = kzalloc(sizeof(*ipc_wwan), GFP_KERNEL); 2942a54f2c7SM Chetan Kumar if (!ipc_wwan) 2952a54f2c7SM Chetan Kumar return NULL; 2962a54f2c7SM Chetan Kumar 2972a54f2c7SM Chetan Kumar ipc_wwan->dev = dev; 2982a54f2c7SM Chetan Kumar ipc_wwan->ipc_imem = ipc_imem; 2992a54f2c7SM Chetan Kumar 30083068395SSergey Ryazanov /* WWAN core will create a netdev for the default IP MUX channel */ 301ca374290SSergey Ryazanov if (wwan_register_ops(ipc_wwan->dev, &iosm_wwan_ops, ipc_wwan, 30283068395SSergey Ryazanov IP_MUX_SESSION_DEFAULT)) { 3032a54f2c7SM Chetan Kumar kfree(ipc_wwan); 3042a54f2c7SM Chetan Kumar return NULL; 3052a54f2c7SM Chetan Kumar } 3062a54f2c7SM Chetan Kumar 3072a54f2c7SM Chetan Kumar return ipc_wwan; 3082a54f2c7SM Chetan Kumar } 3092a54f2c7SM Chetan Kumar 3102a54f2c7SM Chetan Kumar void ipc_wwan_deinit(struct iosm_wwan *ipc_wwan) 3112a54f2c7SM Chetan Kumar { 312322a0ba9SSergey Ryazanov /* This call will remove all child netdev(s) */ 3132a54f2c7SM Chetan Kumar wwan_unregister_ops(ipc_wwan->dev); 3142a54f2c7SM Chetan Kumar 3152a54f2c7SM Chetan Kumar kfree(ipc_wwan); 3162a54f2c7SM Chetan Kumar } 317