Jiaqi-YP7 opened a new pull request, #18859:
URL: https://github.com/apache/nuttx/pull/18859
# drivers/mbox: Add mailbox framework and PL320 driver
## Summary
This change adds a generic mailbox (MBOX) framework under `drivers/mbox`
and an initial ARM PL320 IPCM lower-half driver.
The motivation is to provide a small, reusable kernel-level abstraction for
SoC mailbox controllers. Many multicore SoCs provide mailbox/IPCM hardware
for
short inter-processor messages, event notifications, and resource
coordination,
but each controller usually has slightly different register layout and
completion semantics. Without a common framework, each user has to duplicate
channel lookup, send/receive state handling, timeout handling, callbacks, and
controller-specific glue.
This patch introduces an upper-half/lower-half split:
* The generic upper half defines `struct mbox_dev_s`, `struct mbox_chan_s`,
`struct mbox_sender_s`, `struct mbox_receiver_s`, and the public mailbox
APIs in `include/nuttx/mbox/mbox.h`.
* Lower-half drivers implement `struct mbox_ops_s` to handle controller
details such as channel setup, message send/receive, ACK generation, TX
completion, and hardware ready/available checks.
* The PL320 lower half implements the first mailbox controller using this
interface and exposes `pl320_initialize()` through
`include/nuttx/mbox/pl320.h`.
The generic framework supports the following TX/RX flow:
* TX clients send data through a `struct mbox_sender_s`.
* Blocking sends wait until an ACK/completion is reported or until the
configured timeout expires.
* Non-blocking sends can enqueue data when a TX channel is already active.
* Lower-half drivers call `mbox_chan_tx_done()` when an ACK/completion is
observed, either from an interrupt handler or from polling logic.
* RX lower halves call `mbox_chan_rx_data()` when data is available. The
generic layer receives the message, sends an ACK when supported, and
invokes
the registered RX callback.
* Optional `CONFIG_MBOX_TRACE` statistics are provided for sent, received,
acknowledged, buffered, and timed-out messages.
For PL320, the driver maps each configured mailbox to an `mbox_chan_s`,
programs the source/destination/mask registers during setup, transfers
payloads
through the PL320 data registers, and routes IRQ events to the generic TX
completion or RX receive paths.
## Design
The design separates common mailbox semantics from hardware-specific control.
The framework only assumes that a mailbox channel can report whether TX is
ready, whether RX data is available, and whether a TX message has completed
or
been acknowledged. The exact hardware mechanism remains in the lower-half
driver.
The generic channel object stores common runtime state:
* Channel direction (`MBOX_TX` or `MBOX_RX`)
* Associated lower-half device
* RX callback and callback argument
* TX circular buffer for queued non-blocking messages
* TX semaphore for blocking waiters
* Watchdog timer for TX timeout detection
* TX state and result
* Optional trace statistics
This keeps board/controller drivers small. A lower-half driver only needs to
provide the hardware operations:
* `getchan()` to resolve a platform-specific channel argument
* `setup()` / `shutdown()` for channel ownership and configuration
* `send()` / `recv()` for payload transfer
* `acknowledge()` for RX-side ACK generation when the hardware supports it
* `txfinish()` to clear completion state and prepare for the next transfer
* `setcallback()` for RX callback registration
* `rxavailable()`, `txready()`, and `txacked()` for status checks
The PL320 implementation follows this model directly. Board code supplies a
`struct pl320_config_s` with the local IPCM interrupt index, IRQ number, and
an
array of configured PL320 channels. Each `struct pl320_chan_s` describes the
mailbox index, source core, destination core, whether the channel should be
configured by this side, and the TX buffer used by the generic framework.
## Impact
This is additive and gated by Kconfig:
* `CONFIG_MBOX` enables the generic mailbox framework.
* `CONFIG_MBOX_PL320` enables the ARM PL320 lower-half driver.
* `CONFIG_MBOX_TRACE` optionally enables per-channel statistics.
There is no intended behavior change for existing drivers or boards unless
they enable and instantiate the new mailbox driver.
Build impact is limited to the new `drivers/mbox` directory and the new
`drivers/Kconfig` / `drivers/Makefile` integration. Existing IPCC, RPMsg, and
other driver users are not changed by this patch.
Hardware impact is limited to boards that explicitly initialize a PL320
controller and provide a channel table. The PL320 lower half supports up to
32
mailboxes and transfers up to 28 bytes per PL320 message, matching the seven
32-bit PL320 data registers.
Compatibility impact should be low because the new APIs are introduced under
a
new `include/nuttx/mbox` namespace. No existing public mailbox API is
replaced.
## Basic Usage
Enable the framework and lower-half driver in Kconfig:
```text
CONFIG_MBOX=y
CONFIG_MBOX_PL320=y
```
Optionally enable trace statistics:
```text
CONFIG_MBOX_TRACE=y
```
Board or SoC initialization code should define PL320 channels and initialize
the controller. A TX channel needs a TX buffer for the framework queue:
```c
#include <nuttx/mbox/mbox.h>
#include <nuttx/mbox/pl320.h>
static uint8_t g_pl320_txbuf[128];
static struct pl320_chan_s g_pl320_chans[] =
{
{
.mbox = 0,
.src = 0,
.dst = 1,
.ctrl = true,
.txbuf = g_pl320_txbuf,
.bufsize = sizeof(g_pl320_txbuf),
},
};
static const struct pl320_config_s g_pl320_config =
{
.chans = g_pl320_chans,
.num_chans = nitems(g_pl320_chans),
.ipcmint = 0,
.irq_num = PL320_IRQ,
};
FAR struct mbox_dev_s *mbox;
mbox = pl320_initialize(&g_pl320_config, PL320_BASE);
```
After initialization, client code can resolve a channel and send a message:
```c
FAR struct mbox_chan_s *chan;
struct mbox_sender_s sender;
uint32_t msg = 0x12345678;
int ret;
chan = mbox_get_chan(mbox, (FAR void *)(uintptr_t)0);
if (chan == NULL)
{
return -ENODEV;
}
sender.chan = chan;
sender.priv = NULL;
ret = mbox_send(&sender, &msg, sizeof(msg));
```
For a bounded wait, use `mbox_ticksend()`:
```c
ret = mbox_ticksend(&sender, &msg, sizeof(msg), MSEC2TICK(100));
```
For a non-blocking send, pass a zero timeout. If the channel is active, the
message can be buffered for later transmission. If the hardware is idle but
not
ready, the API returns `-EAGAIN`.
```c
ret = mbox_ticksend(&sender, &msg, sizeof(msg), 0);
```
RX clients register a callback on an RX channel:
```c
static int my_mbox_rxcallback(int ret, FAR struct mbox_chan_s *chan,
FAR void *arg, FAR void *data, size_t size)
{
if (ret < 0)
{
return ret;
}
/* Consume data[0..size - 1]. */
return OK;
}
struct mbox_receiver_s receiver;
receiver.chan = chan;
receiver.priv = NULL;
ret = mbox_register_rxcallback(&receiver, my_mbox_rxcallback, NULL);
```
The lower-half interrupt handler or polling path should notify the framework:
```c
if (MBOX_TX_ACKED(chan))
{
mbox_chan_tx_done(chan);
}
if (MBOX_RX_AVAILABLE(chan))
{
mbox_chan_rx_data(chan);
}
```
When a channel is no longer used, platform code can release framework-owned
resources with:
```c
mbox_chan_deinit(chan);
```
## Testing
Test background:
This was tested on a downstream private SoC platform. The SoC has multiple
processor cores that communicate through mailbox hardware based on ARM PL320.
For this validation, one RISC-V core and one Arm R52 core were selected to
verify PL320-based mailbox communication through the new generic MBOX
framework
and PL320 lower-half driver.
A downstream `ipcmtest` test application was developed for this validation.
The test application uses the public MBOX APIs added by this patch and
covers:
* PL320 channel initialization on both cores
* RX callback registration on both cores
* R52-to-RISC-V single blocking send
* RISC-V-to-R52 single blocking send
* Sequential stress sends
* Non-blocking send with `timeout == 0`
* Timed send with a 5-second timeout
Configuration and board initialization evidence:
The downstream board configuration enabled the generic MBOX framework, the
PL320 lower-half driver, and the `ipcmtest` test app:
```text
CONFIG_MBOX=y
CONFIG_MBOX_PL320=y
CONFIG_IPCMTEST=y
```
Our board/vendor layer provides the PL320 channel table used by the test.
For the tested RISC-V <-> R52 pair, PL320 channel 16 is used for
RISC-V-to-R52 messages, and PL320 channel 17 is used for R52-to-RISC-V
messages:
```c
#define TEST_PL320_TIMEOUT 5
#define TEST_PL320_TX_BUFSIZE (PL320_MAX_DATA_BUF_SIZE * PL320_MSG_SIZE)
struct test_pl320_txbuf_s
{
uint8_t buf[PL320_MAX_DATA_BUF_SIZE][PL320_MSG_SIZE]
__attribute__((aligned(64)));
};
static struct test_pl320_txbuf_s g_pl320_txbuf[2];
static struct pl320_chan_s g_pl320_chans[] =
{
{
.base =
{
.id = 16,
.timeout = TEST_PL320_TIMEOUT,
},
.mbox = 16,
.src = RISCV_PL320_INT,
.dst = R52_PL320_INT,
.ctrl = true,
.txbuf = (uint8_t *)&g_pl320_txbuf[0],
.bufsize = TEST_PL320_TX_BUFSIZE,
},
{
.base =
{
.id = 17,
.timeout = TEST_PL320_TIMEOUT,
},
.mbox = 17,
.src = R52_PL320_INT,
.dst = RISCV_PL320_INT,
.ctrl = true,
.txbuf = (uint8_t *)&g_pl320_txbuf[1],
.bufsize = TEST_PL320_TX_BUFSIZE,
},
};
```
The board/vendor layer also provides the `struct pl320_config_s` passed to
`pl320_initialize()`. The local `ipcmint` and IRQ number are selected for the
core that is currently booting:
```c
static const struct pl320_config_s g_pl320_config =
{
.chans = g_pl320_chans,
.num_chans = ARRAY_SIZE(g_pl320_chans),
.ipcmint = LOCAL_PL320_INT,
.irq_num = LOCAL_PL320_IRQ,
};
```
PL320 is initialized during board mailbox initialization. The returned
`struct mbox_dev_s` is stored by board code and later used by `ipcmtest`:
```c
static FAR struct mbox_dev_s *g_pl320_dev;
int board_mbox_init(void)
{
if (up_cpu_index() == 0)
{
g_pl320_dev = pl320_initialize(&g_pl320_config, PL320_REG_BASE);
if (g_pl320_dev == NULL)
{
return -ENODEV;
}
}
return OK;
}
FAR struct mbox_dev_s *board_get_pl320_dev(void)
{
return g_pl320_dev;
}
```
Test application initialization:
Both cores initialize the PL320 mailbox device, resolve the configured
channel,
and register an RX callback with the generic MBOX API.
RISC-V side RX initialization for messages sent from the R52 core:
```c
FAR struct mbox_dev_s *pl320_dev;
FAR struct mbox_chan_s *rx_chan;
static struct mbox_receiver_s receiver;
int ret;
pl320_dev = board_get_pl320_dev();
rx_chan = mbox_get_chan(pl320_dev, (FAR void *)(uintptr_t)17);
receiver.chan = rx_chan;
receiver.priv = NULL;
ret = mbox_register_rxcallback(&receiver, mbox_callback, NULL);
```
R52 side RX initialization for messages sent from the RISC-V core:
```c
FAR struct mbox_dev_s *pl320_dev;
FAR struct mbox_chan_s *rx_chan;
static struct mbox_receiver_s receiver;
int ret;
pl320_dev = board_get_pl320_dev();
rx_chan = mbox_get_chan(pl320_dev, (FAR void *)(uintptr_t)16);
receiver.chan = rx_chan;
receiver.priv = NULL;
ret = mbox_register_rxcallback(&receiver, mbox_callback, NULL);
```
Callback:
The RX callback checks the lower-half result, verifies that a payload was
provided, increments the RX counter, and prints the received PL320 payload.
```c
static int mbox_callback(int ret, FAR struct mbox_chan_s *chan,
FAR void *arg, FAR void *data, size_t size)
{
FAR uint8_t *msg;
if (ret != 0 || data == NULL)
{
g_rx_errors++;
return -EIO;
}
g_rx_count++;
msg = (FAR uint8_t *)data;
printf("received msg on chan[%d] count=%d size=%zu\n",
chan->id, g_rx_count, size);
printf("%02x %02x %02x %02x ...\n",
msg[0], msg[1], msg[2], msg[3]);
return OK;
}
```
Send-flow:
R52-to-RISC-V send path. The R52 core resolves PL320 TX channel 17 and sends
one 28-byte PL320 payload with `mbox_send()`.
```c
FAR struct mbox_chan_s *tx_chan;
struct mbox_sender_s sender;
uint32_t data[7];
int ret;
tx_chan = mbox_get_chan(pl320_dev, (FAR void *)(uintptr_t)17);
sender.chan = tx_chan;
sender.priv = NULL;
memset(data, 0xff, sizeof(data));
ret = mbox_send(&sender, data, sizeof(data));
```
RISC-V-to-R52 send path. The RISC-V core resolves PL320 TX channel 16 and
sends
one 28-byte PL320 payload with `mbox_send()`.
```c
FAR struct mbox_chan_s *tx_chan;
struct mbox_sender_s sender;
uint32_t data[7];
int ret;
tx_chan = mbox_get_chan(pl320_dev, (FAR void *)(uintptr_t)16);
sender.chan = tx_chan;
sender.priv = NULL;
memset(data, 0xff, sizeof(data));
ret = mbox_send(&sender, data, sizeof(data));
```
Non-blocking send path:
```c
ret = mbox_ticksend(&sender, data, sizeof(data), 0);
```
Timed send path:
```c
struct timespec ts = {.tv_sec = 5, .tv_nsec = 0};
ret = mbox_ticksend(&sender, data, sizeof(data), clock_time2ticks(&ts));
```
Test results:
1. R52-to-RISC-V one-way PL320 send
R52 shell output:
```sh
[r52-0]=>ipcmtest xfer
[r52-0]=>vs_ipcm tx chan[13] (R52-A->R52-B)
mbox_send(R52-A->R52-B) ret=[0] [PASS]
```
RISC-V shell output:
```sh
2025-07-10-14_40_41_146746 0 F received msg on chan[17] (count=1):
2025-07-10-14_40_41_146752 1 F [00:07]: ff ff ff ff ff ff ff ff
2025-07-10-14_40_41_146757 1 F [08:15]: ff ff ff ff ff ff ff ff
2025-07-10-14_40_41_146762 1 F [16:23]: ff ff ff ff ff ff ff ff
2025-07-10-14_40_41_146767 1 F [24:27]: ff ff ff ff
```
2. RISC-V-to-R52 one-way PL320 send
RISC-V shell output:
```sh
[riscv-a]=>ipcmtest xfer
[riscv-a]=>pl320 riscv->r52 tx chan[16]
mbox_send(riscv->r52 pl320 chan[16]) ret=[0] [PASS]
```
R52 shell output:
```text
2025-07-10-14_47_46_286970 0 1 F [00:07]: ff ff ff ff ff ff ff ff
2025-07-10-14_47_46_286992 0 1 F [08:15]: ff ff ff ff ff ff ff ff
2025-07-10-14_47_46_287014 0 1 F [16:23]: ff ff ff ff ff ff ff ff
2025-07-10-14_47_46_287035 0 1 F [24:27]: ff ff ff ff
```
3. Non-blocking send
R52 shell output:
```text
[r52-0]=>ipcmtest nb_send
[r52-0]=>
=== Non-blocking Send Test (timeout=0) ===
[PASS] nonblocking_send
ret=0 (sent immediately)
=== Non-blocking Send: pass=1 fail=0 ===
2025-07-10-14_51_03_536758 0 8 F received msg on chan[12] (count=3):
2025-07-10-14_51_03_536782 0 8 F [00:07]: 01 01 00 02 b0 f8 01 00
2025-07-10-14_51_03_536804 0 8 F [08:15]: ff ff ff ff ff ff ff ff
2025-07-10-14_51_03_536826 0 8 F [16:23]: ff ff ff ff ff ff ff ff
2025-07-10-14_51_03_536847 0 8 F [24:27]: ff ff ff ff
```
RISC-V shell output:
```text
i didn't print here, the result will show in r52 shell
```
4. Trace
R52 shell output:
```text
[r52-0]=>ipcmtest stats
[r52-0]=>ipcmtest: enable CONFIG_MBOX_TRACE for statistics
rx_count=3 rx_errors=0
```
4. Timeout
I made riscv crash to make no-ack scene, r52 send by mbox_ticksend();
R52 shell output:
```text
[r52-0]=>ipcmtest timed_pl320
=== PL320 Timed Send Test (5-second timeout) ===
[r52-0]=>timed_send(r52->riscv pl320 chan[17]) ret=-110 [INFO] timed
out (riscv silent?)
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]