realtek: dsa: rtl83xx: flush scheduled work on removal
The workqueue items don't need to be processed directly when they are scheduled. It can happen that they are simply processed at a much later time. It is therefore necessary to ensure that all workqueue items of a driver are no longer being processed before the driver (or structures of this driver) are destroyed. When skipping this step, the driver driver can cause a kernel Oops on reboot. Unfortunately, it is not recommended [1] to flush items out of the system workqueue - simply because this can cause deadlocks. The driver itself must have a private workqueue which is then flushed. [1] https://lkml.kernel.org/r/49925af7-78a8-a3dd-bce6-cfc02e1a9236@I-love.SAKURA.ne.jp Signed-off-by: Issam Hamdi <ih@simonwunderlich.de> Signed-off-by: Harshal Gohel <hg@simonwunderlich.de> Signed-off-by: Sharadanand Karanjkar <sk@simonwunderlich.de> Link: https://github.com/openwrt/openwrt/pull/19570 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
This commit is contained in:
parent
0fa192a3a2
commit
feec7cf34d
@ -1330,7 +1330,7 @@ static int rtl83xx_netevent_event(struct notifier_block *this,
|
||||
|
||||
pr_debug("%s: updating neighbour on port %d, mac %016llx\n",
|
||||
__func__, port, net_work->mac);
|
||||
schedule_work(&net_work->work);
|
||||
queue_work(priv->wq, &net_work->work);
|
||||
if (err)
|
||||
netdev_warn(dev, "failed to handle neigh update (err %d)\n", err);
|
||||
break;
|
||||
@ -1452,7 +1452,7 @@ static int rtl83xx_fib_event(struct notifier_block *this, unsigned long event, v
|
||||
break;
|
||||
}
|
||||
|
||||
schedule_work(&fib_work->work);
|
||||
queue_work(priv->wq, &fib_work->work);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
@ -1486,6 +1486,7 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
|
||||
priv->ds->ops = &rtl83xx_switch_ops;
|
||||
priv->ds->needs_standalone_vlan_filtering = true;
|
||||
priv->dev = dev;
|
||||
dev_set_drvdata(dev, priv);
|
||||
|
||||
err = devm_mutex_init(dev, &priv->reg_mutex);
|
||||
if (err)
|
||||
@ -1583,10 +1584,16 @@ static int __init rtl83xx_sw_probe(struct platform_device *pdev)
|
||||
return err;
|
||||
}
|
||||
|
||||
priv->wq = create_singlethread_workqueue("rtl83xx");
|
||||
if (!priv->wq) {
|
||||
dev_err(dev, "Error creating workqueue: %d\n", err);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
err = dsa_register_switch(priv->ds);
|
||||
if (err) {
|
||||
dev_err(dev, "Error registering switch: %d\n", err);
|
||||
return err;
|
||||
goto err_register_switch;
|
||||
}
|
||||
|
||||
/* dsa_to_port returns dsa_port from the port list in
|
||||
@ -1694,13 +1701,37 @@ err_register_fib_nb:
|
||||
err_register_ne_nb:
|
||||
unregister_netdevice_notifier(&priv->nb);
|
||||
err_register_nb:
|
||||
dsa_switch_shutdown(priv->ds);
|
||||
err_register_switch:
|
||||
destroy_workqueue(priv->wq);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void rtl83xx_sw_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct rtl838x_switch_priv *priv = platform_get_drvdata(pdev);
|
||||
|
||||
if (!priv)
|
||||
return;
|
||||
|
||||
/* TODO: */
|
||||
pr_debug("Removing platform driver for rtl83xx-sw\n");
|
||||
|
||||
/* unregister notifiers which will create workqueue entries with
|
||||
* references to the switch structures. Also stop self-arming delayed
|
||||
* work items to avoid them still accessing the DSA structures
|
||||
* when they are getting shut down.
|
||||
*/
|
||||
unregister_fib_notifier(&init_net, &priv->fib_nb);
|
||||
unregister_netevent_notifier(&priv->ne_nb);
|
||||
cancel_delayed_work_sync(&priv->counters_work);
|
||||
|
||||
dsa_switch_shutdown(priv->ds);
|
||||
|
||||
destroy_workqueue(priv->wq);
|
||||
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
}
|
||||
|
||||
static const struct of_device_id rtl83xx_switch_of_ids[] = {
|
||||
|
||||
@ -1236,7 +1236,8 @@ static void rtldsa_poll_counters(struct work_struct *work)
|
||||
spin_unlock(&counters->lock);
|
||||
}
|
||||
|
||||
schedule_delayed_work(&priv->counters_work, RTLDSA_COUNTERS_POLL_INTERVAL);
|
||||
queue_delayed_work(priv->wq, &priv->counters_work,
|
||||
RTLDSA_COUNTERS_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
static void rtldsa_init_counters(struct rtl838x_switch_priv *priv)
|
||||
@ -1254,7 +1255,8 @@ static void rtldsa_init_counters(struct rtl838x_switch_priv *priv)
|
||||
}
|
||||
|
||||
INIT_DELAYED_WORK(&priv->counters_work, rtldsa_poll_counters);
|
||||
schedule_delayed_work(&priv->counters_work, RTLDSA_COUNTERS_POLL_INTERVAL);
|
||||
queue_delayed_work(priv->wq, &priv->counters_work,
|
||||
RTLDSA_COUNTERS_POLL_INTERVAL);
|
||||
}
|
||||
|
||||
static void rtldsa_get_strings(struct dsa_switch *ds,
|
||||
|
||||
@ -1143,6 +1143,7 @@ struct rtl838x_switch_priv {
|
||||
u32 lag_primary[MAX_LAGS];
|
||||
u32 is_lagmember[57];
|
||||
u64 lagmembers;
|
||||
struct workqueue_struct *wq;
|
||||
struct notifier_block nb; /* TODO: change to different name */
|
||||
struct notifier_block ne_nb;
|
||||
struct notifier_block fib_nb;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user