[PATCH 5/7] mailbox: Add support for mailbox controllers that can queue
From: Douglas Anderson
Date: Tue Jul 14 2026 - 18:29:58 EST
The API to mailbox clients already allows more than one message to be
queued at once. Mailbox clients can freely queue up several messages
without waiting for an Ack and they will be transferred one at a time.
Currently, all of this queueing is done by the mailbox core, which
never gives more than one message to the actual mailbox controller at
once.
Newer mailbox controllers may have the ability to queue messages
themselves. This can significantly reduce latency in transmitting
mailbox messages since we don't need to wait for an interrupt to be
Acked before sending the next one. Add support to the mailbox core for
mailbox controllers that can queue messages.
Support turns out to be relatively easy to add given the current
architecture of the code. To keep things as backward compatible as
possible and to reduce code changes, still keep the "first" queued
message in the `active_req` field, but also allow transmitting data
that's in the `msg_data` queue. Whenever a message finishes
transmitting, promote the new "first" message to the `active_req`
field.
Signed-off-by: Douglas Anderson <dianders@xxxxxxxxxxxx>
---
drivers/mailbox/mailbox.c | 64 ++++++++++++++++++++++--------
include/linux/mailbox_controller.h | 11 ++++-
2 files changed, 58 insertions(+), 17 deletions(-)
diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c
index d99a08652ef1..1cd9e0ba3531 100644
--- a/drivers/mailbox/mailbox.c
+++ b/drivers/mailbox/mailbox.c
@@ -47,26 +47,34 @@ static int add_to_rbuf(struct mbox_chan *chan, void *mssg)
static void msg_submit(struct mbox_chan *chan)
{
+ struct mbox_controller *mbox = chan->mbox;
unsigned count, idx;
void *data;
int err = -EBUSY;
scoped_guard(spinlock_irqsave, &chan->lock) {
- if (!chan->msg_count || chan->active_req != MBOX_NO_MSG)
- break;
-
- count = chan->msg_count;
- idx = (chan->msg_free + MBOX_TX_QUEUE_LEN - count) % MBOX_TX_QUEUE_LEN;
-
- data = chan->msg_data[idx];
-
- if (chan->cl->tx_prepare)
- chan->cl->tx_prepare(chan->cl, data);
- /* Try to submit a message to the MBOX controller */
- err = chan->mbox->ops->send_data(chan, data);
- if (!err) {
- chan->active_req = data;
- chan->msg_count--;
+ while (true) {
+ count = chan->msg_count - chan->num_queued;
+ if (!count || (!mbox->has_queue && chan->active_req != MBOX_NO_MSG))
+ break;
+
+ idx = (chan->msg_free + MBOX_TX_QUEUE_LEN - count) % MBOX_TX_QUEUE_LEN;
+
+ data = chan->msg_data[idx];
+
+ if (chan->cl->tx_prepare)
+ chan->cl->tx_prepare(chan->cl, data);
+ /* Try to submit a message to the MBOX controller */
+ err = chan->mbox->ops->send_data(chan, data);
+ if (err)
+ break;
+
+ if (chan->active_req == MBOX_NO_MSG) {
+ chan->active_req = data;
+ chan->msg_count--;
+ } else {
+ chan->num_queued++;
+ }
}
}
@@ -83,7 +91,17 @@ static void tx_tick(struct mbox_chan *chan, int r)
scoped_guard(spinlock_irqsave, &chan->lock) {
mssg = chan->active_req;
- chan->active_req = MBOX_NO_MSG;
+ if (chan->num_queued) {
+ unsigned int idx;
+
+ idx = (chan->msg_free + MBOX_TX_QUEUE_LEN - chan->msg_count) %
+ MBOX_TX_QUEUE_LEN;
+ chan->active_req = chan->msg_data[idx];
+ chan->num_queued--;
+ chan->msg_count--;
+ } else {
+ chan->active_req = MBOX_NO_MSG;
+ }
}
/* Submit next message */
@@ -339,6 +357,7 @@ static void mbox_clean_and_put_channel(struct mbox_chan *chan)
scoped_guard(spinlock_irqsave, &chan->lock) {
chan->cl = NULL;
chan->active_req = MBOX_NO_MSG;
+ chan->num_queued = 0;
if (chan->txdone_method == MBOX_TXDONE_BY_ACK)
chan->txdone_method = MBOX_TXDONE_BY_POLL;
}
@@ -359,6 +378,7 @@ static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl)
scoped_guard(spinlock_irqsave, &chan->lock) {
chan->msg_free = 0;
chan->msg_count = 0;
+ chan->num_queued = 0;
chan->active_req = MBOX_NO_MSG;
chan->cl = cl;
init_completion(&chan->tx_complete);
@@ -552,6 +572,17 @@ int mbox_controller_register(struct mbox_controller *mbox)
else /* It has to be ACK then */
txdone = MBOX_TXDONE_BY_ACK;
+ /*
+ * While it should be possible to make queued controllers work with
+ * other txdone mechanisms, extra care would be needed when
+ * scheduling the hrtimer (for "BY_POLL") and extra testing would be
+ * needed in general. For now, disallow.
+ */
+ if (mbox->has_queue && txdone != MBOX_TXDONE_BY_IRQ) {
+ dev_err(mbox->dev, "Queued mailboxes currently need a txdone irq\n");
+ return -EINVAL;
+ }
+
if (txdone == MBOX_TXDONE_BY_POLL) {
if (!mbox->ops->last_tx_done) {
@@ -569,6 +600,7 @@ int mbox_controller_register(struct mbox_controller *mbox)
chan->cl = NULL;
chan->mbox = mbox;
chan->active_req = MBOX_NO_MSG;
+ chan->num_queued = 0;
chan->txdone_method = txdone;
spin_lock_init(&chan->lock);
}
diff --git a/include/linux/mailbox_controller.h b/include/linux/mailbox_controller.h
index 591ccce3de3a..6b5d2be7b47e 100644
--- a/include/linux/mailbox_controller.h
+++ b/include/linux/mailbox_controller.h
@@ -27,7 +27,9 @@ struct mbox_chan;
* if the remote hasn't yet read the last data sent. Actual
* transmission of data is reported by the controller via
* mbox_chan_txdone (if it has some TX ACK irq). It must not
- * sleep.
+ * sleep. If `has_queue` and the controller's queue is full,
+ * -EBUSY should be returned to maintain consistency with the
+ * non-queue case.
* @flush: Called when a client requests transmissions to be blocking but
* the context doesn't allow sleeping. Typically the controller
* will implement a busy loop waiting for the data to flush out.
@@ -69,6 +71,8 @@ struct mbox_chan_ops {
* @ops: Operators that work on each communication chan. Required.
* @chans: Array of channels. Required.
* @num_chans: Number of channels in the 'chans' array. Required.
+ * @has_queue: Indicates if the controller can have more than one
+ * active message at once. Requires txdone_irq.
* @txdone_irq: Indicates if the controller can report to API when
* the last transmitted data was read by the remote.
* Eg, if it has some TX ACK irq.
@@ -90,6 +94,7 @@ struct mbox_controller {
const struct mbox_chan_ops *ops;
struct mbox_chan *chans;
int num_chans;
+ bool has_queue;
bool txdone_irq;
bool txdone_poll;
unsigned txpoll_period;
@@ -129,6 +134,9 @@ struct mbox_controller {
* @msg_count: No. of mssg currently queued
* @msg_free: Index of next available mssg slot
* @msg_data: Hook for data packet
+ * @num_queued: If the mbox `has_queue` then we'll go ahead and try
+ * to send data in the `msg_data` queue. This is the
+ * number that have been successfully queued.
* @lock: Serialise access to the channel
* @con_priv: Hook for controller driver to attach private data
*/
@@ -140,6 +148,7 @@ struct mbox_chan {
int tx_status;
void *active_req;
unsigned msg_count, msg_free;
+ unsigned num_queued;
void *msg_data[MBOX_TX_QUEUE_LEN];
spinlock_t lock; /* Serialise access to the channel */
void *con_priv;
--
2.55.0.141.g00534a21ce-goog