Some mailbox controllers can complete a transaction entirely within the calling context, without going through the queued TX state machine or a TX-done interrupt. Add a synchronous send path so their clients can request one and wait for the result directly.
Controllers advertise support via the new send_data_sync() op and clients opt in by setting tx_sync when requesting the channel.
Channels bound this way must not call mbox_chan_txdone() or mbox_client_txdone(), and mbox_send_message() and mbox_flush() now reject them, since only mbox_send_message_sync() is a valid transmit path. The client remains responsible for serializing calls to mbox_send_message_sync() against mbox_free_channel().
Signed-off-by: Amirreza Zarrabi amirreza.zarrabi@oss.qualcomm.com --- drivers/mailbox/mailbox.c | 72 ++++++++++++++++++++++++++++++++++++-- include/linux/mailbox_client.h | 3 ++ include/linux/mailbox_controller.h | 10 ++++++ 3 files changed, 83 insertions(+), 2 deletions(-)
diff --git a/drivers/mailbox/mailbox.c b/drivers/mailbox/mailbox.c index efacd24a085d..c640b19de608 100644 --- a/drivers/mailbox/mailbox.c +++ b/drivers/mailbox/mailbox.c @@ -166,6 +166,12 @@ EXPORT_SYMBOL_GPL(mbox_chan_received_data); */ void mbox_chan_txdone(struct mbox_chan *chan, int r) { + if (unlikely(chan->txdone_method & MBOX_TXDONE_BY_RETURN)) { + dev_err(chan->mbox->dev, + "TX-done notification on direct synchronous channel\n"); + return; + } + if (unlikely(!(chan->txdone_method & MBOX_TXDONE_BY_IRQ))) { dev_err(chan->mbox->dev, "Controller can't run the TX ticker\n"); @@ -187,6 +193,12 @@ EXPORT_SYMBOL_GPL(mbox_chan_txdone); */ void mbox_client_txdone(struct mbox_chan *chan, int r) { + if (unlikely(chan->txdone_method & MBOX_TXDONE_BY_RETURN)) { + dev_err(chan->mbox->dev, + "TX-done notification on direct synchronous channel\n"); + return; + } + if (unlikely(!(chan->txdone_method & MBOX_TXDONE_BY_ACK))) { dev_err(chan->mbox->dev, "Client can't run the TX ticker\n"); return; @@ -278,6 +290,9 @@ int mbox_send_message(struct mbox_chan *chan, void *mssg) if (!chan || !chan->cl || mssg == MBOX_NO_MSG) return -EINVAL;
+ if (chan->txdone_method & MBOX_TXDONE_BY_RETURN) + return -EOPNOTSUPP; + t = add_to_rbuf(chan, mssg); if (t < 0) { dev_err(chan->mbox->dev, "Try increasing MBOX_TX_QUEUE_LEN\n"); @@ -308,6 +323,43 @@ int mbox_send_message(struct mbox_chan *chan, void *mssg) } EXPORT_SYMBOL_GPL(mbox_send_message);
+/** + * mbox_send_message_sync - Send data and wait for transaction completion + * @chan: Mailbox channel assigned to this client + * @mssg: Client specific message typecasted + * + * For a channel bound with tx_sync, ask the controller to transmit @mssg and + * only return on completion. This function may sleep and must not be called + * from atomic context. @mssg must remain valid until this function returns. + * + * The direct synchronous path does not queue @mssg, does not use active_req, + * and does not use a TX-done notification. The client must serialize this + * function against mbox_free_channel(). + * + * Return: 0 on success or a negative error code. + */ +int mbox_send_message_sync(struct mbox_chan *chan, void *mssg) +{ + int ret; + + if (!chan || !chan->cl || mssg == MBOX_NO_MSG) + return -EINVAL; + + if (!(chan->txdone_method & MBOX_TXDONE_BY_RETURN)) + return -EOPNOTSUPP; + + if (chan->cl->tx_prepare) + chan->cl->tx_prepare(chan->cl, mssg); + /* Try to submit a message to the MBOX controller synchonously */ + ret = chan->mbox->ops->send_data_sync(chan, mssg); + + if (chan->cl->tx_done) + chan->cl->tx_done(chan->cl, mssg, ret); + + return ret; +} +EXPORT_SYMBOL_GPL(mbox_send_message_sync); + /** * mbox_flush - flush a mailbox channel * @chan: mailbox channel to flush @@ -326,8 +378,11 @@ int mbox_flush(struct mbox_chan *chan, unsigned long timeout) { int ret;
+ if (chan->txdone_method & MBOX_TXDONE_BY_RETURN) + return -EOPNOTSUPP; + if (!chan->mbox->ops->flush) - return -ENOTSUPP; + return -EOPNOTSUPP;
ret = chan->mbox->ops->flush(chan, timeout); if (ret < 0) @@ -343,7 +398,9 @@ 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; - if (chan->txdone_method == MBOX_TXDONE_BY_ACK) + if (chan->txdone_method & MBOX_TXDONE_BY_RETURN) + chan->txdone_method &= ~MBOX_TXDONE_BY_RETURN; + else if (chan->txdone_method == MBOX_TXDONE_BY_ACK) chan->txdone_method = MBOX_TXDONE_BY_POLL; }
@@ -355,6 +412,14 @@ static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl) struct device *dev = cl->dev; int ret;
+ if (cl->tx_sync) { + if (!chan->mbox->ops->send_data_sync) + return -EOPNOTSUPP; + + if (cl->tx_block || cl->tx_tout || cl->knows_txdone) + return -EINVAL; + } + if (chan->cl || !try_module_get(chan->mbox->dev->driver->owner)) { dev_err(dev, "%s: mailbox not free\n", __func__); return -EBUSY; @@ -380,6 +445,9 @@ static int __mbox_bind_client(struct mbox_chan *chan, struct mbox_client *cl) } }
+ if (cl->tx_sync) + chan->txdone_method |= MBOX_TXDONE_BY_RETURN; + return 0; }
diff --git a/include/linux/mailbox_client.h b/include/linux/mailbox_client.h index e5997120f45c..32b1d5ad3bfa 100644 --- a/include/linux/mailbox_client.h +++ b/include/linux/mailbox_client.h @@ -21,6 +21,7 @@ struct mbox_chan; * @knows_txdone: If the client could run the TX state machine. Usually * if the client receives some ACK packet for transmission. * Unused if the controller already has TX_Done/RTR IRQ. + * @tx_sync: Bind the channel for mbox_send_message_sync(). * @rx_callback: Atomic callback to provide client the data received * @tx_prepare: Atomic callback to ask client to prepare the payload * before initiating the transmission if required. @@ -31,6 +32,7 @@ struct mbox_client { bool tx_block; unsigned long tx_tout; bool knows_txdone; + bool tx_sync;
void (*rx_callback)(struct mbox_client *cl, void *mssg); void (*tx_prepare)(struct mbox_client *cl, void *mssg); @@ -42,6 +44,7 @@ struct mbox_chan *mbox_request_channel_byname(struct mbox_client *cl, const char *name); struct mbox_chan *mbox_request_channel(struct mbox_client *cl, int index); int mbox_send_message(struct mbox_chan *chan, void *mssg); +int mbox_send_message_sync(struct mbox_chan *chan, void *mssg); int mbox_flush(struct mbox_chan *chan, unsigned long timeout); void mbox_client_txdone(struct mbox_chan *chan, int r); /* atomic */ bool mbox_client_peek_data(struct mbox_chan *chan); /* atomic */ diff --git a/include/linux/mailbox_controller.h b/include/linux/mailbox_controller.h index 26a238a6f941..c7dc098324ef 100644 --- a/include/linux/mailbox_controller.h +++ b/include/linux/mailbox_controller.h @@ -18,6 +18,7 @@ struct mbox_chan; #define MBOX_TXDONE_BY_IRQ BIT(0) /* controller has remote RTR irq */ #define MBOX_TXDONE_BY_POLL BIT(1) /* controller can read status of last TX */ #define MBOX_TXDONE_BY_ACK BIT(2) /* S/W ACK received by Client ticks the TX */ +#define MBOX_TXDONE_BY_RETURN BIT(3) /* TX completes by function return */
/** * struct mbox_chan_ops - methods to control mailbox channels @@ -28,6 +29,14 @@ struct mbox_chan; * transmission of data is reported by the controller via * mbox_chan_txdone (if it has some TX ACK irq). It must not * sleep. + * @send_data_sync: The API asks the MBOX controller driver, in non-atomic + * context, to transmit a message on the bus and wait for the + * transaction to complete. It returns 0 if the transaction + * completed successfully or a negative error code otherwise. + * The controller must not call mbox_chan_txdone() or + * mbox_client_txdone() for this operation. Concurrent calls for one + * controller must support them, serialize them internally, or + * return -EBUSY for a conflicting transaction. * @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. @@ -53,6 +62,7 @@ struct mbox_chan; */ struct mbox_chan_ops { int (*send_data)(struct mbox_chan *chan, void *data); + int (*send_data_sync)(struct mbox_chan *chan, void *data); int (*flush)(struct mbox_chan *chan, unsigned long timeout); int (*startup)(struct mbox_chan *chan); void (*shutdown)(struct mbox_chan *chan);