Async netdev ops are tricky because of the following problems:
1) Freeing the context associated with async netdev ops might require
waiting for completion of the associated work which might require the
rtnl lock or the instance lock. However this will deadlock in
__dev_close_many as the cleanup is done with those locks already held.
2) We need a way to enable/disable async netdev ops depending on the PM
state to allow/prevent hardware access as appropriate.
We solve these problems by introducing a state variable to track
the current state of netdev. This can take the following values:
- ACTIVE (up and normal operation)
- DOWN (down)
- INACTIVE (in suspend/shutdown)
To solve 1, we set the state to down in __dev_close_many. In the
associated op handler, we check for the current state and return if
the netdev is down.
To solve 2, the commit introduces the following functions:
- netif_enable_async_ops -> sets state to ACTIVE
- netif_disable_async_ops -> sets state to INACTIVE and cancels any
pending work as required.
The op implementation can use the state information to do the required
processing.
Signed-off-by: I Viswanath <[email protected]>
---
include/linux/netdevice.h | 29 ++++++++++++++
net/core/dev.c | 84 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 111 insertions(+), 2 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ae269a2e7f4d..6d426dc66af9 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1062,6 +1062,16 @@ struct netdev_net_notifier {
struct notifier_block *nb;
};
+enum netif_async_state {
+ NETIF_ASYNC_ACTIVE,
+ NETIF_ASYNC_DOWN,
+ NETIF_ASYNC_INACTIVE
+};
+
+struct netif_async_ctx {
+ enum netif_async_state state;
+};
+
/*
* This structure defines the management hooks for network devices.
* The following hooks can be defined; unless noted otherwise, they are
@@ -2027,6 +2037,8 @@ enum netdev_reg_state {
* @sfp_bus: attached &struct sfp_bus structure.
*
* @qdisc_tx_busylock: lockdep class annotating Qdisc->busylock spinlock
+ * @async_ctx : Context required for async ops
+ * @needs_async_ctx : Does dev need async op context?
*
* @proto_down: protocol port state information can be sent to the
* switch driver and used to set the phys state of the
@@ -2454,6 +2466,8 @@ struct net_device {
struct phy_device *phydev;
struct sfp_bus *sfp_bus;
struct lock_class_key *qdisc_tx_busylock;
+ struct netif_async_ctx *async_ctx;
+ bool needs_async_ctx;
bool proto_down;
bool irq_affinity_auto;
bool rx_cpu_rmap_auto;
@@ -3376,6 +3390,21 @@ int dev_loopback_xmit(struct net *net, struct sock *sk,
struct sk_buff *newskb);
u16 dev_pick_tx_zero(struct net_device *dev, struct sk_buff *skb,
struct net_device *sb_dev);
+void netif_disable_async_ops(struct net_device *dev);
+void netif_enable_async_ops(struct net_device *dev);
+
+static inline void netif_set_async_state(struct net_device *dev,
+ enum netif_async_state state)
+{
+ dev->async_ctx->state = state;
+}
+
+static inline enum netif_async_state
+netif_get_async_state(struct net_device *dev)
+{
+ return dev->async_ctx->state;
+}
+
int __dev_queue_xmit(struct sk_buff *skb, struct net_device *sb_dev);
int __dev_direct_xmit(struct sk_buff *skb, u16 queue_id);
diff --git a/net/core/dev.c b/net/core/dev.c
index 200d44883fc1..b1797bd28a6b 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -1664,6 +1664,33 @@ static int napi_kthread_create(struct napi_struct *n)
return err;
}
+static int __netif_alloc_async_ctx(struct net_device *dev)
+{
+ dev->async_ctx = kzalloc_obj(*dev->async_ctx);
+ if (!dev->async_ctx)
+ return -ENOMEM;
+
+ netif_set_async_state(dev, NETIF_ASYNC_ACTIVE);
+ return 0;
+}
+
+static int netif_alloc_async_ctx(struct net_device *dev)
+{
+ int ret;
+
+ ret = __netif_alloc_async_ctx(dev);
+ return ret;
+}
+
+static void netif_free_async_ctx(struct net_device *dev)
+{
+ if (!dev->async_ctx)
+ return;
+
+ kfree(dev->async_ctx);
+ dev->async_ctx = NULL;
+}
+
static int __dev_open(struct net_device *dev, struct netlink_ext_ack *extack)
{
const struct net_device_ops *ops = dev->netdev_ops;
@@ -1698,14 +1725,18 @@ static int __dev_open(struct net_device *dev, struct
netlink_ext_ack *extack)
if (ops->ndo_validate_addr)
ret = ops->ndo_validate_addr(dev);
+ if (!ret && dev->needs_async_ctx)
+ ret = netif_alloc_async_ctx(dev);
+
if (!ret && ops->ndo_open)
ret = ops->ndo_open(dev);
netpoll_poll_enable(dev);
- if (ret)
+ if (ret) {
clear_bit(__LINK_STATE_START, &dev->state);
- else {
+ netif_free_async_ctx(dev);
+ } else {
netif_set_up(dev, true);
dev_set_rx_mode(dev);
dev_activate(dev);
@@ -1772,6 +1803,11 @@ static void __dev_close_many(struct list_head *head)
netdev_ops_assert_locked(dev);
+ if (dev->needs_async_ctx) {
+ netif_set_async_state(dev, NETIF_ASYNC_DOWN);
+ netif_free_async_ctx(dev);
+ }
+
if (ops->ndo_stop)
ops->ndo_stop(dev);
@@ -1821,6 +1857,50 @@ void netif_close(struct net_device *dev)
}
EXPORT_SYMBOL(netif_close);
+/* netif_disable_async_ops - disable execution of async NDOs.
+ *
+ * To be used in cases of the device shutting down, suspending or
+ * failing to resume.
+ *
+ * Should be called in the shutdown callback and in the PM suspend
+ * callbacks: @suspend(), @freeze(), @poweroff() and in the error
+ * path of PM resume callbacks.
+ */
+void netif_disable_async_ops(struct net_device *dev)
+{
+ netdev_lock_ops_compat(dev);
+
+ if (!dev->needs_async_ctx || !netif_running(dev)) {
+ netdev_unlock_ops_compat(dev);
+ return;
+ }
+
+ netif_set_async_state(dev, NETIF_ASYNC_INACTIVE);
+ netdev_unlock_ops_compat(dev);
+}
+EXPORT_SYMBOL(netif_disable_async_ops);
+
+/* netif_enable_async_ops - enable execution of async NDOs.
+ *
+ * To be used when the device attempts to resume or fails to suspend.
+ *
+ * Should be called in the PM resume callbacks: @resume(), @thaw(),
+ * @restore() and in the error path of PM suspend callbacks.
+ */
+void netif_enable_async_ops(struct net_device *dev)
+{
+ netdev_lock_ops_compat(dev);
+
+ if (!dev->needs_async_ctx || !netif_running(dev)) {
+ netdev_unlock_ops_compat(dev);
+ return;
+ }
+
+ netif_set_async_state(dev, NETIF_ASYNC_ACTIVE);
+ netdev_unlock_ops_compat(dev);
+}
+EXPORT_SYMBOL(netif_enable_async_ops);
+
void netif_disable_lro(struct net_device *dev)
{
struct net_device *lower_dev;
--
2.47.3