| From 9abefcb1aaa58b9d5aa40a8bb12c87d02415e4c8 Mon Sep 17 00:00:00 2001 |
| From: Sergei Miroshnichenko <sergeimir@emcraft.com> |
| Date: Wed, 7 Sep 2016 16:51:12 +0300 |
| Subject: can: dev: fix deadlock reported after bus-off |
| |
| From: Sergei Miroshnichenko <sergeimir@emcraft.com> |
| |
| commit 9abefcb1aaa58b9d5aa40a8bb12c87d02415e4c8 upstream. |
| |
| A timer was used to restart after the bus-off state, leading to a |
| relatively large can_restart() executed in an interrupt context, |
| which in turn sets up pinctrl. When this happens during system boot, |
| there is a high probability of grabbing the pinctrl_list_mutex, |
| which is locked already by the probe() of other device, making the |
| kernel suspect a deadlock condition [1]. |
| |
| To resolve this issue, the restart_timer is replaced by a delayed |
| work. |
| |
| [1] https://github.com/victronenergy/venus/issues/24 |
| |
| Signed-off-by: Sergei Miroshnichenko <sergeimir@emcraft.com> |
| Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/net/can/dev.c | 27 +++++++++++++++++---------- |
| include/linux/can/dev.h | 3 ++- |
| 2 files changed, 19 insertions(+), 11 deletions(-) |
| |
| --- a/drivers/net/can/dev.c |
| +++ b/drivers/net/can/dev.c |
| @@ -21,6 +21,7 @@ |
| #include <linux/slab.h> |
| #include <linux/netdevice.h> |
| #include <linux/if_arp.h> |
| +#include <linux/workqueue.h> |
| #include <linux/can.h> |
| #include <linux/can/dev.h> |
| #include <linux/can/skb.h> |
| @@ -471,9 +472,8 @@ EXPORT_SYMBOL_GPL(can_free_echo_skb); |
| /* |
| * CAN device restart for bus-off recovery |
| */ |
| -static void can_restart(unsigned long data) |
| +static void can_restart(struct net_device *dev) |
| { |
| - struct net_device *dev = (struct net_device *)data; |
| struct can_priv *priv = netdev_priv(dev); |
| struct net_device_stats *stats = &dev->stats; |
| struct sk_buff *skb; |
| @@ -513,6 +513,14 @@ restart: |
| netdev_err(dev, "Error %d during restart", err); |
| } |
| |
| +static void can_restart_work(struct work_struct *work) |
| +{ |
| + struct delayed_work *dwork = to_delayed_work(work); |
| + struct can_priv *priv = container_of(dwork, struct can_priv, restart_work); |
| + |
| + can_restart(priv->dev); |
| +} |
| + |
| int can_restart_now(struct net_device *dev) |
| { |
| struct can_priv *priv = netdev_priv(dev); |
| @@ -526,8 +534,8 @@ int can_restart_now(struct net_device *d |
| if (priv->state != CAN_STATE_BUS_OFF) |
| return -EBUSY; |
| |
| - /* Runs as soon as possible in the timer context */ |
| - mod_timer(&priv->restart_timer, jiffies); |
| + cancel_delayed_work_sync(&priv->restart_work); |
| + can_restart(dev); |
| |
| return 0; |
| } |
| @@ -548,8 +556,8 @@ void can_bus_off(struct net_device *dev) |
| netif_carrier_off(dev); |
| |
| if (priv->restart_ms) |
| - mod_timer(&priv->restart_timer, |
| - jiffies + (priv->restart_ms * HZ) / 1000); |
| + schedule_delayed_work(&priv->restart_work, |
| + msecs_to_jiffies(priv->restart_ms)); |
| } |
| EXPORT_SYMBOL_GPL(can_bus_off); |
| |
| @@ -658,6 +666,7 @@ struct net_device *alloc_candev(int size |
| return NULL; |
| |
| priv = netdev_priv(dev); |
| + priv->dev = dev; |
| |
| if (echo_skb_max) { |
| priv->echo_skb_max = echo_skb_max; |
| @@ -667,7 +676,7 @@ struct net_device *alloc_candev(int size |
| |
| priv->state = CAN_STATE_STOPPED; |
| |
| - init_timer(&priv->restart_timer); |
| + INIT_DELAYED_WORK(&priv->restart_work, can_restart_work); |
| |
| return dev; |
| } |
| @@ -748,8 +757,6 @@ int open_candev(struct net_device *dev) |
| if (!netif_carrier_ok(dev)) |
| netif_carrier_on(dev); |
| |
| - setup_timer(&priv->restart_timer, can_restart, (unsigned long)dev); |
| - |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(open_candev); |
| @@ -764,7 +771,7 @@ void close_candev(struct net_device *dev |
| { |
| struct can_priv *priv = netdev_priv(dev); |
| |
| - del_timer_sync(&priv->restart_timer); |
| + cancel_delayed_work_sync(&priv->restart_work); |
| can_flush_echo_skb(dev); |
| } |
| EXPORT_SYMBOL_GPL(close_candev); |
| --- a/include/linux/can/dev.h |
| +++ b/include/linux/can/dev.h |
| @@ -32,6 +32,7 @@ enum can_mode { |
| * CAN common private data |
| */ |
| struct can_priv { |
| + struct net_device *dev; |
| struct can_device_stats can_stats; |
| |
| struct can_bittiming bittiming, data_bittiming; |
| @@ -47,7 +48,7 @@ struct can_priv { |
| u32 ctrlmode_static; /* static enabled options for driver/hardware */ |
| |
| int restart_ms; |
| - struct timer_list restart_timer; |
| + struct delayed_work restart_work; |
| |
| int (*do_set_bittiming)(struct net_device *dev); |
| int (*do_set_data_bittiming)(struct net_device *dev); |