xref: /openbmc/linux/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c (revision 1188f7f111c61394ec56beb8e30322305a8220b6)
197fb5e8dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
29dcaec04SSubash Abhinov Kasiviswanathan /* Copyright (c) 2013-2018, The Linux Foundation. All rights reserved.
3ceed73a2SSubash Abhinov Kasiviswanathan  *
4ceed73a2SSubash Abhinov Kasiviswanathan  * RMNET configuration engine
5ceed73a2SSubash Abhinov Kasiviswanathan  */
6ceed73a2SSubash Abhinov Kasiviswanathan 
7ceed73a2SSubash Abhinov Kasiviswanathan #include <net/sock.h>
8ceed73a2SSubash Abhinov Kasiviswanathan #include <linux/module.h>
9ceed73a2SSubash Abhinov Kasiviswanathan #include <linux/netlink.h>
10ceed73a2SSubash Abhinov Kasiviswanathan #include <linux/netdevice.h>
11ceed73a2SSubash Abhinov Kasiviswanathan #include "rmnet_config.h"
12ceed73a2SSubash Abhinov Kasiviswanathan #include "rmnet_handlers.h"
13ceed73a2SSubash Abhinov Kasiviswanathan #include "rmnet_vnd.h"
14ceed73a2SSubash Abhinov Kasiviswanathan #include "rmnet_private.h"
1564b5d1f8SDaniele Palmas #include "rmnet_map.h"
16ceed73a2SSubash Abhinov Kasiviswanathan 
17ceed73a2SSubash Abhinov Kasiviswanathan /* Local Definitions and Declarations */
18ceed73a2SSubash Abhinov Kasiviswanathan 
1914452ca3SSubash Abhinov Kasiviswanathan static const struct nla_policy rmnet_policy[IFLA_RMNET_MAX + 1] = {
2014452ca3SSubash Abhinov Kasiviswanathan 	[IFLA_RMNET_MUX_ID]	= { .type = NLA_U16 },
2114452ca3SSubash Abhinov Kasiviswanathan 	[IFLA_RMNET_FLAGS]	= { .len = sizeof(struct ifla_rmnet_flags) },
2214452ca3SSubash Abhinov Kasiviswanathan };
2314452ca3SSubash Abhinov Kasiviswanathan 
rmnet_is_real_dev_registered(const struct net_device * real_dev)24ceed73a2SSubash Abhinov Kasiviswanathan static int rmnet_is_real_dev_registered(const struct net_device *real_dev)
25ceed73a2SSubash Abhinov Kasiviswanathan {
265c346525SSubash Abhinov Kasiviswanathan 	return rcu_access_pointer(real_dev->rx_handler) == rmnet_rx_handler;
27ceed73a2SSubash Abhinov Kasiviswanathan }
28ceed73a2SSubash Abhinov Kasiviswanathan 
29ceed73a2SSubash Abhinov Kasiviswanathan /* Needs rtnl lock */
30b7f5eb6bSSubash Abhinov Kasiviswanathan struct rmnet_port*
rmnet_get_port_rtnl(const struct net_device * real_dev)31b665f4f8SSubash Abhinov Kasiviswanathan rmnet_get_port_rtnl(const struct net_device *real_dev)
32ceed73a2SSubash Abhinov Kasiviswanathan {
33ceed73a2SSubash Abhinov Kasiviswanathan 	return rtnl_dereference(real_dev->rx_handler_data);
34ceed73a2SSubash Abhinov Kasiviswanathan }
35ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_unregister_real_device(struct net_device * real_dev)36d939b6d3STaehee Yoo static int rmnet_unregister_real_device(struct net_device *real_dev)
37ceed73a2SSubash Abhinov Kasiviswanathan {
38d939b6d3STaehee Yoo 	struct rmnet_port *port = rmnet_get_port_rtnl(real_dev);
39d939b6d3STaehee Yoo 
40b665f4f8SSubash Abhinov Kasiviswanathan 	if (port->nr_rmnet_devs)
41ceed73a2SSubash Abhinov Kasiviswanathan 		return -EINVAL;
42ceed73a2SSubash Abhinov Kasiviswanathan 
4364b5d1f8SDaniele Palmas 	rmnet_map_tx_aggregate_exit(port);
4464b5d1f8SDaniele Palmas 
45ceed73a2SSubash Abhinov Kasiviswanathan 	netdev_rx_handler_unregister(real_dev);
46ceed73a2SSubash Abhinov Kasiviswanathan 
47e7a86c68SSean Tranchetti 	kfree(port);
48e7a86c68SSean Tranchetti 
49ceed73a2SSubash Abhinov Kasiviswanathan 	netdev_dbg(real_dev, "Removed from rmnet\n");
50ceed73a2SSubash Abhinov Kasiviswanathan 	return 0;
51ceed73a2SSubash Abhinov Kasiviswanathan }
52ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_register_real_device(struct net_device * real_dev,struct netlink_ext_ack * extack)532a762e9eSTaehee Yoo static int rmnet_register_real_device(struct net_device *real_dev,
542a762e9eSTaehee Yoo 				      struct netlink_ext_ack *extack)
55ceed73a2SSubash Abhinov Kasiviswanathan {
56b665f4f8SSubash Abhinov Kasiviswanathan 	struct rmnet_port *port;
573352e6c4SSubash Abhinov Kasiviswanathan 	int rc, entry;
58ceed73a2SSubash Abhinov Kasiviswanathan 
59ceed73a2SSubash Abhinov Kasiviswanathan 	ASSERT_RTNL();
60ceed73a2SSubash Abhinov Kasiviswanathan 
612a762e9eSTaehee Yoo 	if (rmnet_is_real_dev_registered(real_dev)) {
622a762e9eSTaehee Yoo 		port = rmnet_get_port_rtnl(real_dev);
632a762e9eSTaehee Yoo 		if (port->rmnet_mode != RMNET_EPMODE_VND) {
642a762e9eSTaehee Yoo 			NL_SET_ERR_MSG_MOD(extack, "bridge device already exists");
652a762e9eSTaehee Yoo 			return -EINVAL;
662a762e9eSTaehee Yoo 		}
672a762e9eSTaehee Yoo 
68ceed73a2SSubash Abhinov Kasiviswanathan 		return 0;
692a762e9eSTaehee Yoo 	}
70ceed73a2SSubash Abhinov Kasiviswanathan 
719c9cc918STaehee Yoo 	port = kzalloc(sizeof(*port), GFP_KERNEL);
72b665f4f8SSubash Abhinov Kasiviswanathan 	if (!port)
73ceed73a2SSubash Abhinov Kasiviswanathan 		return -ENOMEM;
74ceed73a2SSubash Abhinov Kasiviswanathan 
75b665f4f8SSubash Abhinov Kasiviswanathan 	port->dev = real_dev;
76b665f4f8SSubash Abhinov Kasiviswanathan 	rc = netdev_rx_handler_register(real_dev, rmnet_rx_handler, port);
77ceed73a2SSubash Abhinov Kasiviswanathan 	if (rc) {
78b665f4f8SSubash Abhinov Kasiviswanathan 		kfree(port);
79ceed73a2SSubash Abhinov Kasiviswanathan 		return -EBUSY;
80ceed73a2SSubash Abhinov Kasiviswanathan 	}
81ceed73a2SSubash Abhinov Kasiviswanathan 
823352e6c4SSubash Abhinov Kasiviswanathan 	for (entry = 0; entry < RMNET_MAX_LOGICAL_EP; entry++)
833352e6c4SSubash Abhinov Kasiviswanathan 		INIT_HLIST_HEAD(&port->muxed_ep[entry]);
843352e6c4SSubash Abhinov Kasiviswanathan 
8564b5d1f8SDaniele Palmas 	rmnet_map_tx_aggregate_init(port);
8664b5d1f8SDaniele Palmas 
87ceed73a2SSubash Abhinov Kasiviswanathan 	netdev_dbg(real_dev, "registered with rmnet\n");
88ceed73a2SSubash Abhinov Kasiviswanathan 	return 0;
89ceed73a2SSubash Abhinov Kasiviswanathan }
90ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_unregister_bridge(struct rmnet_port * port)91d939b6d3STaehee Yoo static void rmnet_unregister_bridge(struct rmnet_port *port)
9260d58f97SSubash Abhinov Kasiviswanathan {
93d939b6d3STaehee Yoo 	struct net_device *bridge_dev, *real_dev, *rmnet_dev;
94d939b6d3STaehee Yoo 	struct rmnet_port *real_port;
9560d58f97SSubash Abhinov Kasiviswanathan 
9660d58f97SSubash Abhinov Kasiviswanathan 	if (port->rmnet_mode != RMNET_EPMODE_BRIDGE)
9760d58f97SSubash Abhinov Kasiviswanathan 		return;
9860d58f97SSubash Abhinov Kasiviswanathan 
99d939b6d3STaehee Yoo 	rmnet_dev = port->rmnet_dev;
10060d58f97SSubash Abhinov Kasiviswanathan 	if (!port->nr_rmnet_devs) {
101d939b6d3STaehee Yoo 		/* bridge device */
102d939b6d3STaehee Yoo 		real_dev = port->bridge_ep;
103d939b6d3STaehee Yoo 		bridge_dev = port->dev;
10460d58f97SSubash Abhinov Kasiviswanathan 
105d939b6d3STaehee Yoo 		real_port = rmnet_get_port_rtnl(real_dev);
106d939b6d3STaehee Yoo 		real_port->bridge_ep = NULL;
107d939b6d3STaehee Yoo 		real_port->rmnet_mode = RMNET_EPMODE_VND;
10860d58f97SSubash Abhinov Kasiviswanathan 	} else {
109d939b6d3STaehee Yoo 		/* real device */
11060d58f97SSubash Abhinov Kasiviswanathan 		bridge_dev = port->bridge_ep;
11160d58f97SSubash Abhinov Kasiviswanathan 
112d939b6d3STaehee Yoo 		port->bridge_ep = NULL;
113d939b6d3STaehee Yoo 		port->rmnet_mode = RMNET_EPMODE_VND;
11460d58f97SSubash Abhinov Kasiviswanathan 	}
115d939b6d3STaehee Yoo 
116d939b6d3STaehee Yoo 	netdev_upper_dev_unlink(bridge_dev, rmnet_dev);
117d939b6d3STaehee Yoo 	rmnet_unregister_real_device(bridge_dev);
11860d58f97SSubash Abhinov Kasiviswanathan }
11960d58f97SSubash Abhinov Kasiviswanathan 
rmnet_newlink(struct net * src_net,struct net_device * dev,struct nlattr * tb[],struct nlattr * data[],struct netlink_ext_ack * extack)120ceed73a2SSubash Abhinov Kasiviswanathan static int rmnet_newlink(struct net *src_net, struct net_device *dev,
121ceed73a2SSubash Abhinov Kasiviswanathan 			 struct nlattr *tb[], struct nlattr *data[],
122ceed73a2SSubash Abhinov Kasiviswanathan 			 struct netlink_ext_ack *extack)
123ceed73a2SSubash Abhinov Kasiviswanathan {
12414452ca3SSubash Abhinov Kasiviswanathan 	u32 data_format = RMNET_FLAGS_INGRESS_DEAGGREGATION;
125ceed73a2SSubash Abhinov Kasiviswanathan 	struct net_device *real_dev;
126ceed73a2SSubash Abhinov Kasiviswanathan 	int mode = RMNET_EPMODE_VND;
1273352e6c4SSubash Abhinov Kasiviswanathan 	struct rmnet_endpoint *ep;
128b665f4f8SSubash Abhinov Kasiviswanathan 	struct rmnet_port *port;
129ceed73a2SSubash Abhinov Kasiviswanathan 	int err = 0;
130ceed73a2SSubash Abhinov Kasiviswanathan 	u16 mux_id;
131ceed73a2SSubash Abhinov Kasiviswanathan 
13293b5cbfaSTaehee Yoo 	if (!tb[IFLA_LINK]) {
13393b5cbfaSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack, "link not specified");
13493b5cbfaSTaehee Yoo 		return -EINVAL;
13593b5cbfaSTaehee Yoo 	}
13693b5cbfaSTaehee Yoo 
137ceed73a2SSubash Abhinov Kasiviswanathan 	real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK]));
138fcf8f4ebSTaehee Yoo 	if (!real_dev) {
139fcf8f4ebSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack, "link does not exist");
140ceed73a2SSubash Abhinov Kasiviswanathan 		return -ENODEV;
141fcf8f4ebSTaehee Yoo 	}
142ceed73a2SSubash Abhinov Kasiviswanathan 
1439c9cc918STaehee Yoo 	ep = kzalloc(sizeof(*ep), GFP_KERNEL);
1443352e6c4SSubash Abhinov Kasiviswanathan 	if (!ep)
1453352e6c4SSubash Abhinov Kasiviswanathan 		return -ENOMEM;
1463352e6c4SSubash Abhinov Kasiviswanathan 
14714452ca3SSubash Abhinov Kasiviswanathan 	mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
148ceed73a2SSubash Abhinov Kasiviswanathan 
1492a762e9eSTaehee Yoo 	err = rmnet_register_real_device(real_dev, extack);
150ceed73a2SSubash Abhinov Kasiviswanathan 	if (err)
151ceed73a2SSubash Abhinov Kasiviswanathan 		goto err0;
152ceed73a2SSubash Abhinov Kasiviswanathan 
153b665f4f8SSubash Abhinov Kasiviswanathan 	port = rmnet_get_port_rtnl(real_dev);
154fcf8f4ebSTaehee Yoo 	err = rmnet_vnd_newlink(mux_id, dev, port, real_dev, ep, extack);
155ceed73a2SSubash Abhinov Kasiviswanathan 	if (err)
156ceed73a2SSubash Abhinov Kasiviswanathan 		goto err1;
157ceed73a2SSubash Abhinov Kasiviswanathan 
158037f9cdfSTaehee Yoo 	err = netdev_upper_dev_link(real_dev, dev, extack);
159037f9cdfSTaehee Yoo 	if (err < 0)
160037f9cdfSTaehee Yoo 		goto err2;
161037f9cdfSTaehee Yoo 
16291489632SSubash Abhinov Kasiviswanathan 	port->rmnet_mode = mode;
163d939b6d3STaehee Yoo 	port->rmnet_dev = dev;
164032ee468SSubash Abhinov Kasiviswanathan 
1653352e6c4SSubash Abhinov Kasiviswanathan 	hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
1666b8ecc23SSubash Abhinov Kasiviswanathan 
16714452ca3SSubash Abhinov Kasiviswanathan 	if (data[IFLA_RMNET_FLAGS]) {
16814452ca3SSubash Abhinov Kasiviswanathan 		struct ifla_rmnet_flags *flags;
1696b8ecc23SSubash Abhinov Kasiviswanathan 
17014452ca3SSubash Abhinov Kasiviswanathan 		flags = nla_data(data[IFLA_RMNET_FLAGS]);
171d917c35aSBjorn Andersson 		data_format &= ~flags->mask;
172d917c35aSBjorn Andersson 		data_format |= flags->flags & flags->mask;
1736b8ecc23SSubash Abhinov Kasiviswanathan 	}
1746b8ecc23SSubash Abhinov Kasiviswanathan 
175b23e722eSSubash Abhinov Kasiviswanathan 	netdev_dbg(dev, "data format [0x%08X]\n", data_format);
176b23e722eSSubash Abhinov Kasiviswanathan 	port->data_format = data_format;
1776b8ecc23SSubash Abhinov Kasiviswanathan 
178ceed73a2SSubash Abhinov Kasiviswanathan 	return 0;
179ceed73a2SSubash Abhinov Kasiviswanathan 
180037f9cdfSTaehee Yoo err2:
181037f9cdfSTaehee Yoo 	unregister_netdevice(dev);
182d939b6d3STaehee Yoo 	rmnet_vnd_dellink(mux_id, port, ep);
183ceed73a2SSubash Abhinov Kasiviswanathan err1:
184d939b6d3STaehee Yoo 	rmnet_unregister_real_device(real_dev);
185ceed73a2SSubash Abhinov Kasiviswanathan err0:
1866296928fSSubash Abhinov Kasiviswanathan 	kfree(ep);
187ceed73a2SSubash Abhinov Kasiviswanathan 	return err;
188ceed73a2SSubash Abhinov Kasiviswanathan }
189ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_dellink(struct net_device * dev,struct list_head * head)190ceed73a2SSubash Abhinov Kasiviswanathan static void rmnet_dellink(struct net_device *dev, struct list_head *head)
191ceed73a2SSubash Abhinov Kasiviswanathan {
192b37f78f2SSubash Abhinov Kasiviswanathan 	struct rmnet_priv *priv = netdev_priv(dev);
193d939b6d3STaehee Yoo 	struct net_device *real_dev, *bridge_dev;
194d939b6d3STaehee Yoo 	struct rmnet_port *real_port, *bridge_port;
1953352e6c4SSubash Abhinov Kasiviswanathan 	struct rmnet_endpoint *ep;
196d939b6d3STaehee Yoo 	u8 mux_id = priv->mux_id;
197ceed73a2SSubash Abhinov Kasiviswanathan 
198b37f78f2SSubash Abhinov Kasiviswanathan 	real_dev = priv->real_dev;
199ceed73a2SSubash Abhinov Kasiviswanathan 
200d939b6d3STaehee Yoo 	if (!rmnet_is_real_dev_registered(real_dev))
201ceed73a2SSubash Abhinov Kasiviswanathan 		return;
202ceed73a2SSubash Abhinov Kasiviswanathan 
203d939b6d3STaehee Yoo 	real_port = rmnet_get_port_rtnl(real_dev);
204d939b6d3STaehee Yoo 	bridge_dev = real_port->bridge_ep;
205d939b6d3STaehee Yoo 	if (bridge_dev) {
206d939b6d3STaehee Yoo 		bridge_port = rmnet_get_port_rtnl(bridge_dev);
207d939b6d3STaehee Yoo 		rmnet_unregister_bridge(bridge_port);
208d939b6d3STaehee Yoo 	}
209ceed73a2SSubash Abhinov Kasiviswanathan 
210d939b6d3STaehee Yoo 	ep = rmnet_get_endpoint(real_port, mux_id);
2113352e6c4SSubash Abhinov Kasiviswanathan 	if (ep) {
2123352e6c4SSubash Abhinov Kasiviswanathan 		hlist_del_init_rcu(&ep->hlnode);
213d939b6d3STaehee Yoo 		rmnet_vnd_dellink(mux_id, real_port, ep);
2143352e6c4SSubash Abhinov Kasiviswanathan 		kfree(ep);
2153352e6c4SSubash Abhinov Kasiviswanathan 	}
216ceed73a2SSubash Abhinov Kasiviswanathan 
217d939b6d3STaehee Yoo 	netdev_upper_dev_unlink(real_dev, dev);
218d939b6d3STaehee Yoo 	rmnet_unregister_real_device(real_dev);
219ceed73a2SSubash Abhinov Kasiviswanathan 	unregister_netdevice_queue(dev, head);
220ceed73a2SSubash Abhinov Kasiviswanathan }
221ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_force_unassociate_device(struct net_device * real_dev)222037f9cdfSTaehee Yoo static void rmnet_force_unassociate_device(struct net_device *real_dev)
223ceed73a2SSubash Abhinov Kasiviswanathan {
224b37f78f2SSubash Abhinov Kasiviswanathan 	struct hlist_node *tmp_ep;
225b37f78f2SSubash Abhinov Kasiviswanathan 	struct rmnet_endpoint *ep;
226b665f4f8SSubash Abhinov Kasiviswanathan 	struct rmnet_port *port;
227b37f78f2SSubash Abhinov Kasiviswanathan 	unsigned long bkt_ep;
228ceed73a2SSubash Abhinov Kasiviswanathan 	LIST_HEAD(list);
229ceed73a2SSubash Abhinov Kasiviswanathan 
230037f9cdfSTaehee Yoo 	port = rmnet_get_port_rtnl(real_dev);
231ceed73a2SSubash Abhinov Kasiviswanathan 
232d939b6d3STaehee Yoo 	if (port->nr_rmnet_devs) {
233d939b6d3STaehee Yoo 		/* real device */
234d939b6d3STaehee Yoo 		rmnet_unregister_bridge(port);
235b37f78f2SSubash Abhinov Kasiviswanathan 		hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
236b37f78f2SSubash Abhinov Kasiviswanathan 			unregister_netdevice_queue(ep->egress_dev, &list);
237d939b6d3STaehee Yoo 			netdev_upper_dev_unlink(real_dev, ep->egress_dev);
238b37f78f2SSubash Abhinov Kasiviswanathan 			rmnet_vnd_dellink(ep->mux_id, port, ep);
239b37f78f2SSubash Abhinov Kasiviswanathan 			hlist_del_init_rcu(&ep->hlnode);
240b37f78f2SSubash Abhinov Kasiviswanathan 			kfree(ep);
241b37f78f2SSubash Abhinov Kasiviswanathan 		}
242d939b6d3STaehee Yoo 		rmnet_unregister_real_device(real_dev);
243ceed73a2SSubash Abhinov Kasiviswanathan 		unregister_netdevice_many(&list);
244d939b6d3STaehee Yoo 	} else {
245d939b6d3STaehee Yoo 		rmnet_unregister_bridge(port);
246d939b6d3STaehee Yoo 	}
247ceed73a2SSubash Abhinov Kasiviswanathan }
248ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_config_notify_cb(struct notifier_block * nb,unsigned long event,void * data)249ceed73a2SSubash Abhinov Kasiviswanathan static int rmnet_config_notify_cb(struct notifier_block *nb,
250ceed73a2SSubash Abhinov Kasiviswanathan 				  unsigned long event, void *data)
251ceed73a2SSubash Abhinov Kasiviswanathan {
252037f9cdfSTaehee Yoo 	struct net_device *real_dev = netdev_notifier_info_to_dev(data);
253ceed73a2SSubash Abhinov Kasiviswanathan 
254037f9cdfSTaehee Yoo 	if (!rmnet_is_real_dev_registered(real_dev))
255ceed73a2SSubash Abhinov Kasiviswanathan 		return NOTIFY_DONE;
256ceed73a2SSubash Abhinov Kasiviswanathan 
257ceed73a2SSubash Abhinov Kasiviswanathan 	switch (event) {
258ceed73a2SSubash Abhinov Kasiviswanathan 	case NETDEV_UNREGISTER:
259037f9cdfSTaehee Yoo 		netdev_dbg(real_dev, "Kernel unregister\n");
260037f9cdfSTaehee Yoo 		rmnet_force_unassociate_device(real_dev);
261ceed73a2SSubash Abhinov Kasiviswanathan 		break;
262b7f5eb6bSSubash Abhinov Kasiviswanathan 	case NETDEV_CHANGEMTU:
263b7f5eb6bSSubash Abhinov Kasiviswanathan 		if (rmnet_vnd_validate_real_dev_mtu(real_dev))
264b7f5eb6bSSubash Abhinov Kasiviswanathan 			return NOTIFY_BAD;
265b7f5eb6bSSubash Abhinov Kasiviswanathan 		break;
266ceed73a2SSubash Abhinov Kasiviswanathan 	default:
267ceed73a2SSubash Abhinov Kasiviswanathan 		break;
268ceed73a2SSubash Abhinov Kasiviswanathan 	}
269ceed73a2SSubash Abhinov Kasiviswanathan 
270ceed73a2SSubash Abhinov Kasiviswanathan 	return NOTIFY_DONE;
271ceed73a2SSubash Abhinov Kasiviswanathan }
272ceed73a2SSubash Abhinov Kasiviswanathan 
273ceed73a2SSubash Abhinov Kasiviswanathan static struct notifier_block rmnet_dev_notifier __read_mostly = {
274ceed73a2SSubash Abhinov Kasiviswanathan 	.notifier_call = rmnet_config_notify_cb,
275ceed73a2SSubash Abhinov Kasiviswanathan };
276ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_rtnl_validate(struct nlattr * tb[],struct nlattr * data[],struct netlink_ext_ack * extack)277ceed73a2SSubash Abhinov Kasiviswanathan static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[],
278ceed73a2SSubash Abhinov Kasiviswanathan 			       struct netlink_ext_ack *extack)
279ceed73a2SSubash Abhinov Kasiviswanathan {
280ceed73a2SSubash Abhinov Kasiviswanathan 	u16 mux_id;
281ceed73a2SSubash Abhinov Kasiviswanathan 
282fcf8f4ebSTaehee Yoo 	if (!data || !data[IFLA_RMNET_MUX_ID]) {
283fcf8f4ebSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack, "MUX ID not specified");
284ceed73a2SSubash Abhinov Kasiviswanathan 		return -EINVAL;
285fcf8f4ebSTaehee Yoo 	}
286ceed73a2SSubash Abhinov Kasiviswanathan 
28714452ca3SSubash Abhinov Kasiviswanathan 	mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
288fcf8f4ebSTaehee Yoo 	if (mux_id > (RMNET_MAX_LOGICAL_EP - 1)) {
289fcf8f4ebSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack, "invalid MUX ID");
290ceed73a2SSubash Abhinov Kasiviswanathan 		return -ERANGE;
291fcf8f4ebSTaehee Yoo 	}
292ceed73a2SSubash Abhinov Kasiviswanathan 
293ceed73a2SSubash Abhinov Kasiviswanathan 	return 0;
294ceed73a2SSubash Abhinov Kasiviswanathan }
295ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_changelink(struct net_device * dev,struct nlattr * tb[],struct nlattr * data[],struct netlink_ext_ack * extack)29623790ef1SSubash Abhinov Kasiviswanathan static int rmnet_changelink(struct net_device *dev, struct nlattr *tb[],
29723790ef1SSubash Abhinov Kasiviswanathan 			    struct nlattr *data[],
29823790ef1SSubash Abhinov Kasiviswanathan 			    struct netlink_ext_ack *extack)
29923790ef1SSubash Abhinov Kasiviswanathan {
30023790ef1SSubash Abhinov Kasiviswanathan 	struct rmnet_priv *priv = netdev_priv(dev);
30123790ef1SSubash Abhinov Kasiviswanathan 	struct net_device *real_dev;
30223790ef1SSubash Abhinov Kasiviswanathan 	struct rmnet_port *port;
30323790ef1SSubash Abhinov Kasiviswanathan 	u16 mux_id;
30423790ef1SSubash Abhinov Kasiviswanathan 
3053c18aa14SColin Ian King 	if (!dev)
3063c18aa14SColin Ian King 		return -ENODEV;
3073c18aa14SColin Ian King 
3081eb1f43aSTaehee Yoo 	real_dev = priv->real_dev;
3091eb1f43aSTaehee Yoo 	if (!rmnet_is_real_dev_registered(real_dev))
31023790ef1SSubash Abhinov Kasiviswanathan 		return -ENODEV;
31123790ef1SSubash Abhinov Kasiviswanathan 
31223790ef1SSubash Abhinov Kasiviswanathan 	port = rmnet_get_port_rtnl(real_dev);
31323790ef1SSubash Abhinov Kasiviswanathan 
31414452ca3SSubash Abhinov Kasiviswanathan 	if (data[IFLA_RMNET_MUX_ID]) {
31514452ca3SSubash Abhinov Kasiviswanathan 		mux_id = nla_get_u16(data[IFLA_RMNET_MUX_ID]);
3162abb5792SSubash Abhinov Kasiviswanathan 
3172abb5792SSubash Abhinov Kasiviswanathan 		if (mux_id != priv->mux_id) {
3182abb5792SSubash Abhinov Kasiviswanathan 			struct rmnet_endpoint *ep;
3192abb5792SSubash Abhinov Kasiviswanathan 
32023790ef1SSubash Abhinov Kasiviswanathan 			ep = rmnet_get_endpoint(port, priv->mux_id);
3210c29ba1bSColin Ian King 			if (!ep)
3220c29ba1bSColin Ian King 				return -ENODEV;
32323790ef1SSubash Abhinov Kasiviswanathan 
3242abb5792SSubash Abhinov Kasiviswanathan 			if (rmnet_get_endpoint(port, mux_id)) {
3252abb5792SSubash Abhinov Kasiviswanathan 				NL_SET_ERR_MSG_MOD(extack,
3262abb5792SSubash Abhinov Kasiviswanathan 						   "MUX ID already exists");
3272abb5792SSubash Abhinov Kasiviswanathan 				return -EINVAL;
3282abb5792SSubash Abhinov Kasiviswanathan 			}
3292abb5792SSubash Abhinov Kasiviswanathan 
33023790ef1SSubash Abhinov Kasiviswanathan 			hlist_del_init_rcu(&ep->hlnode);
3312abb5792SSubash Abhinov Kasiviswanathan 			hlist_add_head_rcu(&ep->hlnode,
3322abb5792SSubash Abhinov Kasiviswanathan 					   &port->muxed_ep[mux_id]);
33323790ef1SSubash Abhinov Kasiviswanathan 
33423790ef1SSubash Abhinov Kasiviswanathan 			ep->mux_id = mux_id;
33523790ef1SSubash Abhinov Kasiviswanathan 			priv->mux_id = mux_id;
33623790ef1SSubash Abhinov Kasiviswanathan 		}
3372abb5792SSubash Abhinov Kasiviswanathan 	}
33823790ef1SSubash Abhinov Kasiviswanathan 
33914452ca3SSubash Abhinov Kasiviswanathan 	if (data[IFLA_RMNET_FLAGS]) {
34014452ca3SSubash Abhinov Kasiviswanathan 		struct ifla_rmnet_flags *flags;
341b7f5eb6bSSubash Abhinov Kasiviswanathan 		u32 old_data_format;
34223790ef1SSubash Abhinov Kasiviswanathan 
343b7f5eb6bSSubash Abhinov Kasiviswanathan 		old_data_format = port->data_format;
34414452ca3SSubash Abhinov Kasiviswanathan 		flags = nla_data(data[IFLA_RMNET_FLAGS]);
345d917c35aSBjorn Andersson 		port->data_format &= ~flags->mask;
346d917c35aSBjorn Andersson 		port->data_format |= flags->flags & flags->mask;
347b7f5eb6bSSubash Abhinov Kasiviswanathan 
348b7f5eb6bSSubash Abhinov Kasiviswanathan 		if (rmnet_vnd_update_dev_mtu(port, real_dev)) {
349b7f5eb6bSSubash Abhinov Kasiviswanathan 			port->data_format = old_data_format;
350b7f5eb6bSSubash Abhinov Kasiviswanathan 			NL_SET_ERR_MSG_MOD(extack, "Invalid MTU on real dev");
351b7f5eb6bSSubash Abhinov Kasiviswanathan 			return -EINVAL;
352b7f5eb6bSSubash Abhinov Kasiviswanathan 		}
35323790ef1SSubash Abhinov Kasiviswanathan 	}
35423790ef1SSubash Abhinov Kasiviswanathan 
35523790ef1SSubash Abhinov Kasiviswanathan 	return 0;
35623790ef1SSubash Abhinov Kasiviswanathan }
35723790ef1SSubash Abhinov Kasiviswanathan 
rmnet_get_size(const struct net_device * dev)358ceed73a2SSubash Abhinov Kasiviswanathan static size_t rmnet_get_size(const struct net_device *dev)
359ceed73a2SSubash Abhinov Kasiviswanathan {
36014452ca3SSubash Abhinov Kasiviswanathan 	return
36114452ca3SSubash Abhinov Kasiviswanathan 		/* IFLA_RMNET_MUX_ID */
36214452ca3SSubash Abhinov Kasiviswanathan 		nla_total_size(2) +
36314452ca3SSubash Abhinov Kasiviswanathan 		/* IFLA_RMNET_FLAGS */
36414452ca3SSubash Abhinov Kasiviswanathan 		nla_total_size(sizeof(struct ifla_rmnet_flags));
365ceed73a2SSubash Abhinov Kasiviswanathan }
366ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_fill_info(struct sk_buff * skb,const struct net_device * dev)367be81a85fSSubash Abhinov Kasiviswanathan static int rmnet_fill_info(struct sk_buff *skb, const struct net_device *dev)
368be81a85fSSubash Abhinov Kasiviswanathan {
369be81a85fSSubash Abhinov Kasiviswanathan 	struct rmnet_priv *priv = netdev_priv(dev);
370be81a85fSSubash Abhinov Kasiviswanathan 	struct net_device *real_dev;
371be81a85fSSubash Abhinov Kasiviswanathan 	struct ifla_rmnet_flags f;
372be81a85fSSubash Abhinov Kasiviswanathan 	struct rmnet_port *port;
373be81a85fSSubash Abhinov Kasiviswanathan 
374be81a85fSSubash Abhinov Kasiviswanathan 	real_dev = priv->real_dev;
375be81a85fSSubash Abhinov Kasiviswanathan 
376be81a85fSSubash Abhinov Kasiviswanathan 	if (nla_put_u16(skb, IFLA_RMNET_MUX_ID, priv->mux_id))
377be81a85fSSubash Abhinov Kasiviswanathan 		goto nla_put_failure;
378be81a85fSSubash Abhinov Kasiviswanathan 
37964e86fecSSubash Abhinov Kasiviswanathan 	if (rmnet_is_real_dev_registered(real_dev)) {
380be81a85fSSubash Abhinov Kasiviswanathan 		port = rmnet_get_port_rtnl(real_dev);
381be81a85fSSubash Abhinov Kasiviswanathan 		f.flags = port->data_format;
38264e86fecSSubash Abhinov Kasiviswanathan 	} else {
38364e86fecSSubash Abhinov Kasiviswanathan 		f.flags = 0;
38464e86fecSSubash Abhinov Kasiviswanathan 	}
38564e86fecSSubash Abhinov Kasiviswanathan 
386be81a85fSSubash Abhinov Kasiviswanathan 	f.mask  = ~0;
387be81a85fSSubash Abhinov Kasiviswanathan 
388be81a85fSSubash Abhinov Kasiviswanathan 	if (nla_put(skb, IFLA_RMNET_FLAGS, sizeof(f), &f))
389be81a85fSSubash Abhinov Kasiviswanathan 		goto nla_put_failure;
390be81a85fSSubash Abhinov Kasiviswanathan 
391be81a85fSSubash Abhinov Kasiviswanathan 	return 0;
392be81a85fSSubash Abhinov Kasiviswanathan 
393be81a85fSSubash Abhinov Kasiviswanathan nla_put_failure:
394be81a85fSSubash Abhinov Kasiviswanathan 	return -EMSGSIZE;
395be81a85fSSubash Abhinov Kasiviswanathan }
396be81a85fSSubash Abhinov Kasiviswanathan 
397ceed73a2SSubash Abhinov Kasiviswanathan struct rtnl_link_ops rmnet_link_ops __read_mostly = {
398ceed73a2SSubash Abhinov Kasiviswanathan 	.kind		= "rmnet",
399*c4734535SLin Ma 	.maxtype	= IFLA_RMNET_MAX,
400ceed73a2SSubash Abhinov Kasiviswanathan 	.priv_size	= sizeof(struct rmnet_priv),
401ceed73a2SSubash Abhinov Kasiviswanathan 	.setup		= rmnet_vnd_setup,
402ceed73a2SSubash Abhinov Kasiviswanathan 	.validate	= rmnet_rtnl_validate,
403ceed73a2SSubash Abhinov Kasiviswanathan 	.newlink	= rmnet_newlink,
404ceed73a2SSubash Abhinov Kasiviswanathan 	.dellink	= rmnet_dellink,
405ceed73a2SSubash Abhinov Kasiviswanathan 	.get_size	= rmnet_get_size,
40623790ef1SSubash Abhinov Kasiviswanathan 	.changelink     = rmnet_changelink,
40714452ca3SSubash Abhinov Kasiviswanathan 	.policy		= rmnet_policy,
408be81a85fSSubash Abhinov Kasiviswanathan 	.fill_info	= rmnet_fill_info,
409ceed73a2SSubash Abhinov Kasiviswanathan };
410ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_get_port_rcu(struct net_device * real_dev)411102210f7STaehee Yoo struct rmnet_port *rmnet_get_port_rcu(struct net_device *real_dev)
412ceed73a2SSubash Abhinov Kasiviswanathan {
413032ee468SSubash Abhinov Kasiviswanathan 	if (rmnet_is_real_dev_registered(real_dev))
414102210f7STaehee Yoo 		return rcu_dereference_bh(real_dev->rx_handler_data);
415032ee468SSubash Abhinov Kasiviswanathan 	else
416032ee468SSubash Abhinov Kasiviswanathan 		return NULL;
417ceed73a2SSubash Abhinov Kasiviswanathan }
418ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_get_endpoint(struct rmnet_port * port,u8 mux_id)4193352e6c4SSubash Abhinov Kasiviswanathan struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id)
4203352e6c4SSubash Abhinov Kasiviswanathan {
4213352e6c4SSubash Abhinov Kasiviswanathan 	struct rmnet_endpoint *ep;
4223352e6c4SSubash Abhinov Kasiviswanathan 
4233352e6c4SSubash Abhinov Kasiviswanathan 	hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) {
4243352e6c4SSubash Abhinov Kasiviswanathan 		if (ep->mux_id == mux_id)
4253352e6c4SSubash Abhinov Kasiviswanathan 			return ep;
4263352e6c4SSubash Abhinov Kasiviswanathan 	}
4273352e6c4SSubash Abhinov Kasiviswanathan 
4283352e6c4SSubash Abhinov Kasiviswanathan 	return NULL;
4293352e6c4SSubash Abhinov Kasiviswanathan }
4303352e6c4SSubash Abhinov Kasiviswanathan 
rmnet_add_bridge(struct net_device * rmnet_dev,struct net_device * slave_dev,struct netlink_ext_ack * extack)43160d58f97SSubash Abhinov Kasiviswanathan int rmnet_add_bridge(struct net_device *rmnet_dev,
43260d58f97SSubash Abhinov Kasiviswanathan 		     struct net_device *slave_dev,
43360d58f97SSubash Abhinov Kasiviswanathan 		     struct netlink_ext_ack *extack)
43460d58f97SSubash Abhinov Kasiviswanathan {
43560d58f97SSubash Abhinov Kasiviswanathan 	struct rmnet_priv *priv = netdev_priv(rmnet_dev);
43660d58f97SSubash Abhinov Kasiviswanathan 	struct net_device *real_dev = priv->real_dev;
43760d58f97SSubash Abhinov Kasiviswanathan 	struct rmnet_port *port, *slave_port;
43860d58f97SSubash Abhinov Kasiviswanathan 	int err;
43960d58f97SSubash Abhinov Kasiviswanathan 
440102210f7STaehee Yoo 	port = rmnet_get_port_rtnl(real_dev);
44160d58f97SSubash Abhinov Kasiviswanathan 
44260d58f97SSubash Abhinov Kasiviswanathan 	/* If there is more than one rmnet dev attached, its probably being
44360d58f97SSubash Abhinov Kasiviswanathan 	 * used for muxing. Skip the briding in that case
44460d58f97SSubash Abhinov Kasiviswanathan 	 */
445fcf8f4ebSTaehee Yoo 	if (port->nr_rmnet_devs > 1) {
446fcf8f4ebSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack, "more than one rmnet dev attached");
44760d58f97SSubash Abhinov Kasiviswanathan 		return -EINVAL;
448fcf8f4ebSTaehee Yoo 	}
44960d58f97SSubash Abhinov Kasiviswanathan 
4502fb2799aSTaehee Yoo 	if (port->rmnet_mode != RMNET_EPMODE_VND) {
4512fb2799aSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack, "more than one bridge dev attached");
4522fb2799aSTaehee Yoo 		return -EINVAL;
4532fb2799aSTaehee Yoo 	}
4542fb2799aSTaehee Yoo 
455fcf8f4ebSTaehee Yoo 	if (rmnet_is_real_dev_registered(slave_dev)) {
456fcf8f4ebSTaehee Yoo 		NL_SET_ERR_MSG_MOD(extack,
457fcf8f4ebSTaehee Yoo 				   "slave cannot be another rmnet dev");
458fcf8f4ebSTaehee Yoo 
45960d58f97SSubash Abhinov Kasiviswanathan 		return -EBUSY;
460fcf8f4ebSTaehee Yoo 	}
46160d58f97SSubash Abhinov Kasiviswanathan 
4622a762e9eSTaehee Yoo 	err = rmnet_register_real_device(slave_dev, extack);
46360d58f97SSubash Abhinov Kasiviswanathan 	if (err)
46460d58f97SSubash Abhinov Kasiviswanathan 		return -EBUSY;
46560d58f97SSubash Abhinov Kasiviswanathan 
466d939b6d3STaehee Yoo 	err = netdev_master_upper_dev_link(slave_dev, rmnet_dev, NULL, NULL,
467d939b6d3STaehee Yoo 					   extack);
468d939b6d3STaehee Yoo 	if (err) {
469d939b6d3STaehee Yoo 		rmnet_unregister_real_device(slave_dev);
470d939b6d3STaehee Yoo 		return err;
471d939b6d3STaehee Yoo 	}
472d939b6d3STaehee Yoo 
473102210f7STaehee Yoo 	slave_port = rmnet_get_port_rtnl(slave_dev);
47460d58f97SSubash Abhinov Kasiviswanathan 	slave_port->rmnet_mode = RMNET_EPMODE_BRIDGE;
47560d58f97SSubash Abhinov Kasiviswanathan 	slave_port->bridge_ep = real_dev;
476d939b6d3STaehee Yoo 	slave_port->rmnet_dev = rmnet_dev;
47760d58f97SSubash Abhinov Kasiviswanathan 
47860d58f97SSubash Abhinov Kasiviswanathan 	port->rmnet_mode = RMNET_EPMODE_BRIDGE;
47960d58f97SSubash Abhinov Kasiviswanathan 	port->bridge_ep = slave_dev;
48060d58f97SSubash Abhinov Kasiviswanathan 
48160d58f97SSubash Abhinov Kasiviswanathan 	netdev_dbg(slave_dev, "registered with rmnet as slave\n");
48260d58f97SSubash Abhinov Kasiviswanathan 	return 0;
48360d58f97SSubash Abhinov Kasiviswanathan }
48460d58f97SSubash Abhinov Kasiviswanathan 
rmnet_del_bridge(struct net_device * rmnet_dev,struct net_device * slave_dev)48560d58f97SSubash Abhinov Kasiviswanathan int rmnet_del_bridge(struct net_device *rmnet_dev,
48660d58f97SSubash Abhinov Kasiviswanathan 		     struct net_device *slave_dev)
48760d58f97SSubash Abhinov Kasiviswanathan {
488d939b6d3STaehee Yoo 	struct rmnet_port *port = rmnet_get_port_rtnl(slave_dev);
48960d58f97SSubash Abhinov Kasiviswanathan 
490d939b6d3STaehee Yoo 	rmnet_unregister_bridge(port);
49160d58f97SSubash Abhinov Kasiviswanathan 
49260d58f97SSubash Abhinov Kasiviswanathan 	netdev_dbg(slave_dev, "removed from rmnet as slave\n");
49360d58f97SSubash Abhinov Kasiviswanathan 	return 0;
49460d58f97SSubash Abhinov Kasiviswanathan }
49560d58f97SSubash Abhinov Kasiviswanathan 
496ceed73a2SSubash Abhinov Kasiviswanathan /* Startup/Shutdown */
497ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_init(void)498ceed73a2SSubash Abhinov Kasiviswanathan static int __init rmnet_init(void)
499ceed73a2SSubash Abhinov Kasiviswanathan {
500ceed73a2SSubash Abhinov Kasiviswanathan 	int rc;
501ceed73a2SSubash Abhinov Kasiviswanathan 
502ceed73a2SSubash Abhinov Kasiviswanathan 	rc = register_netdevice_notifier(&rmnet_dev_notifier);
503ceed73a2SSubash Abhinov Kasiviswanathan 	if (rc != 0)
504ceed73a2SSubash Abhinov Kasiviswanathan 		return rc;
505ceed73a2SSubash Abhinov Kasiviswanathan 
506ceed73a2SSubash Abhinov Kasiviswanathan 	rc = rtnl_link_register(&rmnet_link_ops);
507ceed73a2SSubash Abhinov Kasiviswanathan 	if (rc != 0) {
508ceed73a2SSubash Abhinov Kasiviswanathan 		unregister_netdevice_notifier(&rmnet_dev_notifier);
509ceed73a2SSubash Abhinov Kasiviswanathan 		return rc;
510ceed73a2SSubash Abhinov Kasiviswanathan 	}
511ceed73a2SSubash Abhinov Kasiviswanathan 	return rc;
512ceed73a2SSubash Abhinov Kasiviswanathan }
513ceed73a2SSubash Abhinov Kasiviswanathan 
rmnet_exit(void)514ceed73a2SSubash Abhinov Kasiviswanathan static void __exit rmnet_exit(void)
515ceed73a2SSubash Abhinov Kasiviswanathan {
516ceed73a2SSubash Abhinov Kasiviswanathan 	rtnl_link_unregister(&rmnet_link_ops);
517037f9cdfSTaehee Yoo 	unregister_netdevice_notifier(&rmnet_dev_notifier);
518ceed73a2SSubash Abhinov Kasiviswanathan }
519ceed73a2SSubash Abhinov Kasiviswanathan 
520ceed73a2SSubash Abhinov Kasiviswanathan module_init(rmnet_init)
521ceed73a2SSubash Abhinov Kasiviswanathan module_exit(rmnet_exit)
522eed22a06STaehee Yoo MODULE_ALIAS_RTNL_LINK("rmnet");
523ceed73a2SSubash Abhinov Kasiviswanathan MODULE_LICENSE("GPL v2");
524