6.5 KiB
Adding a new device
A device is supported by adding one board definition under machine/.
the build selects the definition by name.
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) 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:
#ifndef _MACHINE_MY_BOARD_1_0_H_
#define _MACHINE_MY_BOARD_1_0_H_
#define MACHINE_MODEL "Brand Model V1.0"
#endif
The header is what the UI and the CLI print; machine.h includes it by name.
Note
machine_nameis achar[30]andMACHINE_MODELis copied into it at compile time. A longer model string still builds, but SDCC emitswarning 147: excess elements in array of chars initializerand 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.
#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) |
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/2 for the I2C bus number of the SFP on that port |
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] |
the 4 LED combinations per set; which of the 4 is used depends on how the LED is wired |
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 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 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_dump) are the
tools for that.
Step 4: documentation
Once the board has been tested on hardware:
- Add a row to the table in Supported devices.
- Add
doc/devices/<name>.mddescribing the board, the build target (make MACHINE=MY_BOARD_1_0), what works and what is unverified, plus photos and the PCB silkscreen marking. - If the board shares a PCB with other revisions, follow
doc/devices/KP-9000-6XH-X2.mdand document whichMACHINEto pick for which silkscreen.