summaryrefslogtreecommitdiffstats
path: root/drivers/net/wwan/iosm/iosm_ipc_wwan.c
blob: 4c9022a93e01822eedebe18e00e317969c64d936 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// 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 <net/pkt_sched.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
 */
struct iosm_wwan {
	struct iosm_imem *ipc_imem;
	struct iosm_netdev_priv __rcu *sub_netlist[IP_MUX_SESSION_END + 1];
	struct device *dev;
};

/* 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;

	if (if_id < IP_MUX_SESSION_START ||
	    if_id >= ARRAY_SIZE(ipc_wwan->sub_netlist))
		return -EINVAL;

	/* 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);
		return -ENODEV;
	}

	/* 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);

	return 0;
}

/* 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);

	ipc_imem_sys_wwan_close(priv->ipc_wwan->ipc_imem, priv->if_id,
				priv->ch_id);
	priv->ch_id = -1;

	return 0;
}

/* Transmit a packet */
static netdev_tx_t 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->tx_queue_len = DEFAULT_TX_QUEUE_LEN;

	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->needs_free_netdev = true;

	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;

	if (rcu_access_pointer(ipc_wwan->sub_netlist[if_id]))
		return -EBUSY;

	err = register_netdevice(dev);
	if (err)
		return err;

	rcu_assign_pointer(ipc_wwan->sub_netlist[if_id], priv);
	netif_device_attach(dev);

	return 0;
}

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;

	if (WARN_ON(rcu_access_pointer(ipc_wwan->sub_netlist[if_id]) != priv))
		return;

	RCU_INIT_POINTER(ipc_wwan->sub_netlist[if_id], NULL);
	/* unregistering includes synchronize_net() */
	unregister_netdevice_queue(dev, head);
}

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;
	}

	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);

	kfree(ipc_wwan);
}