summaryrefslogtreecommitdiff
path: root/documentation/6.components/device-driver/mailbox/mailbox.md
blob: 7148a2fd2ff97d05b7065215b4bb04b5f079bcc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
@page page_device_mailbox Mailbox subsystem

# Mailbox subsystem (API)

Header: **`components/drivers/include/drivers/mailbox.h`**. Core: **`components/drivers/mailbox/mailbox.c`**.

The mailbox layer is a **lock-aware wrapper** around hardware **doorbell / message-slot** IPs: one side **sends** a small notification, the peer is woken by **IRQ** and handles **`rx_callback`**. Use for **AMP IPC**, **SCMI** doorbells, **remoteproc** handshakes, and **PIC**-backed mailboxes—not bulk DMA.

Device-tree binding and **`rt-thread,pic-mailbox`**: @ref page_device_mailbox_dm.

---

## When to use this API

| Use mailbox when… | Prefer something else when… |
| --- | --- |
| You need a **hardware channel** with **IRQ to peer** (SoC mailbox IP, PIC mailbox). | **Thread-to-thread** only: semaphore, message queue, completion. |
| Firmware expects **doorbell + shared memory** (SCMI, AMP). | **Large payloads** — put data in SRAM, pass a **pointer** or single word. |
| DT exposes **`mboxes` / `#mbox-cells` / `mbox-names`**. | No controller: implement **`rt_mbox_controller_ops`** and register first. |
| Named **RPMsg** endpoints and bus matching are required. | @ref page_device_rpmsg |

---

## Roles

| Role | Responsibility |
| --- | --- |
| **Controller driver** | **`rt_mbox_controller_register`**: **`num_chans`**, **`ops`**. Implements **`send`**, **`release`**, optional **`peek`**, **`request`**, **`ofw_parse`**. Calls **`rt_mbox_send_done`** when HW TX completes (if applicable). |
| **Client driver** | **`struct rt_mbox_client`** on consumer **`rt_device`**: **`tx_prepare`**, **`tx_done`**, **`rx_callback`**. **`rt_mbox_request_by_index`** or **`by_name`**. |

---

## Data structures

```c
struct rt_mbox_controller {
    struct rt_device *dev;
    const struct rt_mbox_controller_ops *ops;
    rt_size_t num_chans;
    struct rt_mbox_chan *chans;
};

struct rt_mbox_controller_ops {
    rt_err_t (*request)(struct rt_mbox_chan *);
    void (*release)(struct rt_mbox_chan *);
    rt_err_t (*send)(struct rt_mbox_chan *, const void *data);
    rt_bool_t (*peek)(struct rt_mbox_chan *);
    int (*ofw_parse)(struct rt_mbox_controller *, struct rt_ofw_cell_args *);
};

struct rt_mbox_client {
    struct rt_device *dev;
    void (*rx_callback)(struct rt_mbox_client *, void *data);
    void (*tx_prepare)(struct rt_mbox_client *, const void *data);
    void (*tx_done)(struct rt_mbox_client *, const void *data, rt_err_t err);
};
```

---

## Requesting a channel

```c
struct rt_mbox_chan *rt_mbox_request_by_index(struct rt_mbox_client *client, int index);
struct rt_mbox_chan *rt_mbox_request_by_name(struct rt_mbox_client *client, char *name);
rt_err_t rt_mbox_release(struct rt_mbox_chan *chan);
```

| API | Returns |
| --- | --- |
| **`request_by_index`** | Channel pointer or **error pointer** (`rt_is_err_ptr`) |
| **`request_by_name`** | Channel, **error pointer**, or **`NULL`** if name not in **`mbox-names`** |

**Prerequisites**: valid **`client->dev->ofw_node`**; provider probed (**`rt_ofw_data`** = controller). Optional **`ops->request`** runs after channel selection; on failure core **`release`**s and returns error pointer.

---

## Sending: `rt_mbox_send`

