Skip to content

Commit 064c9c3

Browse files
Alex Elderkuba-moo
authored andcommitted
net: ipa: lock when freeing transaction
Transactions sit on one of several lists, depending on their state (allocated, pending, complete, or polled). A spinlock protects against concurrent access when transactions are moved between these lists. Transactions are also reference counted. A newly-allocated transaction has an initial count of 1; a transaction is released in gsi_trans_free() only if its decremented reference count reaches 0. Releasing a transaction includes removing it from the polled (or if unused, allocated) list, so the spinlock is acquired when we release a transaction. The reference count is used to allow a caller to synchronously wait for a committed transaction to complete. In this case, the waiter takes an extra reference to the transaction *before* committing it (so it won't be freed), and releases its reference (calls gsi_trans_free()) when it is done with it. Similarly, gsi_channel_update() takes an extra reference to ensure a transaction isn't released before the function is done operating on it. Until the transaction is moved to the completed list (by this function) it won't be freed, so this reference is taken "safely." But in the quiesce path, we want to wait for the "last" transaction, which we find in the completed or polled list. Transactions on these lists can be freed at any time, so we (try to) prevent that by taking the reference while holding the spinlock. Currently gsi_trans_free() decrements a transaction's reference count unconditionally, acquiring the lock to remove the transaction from its list *only* when the count reaches 0. This does not protect the quiesce path, which depends on the lock to ensure its extra reference prevents release of the transaction. Fix this by only dropping the last reference to a transaction in gsi_trans_free() while holding the spinlock. Fixes: 9dd441e ("soc: qcom: ipa: GSI transactions") Reported-by: Stephen Boyd <[email protected]> Signed-off-by: Alex Elder <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 3fe16ed commit 064c9c3

File tree

1 file changed

+12
-3
lines changed

1 file changed

+12
-3
lines changed

drivers/net/ipa/gsi_trans.c

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,22 +362,31 @@ struct gsi_trans *gsi_channel_trans_alloc(struct gsi *gsi, u32 channel_id,
362362
return trans;
363363
}
364364

365-
/* Free a previously-allocated transaction (used only in case of error) */
365+
/* Free a previously-allocated transaction */
366366
void gsi_trans_free(struct gsi_trans *trans)
367367
{
368+
refcount_t *refcount = &trans->refcount;
368369
struct gsi_trans_info *trans_info;
370+
bool last;
369371

370-
if (!refcount_dec_and_test(&trans->refcount))
372+
/* We must hold the lock to release the last reference */
373+
if (refcount_dec_not_one(refcount))
371374
return;
372375

373376
trans_info = &trans->gsi->channel[trans->channel_id].trans_info;
374377

375378
spin_lock_bh(&trans_info->spinlock);
376379

377-
list_del(&trans->links);
380+
/* Reference might have been added before we got the lock */
381+
last = refcount_dec_and_test(refcount);
382+
if (last)
383+
list_del(&trans->links);
378384

379385
spin_unlock_bh(&trans_info->spinlock);
380386

387+
if (!last)
388+
return;
389+
381390
ipa_gsi_trans_release(trans);
382391

383392
/* Releasing the reserved TREs implicitly frees the sgl[] and

0 commit comments

Comments
 (0)