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