mirror of
https://github.com/logicog/RTLPlayground.git
synced 2026-10-29 17:54:14 +08:00
180 lines
7.1 KiB
Markdown
180 lines
7.1 KiB
Markdown
# Adding a new device
|
||
|
||
A device is supported by adding one board definition `.h` and board configuration `.c` file
|
||
under `machine/`.
|
||
the build selects the definition by name, and CI compiles every file it finds.
|
||
|
||
To compile for your added device, do:
|
||
```
|
||
make MACHINE=<NAME>
|
||
```
|
||
|
||
## What a new device consists of
|
||
|
||
| File | Purpose |
|
||
|------|---------|
|
||
| `machine/<NAME>.c` | the `struct machine machine` board definition |
|
||
| `machine/<NAME>.h` | `MACHINE_MODEL`, the model string shown in the UI and the CLI |
|
||
| `doc/devices/<name>.md` + `doc/supported_devices.md` | (only if the board is verified and you want to support it publicly) user-facing documentation |
|
||
|
||
`<NAME>` is the machine symbol without its `MACHINE_` prefix, so `MACHINE` is
|
||
passed exactly as spelled on the command line: `make MACHINE=MY_BOARD_1_0`
|
||
needs `machine/MY_BOARD_1_0.c` and `machine/MY_BOARD_1_0.h`.
|
||
|
||
## Step 1: the model header
|
||
|
||
`machine/<NAME>.h` only carries the product string. Use the existing files as
|
||
the template:
|
||
|
||
```c
|
||
#ifndef _MACHINE_MY_BOARD_1_0_H_
|
||
#define _MACHINE_MY_BOARD_1_0_H_
|
||
|
||
#define MACHINE_MODEL "Brand Model V1.0"
|
||
|
||
#endif
|
||
```
|
||
|
||
The device product string is what the CLI or the Web-Interface will print to identify the device; `machine.h` includes it by name.
|
||
|
||
> [!NOTE]
|
||
> `machine_name` is a `char[30]` and `MACHINE_MODEL` is copied into it at
|
||
> compile time. A longer model string still builds, but SDCC emits
|
||
> `warning 147: excess elements in array of chars initializer` and the name is
|
||
> truncated in the UI. Keep it to 29 characters or less.
|
||
|
||
## Step 2: the board definition
|
||
|
||
`machine/<NAME>.c` starts with the shared prelude and then defines the single
|
||
`machine` object. Copy the closest existing board and adjust it — do not start
|
||
from a blank file, the field meanings are easier to see in a real example.
|
||
The best choice is usually a device that has the same number and type of Ethernet
|
||
ports and the same number of SFP+ cages.
|
||
|
||
```c
|
||
#include "machine_defs.h"
|
||
|
||
__code const struct machine machine = {
|
||
.machine_name = MACHINE_MODEL,
|
||
.isRTL8373 = 1,
|
||
.min_port = 0,
|
||
.max_port = 8,
|
||
.n_sfp = 1,
|
||
.log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9},
|
||
.phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8},
|
||
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||
|
||
.sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
|
||
.sfp_port[0].pin_los = GPIO37,
|
||
.sfp_port[0].pin_tx_disable = GPIO_NA,
|
||
.sfp_port[0].sds = 1,
|
||
.sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
|
||
.reset_pin = GPIO_NA,
|
||
|
||
.high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
|
||
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1 },
|
||
.led_sets = {
|
||
{ /* RJ45 ports */
|
||
LEDS_2G5 | LEDS_LINK,
|
||
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
|
||
0,
|
||
0
|
||
}, { /* SFP port, single green LED */
|
||
LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
|
||
0,
|
||
0,
|
||
0
|
||
}
|
||
},
|
||
};
|
||
```
|
||
|
||
### Fields
|
||
|
||
| Field | Meaning |
|
||
|-------|---------|
|
||
| `machine_name` | always `MACHINE_MODEL` |
|
||
| `isRTL8373` | `1` for an RTL8373 SoC, `0` for RTL8372 |
|
||
| `min_port` / `max_port` | range of logical port numbers the device has; `min_port` is `0` for RTL8373 boards and `3` for RTL8372 boards (logical ports 1–2 are the SoC's internal ports on the 8372), `max_port` is usually 8. |
|
||
| `n_sfp` | number of SFP+ cages |
|
||
| `n_10g` | SerDes 10G capability used by the RTL837xN init: `0` none, `1` one, `2` two 10G-capable SerDes |
|
||
| `log_to_phys_port[9]` | logical port (0-based index) → physical front-panel port number |
|
||
| `phys_to_log_port[9]` | the inverse, physical port 1 is index 0, unused slots are 0 |
|
||
| `is_sfp[9]` | `0` for copper, `1` for left slot and `2` for right slot |
|
||
| `sfp_port[2]` | per-cage wiring, `sfp_port[0]` is the leftmost cage |
|
||
| `reset_pin` | GPIO of the reset button, `GPIO_NA` if unpopulated |
|
||
| `high_leds` | mux/enable for the LED pins 27–29 (`LED_27`, `LED_28_SYS`, `LED_29`) |
|
||
| `port_led_set[9]` | which LED set (0–3) each logical port uses |
|
||
| `led_sets[4][4]` | Each port can have up to 4 GPIO output pins assigned. 4 sets of LEDs with 4 LED-conditions per set; the conditions define which GPIO pins become active for a port given a port link-state. Which LEDs are then enabled depends on the wiring of the pins to up to 3 LEDs for that port, which can make a logical combination of the 4 LED output-pins. |
|
||
| `led_mux_custom` / `led_mux[28]` | `1` plus the 28 mux values when the stock LED muxing does not fit the board |
|
||
| `mac_flash_offset` | flash offset of the factory MAC address, omit or `0` if the board does not store one |
|
||
|
||
Each `struct sfp_port` entry holds `pin_detect`, `pin_los`, `pin_tx_disable`,
|
||
`sds` (which SerDes drives the cage) and `i2c` (built with `I2CBUS(sda, scl)`,
|
||
or `GPIO_NA`-based values where a cage has no such pin).
|
||
|
||
`I2CBUS(...)` only accepts the pin pairs listed in `machine_defs.h`. A wrong
|
||
pair is caught at compile time: SDCC reports
|
||
`error 176: sizeof applied to an incomplete type` pointing at the offending
|
||
`.i2c` line, because the macro deliberately turns an invalid bus number into an
|
||
array of size 0.
|
||
|
||
### Reusing an existing definition
|
||
|
||
If the new board is electrically identical to one that already exists — same
|
||
port mapping, same SFP wiring, same LEDs — do not copy the file. Make the new
|
||
`.c` a symlink and keep only a distinct `.h`, so the two boards cannot drift
|
||
apart:
|
||
|
||
```
|
||
ln -s KP_9000_6XHML_X2.c machine/MY_BOARD_1_0.c
|
||
```
|
||
|
||
The new board still needs `machine/MY_BOARD_1_0.h` (pick the version number on the PCB) with its own
|
||
`MACHINE_MODEL`, because `MACHINE_MODEL` is what distinguishes the products.
|
||
Existing examples: `machine/PCB_K0402WS_V3.c`, `machine/KP_9000_9XHML_X_V3_2.c`
|
||
and the six `KP_9000_*` revision variants.
|
||
|
||
Two files that differ only in whitespace or comments are duplicates and should
|
||
be collapsed this way.
|
||
|
||
## Step 3: build and verify
|
||
|
||
```
|
||
make MACHINE=MY_BOARD_1_0
|
||
```
|
||
|
||
The generated flash image appears as `output/rtlplayground-<version>-MY_BOARD_1_0.bin`. An
|
||
unknown or empty `MACHINE` fails immediately with a message naming the expected
|
||
file, before anything is compiled.
|
||
|
||
Then run the checks CI runs:
|
||
|
||
```
|
||
make machine_check # compiles machine/<NAME>.c and machine_init.c for every machine
|
||
make -C test # host-side unit tests
|
||
```
|
||
|
||
`make machine_check` discovers the definitions with
|
||
`$(wildcard machine/*.c)`, so a new file is covered without registering it
|
||
anywhere. CI (`.github/workflows/build.yml`) runs it on every push and pull
|
||
request, alongside full builds for a 4+2 and an 8+1 board.
|
||
|
||
A successful compile only proves the definition is syntactically valid. The
|
||
actual behaviour — port numbering, SFP detection, LEDs — has to be confirmed on
|
||
hardware, and the console commands (`port`, `stat`, `sfp`, `leds`) are the
|
||
tools for that.
|
||
|
||
## Step 4: documentation
|
||
|
||
Once the board has been tested on hardware:
|
||
|
||
1. Add a row to the table in [Supported devices](supported_devices.md).
|
||
2. Add `doc/devices/<name>.md` describing the board, the build target
|
||
(`make MACHINE=MY_BOARD_1_0`), what works and what is unverified, plus
|
||
photos and the PCB silkscreen marking.
|
||
3. If the board shares a PCB with other revisions, follow
|
||
`doc/devices/KP-9000-6XH-X2.md` and document which `MACHINE` to pick for
|
||
which silkscreen.
|
||
|