diff --git a/net/can/bcm.c b/net/can/bcm.c index 909b9e684e04..da03aca1f685 100644 --- a/net/can/bcm.c +++ b/net/can/bcm.c @@ -121,11 +121,18 @@ struct bcm_op { struct net_device *rx_reg_dev; }; +struct bcm_notifier_block { + /* Linked to bcm_notifier_list list. Becomes empty when removed. */ + struct list_head list; + /* Notifier call is in progress when this value is odd. */ + unsigned int sequence; +}; + struct bcm_sock { struct sock sk; int bound; int ifindex; - struct notifier_block notifier; + struct bcm_notifier_block notifier; struct list_head rx_ops; struct list_head tx_ops; unsigned long dropped_usr_msgs; @@ -1378,8 +1385,8 @@ static int bcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) /* * notification handler for netdevice status changes */ -static int bcm_notifier(struct notifier_block *nb, unsigned long msg, - void *ptr) +static void bcm_notify(struct bcm_notifier_block *nb, unsigned long msg, + void *ptr) { struct net_device *dev = netdev_notifier_info_to_dev(ptr); struct bcm_sock *bo = container_of(nb, struct bcm_sock, notifier); @@ -1388,10 +1395,10 @@ static int bcm_notifier(struct notifier_block *nb, unsigned long msg, int notify_enodev = 0; if (!net_eq(dev_net(dev), sock_net(sk))) - return NOTIFY_DONE; + return; if (dev->type != ARPHRD_CAN) - return NOTIFY_DONE; + return; switch (msg) { @@ -1426,10 +1433,49 @@ static int bcm_notifier(struct notifier_block *nb, unsigned long msg, sk->sk_error_report(sk); } } +} + +static DECLARE_WAIT_QUEUE_HEAD(bcm_notifier_wait); +static LIST_HEAD(bcm_notifier_list); +static DEFINE_SPINLOCK(bcm_notifier_lock); +static unsigned int bcm_notifier_sequence; /* Takes only even values. */ +static int bcm_notifier(struct notifier_block *nb, unsigned long msg, + void *ptr) +{ + struct bcm_notifier_block *notify; + + spin_lock(&bcm_notifier_lock); + bcm_notifier_sequence += 2; + restart: + list_for_each_entry(notify, &bcm_notifier_list, list) { + if (unlikely(notify->sequence == bcm_notifier_sequence)) + continue; + notify->sequence = bcm_notifier_sequence + 1; + spin_unlock(&bcm_notifier_lock); + bcm_notify(notify, msg, ptr); + spin_lock(&bcm_notifier_lock); + notify->sequence = bcm_notifier_sequence; + /* + * If bcm_release() did not remove this entry while we were at + * bcm_notify(), we can continue list traversal. Otherwise, + * teach bcm_release() to check that the sequence number became + * even, and then restart list traversal. The sequence number + * protects us from calling bcm_notify() for more than once. + */ + if (likely(!list_empty(¬ify->list))) + continue; + wake_up_all(&bcm_notifier_wait); + goto restart; + } + spin_unlock(&bcm_notifier_lock); return NOTIFY_DONE; } +static struct notifier_block canbcm_notifier = { + .notifier_call = bcm_notifier +}; + /* * initial settings for all BCM sockets to be set at socket creation time */ @@ -1446,9 +1492,10 @@ static int bcm_init(struct sock *sk) INIT_LIST_HEAD(&bo->rx_ops); /* set notifier */ - bo->notifier.notifier_call = bcm_notifier; - - register_netdevice_notifier(&bo->notifier); + spin_lock(&bcm_notifier_lock); + bo->notifier.sequence = bcm_notifier_sequence; + list_add_tail(&bo->notifier.list, &bcm_notifier_list); + spin_unlock(&bcm_notifier_lock); return 0; } @@ -1471,7 +1518,10 @@ static int bcm_release(struct socket *sock) /* remove bcm_ops, timer, rx_unregister(), etc. */ - unregister_netdevice_notifier(&bo->notifier); + spin_lock(&bcm_notifier_lock); + list_del_init(&bo->notifier.list); + spin_unlock(&bcm_notifier_lock); + wait_event(bcm_notifier_wait, !(bo->notifier.sequence & 1)); lock_sock(sk); @@ -1705,12 +1755,14 @@ static int __init bcm_module_init(void) } register_pernet_subsys(&canbcm_pernet_ops); + register_netdevice_notifier(&canbcm_notifier); return 0; } static void __exit bcm_module_exit(void) { can_proto_unregister(&bcm_can_proto); + unregister_netdevice_notifier(&canbcm_notifier); unregister_pernet_subsys(&canbcm_pernet_ops); }