diff options
Diffstat (limited to 'drivers/firmware/samsung')
| -rw-r--r-- | drivers/firmware/samsung/exynos-acpm-dvfs.c | 3 | ||||
| -rw-r--r-- | drivers/firmware/samsung/exynos-acpm.c | 141 |
2 files changed, 100 insertions, 44 deletions
diff --git a/drivers/firmware/samsung/exynos-acpm-dvfs.c b/drivers/firmware/samsung/exynos-acpm-dvfs.c index 06bdf62dea1f..fdea7aa24ca0 100644 --- a/drivers/firmware/samsung/exynos-acpm-dvfs.c +++ b/drivers/firmware/samsung/exynos-acpm-dvfs.c @@ -31,6 +31,9 @@ static void acpm_dvfs_set_xfer(struct acpm_xfer *xfer, u32 *cmd, size_t cmdlen, if (response) { xfer->rxcnt = cmdlen; xfer->rxd = cmd; + } else { + xfer->rxcnt = 0; + xfer->rxd = NULL; } } diff --git a/drivers/firmware/samsung/exynos-acpm.c b/drivers/firmware/samsung/exynos-acpm.c index 16c46ed60837..19db3674a28f 100644 --- a/drivers/firmware/samsung/exynos-acpm.c +++ b/drivers/firmware/samsung/exynos-acpm.c @@ -7,11 +7,12 @@ #include <linux/bitfield.h> #include <linux/bitmap.h> -#include <linux/bits.h> +#include <linux/bitops.h> #include <linux/cleanup.h> #include <linux/container_of.h> #include <linux/delay.h> #include <linux/device.h> +#include <linux/find.h> #include <linux/firmware/samsung/exynos-acpm-protocol.h> #include <linux/io.h> #include <linux/iopoll.h> @@ -104,12 +105,15 @@ struct acpm_queue { * * @cmd: pointer to where the data shall be saved. * @n_cmd: number of 32-bit commands. - * @response: true if the client expects the RX data. + * @rxcnt: expected length of the response in 32-bit words. + * @completed: flag indicating if the firmware response has been fully + * processed. */ struct acpm_rx_data { u32 *cmd; size_t n_cmd; - bool response; + size_t rxcnt; + bool completed; }; #define ACPM_SEQNUM_MAX 64 @@ -199,31 +203,33 @@ static void acpm_get_saved_rx(struct acpm_chan *achan, const struct acpm_rx_data *rx_data = &achan->rx_data[tx_seqnum - 1]; u32 rx_seqnum; - if (!rx_data->response) + if (!rx_data->rxcnt) return; rx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, rx_data->cmd[0]); - if (rx_seqnum == tx_seqnum) { + if (rx_seqnum == tx_seqnum) memcpy(xfer->rxd, rx_data->cmd, xfer->rxcnt * sizeof(*xfer->rxd)); - clear_bit(rx_seqnum - 1, achan->bitmap_seqnum); - } } /** * acpm_get_rx() - get response from RX queue. * @achan: ACPM channel info. * @xfer: reference to the transfer to get response for. + * @native_match: pointer to a boolean set to true if the thread natively + * processed its own sequence number during this call. * * Return: 0 on success, -errno otherwise. */ -static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) +static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer, + bool *native_match) { u32 rx_front, rx_seqnum, tx_seqnum, seqnum; const void __iomem *base, *addr; struct acpm_rx_data *rx_data; u32 i, val, mlen; - bool rx_set = false; + + *native_match = false; guard(mutex)(&achan->rx_lock); @@ -232,10 +238,8 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) tx_seqnum = FIELD_GET(ACPM_PROTOCOL_SEQNUM, xfer->txd[0]); - if (i == rx_front) { - acpm_get_saved_rx(achan, xfer, tx_seqnum); + if (i == rx_front) return 0; - } base = achan->rx.base; mlen = achan->mlen; @@ -256,11 +260,16 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) seqnum = rx_seqnum - 1; rx_data = &achan->rx_data[seqnum]; - if (rx_data->response) { + if (rx_data->rxcnt) { if (rx_seqnum == tx_seqnum) { __ioread32_copy(xfer->rxd, addr, xfer->rxcnt); - rx_set = true; - clear_bit(seqnum, achan->bitmap_seqnum); + /* + * Signal completion to the polling thread. + * Pairs with smp_load_acquire() in polling + * loop. + */ + smp_store_release(&rx_data->completed, true); + *native_match = true; } else { /* * The RX data corresponds to another request. @@ -268,10 +277,23 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) * clear yet the bitmap. It will be cleared * after the response is copied to the request. */ - __ioread32_copy(rx_data->cmd, addr, xfer->rxcnt); + __ioread32_copy(rx_data->cmd, addr, + rx_data->rxcnt); + /* + * Signal completion to the polling thread. + * Pairs with smp_load_acquire() in polling + * loop. + */ + smp_store_release(&rx_data->completed, true); } } else { - clear_bit(seqnum, achan->bitmap_seqnum); + /* + * Signal completion to the polling thread. + * Pairs with smp_load_acquire() in polling loop. + */ + smp_store_release(&rx_data->completed, true); + if (rx_seqnum == tx_seqnum) + *native_match = true; } i = (i + 1) % achan->qlen; @@ -280,13 +302,6 @@ static int acpm_get_rx(struct acpm_chan *achan, const struct acpm_xfer *xfer) /* We saved all responses, mark RX empty. */ writel(rx_front, achan->rx.rear); - /* - * If the response was not in this iteration of the queue, check if the - * RX data was previously saved. - */ - if (!rx_set) - acpm_get_saved_rx(achan, xfer, tx_seqnum); - return 0; } @@ -301,6 +316,7 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan, const struct acpm_xfer *xfer) { struct device *dev = achan->acpm->dev; + bool native_match; ktime_t timeout; u32 seqnum; int ret; @@ -309,12 +325,25 @@ static int acpm_dequeue_by_polling(struct acpm_chan *achan, timeout = ktime_add_us(ktime_get(), ACPM_POLL_TIMEOUT_US); do { - ret = acpm_get_rx(achan, xfer); + ret = acpm_get_rx(achan, xfer, &native_match); if (ret) return ret; - if (!test_bit(seqnum - 1, achan->bitmap_seqnum)) + /* + * Safely check if our specific transaction has been processed. + * smp_load_acquire prevents the CPU from speculatively + * executing subsequent instructions before the transaction is + * synchronized. + */ + if (smp_load_acquire(&achan->rx_data[seqnum - 1].completed)) { + /* Retrieve payload if another thread cached it for us */ + if (!native_match) + acpm_get_saved_rx(achan, xfer, seqnum); + + /* Relinquish ownership of the sequence slot */ + clear_bit_unlock(seqnum - 1, achan->bitmap_seqnum); return 0; + } /* Determined experimentally. */ udelay(20); @@ -362,29 +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 */ - do { - if (++achan->seqnum == ACPM_SEQNUM_MAX) - achan->seqnum = 1; - } while (test_bit(achan->seqnum - 1, achan->bitmap_seqnum)); + /* + * 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. + */ + 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); - if (xfer->rxd) - rx_data->response = true; + /* zero means no response expected */ + rx_data->rxcnt = xfer->rxcnt; - /* Flag the index based on seqnum. (seqnum: 1~63, bitmap: 0~62) */ - set_bit(achan->seqnum - 1, achan->bitmap_seqnum); + return 0; } /** @@ -444,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, @@ -526,10 +576,11 @@ static int acpm_achan_alloc_cmds(struct acpm_chan *achan) /** * acpm_free_mbox_chans() - free mailbox channels. - * @acpm: pointer to driver data. + * @data: pointer to driver data. */ -static void acpm_free_mbox_chans(struct acpm_info *acpm) +static void acpm_free_mbox_chans(void *data) { + struct acpm_info *acpm = data; int i; for (i = 0; i < acpm->num_chans; i++) @@ -557,6 +608,10 @@ static int acpm_channels_init(struct acpm_info *acpm) if (!acpm->chans) return -ENOMEM; + ret = devm_add_action_or_reset(dev, acpm_free_mbox_chans, acpm); + if (ret) + return dev_err_probe(dev, ret, "Failed to add mbox free action.\n"); + chans_shmem = acpm->sram_base + readl(&shmem->chans); for (i = 0; i < acpm->num_chans; i++) { @@ -578,10 +633,8 @@ static int acpm_channels_init(struct acpm_info *acpm) cl->dev = dev; achan->chan = mbox_request_channel(cl, 0); - if (IS_ERR(achan->chan)) { - acpm_free_mbox_chans(acpm); + if (IS_ERR(achan->chan)) return PTR_ERR(achan->chan); - } } return 0; |
