| From db355e4ed765bb298b241c7fceceb4fc96c95e16 Mon Sep 17 00:00:00 2001 |
| From: Denis Bolotin <denis.bolotin@cavium.com> |
| Date: Thu, 8 Nov 2018 16:46:09 +0200 |
| Subject: qed: Fix blocking/unlimited SPQ entries leak |
| |
| [ Upstream commit 2632f22ebd08da249c2017962a199a0cfb2324bf ] |
| |
| When there are no SPQ entries left in the free_pool, new entries are |
| allocated and are added to the unlimited list. When an entry in the pool |
| is available, the content is copied from the original entry, and the new |
| entry is sent to the device. qed_spq_post() is not aware of that, so the |
| additional entry is stored in the original entry as p_post_ent, which can |
| later be returned to the pool. |
| |
| Signed-off-by: Denis Bolotin <denis.bolotin@cavium.com> |
| Signed-off-by: Michal Kalderon <michal.kalderon@cavium.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/net/ethernet/qlogic/qed/qed_sp.h | 3 ++ |
| drivers/net/ethernet/qlogic/qed/qed_spq.c | 57 ++++++++++++----------- |
| 2 files changed, 33 insertions(+), 27 deletions(-) |
| |
| diff --git a/drivers/net/ethernet/qlogic/qed/qed_sp.h b/drivers/net/ethernet/qlogic/qed/qed_sp.h |
| index e95431f6acd4..04259df8a5c2 100644 |
| --- a/drivers/net/ethernet/qlogic/qed/qed_sp.h |
| +++ b/drivers/net/ethernet/qlogic/qed/qed_sp.h |
| @@ -167,6 +167,9 @@ struct qed_spq_entry { |
| enum spq_mode comp_mode; |
| struct qed_spq_comp_cb comp_cb; |
| struct qed_spq_comp_done comp_done; /* SPQ_MODE_EBLOCK */ |
| + |
| + /* Posted entry for unlimited list entry in EBLOCK mode */ |
| + struct qed_spq_entry *post_ent; |
| }; |
| |
| struct qed_eq { |
| diff --git a/drivers/net/ethernet/qlogic/qed/qed_spq.c b/drivers/net/ethernet/qlogic/qed/qed_spq.c |
| index 1673fc90027f..43619b6bb232 100644 |
| --- a/drivers/net/ethernet/qlogic/qed/qed_spq.c |
| +++ b/drivers/net/ethernet/qlogic/qed/qed_spq.c |
| @@ -685,6 +685,8 @@ static int qed_spq_add_entry(struct qed_hwfn *p_hwfn, |
| /* EBLOCK responsible to free the allocated p_ent */ |
| if (p_ent->comp_mode != QED_SPQ_MODE_EBLOCK) |
| kfree(p_ent); |
| + else |
| + p_ent->post_ent = p_en2; |
| |
| p_ent = p_en2; |
| } |
| @@ -768,6 +770,25 @@ static int qed_spq_pend_post(struct qed_hwfn *p_hwfn) |
| SPQ_HIGH_PRI_RESERVE_DEFAULT); |
| } |
| |
| +/* Avoid overriding of SPQ entries when getting out-of-order completions, by |
| + * marking the completions in a bitmap and increasing the chain consumer only |
| + * for the first successive completed entries. |
| + */ |
| +static void qed_spq_comp_bmap_update(struct qed_hwfn *p_hwfn, __le16 echo) |
| +{ |
| + u16 pos = le16_to_cpu(echo) % SPQ_RING_SIZE; |
| + struct qed_spq *p_spq = p_hwfn->p_spq; |
| + |
| + __set_bit(pos, p_spq->p_comp_bitmap); |
| + while (test_bit(p_spq->comp_bitmap_idx, |
| + p_spq->p_comp_bitmap)) { |
| + __clear_bit(p_spq->comp_bitmap_idx, |
| + p_spq->p_comp_bitmap); |
| + p_spq->comp_bitmap_idx++; |
| + qed_chain_return_produced(&p_spq->chain); |
| + } |
| +} |
| + |
| int qed_spq_post(struct qed_hwfn *p_hwfn, |
| struct qed_spq_entry *p_ent, u8 *fw_return_code) |
| { |
| @@ -825,11 +846,12 @@ int qed_spq_post(struct qed_hwfn *p_hwfn, |
| p_ent->queue == &p_spq->unlimited_pending); |
| |
| if (p_ent->queue == &p_spq->unlimited_pending) { |
| - /* This is an allocated p_ent which does not need to |
| - * return to pool. |
| - */ |
| + struct qed_spq_entry *p_post_ent = p_ent->post_ent; |
| + |
| kfree(p_ent); |
| - return rc; |
| + |
| + /* Return the entry which was actually posted */ |
| + p_ent = p_post_ent; |
| } |
| |
| if (rc) |
| @@ -843,7 +865,7 @@ int qed_spq_post(struct qed_hwfn *p_hwfn, |
| spq_post_fail2: |
| spin_lock_bh(&p_spq->lock); |
| list_del(&p_ent->list); |
| - qed_chain_return_produced(&p_spq->chain); |
| + qed_spq_comp_bmap_update(p_hwfn, p_ent->elem.hdr.echo); |
| |
| spq_post_fail: |
| /* return to the free pool */ |
| @@ -875,25 +897,8 @@ int qed_spq_completion(struct qed_hwfn *p_hwfn, |
| spin_lock_bh(&p_spq->lock); |
| list_for_each_entry_safe(p_ent, tmp, &p_spq->completion_pending, list) { |
| if (p_ent->elem.hdr.echo == echo) { |
| - u16 pos = le16_to_cpu(echo) % SPQ_RING_SIZE; |
| - |
| list_del(&p_ent->list); |
| - |
| - /* Avoid overriding of SPQ entries when getting |
| - * out-of-order completions, by marking the completions |
| - * in a bitmap and increasing the chain consumer only |
| - * for the first successive completed entries. |
| - */ |
| - __set_bit(pos, p_spq->p_comp_bitmap); |
| - |
| - while (test_bit(p_spq->comp_bitmap_idx, |
| - p_spq->p_comp_bitmap)) { |
| - __clear_bit(p_spq->comp_bitmap_idx, |
| - p_spq->p_comp_bitmap); |
| - p_spq->comp_bitmap_idx++; |
| - qed_chain_return_produced(&p_spq->chain); |
| - } |
| - |
| + qed_spq_comp_bmap_update(p_hwfn, echo); |
| p_spq->comp_count++; |
| found = p_ent; |
| break; |
| @@ -932,11 +937,9 @@ int qed_spq_completion(struct qed_hwfn *p_hwfn, |
| QED_MSG_SPQ, |
| "Got a completion without a callback function\n"); |
| |
| - if ((found->comp_mode != QED_SPQ_MODE_EBLOCK) || |
| - (found->queue == &p_spq->unlimited_pending)) |
| + if (found->comp_mode != QED_SPQ_MODE_EBLOCK) |
| /* EBLOCK is responsible for returning its own entry into the |
| - * free list, unless it originally added the entry into the |
| - * unlimited pending list. |
| + * free list. |
| */ |
| qed_spq_return_entry(p_hwfn, found); |
| |
| -- |
| 2.17.1 |
| |