libie_ctlq_xn_shutdown() waits on xnm->can_destroy for the outstanding
transactions to be returned. libie_ctlq_xn_push_free() signals it with
complete() when the last entry goes back to the free bitmap. Once the
manager is shut down, libie_ctlq_xn_pop_free() hands out no more entries,
so that point is reached exactly once and only one waiter is woken.

Nothing restricts libie_ctlq_xn_shutdown() to a single caller, though.
If two threads call it while a transaction is still outstanding, both see
the entry in use and both wait, and the one that is not woken blocks
forever, as wait_for_completion() has no timeout.

A drained manager stays drained until libie_ctlq_xn_deinit() frees it, so
signal it with complete_all(). All callers waiting at that point are
released, and later ones find the bitmap full and return at once.

Signal it before free_xns_bm_lock is dropped. A caller that finds the
bitmap full then knows the completion has already been signalled. That
leaves libie_ctlq_xn_push_free() holding the lock after it has woken the
waiters, so have a woken waiter take and release free_xns_bm_lock before
it returns, the same lock/unlock pair completion_done() uses to wait for
complete(), see commit dec13c42d21a ("completion: Replace
spin_unlock_wait() with lock/unlock pair"). Its unlock is then the last
access to the manager by the time any caller returns.

The manager must still outlive every caller, since a woken waiter has to
retake the completion and bitmap locks before it returns. Spell that out
in the kernel-doc: libie_ctlq_xn_deinit() must not be called until every
libie_ctlq_xn_shutdown() caller has returned.

The next patch makes idpf call libie_ctlq_xn_shutdown() from its service
task, which can run concurrently with the reset and remove paths.

Fixes: b4ff4e626a7c ("libie: add bookkeeping support for control queue 
messages")
Assisted-by: LLM
Signed-off-by: Emil Tantilov <[email protected]>
---
 drivers/net/ethernet/intel/libie/controlq.c | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/drivers/net/ethernet/intel/libie/controlq.c 
b/drivers/net/ethernet/intel/libie/controlq.c
index 6214fc036ce5f..9bcb022ed9782 100644
--- a/drivers/net/ethernet/intel/libie/controlq.c
+++ b/drivers/net/ethernet/intel/libie/controlq.c
@@ -735,13 +735,10 @@ static bool __libie_ctlq_xn_push_free(struct 
libie_ctlq_xn_manager *xnm,
 static void libie_ctlq_xn_push_free(struct libie_ctlq_xn_manager *xnm,
                                    struct libie_ctlq_xn *xn)
 {
-       bool can_destroy;
-
-       scoped_guard(spinlock, &xnm->free_xns_bm_lock)
-               can_destroy = __libie_ctlq_xn_push_free(xnm, xn);
+       guard(spinlock)(&xnm->free_xns_bm_lock);
 
-       if (can_destroy)
-               complete(&xnm->can_destroy);
+       if (__libie_ctlq_xn_push_free(xnm, xn))
+               complete_all(&xnm->can_destroy);
 }
 
 /**
@@ -1171,7 +1168,9 @@ EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_send_clean, 
"LIBIE_CP");
  * @xnm: pointer to the transaction manager
  *
  * Synchronously terminate existing transactions and stop accepting new ones.
- * Async transactions are discarded without invoking resp_cb.
+ * Async transactions are discarded without invoking resp_cb. May be called
+ * concurrently, libie_ctlq_xn_deinit() must not be called until every caller
+ * has returned.
  */
 void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager *xnm)
 {
@@ -1218,8 +1217,12 @@ void libie_ctlq_xn_shutdown(struct libie_ctlq_xn_manager 
*xnm)
 
        spin_unlock(&xnm->free_xns_bm_lock);
 
-       if (must_wait)
+       if (must_wait) {
                wait_for_completion(&xnm->can_destroy);
+               /* Synchronize with libie_ctlq_xn_push_free() unlocking */
+               spin_lock(&xnm->free_xns_bm_lock);
+               spin_unlock(&xnm->free_xns_bm_lock);
+       }
 }
 EXPORT_SYMBOL_NS_GPL(libie_ctlq_xn_shutdown, "LIBIE_CP");
 
-- 
2.37.3

Reply via email to