Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 1 | /* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. |
| 2 | * |
| 3 | * This program is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License version 2 and |
| 5 | * only version 2 as published by the Free Software Foundation. |
| 6 | * |
| 7 | * This program is distributed in the hope that it will be useful, |
| 8 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | * GNU General Public License for more details. |
| 11 | * |
| 12 | * RMNET configuration engine |
| 13 | * |
| 14 | */ |
| 15 | |
| 16 | #include <net/sock.h> |
| 17 | #include <linux/module.h> |
| 18 | #include <linux/netlink.h> |
| 19 | #include <linux/netdevice.h> |
| 20 | #include "rmnet_config.h" |
| 21 | #include "rmnet_handlers.h" |
| 22 | #include "rmnet_vnd.h" |
| 23 | #include "rmnet_private.h" |
| 24 | |
| 25 | /* Locking scheme - |
| 26 | * The shared resource which needs to be protected is realdev->rx_handler_data. |
| 27 | * For the writer path, this is using rtnl_lock(). The writer paths are |
| 28 | * rmnet_newlink(), rmnet_dellink() and rmnet_force_unassociate_device(). These |
| 29 | * paths are already called with rtnl_lock() acquired in. There is also an |
| 30 | * ASSERT_RTNL() to ensure that we are calling with rtnl acquired. For |
| 31 | * dereference here, we will need to use rtnl_dereference(). Dev list writing |
| 32 | * needs to happen with rtnl_lock() acquired for netdev_master_upper_dev_link(). |
| 33 | * For the reader path, the real_dev->rx_handler_data is called in the TX / RX |
| 34 | * path. We only need rcu_read_lock() for these scenarios. In these cases, |
| 35 | * the rcu_read_lock() is held in __dev_queue_xmit() and |
| 36 | * netif_receive_skb_internal(), so readers need to use rcu_dereference_rtnl() |
| 37 | * to get the relevant information. For dev list reading, we again acquire |
| 38 | * rcu_read_lock() in rmnet_dellink() for netdev_master_upper_dev_get_rcu(). |
| 39 | * We also use unregister_netdevice_many() to free all rmnet devices in |
| 40 | * rmnet_force_unassociate_device() so we dont lose the rtnl_lock() and free in |
| 41 | * same context. |
| 42 | */ |
| 43 | |
| 44 | /* Local Definitions and Declarations */ |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 45 | |
| 46 | struct rmnet_walk_data { |
| 47 | struct net_device *real_dev; |
| 48 | struct list_head *head; |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 49 | struct rmnet_port *port; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 50 | }; |
| 51 | |
| 52 | static int rmnet_is_real_dev_registered(const struct net_device *real_dev) |
| 53 | { |
Subash Abhinov Kasiviswanathan | 5c34652 | 2017-09-21 18:00:36 -0600 | [diff] [blame] | 54 | return rcu_access_pointer(real_dev->rx_handler) == rmnet_rx_handler; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 55 | } |
| 56 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 57 | /* Needs rtnl lock */ |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 58 | static struct rmnet_port* |
| 59 | rmnet_get_port_rtnl(const struct net_device *real_dev) |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 60 | { |
| 61 | return rtnl_dereference(real_dev->rx_handler_data); |
| 62 | } |
| 63 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 64 | static int rmnet_unregister_real_device(struct net_device *real_dev, |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 65 | struct rmnet_port *port) |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 66 | { |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 67 | if (port->nr_rmnet_devs) |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 68 | return -EINVAL; |
| 69 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 70 | kfree(port); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 71 | |
| 72 | netdev_rx_handler_unregister(real_dev); |
| 73 | |
| 74 | /* release reference on real_dev */ |
| 75 | dev_put(real_dev); |
| 76 | |
| 77 | netdev_dbg(real_dev, "Removed from rmnet\n"); |
| 78 | return 0; |
| 79 | } |
| 80 | |
| 81 | static int rmnet_register_real_device(struct net_device *real_dev) |
| 82 | { |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 83 | struct rmnet_port *port; |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 84 | int rc, entry; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 85 | |
| 86 | ASSERT_RTNL(); |
| 87 | |
| 88 | if (rmnet_is_real_dev_registered(real_dev)) |
| 89 | return 0; |
| 90 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 91 | port = kzalloc(sizeof(*port), GFP_ATOMIC); |
| 92 | if (!port) |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 93 | return -ENOMEM; |
| 94 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 95 | port->dev = real_dev; |
| 96 | rc = netdev_rx_handler_register(real_dev, rmnet_rx_handler, port); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 97 | if (rc) { |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 98 | kfree(port); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 99 | return -EBUSY; |
| 100 | } |
| 101 | |
| 102 | /* hold on to real dev for MAP data */ |
| 103 | dev_hold(real_dev); |
| 104 | |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 105 | for (entry = 0; entry < RMNET_MAX_LOGICAL_EP; entry++) |
| 106 | INIT_HLIST_HEAD(&port->muxed_ep[entry]); |
| 107 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 108 | netdev_dbg(real_dev, "registered with rmnet\n"); |
| 109 | return 0; |
| 110 | } |
| 111 | |
Subash Abhinov Kasiviswanathan | 60d58f9 | 2017-10-11 18:43:58 -0600 | [diff] [blame] | 112 | static void rmnet_unregister_bridge(struct net_device *dev, |
| 113 | struct rmnet_port *port) |
| 114 | { |
| 115 | struct net_device *rmnet_dev, *bridge_dev; |
| 116 | struct rmnet_port *bridge_port; |
| 117 | |
| 118 | if (port->rmnet_mode != RMNET_EPMODE_BRIDGE) |
| 119 | return; |
| 120 | |
| 121 | /* bridge slave handling */ |
| 122 | if (!port->nr_rmnet_devs) { |
| 123 | rmnet_dev = netdev_master_upper_dev_get_rcu(dev); |
| 124 | netdev_upper_dev_unlink(dev, rmnet_dev); |
| 125 | |
| 126 | bridge_dev = port->bridge_ep; |
| 127 | |
| 128 | bridge_port = rmnet_get_port_rtnl(bridge_dev); |
| 129 | bridge_port->bridge_ep = NULL; |
| 130 | bridge_port->rmnet_mode = RMNET_EPMODE_VND; |
| 131 | } else { |
| 132 | bridge_dev = port->bridge_ep; |
| 133 | |
| 134 | bridge_port = rmnet_get_port_rtnl(bridge_dev); |
| 135 | rmnet_dev = netdev_master_upper_dev_get_rcu(bridge_dev); |
| 136 | netdev_upper_dev_unlink(bridge_dev, rmnet_dev); |
| 137 | |
| 138 | rmnet_unregister_real_device(bridge_dev, bridge_port); |
| 139 | } |
| 140 | } |
| 141 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 142 | static int rmnet_newlink(struct net *src_net, struct net_device *dev, |
| 143 | struct nlattr *tb[], struct nlattr *data[], |
| 144 | struct netlink_ext_ack *extack) |
| 145 | { |
| 146 | int ingress_format = RMNET_INGRESS_FORMAT_DEMUXING | |
| 147 | RMNET_INGRESS_FORMAT_DEAGGREGATION | |
| 148 | RMNET_INGRESS_FORMAT_MAP; |
| 149 | int egress_format = RMNET_EGRESS_FORMAT_MUXING | |
| 150 | RMNET_EGRESS_FORMAT_MAP; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 151 | struct net_device *real_dev; |
| 152 | int mode = RMNET_EPMODE_VND; |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 153 | struct rmnet_endpoint *ep; |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 154 | struct rmnet_port *port; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 155 | int err = 0; |
| 156 | u16 mux_id; |
| 157 | |
| 158 | real_dev = __dev_get_by_index(src_net, nla_get_u32(tb[IFLA_LINK])); |
| 159 | if (!real_dev || !dev) |
| 160 | return -ENODEV; |
| 161 | |
| 162 | if (!data[IFLA_VLAN_ID]) |
| 163 | return -EINVAL; |
| 164 | |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 165 | ep = kzalloc(sizeof(*ep), GFP_ATOMIC); |
| 166 | if (!ep) |
| 167 | return -ENOMEM; |
| 168 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 169 | mux_id = nla_get_u16(data[IFLA_VLAN_ID]); |
| 170 | |
| 171 | err = rmnet_register_real_device(real_dev); |
| 172 | if (err) |
| 173 | goto err0; |
| 174 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 175 | port = rmnet_get_port_rtnl(real_dev); |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 176 | err = rmnet_vnd_newlink(mux_id, dev, port, real_dev, ep); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 177 | if (err) |
| 178 | goto err1; |
| 179 | |
David Ahern | 42ab19e | 2017-10-04 17:48:47 -0700 | [diff] [blame] | 180 | err = netdev_master_upper_dev_link(dev, real_dev, NULL, NULL, extack); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 181 | if (err) |
| 182 | goto err2; |
| 183 | |
Subash Abhinov Kasiviswanathan | 032ee46 | 2017-09-02 23:30:44 -0600 | [diff] [blame] | 184 | netdev_dbg(dev, "data format [ingress 0x%08X] [egress 0x%08X]\n", |
| 185 | ingress_format, egress_format); |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 186 | port->egress_data_format = egress_format; |
| 187 | port->ingress_data_format = ingress_format; |
Subash Abhinov Kasiviswanathan | 9148963 | 2017-10-11 18:43:54 -0600 | [diff] [blame] | 188 | port->rmnet_mode = mode; |
Subash Abhinov Kasiviswanathan | 032ee46 | 2017-09-02 23:30:44 -0600 | [diff] [blame] | 189 | |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 190 | hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 191 | return 0; |
| 192 | |
| 193 | err2: |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 194 | rmnet_vnd_dellink(mux_id, port, ep); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 195 | err1: |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 196 | rmnet_unregister_real_device(real_dev, port); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 197 | err0: |
| 198 | return err; |
| 199 | } |
| 200 | |
| 201 | static void rmnet_dellink(struct net_device *dev, struct list_head *head) |
| 202 | { |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 203 | struct net_device *real_dev; |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 204 | struct rmnet_endpoint *ep; |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 205 | struct rmnet_port *port; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 206 | u8 mux_id; |
| 207 | |
| 208 | rcu_read_lock(); |
| 209 | real_dev = netdev_master_upper_dev_get_rcu(dev); |
| 210 | rcu_read_unlock(); |
| 211 | |
| 212 | if (!real_dev || !rmnet_is_real_dev_registered(real_dev)) |
| 213 | return; |
| 214 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 215 | port = rmnet_get_port_rtnl(real_dev); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 216 | |
| 217 | mux_id = rmnet_vnd_get_mux(dev); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 218 | netdev_upper_dev_unlink(dev, real_dev); |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 219 | |
| 220 | ep = rmnet_get_endpoint(port, mux_id); |
| 221 | if (ep) { |
| 222 | hlist_del_init_rcu(&ep->hlnode); |
Subash Abhinov Kasiviswanathan | 60d58f9 | 2017-10-11 18:43:58 -0600 | [diff] [blame] | 223 | rmnet_unregister_bridge(dev, port); |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 224 | rmnet_vnd_dellink(mux_id, port, ep); |
| 225 | kfree(ep); |
| 226 | } |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 227 | rmnet_unregister_real_device(real_dev, port); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 228 | |
| 229 | unregister_netdevice_queue(dev, head); |
| 230 | } |
| 231 | |
| 232 | static int rmnet_dev_walk_unreg(struct net_device *rmnet_dev, void *data) |
| 233 | { |
| 234 | struct rmnet_walk_data *d = data; |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 235 | struct rmnet_endpoint *ep; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 236 | u8 mux_id; |
| 237 | |
| 238 | mux_id = rmnet_vnd_get_mux(rmnet_dev); |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 239 | ep = rmnet_get_endpoint(d->port, mux_id); |
| 240 | if (ep) { |
| 241 | hlist_del_init_rcu(&ep->hlnode); |
| 242 | rmnet_vnd_dellink(mux_id, d->port, ep); |
| 243 | kfree(ep); |
| 244 | } |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 245 | netdev_upper_dev_unlink(rmnet_dev, d->real_dev); |
| 246 | unregister_netdevice_queue(rmnet_dev, d->head); |
| 247 | |
| 248 | return 0; |
| 249 | } |
| 250 | |
| 251 | static void rmnet_force_unassociate_device(struct net_device *dev) |
| 252 | { |
| 253 | struct net_device *real_dev = dev; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 254 | struct rmnet_walk_data d; |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 255 | struct rmnet_port *port; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 256 | LIST_HEAD(list); |
| 257 | |
| 258 | if (!rmnet_is_real_dev_registered(real_dev)) |
| 259 | return; |
| 260 | |
| 261 | ASSERT_RTNL(); |
| 262 | |
| 263 | d.real_dev = real_dev; |
| 264 | d.head = &list; |
| 265 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 266 | port = rmnet_get_port_rtnl(dev); |
| 267 | d.port = port; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 268 | |
| 269 | rcu_read_lock(); |
Subash Abhinov Kasiviswanathan | 60d58f9 | 2017-10-11 18:43:58 -0600 | [diff] [blame] | 270 | rmnet_unregister_bridge(dev, port); |
| 271 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 272 | netdev_walk_all_lower_dev_rcu(real_dev, rmnet_dev_walk_unreg, &d); |
| 273 | rcu_read_unlock(); |
| 274 | unregister_netdevice_many(&list); |
| 275 | |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 276 | rmnet_unregister_real_device(real_dev, port); |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 277 | } |
| 278 | |
| 279 | static int rmnet_config_notify_cb(struct notifier_block *nb, |
| 280 | unsigned long event, void *data) |
| 281 | { |
| 282 | struct net_device *dev = netdev_notifier_info_to_dev(data); |
| 283 | |
| 284 | if (!dev) |
| 285 | return NOTIFY_DONE; |
| 286 | |
| 287 | switch (event) { |
| 288 | case NETDEV_UNREGISTER: |
| 289 | netdev_dbg(dev, "Kernel unregister\n"); |
| 290 | rmnet_force_unassociate_device(dev); |
| 291 | break; |
| 292 | |
| 293 | default: |
| 294 | break; |
| 295 | } |
| 296 | |
| 297 | return NOTIFY_DONE; |
| 298 | } |
| 299 | |
| 300 | static struct notifier_block rmnet_dev_notifier __read_mostly = { |
| 301 | .notifier_call = rmnet_config_notify_cb, |
| 302 | }; |
| 303 | |
| 304 | static int rmnet_rtnl_validate(struct nlattr *tb[], struct nlattr *data[], |
| 305 | struct netlink_ext_ack *extack) |
| 306 | { |
| 307 | u16 mux_id; |
| 308 | |
| 309 | if (!data || !data[IFLA_VLAN_ID]) |
| 310 | return -EINVAL; |
| 311 | |
| 312 | mux_id = nla_get_u16(data[IFLA_VLAN_ID]); |
| 313 | if (mux_id > (RMNET_MAX_LOGICAL_EP - 1)) |
| 314 | return -ERANGE; |
| 315 | |
| 316 | return 0; |
| 317 | } |
| 318 | |
| 319 | static size_t rmnet_get_size(const struct net_device *dev) |
| 320 | { |
| 321 | return nla_total_size(2); /* IFLA_VLAN_ID */ |
| 322 | } |
| 323 | |
| 324 | struct rtnl_link_ops rmnet_link_ops __read_mostly = { |
| 325 | .kind = "rmnet", |
| 326 | .maxtype = __IFLA_VLAN_MAX, |
| 327 | .priv_size = sizeof(struct rmnet_priv), |
| 328 | .setup = rmnet_vnd_setup, |
| 329 | .validate = rmnet_rtnl_validate, |
| 330 | .newlink = rmnet_newlink, |
| 331 | .dellink = rmnet_dellink, |
| 332 | .get_size = rmnet_get_size, |
| 333 | }; |
| 334 | |
Subash Abhinov Kasiviswanathan | 032ee46 | 2017-09-02 23:30:44 -0600 | [diff] [blame] | 335 | /* Needs either rcu_read_lock() or rtnl lock */ |
Subash Abhinov Kasiviswanathan | b665f4f | 2017-09-02 23:30:46 -0600 | [diff] [blame] | 336 | struct rmnet_port *rmnet_get_port(struct net_device *real_dev) |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 337 | { |
Subash Abhinov Kasiviswanathan | 032ee46 | 2017-09-02 23:30:44 -0600 | [diff] [blame] | 338 | if (rmnet_is_real_dev_registered(real_dev)) |
| 339 | return rcu_dereference_rtnl(real_dev->rx_handler_data); |
| 340 | else |
| 341 | return NULL; |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 342 | } |
| 343 | |
Subash Abhinov Kasiviswanathan | 3352e6c | 2017-10-11 18:43:57 -0600 | [diff] [blame] | 344 | struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id) |
| 345 | { |
| 346 | struct rmnet_endpoint *ep; |
| 347 | |
| 348 | hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) { |
| 349 | if (ep->mux_id == mux_id) |
| 350 | return ep; |
| 351 | } |
| 352 | |
| 353 | return NULL; |
| 354 | } |
| 355 | |
Subash Abhinov Kasiviswanathan | 60d58f9 | 2017-10-11 18:43:58 -0600 | [diff] [blame] | 356 | int rmnet_add_bridge(struct net_device *rmnet_dev, |
| 357 | struct net_device *slave_dev, |
| 358 | struct netlink_ext_ack *extack) |
| 359 | { |
| 360 | struct rmnet_priv *priv = netdev_priv(rmnet_dev); |
| 361 | struct net_device *real_dev = priv->real_dev; |
| 362 | struct rmnet_port *port, *slave_port; |
| 363 | int err; |
| 364 | |
| 365 | port = rmnet_get_port(real_dev); |
| 366 | |
| 367 | /* If there is more than one rmnet dev attached, its probably being |
| 368 | * used for muxing. Skip the briding in that case |
| 369 | */ |
| 370 | if (port->nr_rmnet_devs > 1) |
| 371 | return -EINVAL; |
| 372 | |
| 373 | if (rmnet_is_real_dev_registered(slave_dev)) |
| 374 | return -EBUSY; |
| 375 | |
| 376 | err = rmnet_register_real_device(slave_dev); |
| 377 | if (err) |
| 378 | return -EBUSY; |
| 379 | |
| 380 | err = netdev_master_upper_dev_link(slave_dev, rmnet_dev, NULL, NULL, |
| 381 | extack); |
| 382 | if (err) |
| 383 | return -EINVAL; |
| 384 | |
| 385 | slave_port = rmnet_get_port(slave_dev); |
| 386 | slave_port->rmnet_mode = RMNET_EPMODE_BRIDGE; |
| 387 | slave_port->bridge_ep = real_dev; |
| 388 | |
| 389 | port->rmnet_mode = RMNET_EPMODE_BRIDGE; |
| 390 | port->bridge_ep = slave_dev; |
| 391 | |
| 392 | netdev_dbg(slave_dev, "registered with rmnet as slave\n"); |
| 393 | return 0; |
| 394 | } |
| 395 | |
| 396 | int rmnet_del_bridge(struct net_device *rmnet_dev, |
| 397 | struct net_device *slave_dev) |
| 398 | { |
| 399 | struct rmnet_priv *priv = netdev_priv(rmnet_dev); |
| 400 | struct net_device *real_dev = priv->real_dev; |
| 401 | struct rmnet_port *port, *slave_port; |
| 402 | |
| 403 | port = rmnet_get_port(real_dev); |
| 404 | port->rmnet_mode = RMNET_EPMODE_VND; |
| 405 | port->bridge_ep = NULL; |
| 406 | |
| 407 | netdev_upper_dev_unlink(slave_dev, rmnet_dev); |
| 408 | slave_port = rmnet_get_port(slave_dev); |
| 409 | rmnet_unregister_real_device(slave_dev, slave_port); |
| 410 | |
| 411 | netdev_dbg(slave_dev, "removed from rmnet as slave\n"); |
| 412 | return 0; |
| 413 | } |
| 414 | |
Subash Abhinov Kasiviswanathan | ceed73a | 2017-08-29 22:44:18 -0600 | [diff] [blame] | 415 | /* Startup/Shutdown */ |
| 416 | |
| 417 | static int __init rmnet_init(void) |
| 418 | { |
| 419 | int rc; |
| 420 | |
| 421 | rc = register_netdevice_notifier(&rmnet_dev_notifier); |
| 422 | if (rc != 0) |
| 423 | return rc; |
| 424 | |
| 425 | rc = rtnl_link_register(&rmnet_link_ops); |
| 426 | if (rc != 0) { |
| 427 | unregister_netdevice_notifier(&rmnet_dev_notifier); |
| 428 | return rc; |
| 429 | } |
| 430 | return rc; |
| 431 | } |
| 432 | |
| 433 | static void __exit rmnet_exit(void) |
| 434 | { |
| 435 | unregister_netdevice_notifier(&rmnet_dev_notifier); |
| 436 | rtnl_link_unregister(&rmnet_link_ops); |
| 437 | } |
| 438 | |
| 439 | module_init(rmnet_init) |
| 440 | module_exit(rmnet_exit) |
| 441 | MODULE_LICENSE("GPL v2"); |