diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 3fa9fe283be4..19db3674a28f 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -390,34 +391,48 @@ static int acpm_wait_for_queue_slots(struct acpm_chan *achan, u32 next_tx_front) * TX queue. * @achan: ACPM channel info. * @xfer: reference to the transfer being prepared. + * + * Return: 0 on success, -errno otherwise. */ -static void acpm_prepare_xfer(struct acpm_chan *achan, - const struct acpm_xfer *xfer) +static int acpm_prepare_xfer(struct acpm_chan *achan, + const struct acpm_xfer *xfer) { struct acpm_rx_data *rx_data; u32 *txd = (u32 *)xfer->txd; + unsigned long size = ACPM_SEQNUM_MAX - 1; + unsigned long bit = achan->seqnum; + + bit = find_next_zero_bit(achan->bitmap_seqnum, size, bit); + if (bit >= size) { + bit = find_first_zero_bit(achan->bitmap_seqnum, size); + if (bit >= size) { + dev_err_ratelimited(achan->acpm->dev, + "ACPM sequence number pool exhausted\n"); + return -EBUSY; + } + } /* - * Prevent chan->seqnum from being re-used. - * test_and_set_bit_lock() provides formal LKMM Acquire semantics. - * It pairs with the RX thread's clear_bit_unlock() to ensure the CPU - * does not speculatively execute the rx_data buffer wipe (memset) - * before the sequence number is safely claimed. + * Execute the atomic set to formally claim the bit and establish + * LKMM Acquire semantics against the RX thread's clear_bit_unlock(). + * A loop is unnecessary because allocations are strictly serialized + * by tx_lock. */ - do { - if (++achan->seqnum == ACPM_SEQNUM_MAX) - achan->seqnum = 1; - /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ - } while (test_and_set_bit_lock(achan->seqnum - 1, achan->bitmap_seqnum)); + if (WARN_ON_ONCE(test_and_set_bit_lock(bit, achan->bitmap_seqnum))) + return -EIO; + /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ + achan->seqnum = bit + 1; txd[0] |= FIELD_PREP(ACPM_PROTOCOL_SEQNUM, achan->seqnum); /* Clear data for upcoming responses */ - rx_data = &achan->rx_data[achan->seqnum - 1]; + rx_data = &achan->rx_data[bit]; rx_data->completed = false; memset(rx_data->cmd, 0, sizeof(*rx_data->cmd) * rx_data->n_cmd); /* zero means no response expected */ rx_data->rxcnt = xfer->rxcnt; + + return 0; } /** @@ -477,7 +492,9 @@ int acpm_do_xfer(struct acpm_handle *handle, const struct acpm_xfer *xfer) if (ret) return ret; - acpm_prepare_xfer(achan, xfer); + ret = acpm_prepare_xfer(achan, xfer); + if (ret) + return ret; /* Write TX command. */ __iowrite32_copy(achan->tx.base + achan->mlen * tx_front,