|
5 | 5 | - [1. Scope](#1-scope) |
6 | 6 | - [2. Where a port plugs in](#2-where-a-port-plugs-in) |
7 | 7 | - [3. The link-layer device interface](#3-the-link-layer-device-interface) |
| 8 | + - [3.1 L2 versus L3 drivers](#31-l2-versus-l3-drivers) |
8 | 9 | - [4. Designing a device driver](#4-designing-a-device-driver) |
9 | 10 | - [4.1 The driver contract](#41-the-driver-contract) |
10 | | - - [4.2 A driver without DMA (PIO / host-backed)](#42-a-driver-without-dma-pio--host-backed) |
| 11 | + - [4.2 The simplest drivers: loopback, PIO, host-backed](#42-the-simplest-drivers-loopback-pio-host-backed) |
11 | 12 | - [4.3 A driver with DMA: descriptor rings](#43-a-driver-with-dma-descriptor-rings) |
12 | 13 | - [4.4 DMA ownership and the poll path](#44-dma-ownership-and-the-poll-path) |
13 | 14 | - [4.5 DMA ownership and the send path](#45-dma-ownership-and-the-send-path) |
@@ -125,9 +126,45 @@ back-pointer to your driver state, the wolfIP equivalent of lwIP's |
125 | 126 | `wolfIP_getdev(s)` and additional interfaces with `wolfIP_getdev_ex(s, idx)`. |
126 | 127 |
|
127 | 128 | `buf` is always a **single, contiguous, linear** frame buffer owned by the |
128 | | -stack — there is no `pbuf` chain to walk. The frame includes the full Ethernet |
129 | | -header. Your driver must not retain the `buf` pointer after the callback |
130 | | -returns. |
| 129 | +stack — there is no `pbuf` chain to walk. On an L2 interface the frame includes |
| 130 | +the full Ethernet header (see below). Your driver must not retain the `buf` |
| 131 | +pointer after the callback returns. |
| 132 | + |
| 133 | +### 3.1 L2 versus L3 drivers |
| 134 | + |
| 135 | +The `non_ethernet` flag selects the driver class: |
| 136 | + |
| 137 | +- **L2 / Ethernet driver** (`non_ethernet = 0`, the default). The driver moves |
| 138 | + complete Ethernet frames: a 14-byte Ethernet header followed by the payload. |
| 139 | + wolfIP performs ARP / neighbour resolution and builds and parses the Ethernet |
| 140 | + header itself. The `buf` passed to `poll` and `send` begins at the Ethernet |
| 141 | + header. The TAP, LPC, VA416xx, and GEM drivers are all L2. |
| 142 | + |
| 143 | +- **L3 / point-to-point driver** (`non_ethernet = 1`). The link carries bare IP |
| 144 | + packets — there is no Ethernet header and no ARP. On transmit, wolfIP strips |
| 145 | + the 14-byte Ethernet header it built before calling your `send`, so `send` |
| 146 | + receives the IP packet (`buf + ETH_HEADER_LEN`, `len - ETH_HEADER_LEN`). On |
| 147 | + receive, your `poll` must return a buffer that begins at the IP header. The |
| 148 | + built-in loopback interface and TUN-style devices |
| 149 | + (`src/port/posix/linux_tun.c`, `IFF_TUN`) are L3. |
| 150 | + |
| 151 | +| | L2 (Ethernet) | L3 (point-to-point) | |
| 152 | +|---|---|---| |
| 153 | +| `non_ethernet` | `0` | `1` | |
| 154 | +| Frame at `poll` / `send` | Ethernet header + IP | IP only | |
| 155 | +| ARP / neighbour resolution | performed by wolfIP | skipped | |
| 156 | +| Examples | TAP, LPC, VA416xx, GEM | loopback `lo`, TUN | |
| 157 | + |
| 158 | +The stack applies the L3 stripping in `wolfIP_ll_send_frame()`: |
| 159 | + |
| 160 | +```c |
| 161 | +if (ll->non_ethernet) |
| 162 | + return ll->send(ll, (uint8_t *)buf + ETH_HEADER_LEN, len - ETH_HEADER_LEN); |
| 163 | +``` |
| 164 | +
|
| 165 | +The `mtu` field always describes wolfIP's internal frame budget *including* |
| 166 | +Ethernet headroom; on an L3 link the maximum IP payload handed to `send` is |
| 167 | +therefore `mtu - ETH_HEADER_LEN`. |
131 | 168 |
|
132 | 169 | --- |
133 | 170 |
|
@@ -164,12 +201,56 @@ before writing a line of driver code: |
164 | 201 | hardware is busy, return `0` (poll) or `-WOLFIP_EAGAIN` (send) and let the |
165 | 202 | next poll cycle make progress. |
166 | 203 |
|
167 | | -### 4.2 A driver without DMA (PIO / host-backed) |
| 204 | +### 4.2 The simplest drivers: loopback, PIO, host-backed |
| 205 | +
|
| 206 | +The very simplest driver has no hardware at all. When `WOLFIP_ENABLE_LOOPBACK` |
| 207 | +is set (and `WOLFIP_MAX_INTERFACES > 1`), `wolfIP_init()` installs an L3 |
| 208 | +loopback interface at index 0 — `ifname` `"lo"`, `non_ethernet = 1`, address |
| 209 | +`127.0.0.1/8` — whose `poll`/`send` move IP packets through a small in-memory |
| 210 | +queue (`src/wolfip.c`): |
| 211 | +
|
| 212 | +```c |
| 213 | +static int wolfIP_loopback_send(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) |
| 214 | +{ |
| 215 | + struct wolfIP *s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); |
| 216 | +
|
| 217 | + if (len == 0 || len > IP_MTU_MAX) |
| 218 | + return 0; |
| 219 | + if (s->loopback_count >= WOLFIP_LOOPBACK_QUEUE_DEPTH) |
| 220 | + return -WOLFIP_EAGAIN; /* queue full: retry later */ |
| 221 | + /* buf is the IP packet — the Ethernet header was already stripped for |
| 222 | + * this non_ethernet device. Store as-is; wolfIP_poll re-adds the prefix. */ |
| 223 | + memcpy(s->loopback_buf[s->loopback_tail], buf, len); |
| 224 | + s->loopback_count++; |
| 225 | + return (int)len; |
| 226 | +} |
| 227 | +
|
| 228 | +static int wolfIP_loopback_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) |
| 229 | +{ |
| 230 | + struct wolfIP *s = WOLFIP_CONTAINER_OF(ll, struct wolfIP, ll_dev); |
| 231 | + uint32_t pending; |
| 232 | +
|
| 233 | + if (s->loopback_count == 0) |
| 234 | + return 0; /* nothing queued */ |
| 235 | + pending = s->loopback_pending_len[s->loopback_head]; |
| 236 | + if (pending > len) |
| 237 | + return 0; |
| 238 | + memcpy(buf, s->loopback_buf[s->loopback_head], pending); |
| 239 | + s->loopback_count--; |
| 240 | + return (int)pending; /* one IP packet */ |
| 241 | +} |
| 242 | +``` |
| 243 | + |
| 244 | +This is the `poll`/`send` contract in its purest form: `send` queues a packet |
| 245 | +(or returns `-WOLFIP_EAGAIN` when the queue is full), `poll` dequeues one packet |
| 246 | +(or returns `0` when empty). No Ethernet header, no DMA, no cache maintenance — |
| 247 | +exactly what an L3 driver does, with an in-memory queue standing in for the wire. |
168 | 248 |
|
169 | | -The simplest possible driver does a register/FIFO read on poll and a |
170 | | -register/FIFO write on send. The POSIX TAP driver is the canonical minimal |
171 | | -example — the "hardware" is a host file descriptor, but the shape is identical |
172 | | -to a small MCU MAC that exposes an RX/TX FIFO (`src/port/posix/tap_linux.c`): |
| 249 | +The next simplest driver is a programmed-I/O Ethernet (L2) MAC: a register/FIFO |
| 250 | +read on poll and a register/FIFO write on send. The POSIX TAP driver is the |
| 251 | +canonical minimal example — the "hardware" is a host file descriptor, but the |
| 252 | +shape is identical to a small MCU MAC that exposes an RX/TX FIFO |
| 253 | +(`src/port/posix/tap_linux.c`): |
173 | 254 |
|
174 | 255 | ```c |
175 | 256 | static int tap_poll(struct wolfIP_ll_dev *ll, void *buf, uint32_t len) |
|
0 commit comments