]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
net: qualcomm: rmnet: Fix crash on real dev unregistration
authorSubash Abhinov Kasiviswanathan <subashab@codeaurora.org>
Fri, 16 Feb 2018 22:56:37 +0000 (15:56 -0700)
committerDavid S. Miller <davem@davemloft.net>
Mon, 19 Feb 2018 16:17:33 +0000 (11:17 -0500)
With CONFIG_DEBUG_PREEMPT enabled, a crash with the following call
stack was observed when removing a real dev which had rmnet devices
attached to it.
To fix this, remove the netdev_upper link APIs and instead use the
existing information in rmnet_port and rmnet_priv to get the
association between real and rmnet devs.

BUG: sleeping function called from invalid context
in_atomic(): 0, irqs_disabled(): 0, pid: 5762, name: ip
Preemption disabled at:
[<ffffff9d49043564>] debug_object_active_state+0xa4/0x16c
Internal error: Oops - BUG: 0 [#1] PREEMPT SMP
Modules linked in:
PC is at ___might_sleep+0x13c/0x180
LR is at ___might_sleep+0x17c/0x180
[<ffffff9d48ce0924>] ___might_sleep+0x13c/0x180
[<ffffff9d48ce09c0>] __might_sleep+0x58/0x8c
[<ffffff9d49d6253c>] mutex_lock+0x2c/0x48
[<ffffff9d48ed4840>] kernfs_remove_by_name_ns+0x48/0xa8
[<ffffff9d48ed6ec8>] sysfs_remove_link+0x30/0x58
[<ffffff9d49b05840>] __netdev_adjacent_dev_remove+0x14c/0x1e0
[<ffffff9d49b05914>] __netdev_adjacent_dev_unlink_lists+0x40/0x68
[<ffffff9d49b08820>] netdev_upper_dev_unlink+0xb4/0x1fc
[<ffffff9d494a29f0>] rmnet_dev_walk_unreg+0x6c/0xc8
[<ffffff9d49b00b40>] netdev_walk_all_lower_dev_rcu+0x58/0xb4
[<ffffff9d494a30fc>] rmnet_config_notify_cb+0xf4/0x134
[<ffffff9d48cd21b4>] raw_notifier_call_chain+0x58/0x78
[<ffffff9d49b028a4>] call_netdevice_notifiers_info+0x48/0x78
[<ffffff9d49b0b568>] rollback_registered_many+0x230/0x3c8
[<ffffff9d49b0b738>] unregister_netdevice_many+0x38/0x94
[<ffffff9d49b1e110>] rtnl_delete_link+0x58/0x88
[<ffffff9d49b201dc>] rtnl_dellink+0xbc/0x1cc
[<ffffff9d49b2355c>] rtnetlink_rcv_msg+0xb0/0x244
[<ffffff9d49b5230c>] netlink_rcv_skb+0xb4/0xdc
[<ffffff9d49b204f4>] rtnetlink_rcv+0x34/0x44
[<ffffff9d49b51af0>] netlink_unicast+0x1ec/0x294
[<ffffff9d49b51fdc>] netlink_sendmsg+0x320/0x390
[<ffffff9d49ae6858>] sock_sendmsg+0x54/0x60
[<ffffff9d49ae6f94>] ___sys_sendmsg+0x298/0x2b0
[<ffffff9d49ae98f8>] SyS_sendmsg+0xb4/0xf0
[<ffffff9d48c83770>] el0_svc_naked+0x24/0x28

Fixes: ceed73a2cf4a ("drivers: net: ethernet: qualcomm: rmnet: Initial implementation")
Fixes: 60d58f971c10 ("net: qualcomm: rmnet: Implement bridge mode")
Signed-off-by: Subash Abhinov Kasiviswanathan <subashab@codeaurora.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c

index 7e7704daf5f1e4928d69c1fa397a5f13354af2b8..c4949183eef3f0654cf8e5855ba6b85bce6b35f3 100644 (file)
 
 /* Local Definitions and Declarations */
 
-struct rmnet_walk_data {
-       struct net_device *real_dev;
-       struct list_head *head;
-       struct rmnet_port *port;
-};
-
 static int rmnet_is_real_dev_registered(const struct net_device *real_dev)
 {
        return rcu_access_pointer(real_dev->rx_handler) == rmnet_rx_handler;
@@ -112,17 +106,14 @@ static int rmnet_register_real_device(struct net_device *real_dev)
 static void rmnet_unregister_bridge(struct net_device *dev,
                                    struct rmnet_port *port)
 {
-       struct net_device *rmnet_dev, *bridge_dev;
        struct rmnet_port *bridge_port;
+       struct net_device *bridge_dev;
 
        if (port->rmnet_mode != RMNET_EPMODE_BRIDGE)
                return;
 
        /* bridge slave handling */
        if (!port->nr_rmnet_devs) {
-               rmnet_dev = netdev_master_upper_dev_get_rcu(dev);
-               netdev_upper_dev_unlink(dev, rmnet_dev);
-
                bridge_dev = port->bridge_ep;
 
                bridge_port = rmnet_get_port_rtnl(bridge_dev);
@@ -132,9 +123,6 @@ static void rmnet_unregister_bridge(struct net_device *dev,
                bridge_dev = port->bridge_ep;
 
                bridge_port = rmnet_get_port_rtnl(bridge_dev);
-               rmnet_dev = netdev_master_upper_dev_get_rcu(bridge_dev);
-               netdev_upper_dev_unlink(bridge_dev, rmnet_dev);
-
                rmnet_unregister_real_device(bridge_dev, bridge_port);
        }
 }
@@ -173,10 +161,6 @@ static int rmnet_newlink(struct net *src_net, struct net_device *dev,
        if (err)
                goto err1;
 
-       err = netdev_master_upper_dev_link(dev, real_dev, NULL, NULL, extack);
-       if (err)
-               goto err2;
-
        port->rmnet_mode = mode;
 
        hlist_add_head_rcu(&ep->hlnode, &port->muxed_ep[mux_id]);
@@ -193,8 +177,6 @@ static int rmnet_newlink(struct net *src_net, struct net_device *dev,
 
        return 0;
 
-err2:
-       rmnet_vnd_dellink(mux_id, port, ep);
 err1:
        rmnet_unregister_real_device(real_dev, port);
 err0:
@@ -204,14 +186,13 @@ err0:
 
 static void rmnet_dellink(struct net_device *dev, struct list_head *head)
 {
+       struct rmnet_priv *priv = netdev_priv(dev);
        struct net_device *real_dev;
        struct rmnet_endpoint *ep;
        struct rmnet_port *port;
        u8 mux_id;
 
-       rcu_read_lock();
-       real_dev = netdev_master_upper_dev_get_rcu(dev);
-       rcu_read_unlock();
+       real_dev = priv->real_dev;
 
        if (!real_dev || !rmnet_is_real_dev_registered(real_dev))
                return;
@@ -219,7 +200,6 @@ static void rmnet_dellink(struct net_device *dev, struct list_head *head)
        port = rmnet_get_port_rtnl(real_dev);
 
        mux_id = rmnet_vnd_get_mux(dev);
-       netdev_upper_dev_unlink(dev, real_dev);
 
        ep = rmnet_get_endpoint(port, mux_id);
        if (ep) {
@@ -233,30 +213,13 @@ static void rmnet_dellink(struct net_device *dev, struct list_head *head)
        unregister_netdevice_queue(dev, head);
 }
 
-static int rmnet_dev_walk_unreg(struct net_device *rmnet_dev, void *data)
-{
-       struct rmnet_walk_data *d = data;
-       struct rmnet_endpoint *ep;
-       u8 mux_id;
-
-       mux_id = rmnet_vnd_get_mux(rmnet_dev);
-       ep = rmnet_get_endpoint(d->port, mux_id);
-       if (ep) {
-               hlist_del_init_rcu(&ep->hlnode);
-               rmnet_vnd_dellink(mux_id, d->port, ep);
-               kfree(ep);
-       }
-       netdev_upper_dev_unlink(rmnet_dev, d->real_dev);
-       unregister_netdevice_queue(rmnet_dev, d->head);
-
-       return 0;
-}
-
 static void rmnet_force_unassociate_device(struct net_device *dev)
 {
        struct net_device *real_dev = dev;
-       struct rmnet_walk_data d;
+       struct hlist_node *tmp_ep;
+       struct rmnet_endpoint *ep;
        struct rmnet_port *port;
+       unsigned long bkt_ep;
        LIST_HEAD(list);
 
        if (!rmnet_is_real_dev_registered(real_dev))
@@ -264,16 +227,19 @@ static void rmnet_force_unassociate_device(struct net_device *dev)
 
        ASSERT_RTNL();
 
-       d.real_dev = real_dev;
-       d.head = &list;
-
        port = rmnet_get_port_rtnl(dev);
-       d.port = port;
 
        rcu_read_lock();
        rmnet_unregister_bridge(dev, port);
 
-       netdev_walk_all_lower_dev_rcu(real_dev, rmnet_dev_walk_unreg, &d);
+       hash_for_each_safe(port->muxed_ep, bkt_ep, tmp_ep, ep, hlnode) {
+               unregister_netdevice_queue(ep->egress_dev, &list);
+               rmnet_vnd_dellink(ep->mux_id, port, ep);
+
+               hlist_del_init_rcu(&ep->hlnode);
+               kfree(ep);
+       }
+
        rcu_read_unlock();
        unregister_netdevice_many(&list);
 
@@ -422,11 +388,6 @@ int rmnet_add_bridge(struct net_device *rmnet_dev,
        if (err)
                return -EBUSY;
 
-       err = netdev_master_upper_dev_link(slave_dev, rmnet_dev, NULL, NULL,
-                                          extack);
-       if (err)
-               return -EINVAL;
-
        slave_port = rmnet_get_port(slave_dev);
        slave_port->rmnet_mode = RMNET_EPMODE_BRIDGE;
        slave_port->bridge_ep = real_dev;
@@ -449,7 +410,6 @@ int rmnet_del_bridge(struct net_device *rmnet_dev,
        port->rmnet_mode = RMNET_EPMODE_VND;
        port->bridge_ep = NULL;
 
-       netdev_upper_dev_unlink(slave_dev, rmnet_dev);
        slave_port = rmnet_get_port(slave_dev);
        rmnet_unregister_real_device(slave_dev, slave_port);