```c
rt_err_t rt_mbox_send(struct rt_mbox_chan *chan, const void *data, rt_uint32_t timeout_ms);
void rt_mbox_send_done(struct rt_mbox_chan *chan, rt_err_t err);
```

**Flow** (under **`chan->lock`**):

1. Optional **`client->tx_prepare(client, data)`**
2. **`ctrl->ops->send(chan, data)`** — return value is **only** the ops result
3. On success: store **`chan->data`**, clear **`complete`**; if **`timeout_ms != RT_WAITING_FOREVER`**, start one-shot timer
4. On send failure: **`complete = RT_TRUE`** immediately

**`RT_EOK` from `rt_mbox_send`** means the controller **accepted** the submission, not that **`tx_done`** ran.

**`rt_mbox_send_done`**: clears **`chan->data`**, calls **`tx_done`**, sets **`complete = RT_TRUE`**. Controller must invoke after HW TX finish (or on error). Timer calls it with **`-RT_ETIMEOUT`** if still incomplete.

**Implications**:

- Do not free **`data`** (or pointed buffer) until **`tx_done`** or synchronous send failure
- **`tx_prepare`** runs **with channel spinlock** — non-blocking only
- **`RT_WAITING_FOREVER`**: no timer; requires **`send_done`** from controller for **`tx_done`** (see @ref page_device_mailbox_dm for PIC caveat)

---

## Receiving: `rt_mbox_recv`

```c
rt_err_t rt_mbox_recv(struct rt_mbox_chan *chan, void *data);
```

Does **not** read hardware. Invokes **`client->rx_callback(client, data)`** if set. **Controller ISR** reads registers/FIFO, then passes payload pointer (e.g. local **`uint32_t`**) into **`rt_mbox_recv`**.

---

## `rt_mbox_peek`

```c
rt_bool_t rt_mbox_peek(struct rt_mbox_chan *chan);
```

Forwards to **`ops->peek`** if present; else **`RT_FALSE`**.

---

## Controller unregister

**`rt_mbox_controller_unregister`**: removes from global list, **`rt_dm_dev_unbind_fwdata`**, **`rt_mbox_release`** on every channel (reverse order), frees **`chans`**. **`release`** must be safe even if **`request`** never ran for a channel.

---

## Example (client sketch)

```c
static void my_rx(struct rt_mbox_client *cl, void *data)
{
    rt_uint32_t msg = *(rt_uint32_t *)data;
    /* handle doorbell */
}

static void my_tx_done(struct rt_mbox_client *cl, const void *data, rt_err_t err)
{
    /* release buffer */
}

static struct rt_mbox_client mbox_cl = {
    .dev = &my_pdev->parent,
    .rx_callback = my_rx,
    .tx_done = my_tx_done,
};

static int my_probe(struct rt_platform_device *pdev)
{
    struct rt_mbox_chan *chan;

    mbox_cl.dev = &pdev->parent;
    chan = rt_mbox_request_by_name(&mbox_cl, "tx");
    if (!chan || rt_is_err_ptr(chan))
        return -RT_ERROR;

    rt_uint32_t doorbell = MY_MSG_ID;
    return rt_mbox_send(chan, &doorbell, 100);  /* 100 ms tx timeout */
}
```

---

## Pitfalls

| Issue | Mitigation |
| --- | --- |
| **No `send_done` in controller** | Use finite timeout or fire-and-forget (@ref page_device_mailbox_dm — PIC) |
| **`release` NULL** | Always implement **`ops->release`** |
| **Blocking in `tx_prepare`** | Deadlock risk under spinlock |
| **Confusing return types** | Distinguish **`NULL`** (name) vs **`rt_err_ptr`** (index) |

---

## See also

- @ref page_device_mailbox_dm
- @ref page_device_rpmsg
- @ref page_device_scmi
- @ref page_device_pic
- `components/drivers/mailbox/mailbox.c`
- `components/drivers/mailbox/mailbox-pic.c`