From 494bef4c2a087876e75f3e95f7f63b06d6a65921 Mon Sep 17 00:00:00 2001 From: Edward Cree <ecree@solarflare.com> Date: Fri, 13 Apr 2018 19:17:22 +0100 Subject: [PATCH 1/3] sfc: insert ARFS filters with replace_equal=true Necessary to allow redirecting a flow when the application moves. Fixes: 3af0f34290f6 ("sfc: replace asynchronous filter operations") Signed-off-by: Edward Cree <ecree@solarflare.com> Signed-off-by: David S. Miller <davem@davemloft.net> --- drivers/net/ethernet/sfc/rx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/ethernet/sfc/rx.c b/drivers/net/ethernet/sfc/rx.c index 95682831484e84..13b0eb71dbf30f 100644 --- a/drivers/net/ethernet/sfc/rx.c +++ b/drivers/net/ethernet/sfc/rx.c @@ -851,7 +851,7 @@ static void efx_filter_rfs_work(struct work_struct *data) struct efx_channel *channel = efx_get_channel(efx, req->rxq_index); int rc; - rc = efx->type->filter_insert(efx, &req->spec, false); + rc = efx->type->filter_insert(efx, &req->spec, true); if (rc >= 0) { /* Remember this so we can check whether to expire the filter * later. From a7f80189e41c96c0c6210e9198a31859c91eb3e5 Mon Sep 17 00:00:00 2001 From: Edward Cree <ecree@solarflare.com> Date: Fri, 13 Apr 2018 19:17:49 +0100 Subject: [PATCH 2/3] sfc: pass the correctly bogus filter_id to rps_may_expire_flow() When we inserted an ARFS filter for ndo_rx_flow_steer(), we didn't know what the filter ID would be, so we just returned 0. Thus, we must also pass 0 as the filter ID when calling rps_may_expire_flow() for it, and rely on the flow_id to identify what we're talking about. Fixes: 3af0f34290f6 ("sfc: replace asynchronous filter operations") Signed-off-by: Edward Cree <ecree@solarflare.com> Signed-off-by: David S. Miller <davem@davemloft.net> --- drivers/net/ethernet/sfc/ef10.c | 3 +-- drivers/net/ethernet/sfc/farch.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/ethernet/sfc/ef10.c b/drivers/net/ethernet/sfc/ef10.c index 50daad0a14823f..36f24c7e553a32 100644 --- a/drivers/net/ethernet/sfc/ef10.c +++ b/drivers/net/ethernet/sfc/ef10.c @@ -4776,8 +4776,7 @@ static bool efx_ef10_filter_rfs_expire_one(struct efx_nic *efx, u32 flow_id, goto out_unlock; } - if (!rps_may_expire_flow(efx->net_dev, spec->dmaq_id, - flow_id, filter_idx)) { + if (!rps_may_expire_flow(efx->net_dev, spec->dmaq_id, flow_id, 0)) { ret = false; goto out_unlock; } diff --git a/drivers/net/ethernet/sfc/farch.c b/drivers/net/ethernet/sfc/farch.c index 4a19c7efdf8d6c..7174ef5e5c5e31 100644 --- a/drivers/net/ethernet/sfc/farch.c +++ b/drivers/net/ethernet/sfc/farch.c @@ -2912,7 +2912,7 @@ bool efx_farch_filter_rfs_expire_one(struct efx_nic *efx, u32 flow_id, if (test_bit(index, table->used_bitmap) && table->spec[index].priority == EFX_FILTER_PRI_HINT && rps_may_expire_flow(efx->net_dev, table->spec[index].dmaq_id, - flow_id, index)) { + flow_id, 0)) { efx_farch_filter_table_clear_entry(efx, table, index); ret = true; } From f993740ee05821307eca03d23d468895740450f8 Mon Sep 17 00:00:00 2001 From: Edward Cree <ecree@solarflare.com> Date: Fri, 13 Apr 2018 19:18:09 +0100 Subject: [PATCH 3/3] sfc: limit ARFS workitems in flight per channel A misconfigured system (e.g. with all interrupts affinitised to all CPUs) may produce a storm of ARFS steering events. With the existing sfc ARFS implementation, that could create a backlog of workitems that grinds the system to a halt. To prevent this, limit the number of workitems that may be in flight for a given SFC device to 8 (EFX_RPS_MAX_IN_FLIGHT), and return EBUSY from our ndo_rx_flow_steer method if the limit is reached. Given this limit, also store the workitems in an array of slots within the struct efx_nic, rather than dynamically allocating for each request. The limit should not negatively impact performance, because it is only likely to be hit in cases where ARFS will be ineffective anyway. Signed-off-by: Edward Cree <ecree@solarflare.com> Signed-off-by: David S. Miller <davem@davemloft.net> --- drivers/net/ethernet/sfc/net_driver.h | 25 ++++++++++++ drivers/net/ethernet/sfc/rx.c | 58 ++++++++++++++------------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/drivers/net/ethernet/sfc/net_driver.h b/drivers/net/ethernet/sfc/net_driver.h index 5e379a83c729dd..eea3808b3f250f 100644 --- a/drivers/net/ethernet/sfc/net_driver.h +++ b/drivers/net/ethernet/sfc/net_driver.h @@ -733,6 +733,27 @@ struct efx_rss_context { u32 rx_indir_table[128]; }; +#ifdef CONFIG_RFS_ACCEL +/** + * struct efx_async_filter_insertion - Request to asynchronously insert a filter + * @net_dev: Reference to the netdevice + * @spec: The filter to insert + * @work: Workitem for this request + * @rxq_index: Identifies the channel for which this request was made + * @flow_id: Identifies the kernel-side flow for which this request was made + */ +struct efx_async_filter_insertion { + struct net_device *net_dev; + struct efx_filter_spec spec; + struct work_struct work; + u16 rxq_index; + u32 flow_id; +}; + +/* Maximum number of ARFS workitems that may be in flight on an efx_nic */ +#define EFX_RPS_MAX_IN_FLIGHT 8 +#endif /* CONFIG_RFS_ACCEL */ + /** * struct efx_nic - an Efx NIC * @name: Device name (net device name or bus id before net device registered) @@ -850,6 +871,8 @@ struct efx_rss_context { * @rps_expire_channel: Next channel to check for expiry * @rps_expire_index: Next index to check for expiry in * @rps_expire_channel's @rps_flow_id + * @rps_slot_map: bitmap of in-flight entries in @rps_slot + * @rps_slot: array of ARFS insertion requests for efx_filter_rfs_work() * @active_queues: Count of RX and TX queues that haven't been flushed and drained. * @rxq_flush_pending: Count of number of receive queues that need to be flushed. * Decremented when the efx_flush_rx_queue() is called. @@ -1004,6 +1027,8 @@ struct efx_nic { struct mutex rps_mutex; unsigned int rps_expire_channel; unsigned int rps_expire_index; + unsigned long rps_slot_map; + struct efx_async_filter_insertion rps_slot[EFX_RPS_MAX_IN_FLIGHT]; #endif atomic_t active_queues; diff --git a/drivers/net/ethernet/sfc/rx.c b/drivers/net/ethernet/sfc/rx.c index 13b0eb71dbf30f..9c593c661cbfd9 100644 --- a/drivers/net/ethernet/sfc/rx.c +++ b/drivers/net/ethernet/sfc/rx.c @@ -827,28 +827,13 @@ MODULE_PARM_DESC(rx_refill_threshold, #ifdef CONFIG_RFS_ACCEL -/** - * struct efx_async_filter_insertion - Request to asynchronously insert a filter - * @net_dev: Reference to the netdevice - * @spec: The filter to insert - * @work: Workitem for this request - * @rxq_index: Identifies the channel for which this request was made - * @flow_id: Identifies the kernel-side flow for which this request was made - */ -struct efx_async_filter_insertion { - struct net_device *net_dev; - struct efx_filter_spec spec; - struct work_struct work; - u16 rxq_index; - u32 flow_id; -}; - static void efx_filter_rfs_work(struct work_struct *data) { struct efx_async_filter_insertion *req = container_of(data, struct efx_async_filter_insertion, work); struct efx_nic *efx = netdev_priv(req->net_dev); struct efx_channel *channel = efx_get_channel(efx, req->rxq_index); + int slot_idx = req - efx->rps_slot; int rc; rc = efx->type->filter_insert(efx, &req->spec, true); @@ -878,8 +863,8 @@ static void efx_filter_rfs_work(struct work_struct *data) } /* Release references */ + clear_bit(slot_idx, &efx->rps_slot_map); dev_put(req->net_dev); - kfree(req); } int efx_filter_rfs(struct net_device *net_dev, const struct sk_buff *skb, @@ -888,22 +873,36 @@ int efx_filter_rfs(struct net_device *net_dev, const struct sk_buff *skb, struct efx_nic *efx = netdev_priv(net_dev); struct efx_async_filter_insertion *req; struct flow_keys fk; + int slot_idx; + int rc; - if (flow_id == RPS_FLOW_ID_INVALID) - return -EINVAL; + /* find a free slot */ + for (slot_idx = 0; slot_idx < EFX_RPS_MAX_IN_FLIGHT; slot_idx++) + if (!test_and_set_bit(slot_idx, &efx->rps_slot_map)) + break; + if (slot_idx >= EFX_RPS_MAX_IN_FLIGHT) + return -EBUSY; - if (!skb_flow_dissect_flow_keys(skb, &fk, 0)) - return -EPROTONOSUPPORT; + if (flow_id == RPS_FLOW_ID_INVALID) { + rc = -EINVAL; + goto out_clear; + } - if (fk.basic.n_proto != htons(ETH_P_IP) && fk.basic.n_proto != htons(ETH_P_IPV6)) - return -EPROTONOSUPPORT; - if (fk.control.flags & FLOW_DIS_IS_FRAGMENT) - return -EPROTONOSUPPORT; + if (!skb_flow_dissect_flow_keys(skb, &fk, 0)) { + rc = -EPROTONOSUPPORT; + goto out_clear; + } - req = kmalloc(sizeof(*req), GFP_ATOMIC); - if (!req) - return -ENOMEM; + if (fk.basic.n_proto != htons(ETH_P_IP) && fk.basic.n_proto != htons(ETH_P_IPV6)) { + rc = -EPROTONOSUPPORT; + goto out_clear; + } + if (fk.control.flags & FLOW_DIS_IS_FRAGMENT) { + rc = -EPROTONOSUPPORT; + goto out_clear; + } + req = efx->rps_slot + slot_idx; efx_filter_init_rx(&req->spec, EFX_FILTER_PRI_HINT, efx->rx_scatter ? EFX_FILTER_FLAG_RX_SCATTER : 0, rxq_index); @@ -933,6 +932,9 @@ int efx_filter_rfs(struct net_device *net_dev, const struct sk_buff *skb, req->flow_id = flow_id; schedule_work(&req->work); return 0; +out_clear: + clear_bit(slot_idx, &efx->rps_slot_map); + return rc; } bool __efx_filter_rfs_expire(struct efx_nic *efx, unsigned int quota)