| From stable+bounces-201110-greg=kroah.com@vger.kernel.org Mon Dec 15 22:51:01 2025 |
| From: Tony Nguyen <anthony.l.nguyen@intel.com> |
| Date: Mon, 15 Dec 2025 13:42:46 -0800 |
| Subject: idpf: stop Tx if there are insufficient buffer resources |
| To: stable@vger.kernel.org |
| Cc: Joshua Hay <joshua.a.hay@intel.com>, anthony.l.nguyen@intel.com, madhu.chittim@intel.com, decot@google.com, lrizzo@google.com, brianvv@google.com, Samuel Salin <Samuel.salin@intel.com> |
| Message-ID: <20251215214303.2608822-8-anthony.l.nguyen@intel.com> |
| |
| From: Joshua Hay <joshua.a.hay@intel.com> |
| |
| [ Upstream commit 0c3f135e840d4a2ba4253e15d530ec61bc30718e ] |
| |
| The Tx refillq logic will cause packets to be silently dropped if there |
| are not enough buffer resources available to send a packet in flow |
| scheduling mode. Instead, determine how many buffers are needed along |
| with number of descriptors. Make sure there are enough of both resources |
| to send the packet, and stop the queue if not. |
| |
| Fixes: 7292af042bcf ("idpf: fix a race in txq wakeup") |
| Signed-off-by: Joshua Hay <joshua.a.hay@intel.com> |
| Reviewed-by: Madhu Chittim <madhu.chittim@intel.com> |
| Tested-by: Samuel Salin <Samuel.salin@intel.com> |
| Signed-off-by: Tony Nguyen <anthony.l.nguyen@intel.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c | 4 - |
| drivers/net/ethernet/intel/idpf/idpf_txrx.c | 47 +++++++++++++------- |
| drivers/net/ethernet/intel/idpf/idpf_txrx.h | 15 +++++- |
| 3 files changed, 47 insertions(+), 19 deletions(-) |
| |
| --- a/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c |
| +++ b/drivers/net/ethernet/intel/idpf/idpf_singleq_txrx.c |
| @@ -415,11 +415,11 @@ netdev_tx_t idpf_tx_singleq_frame(struct |
| { |
| struct idpf_tx_offload_params offload = { }; |
| struct idpf_tx_buf *first; |
| + u32 count, buf_count = 1; |
| int csum, tso, needed; |
| - unsigned int count; |
| __be16 protocol; |
| |
| - count = idpf_tx_desc_count_required(tx_q, skb); |
| + count = idpf_tx_res_count_required(tx_q, skb, &buf_count); |
| if (unlikely(!count)) |
| return idpf_tx_drop_skb(tx_q, skb); |
| |
| --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.c |
| +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.c |
| @@ -2160,15 +2160,22 @@ void idpf_tx_splitq_build_flow_desc(unio |
| desc->flow.qw1.compl_tag = cpu_to_le16(params->compl_tag); |
| } |
| |
| -/* Global conditions to tell whether the txq (and related resources) |
| - * has room to allow the use of "size" descriptors. |
| +/** |
| + * idpf_tx_splitq_has_room - check if enough Tx splitq resources are available |
| + * @tx_q: the queue to be checked |
| + * @descs_needed: number of descriptors required for this packet |
| + * @bufs_needed: number of Tx buffers required for this packet |
| + * |
| + * Return: 0 if no room available, 1 otherwise |
| */ |
| -static int idpf_txq_has_room(struct idpf_tx_queue *tx_q, u32 size) |
| +static int idpf_txq_has_room(struct idpf_tx_queue *tx_q, u32 descs_needed, |
| + u32 bufs_needed) |
| { |
| - if (IDPF_DESC_UNUSED(tx_q) < size || |
| + if (IDPF_DESC_UNUSED(tx_q) < descs_needed || |
| IDPF_TX_COMPLQ_PENDING(tx_q->txq_grp) > |
| IDPF_TX_COMPLQ_OVERFLOW_THRESH(tx_q->txq_grp->complq) || |
| - IDPF_TX_BUF_RSV_LOW(tx_q)) |
| + IDPF_TX_BUF_RSV_LOW(tx_q) || |
| + idpf_tx_splitq_get_free_bufs(tx_q->refillq) < bufs_needed) |
| return 0; |
| return 1; |
| } |
| @@ -2177,14 +2184,21 @@ static int idpf_txq_has_room(struct idpf |
| * idpf_tx_maybe_stop_splitq - 1st level check for Tx splitq stop conditions |
| * @tx_q: the queue to be checked |
| * @descs_needed: number of descriptors required for this packet |
| + * @bufs_needed: number of buffers needed for this packet |
| * |
| - * Returns 0 if stop is not needed |
| + * Return: 0 if stop is not needed |
| */ |
| static int idpf_tx_maybe_stop_splitq(struct idpf_tx_queue *tx_q, |
| - unsigned int descs_needed) |
| + u32 descs_needed, |
| + u32 bufs_needed) |
| { |
| + /* Since we have multiple resources to check for splitq, our |
| + * start,stop_thrs becomes a boolean check instead of a count |
| + * threshold. |
| + */ |
| if (netif_subqueue_maybe_stop(tx_q->netdev, tx_q->idx, |
| - idpf_txq_has_room(tx_q, descs_needed), |
| + idpf_txq_has_room(tx_q, descs_needed, |
| + bufs_needed), |
| 1, 1)) |
| return 0; |
| |
| @@ -2226,14 +2240,16 @@ void idpf_tx_buf_hw_update(struct idpf_t |
| } |
| |
| /** |
| - * idpf_tx_desc_count_required - calculate number of Tx descriptors needed |
| + * idpf_tx_res_count_required - get number of Tx resources needed for this pkt |
| * @txq: queue to send buffer on |
| * @skb: send buffer |
| + * @bufs_needed: (output) number of buffers needed for this skb. |
| * |
| - * Returns number of data descriptors needed for this skb. |
| + * Return: number of data descriptors and buffers needed for this skb. |
| */ |
| -unsigned int idpf_tx_desc_count_required(struct idpf_tx_queue *txq, |
| - struct sk_buff *skb) |
| +unsigned int idpf_tx_res_count_required(struct idpf_tx_queue *txq, |
| + struct sk_buff *skb, |
| + u32 *bufs_needed) |
| { |
| const struct skb_shared_info *shinfo; |
| unsigned int count = 0, i; |
| @@ -2244,6 +2260,7 @@ unsigned int idpf_tx_desc_count_required |
| return count; |
| |
| shinfo = skb_shinfo(skb); |
| + *bufs_needed += shinfo->nr_frags; |
| for (i = 0; i < shinfo->nr_frags; i++) { |
| unsigned int size; |
| |
| @@ -2688,11 +2705,11 @@ static netdev_tx_t idpf_tx_splitq_frame( |
| .prev_ntu = tx_q->next_to_use, |
| }; |
| struct idpf_tx_buf *first; |
| - unsigned int count; |
| + u32 count, buf_count = 1; |
| u32 buf_id; |
| int tso; |
| |
| - count = idpf_tx_desc_count_required(tx_q, skb); |
| + count = idpf_tx_res_count_required(tx_q, skb, &buf_count); |
| if (unlikely(!count)) |
| return idpf_tx_drop_skb(tx_q, skb); |
| |
| @@ -2702,7 +2719,7 @@ static netdev_tx_t idpf_tx_splitq_frame( |
| |
| /* Check for splitq specific TX resources */ |
| count += (IDPF_TX_DESCS_PER_CACHE_LINE + tso); |
| - if (idpf_tx_maybe_stop_splitq(tx_q, count)) { |
| + if (idpf_tx_maybe_stop_splitq(tx_q, count, buf_count)) { |
| idpf_tx_buf_hw_update(tx_q, tx_q->next_to_use, false); |
| |
| return NETDEV_TX_BUSY; |
| --- a/drivers/net/ethernet/intel/idpf/idpf_txrx.h |
| +++ b/drivers/net/ethernet/intel/idpf/idpf_txrx.h |
| @@ -1034,6 +1034,17 @@ static inline void idpf_vport_intr_set_w |
| reg->dyn_ctl); |
| } |
| |
| +/** |
| + * idpf_tx_splitq_get_free_bufs - get number of free buf_ids in refillq |
| + * @refillq: pointer to refillq containing buf_ids |
| + */ |
| +static inline u32 idpf_tx_splitq_get_free_bufs(struct idpf_sw_queue *refillq) |
| +{ |
| + return (refillq->next_to_use > refillq->next_to_clean ? |
| + 0 : refillq->desc_count) + |
| + refillq->next_to_use - refillq->next_to_clean - 1; |
| +} |
| + |
| int idpf_vport_singleq_napi_poll(struct napi_struct *napi, int budget); |
| void idpf_vport_init_num_qs(struct idpf_vport *vport, |
| struct virtchnl2_create_vport *vport_msg); |
| @@ -1061,8 +1072,8 @@ void idpf_tx_buf_hw_update(struct idpf_t |
| bool xmit_more); |
| unsigned int idpf_size_to_txd_count(unsigned int size); |
| netdev_tx_t idpf_tx_drop_skb(struct idpf_tx_queue *tx_q, struct sk_buff *skb); |
| -unsigned int idpf_tx_desc_count_required(struct idpf_tx_queue *txq, |
| - struct sk_buff *skb); |
| +unsigned int idpf_tx_res_count_required(struct idpf_tx_queue *txq, |
| + struct sk_buff *skb, u32 *buf_count); |
| void idpf_tx_timeout(struct net_device *netdev, unsigned int txqueue); |
| netdev_tx_t idpf_tx_singleq_frame(struct sk_buff *skb, |
| struct idpf_tx_queue *tx_q); |