Merge branch 'main' into main

This commit is contained in:
SkullyDev authored and GitHub committed 2026-09-19 13:07:28 +03:00
commit b9b50f9a70
38 files changed
+2135 -130

No files matched your search

+3
View File
@@ -3,6 +3,7 @@ name: Build firmware
on: on:
push: push:
branches: ['**'] branches: ['**']
pull_request:
jobs: jobs:
build: build:
@@ -17,6 +18,8 @@ jobs:
run: | run: |
apt update apt update
apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y apt install make gcc sdcc xxd python-is-python3 libjson-c-dev zlib1g-dev -y
- name: Run the host-side unit tests
run: make -C test
- name: Check if machine.c can be compiled for all machines - name: Check if machine.c can be compiled for all machines
run: make machine_check run: make machine_check
- name: Make project - name: Make project
+4
View File
@@ -7,3 +7,7 @@ version.h
tools/httpd_sim tools/httpd_sim
tools/injector tools/injector
tools/fileadder tools/fileadder
# host-test stand-ins for the two generated headers
!test/stub/html_data.h
!test/stub/version.h
+16 -16
View File
@@ -2,15 +2,15 @@
A Playground for Firmware development for advanced user of RTL8372/RTL8373 based 2.5GBit Switches. A Playground for Firmware development for advanced user of RTL8372/RTL8373 based 2.5GBit Switches.
For each hardware configuration of these devices, there is usually a managed and an For each hardware configuration of these devices, there is usually a managed and an
umanaged version sold, with mostly identical hardware. The aim is to provide management unmanaged version sold, with mostly identical hardware. The aim is to provide management
features also for unmanaged devices with additional features such as Management VLAN, features also for unmanaged devices with additional features such as Management VLAN,
dhcp servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however DHCP servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however
only the following features are provided: only the following features are provided:
- A modern web-interface with mouse-over to display further information - A modern web-interface with mouse-over to display further information
- A serial console interface to configure all features - A serial console interface to configure all features
- IGMP to configure Multicast streaming - IGMP to configure Multicast streaming
- Port configuration showing detailed informtion about own and Link-partner advertised - Port configuration showing detailed information about own and Link-partner advertised
Speed settins and configuration of these settings on the local side Speed settings and configuration of these settings on the local side
- Per-port configuration of frame sizes (MTUs) for Jumbo-Frame support or limiting MTUs - Per-port configuration of frame sizes (MTUs) for Jumbo-Frame support or limiting MTUs
for particular devices for particular devices
- EEE (Energy Efficient Ethernet) can be configured per-port. Detailed information is - EEE (Energy Efficient Ethernet) can be configured per-port. Detailed information is
@@ -25,7 +25,7 @@ only the following features are provided:
- Firmware updates via the web - Firmware updates via the web
- Installation as a firmware upgrade from the original web-interface - Installation as a firmware upgrade from the original web-interface
<img width="1420" height="623" alt="GUI" src="doc/images/gui.png" /> <img width="1673" height="977" alt="GUI" src="doc/images/gui.png" />
While the firmware provides already considerable improvements over the original managed firmware, While the firmware provides already considerable improvements over the original managed firmware,
the firmware still lacks support for the proprietary loop prevention the firmware still lacks support for the proprietary loop prevention
@@ -37,7 +37,7 @@ a backup of the original flash content via a SOIC clamp such as also used for BI
backups and can re-install that firmware in case something is wrong. For this no soldering backups and can re-install that firmware in case something is wrong. For this no soldering
skills are necessary. skills are necessary.
The firmware supports all hardware featues of devices with The firmware supports all hardware features of devices with
- 4 2.5GBit ports + 2 SFP+ ports - 4 2.5GBit ports + 2 SFP+ ports
- 5 2.5GBIT + 1 SFP+ port - 5 2.5GBIT + 1 SFP+ port
- 8 2.5GBit + 1 SFP+ port - 8 2.5GBit + 1 SFP+ port
@@ -131,7 +131,7 @@ rtlplayground-v0.1.0-12c98ba-dirty-LIANGUO_ZX_SWTGW215AS.bin
> [!CAUTION] > [!CAUTION]
> This image can be flashed directly to the chip OR through the firmware update/upgrade > This image can be flashed directly to the chip OR through the firmware update/upgrade
> interface of RTLPlaygound interface > interface of RTLPlayground interface
## (2) Compiling for OEM running device with management options (web upgrade) ## (2) Compiling for OEM running device with management options (web upgrade)
@@ -179,7 +179,7 @@ ghidra see this information about [Ghidra images](doc/ghidra.md).
## (4) Installation through the Web interface (software way) ## (4) Installation through the Web interface (software way)
Managed switches (OEM firmware of RTLplaygroud firmware) can be upgraded via the web interface. Managed switches (OEM firmware of RTLplayground firmware) can be upgraded via the web interface.
Unmanaged switch cannot be flashed this way (see 5). Unmanaged switch cannot be flashed this way (see 5).
Go to "Firmware update" tab, select the correct file. Go to "Firmware update" tab, select the correct file.
@@ -190,7 +190,7 @@ Go to "Firmware update" tab, select the correct file.
> [!CAUTION] > [!CAUTION]
> Check one more time that your device matches the machine type before flashing. > Check one more time that your device matches the machine type before flashing.
> Be shure you have a backup of the original firmware before diving in RTLPlaygroung. > Be sure you have a backup of the original firmware before diving in RTLPlayground.
Finally, push the Upload File Button and you're done ! Finally, push the Upload File Button and you're done !
@@ -198,7 +198,7 @@ Finally, push the Upload File Button and you're done !
## (5) Flashing the ROM directly (hardware way, but also only way to rescue) ## (5) Flashing the ROM directly (hardware way, but also only way to rescue)
This procedure is the only way to flash unmanaged switches, if the ROM chip is large enough. This procedure is the only way to flash unmanaged switches, if the ROM chip is large enough.
This is also the only way to unbrick your device if something went wroong. This is also the only way to unbrick your device if something went wrong.
> [!IMPORTANT] > [!IMPORTANT]
> You need a SOIC-8 clip to flash the ROM chip directly onboard. > You need a SOIC-8 clip to flash the ROM chip directly onboard.
@@ -218,7 +218,7 @@ This is also the only way to unbrick your device if something went wroong.
- ERASE THE ROM (BLANK) ! - ERASE THE ROM (BLANK) !
- Load the firmware into IMSProg. - Load the firmware into IMSProg.
- Flash is to the ROM chip. - Flash is to the ROM chip.
- Disconect the clip from the ROM chip. - Disconnect the clip from the ROM chip.
- You're done, ready for the first boot. - You're done, ready for the first boot.
## (6) Connecting a serial interface (optional) ## (6) Connecting a serial interface (optional)
@@ -233,7 +233,7 @@ When you power up the switch, the device will perform some examples and provide
## (8) The web-interface ## (8) The web-interface
The web-interface can be reached under the [default 192.168.10.247](http://192.168.10.247) unless you The web-interface can be reached under the [default 192.168.10.247](http://192.168.10.247) unless you
specified an IP adress in the config.txt before compilation. specified an IP address in the config.txt before compilation.
> [!TIP] > [!TIP]
> The default password is `1234`. > The default password is `1234`.
@@ -333,13 +333,13 @@ While in compilation part, you might write directly to config.txt file before ma
nano config.txt nano config.txt
``` ```
If you want to modify settings after the flash is done, go to the Advanced Settings tab in System Settings If you want to modify settings after the flash is done, go to the System tab and find the Startup Configuration.
<img width="1085" height="646" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" /> <img width="1673" height="978" alt="ADVANCED SETTINGS" src="doc/images/advanced_settings.png" />
``` ```
ip xxx.xxx.xxx.xxx = IP adress of the switch ip xxx.xxx.xxx.xxx = IP address of the switch
gw yyy.yyy.yyy.yyy = IP adress of the gateway gw yyy.yyy.yyy.yyy = IP address of the gateway
netmask zzz.zzz.zzz.zzz = Network mask of the switch netmask zzz.zzz.zzz.zzz = Network mask of the switch
port x name xxx = Name xxx the port number x port x name xxx = Name xxx the port number x
port z 1g = Set 1g speed for port z port z 1g = Set 1g speed for port z
+73 -52
View File
@@ -426,6 +426,22 @@ err:
return 0; return 0;
} }
static void cmd_error(__code const char *msg)
{
err_status = ERR_INVALID_ARGUMENT;
print_string("Error: ");
print_string(msg);
}
static uint8_t name_char(uint8_t c)
{
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
return '.';
return c;
}
// Sets the management MAC from a "mac <aa:bb:cc:dd:ee:ff>" command (or prints // Sets the management MAC from a "mac <aa:bb:cc:dd:ee:ff>" command (or prints
// the current one with a bare "mac"). Refuses blank/multicast/locally // the current one with a bare "mac"). Refuses blank/multicast/locally
// administered/all-zero-OUI addresses so the running MAC stays reachable, and // administered/all-zero-OUI addresses so the running MAC stays reachable, and
@@ -441,11 +457,11 @@ void parse_mac_cmd(void)
return; return;
} }
if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1])) { if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1])) {
print_string("Error: mac [<aa:bb:cc:dd:ee:ff>]\n"); cmd_error("mac [<aa:bb:cc:dd:ee:ff>]\n");
return; return;
} }
if ((mac_parse_result[0] & 0x03) || ((mac_parse_result[0] | mac_parse_result[1] | mac_parse_result[2]) == 0)) { if ((mac_parse_result[0] & 0x03) || ((mac_parse_result[0] | mac_parse_result[1] | mac_parse_result[2]) == 0)) {
print_string("refusing: must be unicast, globally administered\n"); cmd_error("the MAC must be unicast and globally administered\n");
return; return;
} }
if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) { if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) {
@@ -523,7 +539,7 @@ void parse_lag(void)
port_lag_members_set(group, members); port_lag_members_set(group, members);
return; return;
err: err:
print_string("Error: lag (show | <1-4> (d | <port>...))\n"); cmd_error("lag (show | <1-4> (d | <port>...))\n");
} }
@@ -565,7 +581,7 @@ void parse_lag_hash(void)
port_lag_hash_set(group, hash); port_lag_hash_set(group, hash);
return; return;
err: err:
print_string("Error: laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n"); cmd_error("laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n");
} }
@@ -619,7 +635,7 @@ void parse_vlan(void)
*vlan_str++ = hex[(vlan_settings.vlan >> 4) & 0xf] ; *vlan_str++ = hex[(vlan_settings.vlan >> 4) & 0xf] ;
*vlan_str++ = hex[vlan_settings.vlan & 0xf]; *vlan_str++ = hex[vlan_settings.vlan & 0xf];
while(!cmd_is_space_or_nul(pos)) { while(!cmd_is_space_or_nul(pos)) {
uint8_t c = cmd_buffer[pos++]; uint8_t c = name_char(cmd_buffer[pos++]);
write_char(c); write_char(c);
*vlan_str++ = c; *vlan_str++ = c;
} }
@@ -663,7 +679,7 @@ void parse_vlan(void)
} }
return; return;
err: err:
print_string("Error: vlan (<vlan-id>|show) [port][t]...\n"); cmd_error("vlan (<vlan-id>|show) [port][t]...\n");
} }
@@ -714,7 +730,7 @@ void parse_isolate(void)
return; return;
err: err:
print_string("Error: isolate <port> [show|off] [port]...\n"); cmd_error("isolate <port> [show|off] [port]...\n");
} }
@@ -751,7 +767,7 @@ void parse_ingress(void)
// Setting mode for all ports at once // Setting mode for all ports at once
for (log_port = machine.min_port; log_port <= machine.max_port; log_port++) { for (log_port = machine.min_port; log_port <= machine.max_port; log_port++) {
if (!port_ingress_filter(log_port, mode)) { if (!port_ingress_filter(log_port, mode)) {
print_string("Error setting ingress filter for port "); print_phys_port(log_port); write_char('\n'); cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n');
return; return;
} }
print_string("All ports ingress filter set to: "); print_string("All ports ingress filter set to: ");
@@ -762,18 +778,18 @@ void parse_ingress(void)
idx = cmd_words_b[w]; idx = cmd_words_b[w];
uint8_t ret = cmd_parse_port(idx); uint8_t ret = cmd_parse_port(idx);
if (ret != 1) { if (ret != 1) {
print_string("Invalid physical port number\n"); cmd_error("Invalid physical port number\n");
continue; continue;
} }
log_port = atoi_results_u8; log_port = atoi_results_u8;
idx += ret; idx += ret;
if (!vlan_ingress_mode_parse(cmd_buffer[idx++], &mode) || !cmd_is_space_or_nul(idx)) { if (!vlan_ingress_mode_parse(cmd_buffer[idx++], &mode) || !cmd_is_space_or_nul(idx)) {
print_string("Invalid ingress mode for port "); print_phys_port(log_port); print_string(" in ingress command\n"); cmd_error("Invalid ingress mode for port "); print_phys_port(log_port); print_string(" in ingress command\n");
goto err; goto err;
} }
if (!port_ingress_filter(log_port, mode)) { if (!port_ingress_filter(log_port, mode)) {
print_string("Error setting ingress filter for port "); print_phys_port(log_port); write_char('\n'); cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n');
return; return;
} }
print_string("Port "); print_phys_port(log_port); print_string("Port "); print_phys_port(log_port);
@@ -783,7 +799,7 @@ void parse_ingress(void)
} }
return; return;
err: err:
print_string("Error: ingress [p]<u/t/a>...\n"); cmd_error("ingress [p]<u/t/a>...\n");
} }
void parse_mirror(void) void parse_mirror(void)
@@ -848,7 +864,7 @@ void parse_mirror(void)
return; return;
err: err:
print_string("Port/command missing: mirror [status/off/<mirroring port> [port][t/r]]...\n"); cmd_error("Port/command missing: mirror [status/off/<mirroring port> [port][t/r]]...\n");
return; return;
} }
@@ -856,7 +872,7 @@ err:
void parse_port(void) void parse_port(void)
{ {
if (cmd_words_len < 3) { if (cmd_words_len < 3) {
print_string("\nUsage:" \ cmd_error("\nUsage:" \
"\nport <port> [show|on|off]" \ "\nport <port> [show|on|off]" \
"\nport <port> [10m|100m|1g|2g5|duplex] [half|full]" \ "\nport <port> [10m|100m|1g|2g5|duplex] [half|full]" \
"\nport <port> name [custom port name]\n"); "\nport <port> name [custom port name]\n");
@@ -864,7 +880,7 @@ void parse_port(void)
} }
if (cmd_parse_port_separator(cmd_words_b[1]) == 0) { if (cmd_parse_port_separator(cmd_words_b[1]) == 0) {
print_string("Invalid port number\n"); cmd_error("Invalid port number\n");
return; return;
} }
phy_settings.port = atoi_results_u8; phy_settings.port = atoi_results_u8;
@@ -881,7 +897,7 @@ void parse_port(void)
} else if (cmd_compare(2, "name")) { } else if (cmd_compare(2, "name")) {
uint8_t i = 0; uint8_t i = 0;
while ( (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != NUL) ) { while ( (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != NUL) ) {
port_names[phy_settings.port][i] = cmd_buffer[cmd_words_b[3] + i]; port_names[phy_settings.port][i] = name_char(cmd_buffer[cmd_words_b[3] + i]);
i++; i++;
} }
port_names[phy_settings.port][i] = NUL; port_names[phy_settings.port][i] = NUL;
@@ -942,7 +958,7 @@ void parse_port(void)
phy_settings.duplex = PHY_DUPLEX_HALF; phy_settings.duplex = PHY_DUPLEX_HALF;
phy_set_duplex(); phy_set_duplex();
} else { } else {
print_string("Unknown port command\n"); cmd_error("Unknown port command\n");
} }
} }
@@ -970,7 +986,7 @@ void parse_mtu(void)
print_byte(p); print_byte(p);
if (atoi_short(cmd_words_b[2]) == 0 || atoi_results_short < 64 || atoi_results_short > 0x3fff) { if (atoi_short(cmd_words_b[2]) == 0 || atoi_results_short < 64 || atoi_results_short > 0x3fff) {
print_string("MTU must be 64..16383\n"); cmd_error("MTU must be 64..16383\n");
return; return;
} }
REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (atoi_results_short >> 10) & 0xf, (atoi_results_short >> 2) & 0xff, REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (atoi_results_short >> 10) & 0xf, (atoi_results_short >> 2) & 0xff,
@@ -979,7 +995,7 @@ void parse_mtu(void)
return; return;
err: err:
print_string("mtu [port] [size]\n"); cmd_error("mtu [port] [size]\n");
return; return;
} }
@@ -1036,11 +1052,11 @@ void parse_sfp(void)
idx += ret; idx += ret;
slot = atoi_results_u8 - 1; slot = atoi_results_u8 - 1;
if (ret == 0 || !cmd_is_space(idx) || slot > 1) { if (ret == 0 || !cmd_is_space(idx) || slot > 1) {
print_string("Illegal SFP slot number\n"); cmd_error("Illegal SFP slot number\n");
return; return;
} }
if (slot >= machine.n_sfp) { if (slot >= machine.n_sfp) {
print_string("SFP slot not present\n"); cmd_error("SFP slot not present\n");
return; return;
} }
@@ -1066,7 +1082,7 @@ void parse_sfp(void)
handle_sfp(); handle_sfp();
return; return;
err: err:
print_string("\nUsage:\n\tsfp\n\tsfp [1|2] [1g|2g5|10g]\n"); cmd_error("\nUsage:\n\tsfp\n\tsfp [1|2] [1g|2g5|10g]\n");
} }
@@ -1098,7 +1114,7 @@ void parse_regget(void)
return; return;
err: err:
print_string("usage: regget <hexvalue>\n\tlike: regget 0BB0 or regget 0c"); cmd_error("regget <hexvalue>\n\tlike: regget 0BB0 or regget 0c");
return; return;
} }
@@ -1145,7 +1161,7 @@ void parse_regset(void)
return; return;
err: err:
print_string("usage: regset <hexvalue> <hexvalue>\n\tlike regset 0b abcd1234."); cmd_error("regset <hexvalue> <hexvalue>\n\tlike regset 0b abcd1234.");
} }
@@ -1188,7 +1204,7 @@ void parse_sdsget(void)
return; return;
err: err:
print_string("usage: sdsget <sds-id> <hex:page> <hex:reg>\n"); cmd_error("sdsget <sds-id> <hex:page> <hex:reg>\n");
return; return;
} }
@@ -1244,7 +1260,7 @@ void parse_sdsset(void)
return; return;
err: err:
print_string("usage: sdsset <sds-id> <hex:page> <hex:reg> <hex:val>\n"); cmd_error("sdsset <sds-id> <hex:page> <hex:reg> <hex:val>\n");
return; return;
} }
@@ -1293,7 +1309,7 @@ void parse_phyget(void)
return; return;
err: err:
print_string("usage: phyget <phy-id> <dev-id> <hex:reg>\n"); cmd_error("phyget <phy-id> <dev-id> <hex:reg>\n");
return; return;
} }
@@ -1353,7 +1369,7 @@ void parse_physet(void)
return; return;
err: err:
print_string("usage: physet <phy-id> <dev-id> <hex:reg> <hex:val>\n"); cmd_error("physet <phy-id> <dev-id> <hex:reg> <hex:val>\n");
return; return;
} }
@@ -1388,7 +1404,7 @@ void parse_passwd(void)
passwd[j] = NUL; passwd[j] = NUL;
return; return;
} }
print_string("Missing password\n"); cmd_error("Missing password\n");
} }
@@ -1412,7 +1428,7 @@ void parse_eee(void)
} else if (cmd_is_space_or_nul(idx)) { } else if (cmd_is_space_or_nul(idx)) {
// Word 2 is a port number // Word 2 is a port number
if (cmd_parse_port_separator(idx) == 0) { if (cmd_parse_port_separator(idx) == 0) {
print_string("Speed word invalid, use: [100m|1g|2g5]\n"); cmd_error("Speed word invalid, use: [100m|1g|2g5]\n");
return; return;
} }
port = atoi_results_u8; port = atoi_results_u8;
@@ -1431,7 +1447,7 @@ void parse_eee(void)
speed = EEE_2G5; speed = EEE_2G5;
else else
{ {
print_string("Speed word invalid, use: [100m|1g|2g5]\n"); cmd_error("Speed word invalid, use: [100m|1g|2g5]\n");
return; return;
} }
} }
@@ -1451,7 +1467,7 @@ void parse_eee(void)
else else
port_eee_status_all(); port_eee_status_all();
} else { } else {
print_string("eee [on|off|status] [port] [100m|1g|2g5]\n"); cmd_error("eee [on|off|status] [port] [100m|1g|2g5]\n");
} }
} }
@@ -1522,7 +1538,7 @@ void parse_bw(void)
return; return;
err: err:
print_string("usage: bw [in|out|status] <port> [<hexvalue>|off|drop|fc]\n"); cmd_error("bw [in|out|status] <port> [<hexvalue>|off|drop|fc]\n");
} }
void parse_syslog(void) void parse_syslog(void)
@@ -1560,7 +1576,7 @@ void parse_syslog(void)
if (was_enabled) if (was_enabled)
syslog_start(); syslog_start();
} else { } else {
print_string("Invalid IP address\n"); cmd_error("Invalid IP address\n");
} }
} else if (cmd_compare(1, "port")) { } else if (cmd_compare(1, "port")) {
if (cmd_words_len < 3) { // no additional argument -> print current port if (cmd_words_len < 3) { // no additional argument -> print current port
@@ -1571,7 +1587,7 @@ void parse_syslog(void)
/* atoi_short() returns zero for no digits and for a value past /* atoi_short() returns zero for no digits and for a value past
* 65535; port zero is not a port either, so both tests are needed. */ * 65535; port zero is not a port either, so both tests are needed. */
if (!atoi_short(cmd_words_b[2]) || !atoi_results_short) { if (!atoi_short(cmd_words_b[2]) || !atoi_results_short) {
print_string("Invalid port\n"); cmd_error("Invalid port\n");
return; return;
} }
/* The port is frozen into the connection by uip_udp_new(), so /* The port is frozen into the connection by uip_udp_new(), so
@@ -1587,9 +1603,9 @@ void parse_syslog(void)
} }
else else
{ {
print_string("Error: syslog [on|off|ip [ip-address]|port [number]]\n"); cmd_error("syslog [on|off|ip [ip-address]|port [number]]\n"
print_string(" on/off enables or disables syslog, ip sets the syslog server IP address,\n"); " on/off enables or disables syslog, ip sets the syslog server IP address,\n"
print_string(" port sets the destination UDP port (default 514)\n"); " port sets the destination UDP port (default 514)\n");
} }
} }
@@ -1757,7 +1773,7 @@ void cmd_parser(void) __banked
print_string("Setting ip: "); print_string("Setting ip: ");
print_ip(ip); write_char('\n'); print_ip(ip); write_char('\n');
} else { } else {
print_string("Invalid IP address\n" \ cmd_error("Invalid IP address\n" \
"Error: ip [<ip-address>|dhcp]\n" \ "Error: ip [<ip-address>|dhcp]\n" \
" The dhcp option enables the dhcp client, calling ip without options prints the current IP\n" \ " The dhcp option enables the dhcp client, calling ip without options prints the current IP\n" \
" Calling with a valid IP address will stop any ongoing dhcp client and set the IP address\n"); " Calling with a valid IP address will stop any ongoing dhcp client and set the IP address\n");
@@ -1773,7 +1789,7 @@ void cmd_parser(void) __banked
print_string("Setting gw: "); print_string("Setting gw: ");
print_ip(ip); write_char('\n'); print_ip(ip); write_char('\n');
} else { } else {
print_string("Invalid IP address\n" \ cmd_error("Invalid IP address\n" \
"Error: gw <ip-address>\n"); "Error: gw <ip-address>\n");
} }
} }
@@ -1787,7 +1803,7 @@ void cmd_parser(void) __banked
print_string("Setting netmask: "); print_string("Setting netmask: ");
print_ip(ip); write_char('\n'); print_ip(ip); write_char('\n');
} else { } else {
print_string("Invalid IP address\n"); cmd_error("Invalid IP address\n");
} }
} }
write_char('\n'); write_char('\n');
@@ -1803,8 +1819,9 @@ void cmd_parser(void) __banked
igmp_setup(); igmp_setup();
else if (cmd_compare(1, "show")) else if (cmd_compare(1, "show"))
igmp_show(); igmp_show();
else else {
print_string("Error: igmp on|off|show\n"); cmd_error("igmp on|off|show\n");
}
} else if (cmd_compare(0, "mac")) { } else if (cmd_compare(0, "mac")) {
parse_mac_cmd(); parse_mac_cmd();
} else if (cmd_compare(0, "hostname")) { } else if (cmd_compare(0, "hostname")) {
@@ -1822,13 +1839,11 @@ void cmd_parser(void) __banked
uint8_t c = *hp++; uint8_t c = *hp++;
if (c == NUL || c == '\r' || c == '\n') if (c == NUL || c == '\r' || c == '\n')
break; break;
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\') *dst++ = name_char(c);
c = '.';
*dst++ = c;
} }
*dst = NUL; *dst = NUL;
} else { } else {
print_string("Error: hostname [name] - the name must not contain spaces\n"); cmd_error("hostname [name] - the name must not contain spaces\n");
} }
} else if (cmd_compare(0, "stp")) { } else if (cmd_compare(0, "stp")) {
stp_parse(); stp_parse();
@@ -1836,8 +1851,9 @@ void cmd_parser(void) __banked
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
port_pvid_set(atoi_results_u8, atoi_results_short); port_pvid_set(atoi_results_u8, atoi_results_short);
else else {
print_string("Error: pvid <port> <1-4094>\n"); cmd_error("pvid <port> <1-4094>\n");
}
} else if (cmd_compare(0, "vlan")) { } else if (cmd_compare(0, "vlan")) {
parse_vlan(); parse_vlan();
} else if (cmd_compare(0, "isolate")) { } else if (cmd_compare(0, "isolate")) {
@@ -1897,11 +1913,11 @@ void cmd_parser(void) __banked
parse_ingress(); parse_ingress();
} }
else { else {
print_string("Unknown command\n"); cmd_error("Unknown command\n");
} }
if (save_cmd && cmd_words_len) { if (save_cmd && cmd_words_len && err_status == ERR_OK) {
// Find end of the cmd-buffer, looking for the NUL-byte. // Find end of the cmd-buffer, looking for the NUL-byte.
uint8_t i = cmd_words_b[cmd_words_len - 1]; uint8_t i = cmd_words_b[cmd_words_len - 1];
do { do {
@@ -1995,6 +2011,7 @@ config_done:
// If a command is too long or can't be tokenized, remaining commands are not executed // If a command is too long or can't be tokenized, remaining commands are not executed
// Returns the status via `err_status`-variable. // Returns the status via `err_status`-variable.
void execute_commands(__xdata uint8_t *p) __banked { void execute_commands(__xdata uint8_t *p) __banked {
__xdata uint8_t failed = ERR_OK;
err_status = ERR_OK; err_status = ERR_OK;
uint8_t cmd_idx = 0; uint8_t cmd_idx = 0;
while (1) { while (1) {
@@ -2005,9 +2022,13 @@ void execute_commands(__xdata uint8_t *p) __banked {
if (err_status != ERR_OK) if (err_status != ERR_OK)
return; return;
cmd_parser(); cmd_parser();
if (failed == ERR_OK)
failed = err_status;
} }
if (*p == 0) if (*p == 0) {
err_status = failed;
return; return;
}
cmd_idx = 0; cmd_idx = 0;
} else { } else {
if (cmd_idx < (CMD_BUF_SIZE - 1)) { if (cmd_idx < (CMD_BUF_SIZE - 1)) {
+53
View File
@@ -0,0 +1,53 @@
# IG108
Following is documentation for unmanaged switch marked as `IG108`.
Original software is running UART on 9600 baud rate.
Using SPI clamp in-board is the only method for initial installation.
### Brands
| Brand | Type | Managed | PCB | Flash | Chip RTL |
|---------|------------|---------|------------|-------------|---------------|
| Goalake | IG108 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
| Netcore | GS9 | No | PB-2132 | cFeon Q32A | 8373N + 8224N |
### What works
- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- SFP port with 1G/2.5G/10G modules
- LEDs
### PCB overview
**Board markings**
- Top silkscreen: PB-2132
Front Goalake:
<img src="photos/PB-2132/case.jpg" width="600" />
Label Goalake:
<img src="photos/PB-2132/label.jpg" width="600" />
Top side:
<img src="photos/PB-2132/PCB-top.jpg" width="600" />
Bottom:
<img src="photos/PB-2132/PCB-bottom.jpg" width="600" />
### Serial console
The PCB has unpopulated through-holes near the SFP Port, with description of each pin, works out of the box.
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
### Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 923 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 120 KiB

+3 -1
View File
@@ -6,6 +6,7 @@ The following devices have been tested and are fully working:
| Ampcom | WAM902-SWTG018AS| No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 | | Ampcom | WAM902-SWTG018AS| No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 |
| Davuaz | Da-K6501W | No | [PCB-K0501W-V2.0](devices/K0501W_V2_0.md) | | 5 + 1 | | Davuaz | Da-K6501W | No | [PCB-K0501W-V2.0](devices/K0501W_V2_0.md) | | 5 + 1 |
| FOXNEO | FNS-1200P | No | [PCB-K0402W-U13-V2.0](devices/FNS-1200P.md) | 2M | 4 + 2 | | FOXNEO | FNS-1200P | No | [PCB-K0402W-U13-V2.0](devices/FNS-1200P.md) | 2M | 4 + 2 |
| Goalake | IG108 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 | | Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 | | Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 |
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 | | Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
@@ -21,9 +22,11 @@ The following devices have been tested and are fully working:
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.1](devices/2M-PCB23-V3_1.md) | 2M | 8 + 1 | | keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.1](devices/2M-PCB23-V3_1.md) | 2M | 8 + 1 |
| LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 | | LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 |
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 | | Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
| Mokerlink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 | | Mokerlink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 |
| Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 | | Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| MokerLink| POE-2G080110GS | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 | | MokerLink| POE-2G080110GS | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 |
| Netcore | GS9 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 |
| Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 | | Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 |
| Ruiying | RY-8GT-1SX | No | [FG-8GT-1SX](devices/FG-8GT-1SX.md) | 1M | 8 + 1 | | Ruiying | RY-8GT-1SX | No | [FG-8GT-1SX](devices/FG-8GT-1SX.md) | 1M | 8 + 1 |
| SiscoLink | SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 | | SiscoLink | SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
@@ -33,7 +36,6 @@ The following devices have been tested and are fully working:
| Xikestore| SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 | | Xikestore| SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 |
| XikeStor | SKS3200-8E1X | Yes | [SWTG118AS-V2.1-17462](devices/SWTGW218AS.md) | 2M | 8 + 1 | | XikeStor | SKS3200-8E1X | Yes | [SWTG118AS-V2.1-17462](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| Ztyuav | Z-QWYT0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 | | Ztyuav | Z-QWYT0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 |
For KP-9000-6XH-X2 / KP-9000-6XHML-X2 / Mokerlink 2G040210GSM devices, select For KP-9000-6XH-X2 / KP-9000-6XHML-X2 / Mokerlink 2G040210GSM devices, select
the machine target by PCB revision. The ML/non-ML or managed/unmanaged label the machine target by PCB revision. The ML/non-ML or managed/unmanaged label
+6 -1
View File
@@ -533,13 +533,18 @@ TABS.forEach(function(tb){
}); });
$("burger").addEventListener("click",function(){$("nav").classList.toggle("open")}); $("burger").addEventListener("click",function(){$("nav").classList.toggle("open")});
var IDLE_STOP_MS=600000,lastInput=Date.now();
["mousemove","mousedown","keydown","touchstart","wheel"].forEach(function(ev){
addEventListener(ev,function(){lastInput=Date.now()},{passive:true});
});
function Poller(fn,ms){this.fn=fn;this.ms=ms;this.on=false;this.t=null} function Poller(fn,ms){this.fn=fn;this.ms=ms;this.on=false;this.t=null}
Poller.prototype.start=function(){if(this.on)return;this.on=true;this.tick()}; Poller.prototype.start=function(){if(this.on)return;this.on=true;this.tick()};
Poller.prototype.stop=function(){this.on=false;clearTimeout(this.t)}; Poller.prototype.stop=function(){this.on=false;clearTimeout(this.t)};
Poller.prototype.tick=function(){ Poller.prototype.tick=function(){
var self=this; var self=this;
if(!self.on)return; if(!self.on)return;
var run=document.hidden?Promise.resolve():Promise.resolve().then(self.fn).catch(function(){}); var quiet=document.hidden||Date.now()-lastInput>IDLE_STOP_MS;
var run=quiet?Promise.resolve():Promise.resolve().then(self.fn).catch(function(){});
run.then(function(){ if(self.on)self.t=setTimeout(function(){self.tick()},self.ms); }); run.then(function(){ if(self.on)self.t=setTimeout(function(){self.tick()},self.ms); });
}; };
+25 -20
View File
@@ -37,6 +37,7 @@ __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
__xdata uint8_t entry; __xdata uint8_t entry;
__xdata uint16_t slen; __xdata uint16_t slen;
__xdata uint16_t o_idx; __xdata uint16_t o_idx;
__xdata uint16_t sent_len;
__xdata uint16_t len_left; __xdata uint16_t len_left;
__xdata uint16_t cont_len; __xdata uint16_t cont_len;
__xdata uint32_t cont_addr; __xdata uint32_t cont_addr;
@@ -340,15 +341,16 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
read_reg_timer(&now); read_reg_timer(&now);
if (session) { if (session && session_id[0]) {
if (now - last_session_use > SESSION_TIMEOUT) { if (now - last_session_use > SESSION_TIMEOUT) {
dbg_string("Session expired\n"); dbg_string("Session expired\n");
} else { } else {
if (is_word_x(session, session_id)) { if (is_word_x(session, session_id)) {
authenticated = 1; authenticated = 1;
last_session_use = now; last_session_use = now;
} else } else {
dbg_string("Invalid session cookie!\n"); dbg_string("Invalid session cookie!\n");
}
} }
} }
return p; return p;
@@ -559,7 +561,7 @@ static void handle_config_fragment(__xdata uint8_t *p)
__xdata uint16_t frag_len; __xdata uint16_t frag_len;
uint8_t taken; uint8_t taken;
frag_len = uip_len - (p - uip_appdata); frag_len = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) {
print_string("Configuration too large, aborting.\n"); print_string("Configuration too large, aborting.\n");
config_upload = 0; config_upload = 0;
@@ -589,7 +591,7 @@ static void handle_firmware_fragment(__xdata uint8_t *p)
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate); __xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
__xdata uint16_t frag_len, payload_start; __xdata uint16_t frag_len, payload_start;
frag_len = uip_len - (p - uip_appdata); frag_len = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) {
print_string("Firmware upload header too large, aborting.\n"); print_string("Firmware upload header too large, aborting.\n");
config_upload = 0; config_upload = 0;
@@ -684,7 +686,7 @@ static uint8_t post_body_take(__xdata uint8_t *p)
send_bad_request(); send_bad_request();
return 0; return 0;
} }
have = uip_len - (p - uip_appdata); have = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (have >= content_length) { if (have >= content_length) {
p[content_length] = NUL; p[content_length] = NUL;
return 1; return 1;
@@ -826,6 +828,13 @@ void handle_post(void)
} }
static void tx_send(uint16_t len)
{
sent_len = len;
uip_send(outbuf + o_idx, len);
}
void httpd_appcall(void) void httpd_appcall(void)
{ {
__xdata struct httpd_state * __xdata s = &(uip_conn->appstate); __xdata struct httpd_state * __xdata s = &(uip_conn->appstate);
@@ -861,9 +870,9 @@ void httpd_appcall(void)
} }
} else if (uip_acked() && s->tstate == TSTATE_TX) { } else if (uip_acked() && s->tstate == TSTATE_TX) {
dbg_string("ACK\n"); dbg_string("ACK\n");
if (slen > uip_mss()) { if (slen > sent_len) {
slen -= uip_mss(); slen -= sent_len;
o_idx += uip_mss(); o_idx += sent_len;
} else { } else {
slen = 0; slen = 0;
} }
@@ -872,11 +881,11 @@ void httpd_appcall(void)
if (slen > uip_mss()) { if (slen > uip_mss()) {
dbg_string("Sending A: "); dbg_short(slen); dbg_char('\n'); dbg_string("Sending A: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss()); tx_send(uip_mss());
s->tstate = TSTATE_TX; s->tstate = TSTATE_TX;
} else if (slen > 0) { } else if (slen > 0) {
dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n'); dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen); tx_send(slen);
s->tstate = TSTATE_TX; s->tstate = TSTATE_TX;
} else if (cont_len) { } else if (cont_len) {
dbg_string("CONT cont_len: "); dbg_short(cont_len); dbg_string("CONT cont_len: "); dbg_short(cont_len);
@@ -887,7 +896,7 @@ void httpd_appcall(void)
flash_region.len = slen; flash_region.len = slen;
flash_read_bulk(outbuf); flash_read_bulk(outbuf);
o_idx = 0; o_idx = 0;
uip_send(outbuf + o_idx, slen); tx_send(slen);
cont_len -= slen; cont_len -= slen;
cont_addr += slen; cont_addr += slen;
s->tstate = TSTATE_TX; s->tstate = TSTATE_TX;
@@ -1045,24 +1054,20 @@ do_send:
o_idx = 0; o_idx = 0;
if (slen > uip_mss()) { if (slen > uip_mss()) {
dbg_string("Sending a: "); dbg_short(slen); dbg_char('\n'); dbg_string("Sending a: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss()); tx_send(uip_mss());
dbg_string("Sending a done\n"); dbg_string("Sending a done\n");
} else { } else {
dbg_string("Sending b: "); dbg_short(slen); dbg_char('\n'); dbg_string("Sending b: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen); tx_send(slen);
dbg_string("Sending b done\n"); dbg_string("Sending b done\n");
} }
s->tstate = TSTATE_TX; s->tstate = TSTATE_TX;
} else if (uip_rexmit()) { // Connection established, need to rexmit? } else if (uip_rexmit()) { // Connection established, need to rexmit?
dbg_string("RETRANSMIT requested\n"); dbg_string("RETRANSMIT requested\n");
if (slen > uip_mss()) { if (sent_len) {
dbg_string("Sending C: "); dbg_short(slen); dbg_char('\n'); dbg_string("Sending C: "); dbg_short(sent_len); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss()); tx_send(sent_len);
dbg_string("Sending C done\n"); dbg_string("Sending C done\n");
} else if (slen > 0) {
dbg_string("Sending D: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen);
dbg_string("Sending D done\n");
} }
s->tstate = TSTATE_TX; s->tstate = TSTATE_TX;
uip_len = 0; uip_len = 0;
+40 -19
View File
@@ -70,6 +70,21 @@ void char_to_html(char c)
} }
static void str_x_to_html(__xdata const char *s)
{
while (*s)
outbuf[slen++] = *s++;
}
void json_char_to_html(uint8_t c)
{
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
c = '.';
outbuf[slen++] = c;
}
// Convert uint8_t to ascii HEX char. // Convert uint8_t to ascii HEX char.
void byte_to_html(uint8_t val) void byte_to_html(uint8_t val)
{ {
@@ -175,6 +190,20 @@ void reg_to_html_long(uint16_t reg)
} }
/* The 64-bit counter of the last STAT_GET as one number: the low word keeps
* its leading zeros whenever the high word is set. */
void counter_to_html(void)
{
reg_read_m(RTL837X_STAT_V_HIGH);
if (SFR_DATA_24 | SFR_DATA_16 | SFR_DATA_8 | SFR_DATA_0) {
sfr_data_to_html();
reg_to_html_long(RTL837X_STAT_V_LOW);
} else {
reg_to_html(RTL837X_STAT_V_LOW);
}
}
void send_sfp_info(uint8_t sfp) void send_sfp_info(uint8_t sfp)
{ {
// This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields // This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields
@@ -184,8 +213,8 @@ void send_sfp_info(uint8_t sfp)
if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes
continue; continue;
uint8_t c = sfp_buf[i & 0xf]; uint8_t c = sfp_buf[i & 0xf];
if (c && c != 0xa0) // a0 is the byte read from a non-existant I2C EEPROM if (c)
char_to_html(c); json_char_to_html(c);
} }
} }
@@ -279,7 +308,7 @@ void send_vlan(uint16_t vlan)
dbg_string("VLAN has no name\n"); dbg_string("VLAN has no name\n");
} else { } else {
while(vlan_names[n] && vlan_names[n] != ' ') while(vlan_names[n] && vlan_names[n] != ' ')
char_to_html(vlan_names[n++]); json_char_to_html(vlan_names[n++]);
} }
slen += strtox(outbuf + slen, "\",\"pvid\":\"0x"); slen += strtox(outbuf + slen, "\",\"pvid\":\"0x");
uint16_t pvid_mask = 0; uint16_t pvid_mask = 0;
@@ -311,8 +340,7 @@ bool send_counters(uint8_t phys_port)
for (uint8_t counter = 0; counter < 0x37; counter++) { for (uint8_t counter = 0; counter < 0x37; counter++) {
STAT_GET(counter, log_port); STAT_GET(counter, log_port);
slen += strtox(outbuf + slen, "\"0x"); slen += strtox(outbuf + slen, "\"0x");
reg_to_html(RTL837X_STAT_V_HIGH); counter_to_html();
reg_to_html_long(RTL837X_STAT_V_LOW);
char_to_html('\"'); char_to_html('\"');
if (counter != 0x36) if (counter != 0x36)
char_to_html(','); char_to_html(',');
@@ -432,8 +460,6 @@ void l2_delete(uint16_t idx)
slen = strtox(outbuf, HTTP_RESPONCE_JSON); slen = strtox(outbuf, HTTP_RESPONCE_JSON);
dbg_string("L2 DELETE\n"); dbg_string("L2 DELETE\n");
dbg_short(idx); dbg_short(idx);
__xdata uint8_t entries_left = L2_MAX_TRANSFER;
do { do {
reg_read(RTL837X_TBL_CTRL); reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE); } while (SFR_DATA_0 & TBL_EXECUTE);
@@ -814,7 +840,7 @@ void send_status(void)
itoa_html(i); itoa_html(i);
slen += strtox(outbuf + slen, ",\"name\":\""); slen += strtox(outbuf + slen, ",\"name\":\"");
for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) { for (uint8_t j = 0; j < PORT_NAME_SIZE && port_names[i][j]; j++) {
char_to_html(port_names[i][j]); json_char_to_html(port_names[i][j]);
} }
slen += strtox(outbuf + slen, "\""); slen += strtox(outbuf + slen, "\"");
@@ -853,14 +879,11 @@ void send_status(void)
sfp_send_data(sfp, 238, 1); sfp_send_data(sfp, 238, 1);
} }
slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\""); slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_vendor[sfp][s]; s++) str_x_to_html(sfp_module_vendor[sfp]);
outbuf[slen++] = sfp_module_vendor[sfp][s];
slen += strtox(outbuf + slen,"\",\"sfp_model\":\""); slen += strtox(outbuf + slen,"\",\"sfp_model\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_model[sfp][s]; s++) str_x_to_html(sfp_module_model[sfp]);
outbuf[slen++] = sfp_module_model[sfp][s];
slen += strtox(outbuf + slen,"\",\"sfp_serial\":\""); slen += strtox(outbuf + slen,"\",\"sfp_serial\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_serial[sfp][s]; s++) str_x_to_html(sfp_module_serial[sfp]);
outbuf[slen++] = sfp_module_serial[sfp][s];
slen += strtox(outbuf + slen,"\",\"sfp_los\":"); slen += strtox(outbuf + slen,"\",\"sfp_los\":");
if (machine.sfp_port[sfp].pin_los == GPIO_NA) { if (machine.sfp_port[sfp].pin_los == GPIO_NA) {
slen += strtox(outbuf + slen,"null"); slen += strtox(outbuf + slen,"null");
@@ -914,8 +937,7 @@ void send_status(void)
STAT_GET(STAT_COUNTER_TX_PKTS, i); STAT_GET(STAT_COUNTER_TX_PKTS, i);
slen += strtox(outbuf + slen, ",\"txG\":\"0x"); slen += strtox(outbuf + slen, ",\"txG\":\"0x");
reg_to_html(RTL837X_STAT_V_HIGH); counter_to_html();
reg_to_html(RTL837X_STAT_V_LOW);
slen += strtox(outbuf + slen, "\",\"txB\":\"0x"); slen += strtox(outbuf + slen, "\",\"txB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i); STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -923,8 +945,7 @@ void send_status(void)
slen += strtox(outbuf + slen, "\",\"rxG\":\"0x"); slen += strtox(outbuf + slen, "\",\"rxG\":\"0x");
STAT_GET(STAT_COUNTER_RX_PKTS, i); STAT_GET(STAT_COUNTER_RX_PKTS, i);
reg_to_html(RTL837X_STAT_V_HIGH); counter_to_html();
reg_to_html(RTL837X_STAT_V_LOW);
slen += strtox(outbuf + slen, "\",\"rxB\":\"0x"); slen += strtox(outbuf + slen, "\",\"rxB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i); STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -1034,7 +1055,7 @@ void send_vlanlist(void)
n = vlan_name(i); n = vlan_name(i);
if (n != 0xffff) { if (n != 0xffff) {
while (vlan_names[n] && vlan_names[n] != ' ') while (vlan_names[n] && vlan_names[n] != ' ')
char_to_html(vlan_names[n++]); json_char_to_html(vlan_names[n++]);
} }
slen += strtox(outbuf + slen, "\"}"); slen += strtox(outbuf + slen, "\"}");
+41 -1
View File
@@ -1182,6 +1182,7 @@ __code const struct machine machine = {
0 0
}, },
}, },
};
#elif defined MACHINE_POE_2G080110GS #elif defined MACHINE_POE_2G080110GS
__code const struct machine machine = { __code const struct machine machine = {
.machine_name = "MokerLink POE-2G080110GS", .machine_name = "MokerLink POE-2G080110GS",
@@ -1225,6 +1226,46 @@ __code const struct machine machine = {
0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1a, 0x19, 0x1d, 0x1e, 0x1c,
0x1d, 0x20, 0x21 }, 0x1d, 0x20, 0x21 },
}; };
#elif defined MACHINE_PB_2132
__code const struct machine machine = {
.machine_name = "PB-2132",
.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 = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 },
.reset_pin = GPIO_NA,
.high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1},
.led_sets = {
{
/* LED0 = RJ45: Amber LED: 2.5G */
LEDS_2G5 | LEDS_LINK | LEDS_ACT,
/* LED1 = RJ45: Green LED: 10M / 100M / 1000M */
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
0,
0
},
{
/* SFP+ single Green LED. */
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_LINK | LEDS_ACT,
0,
0,
0
},
},
};
#elif defined MACHINE_HASIVO_S600W_4GT_2XGT_SE #elif defined MACHINE_HASIVO_S600W_4GT_2XGT_SE
__code const struct machine machine = { __code const struct machine machine = {
@@ -1268,7 +1309,6 @@ __code const struct machine machine = {
}, },
}; };
#else #else
#error "Please select a machine type in machine.h" #error "Please select a machine type in machine.h"
#endif #endif
+4 -2
View File
@@ -44,7 +44,9 @@
// #define MACHINE_SWTG024AS_V2_0 // #define MACHINE_SWTG024AS_V2_0
// #define MACHINE_FG_4GT_2SX_V2_0 // #define MACHINE_FG_4GT_2SX_V2_0
// #define MACHINE_FG_8GT_1SX // #define MACHINE_FG_8GT_1SX
// #define MACHINE_LIANGUO_HYWS_SGT0108S
// #define MACHINE_POE_2G080110GS // #define MACHINE_POE_2G080110GS
// #define MACHINE_PB_2132
// #define MACHINE_HASIVO_S600W_4GT_2XGT_SE // #define MACHINE_HASIVO_S600W_4GT_2XGT_SE
typedef struct { typedef struct {
@@ -75,7 +77,7 @@ struct sfp_port
i2c_bus_t i2c; i2c_bus_t i2c;
}; };
typedef struct machine { struct machine {
char machine_name[30]; char machine_name[30];
uint8_t isRTL8373; uint8_t isRTL8373;
// Lowest logical port number // Lowest logical port number
@@ -103,7 +105,7 @@ typedef struct machine {
uint32_t mac_flash_offset; uint32_t mac_flash_offset;
}; };
typedef struct machine_runtime struct machine_runtime
{ {
uint8_t isRTL8373 : 1; uint8_t isRTL8373 : 1;
uint8_t isN : 1; uint8_t isN : 1;
+14 -7
View File
@@ -54,6 +54,7 @@ extern __xdata uint8_t sbuf[SBUF_SIZE];
#define ERR_OK 0 #define ERR_OK 0
#define ERR_TOO_MANY_ARGUMENTS 1 #define ERR_TOO_MANY_ARGUMENTS 1
#define ERR_CMD_TOO_LONG 2 #define ERR_CMD_TOO_LONG 2
#define ERR_INVALID_ARGUMENT 3
// For RX data, a propriatary RTL FRAME is inserted. Instead of 0x0800 for IPv4, // For RX data, a propriatary RTL FRAME is inserted. Instead of 0x0800 for IPv4,
// the RTL_FRAME_TAG_ID is used as part of an 8-byte tag. When VLAN is activated, // the RTL_FRAME_TAG_ID is used as part of an 8-byte tag. When VLAN is activated,
@@ -134,9 +135,9 @@ extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2];
extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata struct uip_eth_addr uip_ethaddr;
// Headers for calls in the common code area (HOME/BANK0) // Headers for calls in the common code area (HOME/BANK0)
void print_string_no_syslog(__code char *p); void print_string_no_syslog(__code const char *p);
void print_string_newline_no_syslog(__code char *p); void print_string_newline_no_syslog(__code const char *p);
void print_string(__code char *p); void print_string(__code const char *p);
void print_string_x(__xdata char *p); void print_string_x(__xdata char *p);
void print_long(uint32_t a); void print_long(uint32_t a);
void print_short(uint16_t a); void print_short(uint16_t a);
@@ -158,7 +159,6 @@ void reg_write_m(uint16_t reg_addr);
void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg); void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg);
void sds_write_v(uint8_t sds_id, uint8_t page, uint8_t reg, uint16_t v); void sds_write_v(uint8_t sds_id, uint8_t page, uint8_t reg, uint16_t v);
void delay(uint16_t t); void delay(uint16_t t);
void sleep(uint16_t t);
void write_char_no_syslog(char c); void write_char_no_syslog(char c);
void write_char(char c); void write_char(char c);
void print_reg(uint16_t reg); void print_reg(uint16_t reg);
@@ -170,15 +170,22 @@ uint8_t reg_bit_test(uint16_t reg_addr, char bit);
void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set); void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set);
void sfr_set_zero(void); void sfr_set_zero(void);
void reset_chip(void); void reset_chip(void);
/* Firmware implementations that shadow libc names. Host unit-test builds
* (RTLP_HOST_TEST) hide these prototypes so they don't clash with glibc;
* argument order matches libc, so on-host callers transparently use the
* C library. See test/. */
#ifndef RTLP_HOST_TEST
void sleep(uint16_t t);
void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len); void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len);
void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len);
void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len); void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len);
int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len); int memcmp(__xdata const void *a, __xdata const void *b, uint16_t len);
uint16_t strlen(__code const char *s); uint16_t strlen(__code const char *s);
uint16_t strlen_x(__xdata const char *s);
uint16_t strtox(__xdata uint8_t *dst, __code const char *s);
uint16_t strcpy(__xdata uint8_t *dst, const char *s); uint16_t strcpy(__xdata uint8_t *dst, const char *s);
char strcmp(__xdata const uint8_t *a, __code const uint8_t *b); char strcmp(__xdata const uint8_t *a, __code const uint8_t *b);
#endif
void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len);
uint16_t strlen_x(__xdata const char *s);
uint16_t strtox(__xdata uint8_t *dst, __code const char *s);
bool strstart(__xdata const uint8_t *a, __code const uint8_t *b); bool strstart(__xdata const uint8_t *a, __code const uint8_t *b);
bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b); bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b);
void tcpip_output(void); void tcpip_output(void);
+6 -1
View File
@@ -221,7 +221,9 @@ void flash_read_jedecid(void)
*/ */
void flash_read_bulk(__xdata uint8_t *dst) void flash_read_bulk(__xdata uint8_t *dst)
{ {
short status; if (!flash_region.len)
return;
flash_configure_sio(); flash_configure_sio();
while (flash_read_status() & STATUS_REG_BUSY_MASK); while (flash_read_status() & STATUS_REG_BUSY_MASK);
flash_configure_mmio(); flash_configure_mmio();
@@ -258,6 +260,9 @@ void flash_read_bulk(__xdata uint8_t *dst)
void flash_read_security(void) void flash_read_security(void)
{ {
if (!flash_region.len)
return;
flash_configure_sio(); flash_configure_sio();
while (flash_read_status() & STATUS_REG_BUSY_MASK); while (flash_read_status() & STATUS_REG_BUSY_MASK);
+6 -6
View File
@@ -102,9 +102,9 @@ static void port_pvid_write(uint8_t port, __xdata uint16_t pvid)
reg_read_m(reg); reg_read_m(reg);
if (port & 0x1) { if (port & 0x1) {
REG_WRITE(reg, sfr_data[0], pvid >> 4, sfr_data[2] & 0x0f | (pvid << 4), sfr_data[3]); REG_WRITE(reg, sfr_data[0], pvid >> 4, (sfr_data[2] & 0x0f) | (pvid << 4), sfr_data[3]);
} else { } else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0 | (pvid >> 8), pvid); REG_WRITE(reg, sfr_data[0], sfr_data[1], (sfr_data[2] & 0xf0) | (pvid >> 8), pvid);
} }
} }
@@ -285,7 +285,7 @@ void vlan_setup(void) __banked
#endif #endif
reg_read_m(reg); reg_read_m(reg);
if (i & 0x1) { if (i & 0x1) {
REG_WRITE(reg, sfr_data[0], 0, sfr_data[2] & 0x0f | 0x10, sfr_data[3]); REG_WRITE(reg, sfr_data[0], 0, (sfr_data[2] & 0x0f) | 0x10, sfr_data[3]);
} else { } else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01); REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01);
} }
@@ -758,7 +758,7 @@ void port_eee_enable_all(__xdata uint8_t speed) __banked
port_eee_enable(i, speed); port_eee_enable(i, speed);
} else { } else {
if (speed & EEE_10G) if (speed & EEE_10G)
port_eee_enable(i, speed & EEE_NORESET | EEE_2G5); port_eee_enable(i, (speed & EEE_NORESET) | EEE_2G5);
else else
port_eee_enable(i, speed); port_eee_enable(i, speed);
} }
@@ -899,7 +899,7 @@ void vlan_dump(void) __banked
/** Set the ingress VLAN filtering */ /** Set the ingress VLAN filtering */
bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
{ {
if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) { if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false; return false;
} }
if (enabled) if (enabled)
@@ -913,7 +913,7 @@ bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
/** Get the ingress VLAN filtering status */ /** Get the ingress VLAN filtering status */
bool port_ingress_vlan_filter_get(uint8_t port) __banked bool port_ingress_vlan_filter_get(uint8_t port) __banked
{ {
if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) { if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false; return false;
} }
+3
View File
@@ -15,6 +15,8 @@
#include "uip.h" #include "uip.h"
#include "machine.h" #include "machine.h"
extern __xdata uint8_t err_status;
// All entry points are __banked and nothing here runs from an interrupt, // All entry points are __banked and nothing here runs from an interrupt,
// so the module does not need to stay in the resident bank // so the module does not need to stay in the resident bank
#pragma codeseg BANK2 #pragma codeseg BANK2
@@ -1134,5 +1136,6 @@ void stp_parse(void) __banked __reentrant
} }
return; return;
err: err:
err_status = ERR_INVALID_ARGUMENT;
print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n"); print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9>|lag <1-4> on|off|edge|cost|prio|guard|filter ...\n");
} }
+3 -3
View File
@@ -303,19 +303,19 @@ void itoa_short(uint16_t v)
} }
void print_string(__code char *p) void print_string(__code const char *p)
{ {
while (*p) while (*p)
write_char(*p++); write_char(*p++);
} }
void print_string_no_syslog(__code char *p) void print_string_no_syslog(__code const char *p)
{ {
while (*p) while (*p)
write_char_no_syslog(*p++); write_char_no_syslog(*p++);
} }
void print_string_newline_no_syslog(__code char *p) void print_string_newline_no_syslog(__code const char *p)
{ {
write_char_no_syslog('\n'); write_char_no_syslog('\n');
print_string_no_syslog(p); print_string_no_syslog(p);
+6 -1
View File
@@ -88,7 +88,12 @@ bool sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t lengt
return false; return false;
dst[length] = NUL; dst[length] = NUL;
memcpy(dst, sfp_buf, length); for (uint8_t i = 0; i < length; i++) {
uint8_t c = sfp_buf[i];
if (c && (c < 0x20 || c > 0x7e || c == '"' || c == '\\'))
c = '.';
dst[i] = c;
}
while (length > 0 && dst[--length] == ' ') while (length > 0 && dst[--length] == ' ')
dst[length] = NUL; dst[length] = NUL;
+69
View File
@@ -0,0 +1,69 @@
# Host unit-test harness for RTLPlayground firmware logic.
#
# Compiles selected firmware translation units with host gcc + AddressSanitizer,
# force-including sdcc_shim.h to erase 8051 keywords, and links them against
# mocked hardware edges (support.c). No SDCC, no flashing, no hardware.
#
# make build + run all tests
# make run run tests
# make clean
CC = gcc
SRCROOT = ..
# -include shim first so __xdata/__code/__banked vanish before any firmware header.
CFLAGS = -std=gnu11 -g -O1 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unknown-pragmas \
-Wno-pointer-sign \
-DRTLP_HOST_TEST \
-fsanitize=address,undefined -fno-omit-frame-pointer \
-include sdcc_shim.h -Istub -I. -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip
ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),)
CFLAGS += -Wno-builtin-declaration-mismatch
endif
LDFLAGS = -fsanitize=address,undefined
BUILDDIR = build
# One binary per subsystem under test.
TESTS = $(BUILDDIR)/test_cmd_editor \
$(BUILDDIR)/test_port_tables \
$(BUILDDIR)/test_page_json \
$(BUILDDIR)/test_httpd_tx
# The table tests link the register mock and one environment file; stub/
# stands in for the two headers a firmware build generates.
TABLE_ENV = support.c hw_mock.c env_tables.c $(SRCROOT)/cmd_editor.c \
$(SRCROOT)/rtl837x_port.c $(SRCROOT)/httpd/page_impl.c
all: run
$(BUILDDIR):
mkdir -p $(BUILDDIR)
# cmd_editor.c is compiled UNMODIFIED from the firmware tree.
$(BUILDDIR)/test_cmd_editor: test_cmd_editor.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS)
# rtl837x_port.c and httpd/page_impl.c are compiled UNMODIFIED from the firmware tree.
$(BUILDDIR)/test_port_tables: test_port_tables.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR)
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
$(BUILDDIR)/test_page_json: test_page_json.c $(TABLE_ENV) hw_mock.h | $(BUILDDIR)
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
# httpd/httpd.c and uip/uip.c are compiled UNMODIFIED from the firmware tree;
# this test carries the environment below them itself.
$(BUILDDIR)/test_httpd_tx: test_httpd_tx.c $(SRCROOT)/httpd/httpd.c $(SRCROOT)/uip/uip.c | $(BUILDDIR)
$(CC) $(CFLAGS) $(filter %.c,$^) -o $@ $(LDFLAGS)
run: $(TESTS)
@fail=0; for t in $(TESTS); do \
echo "=== $$t ==="; \
$$t || fail=1; \
echo; \
done; \
if [ $$fail -ne 0 ]; then echo "TESTS FAILED"; exit 1; else echo "ALL TESTS PASSED"; fi
clean:
rm -rf $(BUILDDIR)
.PHONY: all run clean
+62
View File
@@ -0,0 +1,62 @@
# Host unit-test harness
Compile and test individual firmware translation units on the build host with
**gcc + AddressSanitizer + UBSan** — no SDCC, no flashing, no hardware. This is
the fast inner loop for the logic-level bugs in `../../NOTES/08-findings.md`
(parser, line editor, HTTP header, DHCP): edit → `make` → see red/green in
seconds instead of a minutes-long flash-and-reboot cycle.
## Run
```
cd test
make # build + run all tests (ASan/UBSan on)
make clean
```
Exit status is non-zero if any check fails, a sanitizer trips, or the watchdog
fires — so it drops straight into CI.
## How it works
- **`sdcc_shim.h`** (force-included) erases SDCC 8051 keywords (`__xdata`,
`__code`, `__banked`, …) so firmware sources compile under host gcc. It changes
no logic — the code under test is byte-for-byte the firmware source.
- **`hw_mock.c` / `hw_mock.h`** simulate the ASIC edge: a flat register file
behind `reg_read`/`reg_write` and the `SFR_DATA_*` registers, with a table
engine that performs a VLAN or L2 operation the moment `TBL_CTRL` is written,
fetches a MIB counter on `STAT_GET` and drops entries on `L2_TBL_FLUSH_CTRL`.
Nothing ever reports busy, so a polling loop runs once. The header states the
VLAN and L2 entry layouts independently of the firmware, so a test can hold
what `rtl837x_port.c` writes against what `page_impl.c` reads.
- **`env_tables.c`** carries the globals and leaf calls those two modules link
against; **`stub/`** stands in for the two headers only a firmware build
generates, so the harness needs no SDCC build first.
- **`support.c` / `support.h`** mock the hardware edges: the 16-byte serial ring
(`sbuf`), the command/history buffers, and the character-output sink
(`write_char` etc.). Buffers are sized **exactly** as on target, so ASan
redzones catch the same off-by-one overflows the 8051 hits.
- Builds define **`RTLP_HOST_TEST`**, which hides the firmware's libc-named
prototypes (`memset`/`strlen`/…) in `rtl837x_common.h` so they don't clash with
glibc. Argument order matches libc, so on-host callers transparently use the C
library. This guard is compiled out of normal firmware builds — zero on-target
effect.
- A **SIGALRM watchdog** (`watchdog_arm`) turns an infinite-loop bug into a fast
test failure instead of a hung runner.
## Current coverage
| Test binary | TU under test | Findings exercised |
|-------------|---------------|--------------------|
| `test_cmd_editor` | `cmd_editor.c` | **C4** — full-line hang + `cmd_buffer` 1-byte overflow; basic entry & backspace regressions |
| `test_port_tables` | `rtl837x_port.c` | VLAN entry layout and round trip, PVID register sharing, static multicast and management entries, per-port flush, trunk membership and hash seed |
| `test_page_json` | `httpd/page_impl.c` + `rtl837x_port.c` | `/vlan.json`, `/vlanlist`, `/l2.json` (walk, wrap marker, paging inside `outbuf`), 64-bit counters in `/status.json` and `/counters.json` |
| `test_httpd_tx` | `httpd/httpd.c` + `uip/uip.c` | a GET of a static file against a client that moves its receive window: ACK accounting, continuation chunks out of flash, retransmission |
## Adding a test for another module
1. Write `test_<module>.c` with `main()` driving the module's entry points and
`CHECK(cond, msg)` assertions.
2. Add any missing mocked symbols to `support.c` (only what the linker asks for).
3. Add a target in the `Makefile` listing `test_<module>.c support.c ../<module>.c`.
4. If the module calls a firmware libc-name (`memcpy`/`strcpy`/…), it will use the
C library on host (compatible arg order); provide a `fw_*`-style mock only if
the semantics differ in a way the test cares about.
Good next targets (pure logic, high finding density): `cmd_parser.c` (C6/C8),
the HTTP header parser in `httpd/httpd.c` (S1), `dhcp.c` (S5).
+123
View File
@@ -0,0 +1,123 @@
/*
* env_tables.c - what rtl837x_port.c and httpd/page_impl.c link against
* besides the register mock: the console, the buffers page_impl writes into,
* the machine description and leaf calls into subsystems not under test.
* Buffers keep their firmware sizes so AddressSanitizer sees the same bounds
* the 8051 has.
*/
#include <stdint.h>
#include <stdio.h>
#include "rtl837x_common.h"
#include "rtl837x_port.h"
#include "machine.h"
#include "uip.h"
#include "rtl837x_flash.h"
#include "syslog.h"
#include "rtl837x_phy.h"
#include "support.h"
/* ---- console: everything lands in out_buf (support.c) ---- */
void print_byte(uint8_t v)
{
static const char h[] = "0123456789abcdef";
write_char(h[(v >> 4) & 0xf]);
write_char(h[v & 0xf]);
}
void print_short(uint16_t v) { print_byte(v >> 8); print_byte(v); }
void print_long(uint32_t v) { print_short(v >> 16); print_short(v); }
void print_reg(uint16_t v) { print_short(v); }
void itoa_short(uint16_t v)
{
char b[6]; int n = 0;
do { b[n++] = '0' + v % 10; v /= 10; } while (v);
while (n) write_char(b[--n]);
}
void dbg_string(char *p) { (void)p; }
void dbg_short(uint16_t v) { (void)v; }
void dbg_char(char c) { (void)c; }
void dbg_byte(uint8_t v) { (void)v; }
/* ---- copied verbatim from rtlplayground.c ---- */
uint16_t strtox(uint8_t *dst, const char *s)
{
uint8_t *b = dst;
while (*s)
*dst++ = *s++;
*dst = 0;
return dst - b;
}
const uint16_t bit_mask[16] = {
0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000, 0x8000
};
const uint8_t hex_chars[] = "0123456789abcdef";
uint8_t *hex = (uint8_t *)hex_chars;
/* ---- a 9-port RTL8373 whose logical ports map to themselves ---- */
const struct machine machine = {
.machine_name = "HOSTTEST",
.min_port = 0,
.max_port = 8,
.n_sfp = 0,
.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 },
};
struct machine_runtime machine_detected = { .isRTL8373 = 1 };
/* ---- firmware state the modules read or write ---- */
uint8_t outbuf[TCP_OUTBUF_SIZE];
uint16_t slen;
uint16_t management_vlan = 1;
uint16_t cont_len;
uint32_t cont_addr;
uint8_t vlan_names[VLAN_NAMES_SIZE];
uint16_t vlan_ptr;
uint8_t sfp_pins_last = 0xff;
uint8_t sfp_options[2];
char sfp_module_vendor[2][17];
char sfp_module_model[2][17];
char sfp_module_serial[2][17];
char hostname[24] = "hosttest";
char port_names[9][PORT_NAME_SIZE];
struct flash_region_t flash_region;
struct syslog_state syslog_state;
bool stp_enabled;
uip_ipaddr_t uip_hostaddr, uip_draddr, uip_netmask;
struct uip_eth_addr uip_ethaddr = { .addr = { 0x02, 0x11, 0x22, 0x33, 0x44, 0x55 } };
/* ---- leaf calls into subsystems not under test ---- */
void flash_read_bulk(uint8_t *dst) { (void)dst; }
char *get_flash_size_str(void) { return (char *)"2M"; }
uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) { (void)slot; (void)reg; return 0; }
bool gpio_pin_test(uint8_t pin) { (void)pin; return false; }
void phy_read(uint8_t phy_id, uint8_t dev_id, uint16_t reg) { (void)phy_id; (void)dev_id; (void)reg; }
void phy_write(uint8_t phy_id, uint8_t dev_id, uint16_t reg, uint16_t val) { (void)phy_id; (void)dev_id; (void)reg; (void)val; }
void phy_reset(uint8_t port) { (void)port; }
uint8_t stp_port_role(uint8_t port) { (void)port; return 0; }
uint8_t stp_port_state(uint8_t port) { (void)port; return 3; }
/* ---- STP state send_stp() prints; the tree is not under test here ---- */
#include "rtl837x_stp.h"
struct bridge root_bridge;
struct bridge stp_dbridge[STP_ENTITIES];
uint16_t stp_bpdu_age[STP_ENTITIES];
uint16_t stp_dpid[STP_ENTITIES];
uint16_t stp_lag_mask[STP_LAG_COUNT];
uint16_t stp_tc_count;
uint32_t root_bridge_cost;
uint32_t stp_dcost[STP_ENTITIES];
uint32_t stp_pcost[STP_ENTITIES];
uint8_t stp_ent_of[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
uint8_t stp_fwddelay_s = 15, stp_hello_s = 2, stp_maxage_s = 20;
uint8_t stp_pflags[STP_ENTITIES];
uint8_t stp_pp2p[STP_ENTITIES];
uint8_t stp_pprio[STP_ENTITIES];
uint8_t stp_prio = 0x80, stp_root_port = 0xff, stp_rstp = 1, stp_txhold = 6;
/* ---- SFP: no module present, reads fail ---- */
uint8_t sfp_buf[16];
bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) { (void)slot; (void)reg; (void)len; return false; }
void print_phys_port(uint8_t port) { itoa_short(machine.log_to_phys_port[port]); }
+342
View File
@@ -0,0 +1,342 @@
/*
* hw_mock.c - the ASIC edge: SFR storage, register file, table engine.
*
* The shim declares every SFR extern so that including rtl837x_sfr.h from many
* translation units does not define them repeatedly. Undefining the four
* keywords here and re-including the header emits the definitions once, so
* the mock tracks the real register list without a hand-kept copy.
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#undef __sfr
#undef __sfr16
#undef __sfr32
#undef __sbit
#define __sfr volatile uint8_t
#define __sfr16 volatile uint16_t
#define __sfr32 volatile uint32_t
#define __sbit bool
#include "rtl837x_sfr.h"
#undef __sfr
#undef __sfr16
#undef __sfr32
#undef __sbit
#define __sfr extern volatile uint8_t
#define __sfr16 extern volatile uint16_t
#define __sfr32 extern volatile uint32_t
#define __sbit extern bool
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "hw_mock.h"
uint8_t sfr_data[4];
unsigned long hw_reads, hw_writes;
static uint32_t regfile[0x10000];
static uint32_t vlan_tbl[4096];
static struct hw_l2 l2[HW_L2_MAX];
static uint64_t counters[16][64];
void hw_reset(void)
{
memset(regfile, 0, sizeof(regfile));
memset(vlan_tbl, 0, sizeof(vlan_tbl));
memset(l2, 0, sizeof(l2));
memset(counters, 0, sizeof(counters));
memset(sfr_data, 0, sizeof(sfr_data));
hw_reads = hw_writes = 0;
}
void hw_reg_set(uint16_t addr, uint32_t v) { regfile[addr] = v; }
uint32_t hw_reg_get(uint16_t addr) { return regfile[addr]; }
uint32_t hw_vlan_word(uint16_t vid) { return vlan_tbl[vid & 0xfff]; }
void hw_counter_set(uint8_t port, uint8_t counter, uint64_t value) { counters[port & 0xf][counter & 0x3f] = value; }
/* ---- L2 table -------------------------------------------------------- */
static int l2_cmp(const void *x, const void *y)
{
const struct hw_l2 *a = x, *b = y;
if (a->present != b->present)
return a->present ? -1 : 1;
return (int)a->idx - (int)b->idx;
}
static void l2_sort(void) { qsort(l2, HW_L2_MAX, sizeof(l2[0]), l2_cmp); }
int hw_l2_count(void)
{
int n = 0;
for (int i = 0; i < HW_L2_MAX; i++)
n += l2[i].present;
return n;
}
const struct hw_l2 *hw_l2_at(int n)
{
l2_sort();
return (n >= 0 && n < hw_l2_count()) ? &l2[n] : 0;
}
static void l2_key(const struct hw_l2 *e, uint8_t mac[6], uint16_t *vid)
{
mac[0] = e->b >> 8; mac[1] = e->b;
mac[2] = e->a >> 24; mac[3] = e->a >> 16; mac[4] = e->a >> 8; mac[5] = e->a;
*vid = ((e->b >> 16) & 0xfff);
}
const struct hw_l2 *hw_l2_find(const uint8_t mac[6], uint16_t vid)
{
for (int i = 0; i < HW_L2_MAX; i++) {
uint8_t m[6]; uint16_t v;
if (!l2[i].present)
continue;
l2_key(&l2[i], m, &v);
if (v == vid && !memcmp(m, mac, 6))
return &l2[i];
}
return 0;
}
static struct hw_l2 *l2_free(void)
{
for (int i = 0; i < HW_L2_MAX; i++)
if (!l2[i].present)
return &l2[i];
return 0;
}
static uint16_t l2_hash(const uint8_t mac[6], uint16_t vid)
{
uint16_t h = vid;
for (int i = 0; i < 6; i++)
h = (uint16_t)(h * 31 + mac[i]);
return h & 0xfff;
}
void hw_l2_put(uint16_t idx, const uint8_t mac[6], uint16_t vid, uint16_t port_or_pmask, bool is_static)
{
struct hw_l2 *e = (struct hw_l2 *)hw_l2_find(mac, vid);
bool mc = mac[0] & 1;
if (!e)
e = l2_free();
e->present = true;
e->idx = idx & 0xfff;
e->a = ((uint32_t)mac[2] << 24) | ((uint32_t)mac[3] << 16) | ((uint32_t)mac[4] << 8) | mac[5];
e->b = (0x20u << 24) | ((uint32_t)(vid & 0xfff) << 16) | ((uint32_t)mac[0] << 8) | mac[1];
if (mc) {
e->b |= (uint32_t)(port_or_pmask & 0x3) << 30;
e->c = (port_or_pmask >> 2) & 0xff;
} else {
e->b |= (uint32_t)(port_or_pmask & 0x3) << 30;
e->c = (port_or_pmask >> 2) & 0x3;
}
if (is_static)
e->c |= 1u << 16;
}
void hw_l2_decode(const struct hw_l2 *e, uint8_t mac[6], uint16_t *vid, uint16_t *port_or_pmask,
bool *valid, bool *is_static, bool *mc)
{
l2_key(e, mac, vid);
*valid = (e->b >> 29) & 1;
*is_static = (e->c >> 16) & 1;
*mc = mac[0] & 1;
if (*mc)
*port_or_pmask = ((e->b >> 30) & 0x3) | ((e->c & 0xff) << 2);
else
*port_or_pmask = ((e->b >> 30) & 0x3) | ((e->c & 0x3) << 2);
}
/* ---- the engine ------------------------------------------------------ */
static void l2_out(const struct hw_l2 *e, uint16_t idx)
{
regfile[RTL837x_L2_DATA_OUT_A] = e ? e->a : 0;
regfile[RTL837x_L2_DATA_OUT_B] = e ? e->b : 0;
regfile[RTL837x_L2_DATA_OUT_C] = e ? e->c : 0;
regfile[RTL837x_TBL_DATA_0] = (regfile[RTL837x_TBL_DATA_0] & ~0x1fffu) | (e ? 0x1000 : 0) | (idx & 0xfff);
}
static void table_exec(uint32_t ctrl)
{
uint16_t idx = ((ctrl >> 16) & 0xfff);
uint8_t type = ctrl >> 8;
bool write = ctrl & TBL_WRITE;
if (type == TBL_VLAN) {
if (write)
vlan_tbl[idx] = regfile[RTL837x_TBL_DATA_IN_A];
else
regfile[RTL837x_L2_DATA_OUT_A] = vlan_tbl[idx];
return;
}
if (type != TBL_L2_UNICAST)
return;
uint32_t d0 = regfile[RTL837x_TBL_DATA_0];
uint8_t method = (d0 >> 14) & 0x3;
bool clear = (d0 >> 18) & 1;
if (method == TBL_LUTREAD_MAC) {
struct hw_l2 key = { .present = true, .a = regfile[RTL837x_TBL_DATA_IN_A],
.b = regfile[RTL837x_TBL_DATA_IN_B], .c = regfile[RTL837x_TBL_DATA_IN_C] };
uint8_t mac[6]; uint16_t vid;
l2_key(&key, mac, &vid);
struct hw_l2 *e = (struct hw_l2 *)hw_l2_find(mac, vid);
if (write) {
if (clear) {
if (e)
e->present = false;
l2_out(0, 0);
return;
}
if (!e) {
e = l2_free();
e->idx = l2_hash(mac, vid);
}
e->present = true;
e->a = key.a; e->b = key.b; e->c = key.c;
l2_out(e, e->idx);
return;
}
l2_out(e, e ? e->idx : 0);
return;
}
/* next-entry reads: the first present entry at or after idx, wrapping */
l2_sort();
int n = hw_l2_count();
struct hw_l2 *e = 0;
for (int i = 0; i < n; i++)
if (l2[i].idx >= idx) { e = &l2[i]; break; }
if (!e && n)
e = &l2[0];
if (write) {
/* a write by index replaces the entry that sits there */
for (int i = 0; i < n; i++)
if (l2[i].idx == idx) { e = &l2[i]; break; }
if (!e)
e = l2_free();
e->present = (regfile[RTL837x_TBL_DATA_IN_B] >> 29) & 1;
e->idx = idx;
e->a = regfile[RTL837x_TBL_DATA_IN_A];
e->b = regfile[RTL837x_TBL_DATA_IN_B];
e->c = regfile[RTL837x_TBL_DATA_IN_C];
return;
}
l2_out(e, e ? e->idx : idx);
}
static void flush_exec(uint32_t ctrl)
{
uint16_t mask = ctrl & 0x3ff;
bool dynamic_only = regfile[RTL837x_L2_TBL_FLUSH_CNF] == 0;
for (int i = 0; i < HW_L2_MAX; i++) {
uint8_t mac[6]; uint16_t vid, p; bool valid, st, mc;
if (!l2[i].present)
continue;
hw_l2_decode(&l2[i], mac, &vid, &p, &valid, &st, &mc);
if (mc || (st && dynamic_only))
continue;
if ((mask >> p) & 1)
l2[i].present = false;
}
}
static void stat_exec(uint32_t v)
{
uint8_t cnt = (uint8_t)(((v >> 8) & 0xff) << 3) | ((v >> 5) & 0x7);
uint8_t port = (v >> 1) & 0xf;
uint64_t c = counters[port][cnt & 0x3f];
regfile[RTL837X_STAT_V_HIGH] = (uint32_t)(c >> 32);
regfile[RTL837X_STAT_V_LOW] = (uint32_t)c;
}
static void reg_store(uint16_t addr, uint32_t v)
{
hw_writes++;
regfile[addr] = v;
switch (addr) {
case RTL837X_TBL_CTRL:
if (v & TBL_EXECUTE) {
table_exec(v);
regfile[addr] = v & ~(uint32_t)TBL_EXECUTE;
}
break;
case RTL837x_L2_TBL_FLUSH_CTRL:
if (v & L2_TBL_FLUSH_EXEC) {
flush_exec(v);
regfile[addr] = v & ~(uint32_t)L2_TBL_FLUSH_EXEC;
}
break;
case RTL837X_STAT_GET:
if (v & 1) {
stat_exec(v);
regfile[addr] = v & ~1u;
}
break;
}
}
static uint32_t sfr_word(void)
{
return ((uint32_t)SFR_DATA_24 << 24) | ((uint32_t)SFR_DATA_16 << 16) | ((uint32_t)SFR_DATA_8 << 8) | SFR_DATA_0;
}
static void sfr_load(uint32_t v)
{
SFR_DATA_24 = v >> 24; SFR_DATA_16 = v >> 16; SFR_DATA_8 = v >> 8; SFR_DATA_0 = v;
}
void reg_read(uint16_t addr)
{
hw_reads++;
sfr_load(regfile[addr]);
}
void reg_read_m(uint16_t addr)
{
reg_read(addr);
sfr_data[0] = SFR_DATA_24; sfr_data[1] = SFR_DATA_16; sfr_data[2] = SFR_DATA_8; sfr_data[3] = SFR_DATA_0;
}
void reg_write(uint16_t addr)
{
reg_store(addr, sfr_word());
}
void reg_write_m(uint16_t addr)
{
sfr_load(((uint32_t)sfr_data[0] << 24) | ((uint32_t)sfr_data[1] << 16) | ((uint32_t)sfr_data[2] << 8) | sfr_data[3]);
reg_write(addr);
}
void reg_bit_set(uint16_t addr, char bit)
{
reg_read_m(addr);
sfr_data[3 - (bit >> 3)] |= 1 << (bit & 7);
reg_write_m(addr);
}
void reg_bit_clear(uint16_t addr, char bit)
{
reg_read_m(addr);
sfr_data[3 - (bit >> 3)] &= ~(1 << (bit & 7));
reg_write_m(addr);
}
uint8_t reg_bit_test(uint16_t addr, char bit)
{
reg_read_m(addr);
return (sfr_data[3 - (bit >> 3)] >> (bit & 7)) & 1;
}
void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set)
{
sfr_data[3 - n] = (sfr_data[3 - n] & ~mask) | set;
}
+59
View File
@@ -0,0 +1,59 @@
/*
* hw_mock.h - a simulated RTL837x register file and table engine.
*
* The firmware reaches the ASIC only through reg_read/reg_read_m/reg_write/
* reg_write_m and the SFR_DATA_* registers, so mocking that edge is enough to
* run the table code on the host. Every register is a 32-bit word in a flat
* file; a few addresses are live: writing TBL_CTRL with EXECUTE performs the
* table operation at once and clears the bit, STAT_GET fetches a counter,
* L2_TBL_FLUSH_CTRL drops entries. Nothing here ever reports busy, so a
* polling loop in the firmware runs exactly once.
*
* The VLAN and L2 entry layouts mirror what rtl837x_port.c writes and what
* httpd/page_impl.c reads; the decoders below are an independent statement
* of that layout, so a test can compare the firmware's two ends against it.
*/
#ifndef TEST_HW_MOCK_H
#define TEST_HW_MOCK_H
#include <stdint.h>
#include <stdbool.h>
void hw_reset(void); /* every register 0, tables empty */
void hw_reg_set(uint16_t addr, uint32_t v);
uint32_t hw_reg_get(uint16_t addr);
extern uint8_t sfr_data[4]; /* the firmware's register scratch, owned here */
extern unsigned long hw_reads; /* reg_read + reg_read_m calls since hw_reset */
extern unsigned long hw_writes; /* reg_write + reg_write_m calls since hw_reset */
/* VLAN table: the 32-bit word the firmware wrote for that VID, 0 = never written */
uint32_t hw_vlan_word(uint16_t vid);
/* L2 table: entries as the three data words the firmware exchanges with the
* engine. Word B: bit 29 valid, bits 31:30 the low two bits of the port (or
* of the member mask), bits 27:16 VID, bits 15:0 the first two MAC octets.
* Word A: the last four MAC octets. Word C: bit 16 static, bits 1:0 the high
* two bits of the port; for a multicast MAC bits 7:0 are the member mask
* shifted right by two. */
#define HW_L2_MAX 64
struct hw_l2 {
bool present;
uint16_t idx;
uint32_t a, b, c; /* DATA_IN_A/B/C on write, L2_DATA_OUT_A/B/C on read */
};
/* Pointers returned below stay valid until the next table operation. */
int hw_l2_count(void);
const struct hw_l2 *hw_l2_at(int n); /* n-th present entry in idx order */
const struct hw_l2 *hw_l2_find(const uint8_t mac[6], uint16_t vid);
/* Put an entry the way the switch would hold it: a learned unicast entry on
* one port, or a static one. mc entries use pmask instead of a port. */
void hw_l2_put(uint16_t idx, const uint8_t mac[6], uint16_t vid, uint16_t port_or_pmask, bool is_static);
void hw_l2_decode(const struct hw_l2 *e, uint8_t mac[6], uint16_t *vid, uint16_t *port_or_pmask,
bool *valid, bool *is_static, bool *mc);
/* MIB counter the next STAT_GET for (port, counter) returns */
void hw_counter_set(uint8_t port, uint8_t counter, uint64_t value);
#endif /* TEST_HW_MOCK_H */
+56
View File
@@ -0,0 +1,56 @@
/*
* sdcc_shim.h — neutralize SDCC 8051 keywords so firmware translation units
* compile under host gcc/clang for off-target unit testing.
*
* Force-included (gcc -include) ahead of every firmware source under test.
* It only erases storage/qualifier keywords and the banked-call attribute;
* it does NOT change any logic, so the code under test is byte-for-byte the
* firmware source. Hardware/register access is provided by mocks (support.c),
* never by this file.
*/
#ifndef SDCC_SHIM_H
#define SDCC_SHIM_H
#include <stdbool.h>
#include <stdint.h>
/* Memory-space qualifiers → nothing (host has one flat address space). */
#define __xdata
#define __code
#define __pdata
#define __idata
#define __data
/* bool, not unsigned char: SDCC's bit types normalize any assigned value
* to 0/1, which _Bool reproduces exactly. */
#define __bit bool
/* Special-function registers are declared in rtl837x_sfr.h as
* `__sfr __at(0xNN) NAME;`. With __at erased those become plain declarations,
* so they must carry extern or every translation unit including the header
* defines its own copy. __sbit is the bit-in-an-SFR form and only appears
* there; the local bit variable keyword is __bit above. test/hw_mock.c
* undefines these four and re-includes the header once to emit the storage. */
#define __sfr extern volatile uint8_t
#define __sfr16 extern volatile uint16_t
#define __sfr32 extern volatile uint32_t
#define __sbit extern bool
/* The firmware ships its own memcpy/memset/strlen/strcpy/sleep, and
* rtl837x_common.h hides those prototypes under RTLP_HOST_TEST so host builds
* take the C library versions. Something has to declare them, and firmware
* sources do call memset and memcpy; this header is force-included ahead of
* everything, so pulling <string.h> in here puts the libc versions in scope
* before any firmware header and nothing clashes. sleep() is not in
* <string.h> and is declared on its own rather than through <unistd.h>. */
#include <string.h>
extern unsigned int sleep(unsigned int seconds);
/* Function attributes → nothing (no banking / no 8051 calling conventions). */
#define __banked
#define __reentrant
#define __naked
#define __using(n)
#define __interrupt(n)
#define __at(addr)
#endif /* SDCC_SHIM_H */
+11
View File
@@ -0,0 +1,11 @@
/* Stand-in for the generated html_data.h: the table generators under test
* never touch the embedded file list, and the real header exists only after
* a firmware build, which the host harness must not depend on. */
#ifndef FDATA_DEFS_H
#define FDATA_DEFS_H
#include <stdint.h>
typedef enum mime_type_e { mime_HTML = 0, mime_SVG, mime_ICO, mime_PNG, mime_JS, mime_CSS, mime_TXT } mime_type_t;
struct f_data { __code char *file; uint32_t start; uint16_t len; mime_type_t mime; uint8_t gzip; };
#define FDATA_START_login_html 0x00040000UL
#define FDATA_START_favicon_ico 0x00041000UL
#endif
+5
View File
@@ -0,0 +1,5 @@
#ifndef VERSION_H
#define VERSION_H
#define VERSION_SW "host-test"
#define BUILD_DATE "1970-01-01 00:00:00"
#endif
+103
View File
@@ -0,0 +1,103 @@
/*
* support.c — mock hardware edges + test environment for the host harness.
*
* Everything the firmware expects the platform/other-TUs to provide, but which
* has no business touching real hardware in a unit test, lives here: the serial
* ring, the command/history buffers, the character-output sink, and stubs for
* the print helpers. The buffers are sized EXACTLY as on target so that
* AddressSanitizer redzones catch the same off-by-one overflows the 8051 hits.
*/
#include "sdcc_shim.h"
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <signal.h>
/* Declared by hand instead of <unistd.h>, whose sleep() prototype clashes with
* the firmware's own sleep() declaration in rtl837x_common.h. */
extern unsigned int alarm(unsigned int);
extern void _exit(int);
#include "rtl837x_common.h" /* SBUF_SIZE, CMD_BUF_SIZE, CMD_HISTORY_SIZE, decls */
#include "support.h"
/* --- symbols the editor/parser reference (extern in the firmware headers) --- */
__xdata volatile uint8_t sbuf_ptr;
__xdata uint8_t sbuf[SBUF_SIZE];
uint8_t cmd_buffer[CMD_BUF_SIZE]; /* ASan-instrumented: OOB caught */
uint8_t cmd_available;
uint8_t err_status;
uint8_t cmd_history[CMD_HISTORY_SIZE];
uint16_t cmd_history_ptr;
/* editor-internal read pointer (defined in cmd_editor.c, external linkage) */
extern __xdata uint8_t l;
/* the editor entry points under test */
void cmd_editor_init(void);
void cmd_edit(void);
/* ------------------------------- output sink ------------------------------- */
char out_buf[8192];
size_t out_len;
void out_reset(void) { out_len = 0; out_buf[0] = 0; }
static void out_putc(char c) {
if (out_len < sizeof(out_buf) - 1) { out_buf[out_len++] = c; out_buf[out_len] = 0; }
}
/* --- mocked print helpers (signatures match rtl837x_common.h post-shim) ---- */
void write_char(char c) { out_putc(c); }
void print_string(const char *p) { while (*p) out_putc(*p++); }
void print_string_x(char *p) { while (*p) out_putc(*p++); }
void print_cmd_prompt(void) { print_string((char *)"> "); }
void itoa(uint8_t v) { /* firmware prints a decimal number */
char t[4]; int n = 0;
if (!v) { out_putc('0'); return; }
while (v) { t[n++] = '0' + v % 10; v /= 10; }
while (n) out_putc(t[--n]);
}
/* ------------------------------ environment -------------------------------- */
void env_reset(void) {
sbuf_ptr = 0;
t_memset(sbuf, 0, sizeof(sbuf));
t_memset(cmd_buffer, 0, sizeof(cmd_buffer));
t_memset(cmd_history, 0, sizeof(cmd_history));
cmd_history_ptr = 0;
cmd_available = 0;
err_status = 0;
out_reset();
cmd_editor_init(); /* sets l = sbuf_ptr, clears cursor/len */
}
/* Push one byte into the ring, draining first if it is full (leave 1 slot). */
static void ring_push(uint8_t b) {
while (((sbuf_ptr + 1) & SBUF_MASK) == l)
cmd_edit(); /* drain; under the C4 bug this spins */
sbuf[sbuf_ptr] = b;
sbuf_ptr = (sbuf_ptr + 1) & SBUF_MASK;
}
void feed_n(const char *s, size_t n) {
for (size_t i = 0; i < n; i++) ring_push((uint8_t)s[i]);
cmd_edit();
}
void feed(const char *s) { feed_n(s, t_strlen(s)); }
/* -------------------------------- watchdog --------------------------------- */
static const char *wd_what;
static void wd_fire(int sig) {
(void)sig;
/* async-signal-unsafe write is acceptable: we abort immediately after. */
fprintf(stderr, "\n HANG DETECTED (watchdog): %s\n", wd_what ? wd_what : "?");
_exit(2);
}
void watchdog_arm(unsigned seconds, const char *what) {
wd_what = what;
signal(SIGALRM, wd_fire);
alarm(seconds);
}
void watchdog_disarm(void) { alarm(0); }
/* -------------------------------- framework -------------------------------- */
int tests_run, tests_failed;
+53
View File
@@ -0,0 +1,53 @@
/*
* support.h — test harness API for off-target firmware unit tests.
*
* Provides the mock environment (serial ring, command buffers, output capture)
* plus a tiny assertion framework. The firmware sources under test are compiled
* unmodified against this; only the hardware edges are mocked here.
*/
#ifndef TEST_SUPPORT_H
#define TEST_SUPPORT_H
#include <stdint.h>
#include <stddef.h>
/* Distinct-named string helpers so host code needn't include <string.h>, which
* would clash with the firmware's own memset/strlen/strcpy prototypes in
* rtl837x_common.h (different 8051 signatures). */
static inline void t_memset(void *d, int c, size_t n) {
unsigned char *p = d; while (n--) *p++ = (unsigned char)c;
}
static inline size_t t_strlen(const char *s) { const char *p = s; while (*p) p++; return (size_t)(p - s); }
static inline int t_strcmp(const char *a, const char *b) {
while (*a && *a == *b) { a++; b++; } return (unsigned char)*a - (unsigned char)*b;
}
/* ---- captured terminal output (what the firmware would send over serial) ---- */
extern char out_buf[8192];
extern size_t out_len;
void out_reset(void);
/* ---- editor/parser environment reset ---- */
void env_reset(void); /* clears ring, buffers, editor state */
/* ---- drive the serial input ring exactly like the RX ISR would ----
* Pushes bytes into the 16-byte sbuf ring, draining via cmd_edit() whenever the
* ring would overflow, then a final drain. Mirrors real ISR→editor handoff.
*/
void feed(const char *s); /* NUL-terminated */
void feed_n(const char *s, size_t n); /* explicit length (for embedded NULs) */
/* ---- watchdog: arm before calling into code that could spin (C4 hang) ---- */
void watchdog_arm(unsigned seconds, const char *what);
void watchdog_disarm(void);
/* ---- minimal test framework ---- */
extern int tests_run, tests_failed;
#define CHECK(cond, msg) do { \
tests_run++; \
if (!(cond)) { tests_failed++; \
printf(" FAIL: %s (%s:%d)\n", (msg), __FILE__, __LINE__); \
} else { printf(" ok: %s\n", (msg)); } \
} while (0)
#endif /* TEST_SUPPORT_H */
+76
View File
@@ -0,0 +1,76 @@
/*
* test_cmd_editor.c — host unit tests for cmd_editor.c (the serial line editor).
*
* Focus: finding C4 (08-findings.md) — a full input line both HANGS the editor
* (continue without advancing the ring read pointer) and OVERFLOWS cmd_buffer by
* one byte (guard off-by-one lets cmd_line_len reach CMD_BUF_SIZE).
*
* These tests are RED against the current source and GREEN after the fix, with
* no hardware and no flash cycle. The hang is caught by a SIGALRM watchdog; the
* overflow is caught by AddressSanitizer redzones on cmd_buffer[].
*/
#include "sdcc_shim.h"
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "rtl837x_common.h" /* CMD_BUF_SIZE */
#include "support.h"
/* editor state (globals in cmd_editor.c, external linkage) */
extern __xdata uint8_t cmd_line_len;
extern uint8_t cmd_buffer[];
extern uint8_t cmd_available;
static void test_basic_line(void) {
printf("[test] basic line entry + Enter\n");
env_reset();
feed("vlan 199\n");
CHECK(cmd_available == 1, "Enter marks a command available");
CHECK(t_strcmp((char *)cmd_buffer, "vlan 199") == 0, "cmd_buffer holds the typed line");
}
static void test_backspace(void) {
printf("[test] backspace edits the line\n");
env_reset();
feed("helo");
feed("\b"); /* delete the 'o' -> wait, cursor at end: removes last char */
feed("lo\n");
CHECK(t_strcmp((char *)cmd_buffer, "hello") == 0, "backspace then retype yields 'hello'");
}
/* C4 part 1: a line that fills the buffer must not spin forever. */
static void test_full_line_no_hang(void) {
char big[200];
printf("[test] C4: overlong line does not hang the editor\n");
env_reset();
t_memset(big, 'a', sizeof(big));
watchdog_arm(5, "C4 full-line hang (editor spun without advancing ring ptr)");
feed_n(big, sizeof(big)); /* 200 printable chars, no newline */
watchdog_disarm();
/* Reaching here means no hang. With the fix the line caps one short of full. */
CHECK(cmd_line_len == CMD_BUF_SIZE - 1,
"line length caps at CMD_BUF_SIZE-1 (room reserved for NUL)");
}
/* C4 part 2: filling the buffer then Enter must not write cmd_buffer[CMD_BUF_SIZE]. */
static void test_full_line_no_overflow(void) {
char full[CMD_BUF_SIZE]; /* 128 printable chars, exactly buffer size */
printf("[test] C4: full line + Enter does not overflow cmd_buffer\n");
env_reset();
t_memset(full, 'b', sizeof(full));
watchdog_arm(5, "C4 overflow-path hang");
feed_n(full, sizeof(full));
feed("\n"); /* under the bug, writes cmd_buffer[128] -> ASan trap */
watchdog_disarm();
CHECK(1, "128-char line + Enter completes without ASan overflow");
}
int main(void) {
printf("== cmd_editor host tests ==\n");
test_basic_line();
test_backspace();
test_full_line_no_hang();
test_full_line_no_overflow();
printf("\n%d checks, %d failed\n", tests_run, tests_failed);
return tests_failed ? 1 : 0;
}
+473
View File
@@ -0,0 +1,473 @@
/*
* Host-side bench for the httpd transmit path.
*
* Compiles the UNMODIFIED httpd/httpd.c and uip/uip.c and drives them through a
* real handshake and a real GET from a client that moves its receive window.
* Everything below the two modules - flash, console, JSON pages - is mocked
* here, so what the bench observes is the byte stream the firmware would put on
* the wire.
*
* The file served out of the simulated flash carries a position-dependent
* pattern, so a duplicated or skipped range shows up as a mismatch at a known
* offset instead of an anonymous "content differs".
*
* Run: make -C test (exit code 0 = all scenarios pass)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Some hosts ship these in <sys/_endian.h>, and they assign to their argument;
* uip.h refuses to be included next to them. The firmware definitions are the
* ones this bench needs, so the host macros go first. */
#undef HTONS
#undef NTOHS
#include "httpd.h"
#include "uip.h"
#include "rtl837x_common.h"
#include "rtl837x_flash.h"
#include "page_impl.h"
#include "html_data.h"
/* Private to uip.c, and the client needs them to build segments. */
#define TCP_SYN 0x02
#define TCP_PSH 0x08
#define TCP_ACK 0x10
#define TCPH ((struct uip_tcpip_hdr *)&uip_buf[UIP_LLH_LEN])
#define MSS_FULL 1460
#define SMALL_WINDOW 600
/* httpd.c serves this one file without a session, which keeps the bench clear
* of the login machinery: what is under test is the transmit path. */
#define FILE_START FDATA_START_login_html
#define FILE_NAME "/login.html"
#define FILE_LEN 6000
#define STREAM_MAX 16384
static int failures;
static int verbose;
#define CHECK(cond, name) do { \
if (cond) printf("PASS %s\n", name); \
else { printf("FAIL %s\n", name); failures++; } \
} while (0)
static uint8_t pattern(uint32_t addr)
{
return (uint8_t)((addr * 7u) + (addr >> 8));
}
/* ---- firmware environment below httpd.c --------------------------------- */
volatile uint8_t sfr_data[4];
volatile uint32_t ticks;
uint8_t cmd_capture;
uint8_t err_status;
uint8_t *hex = (uint8_t *)"0123456789abcdef";
uint16_t crc_value;
uint8_t *HTTP_RESPONCE_TXT = (uint8_t *)"HTTP/1.1 200 OK\r\n\r\n";
uint32_t flash_size = 0x80000;
uint8_t flash_buf[FLASH_BUF_SIZE];
struct flash_region_t flash_region;
char *mime_strings[] = { "text/html", "image/svg+xml", "image/x-icon",
"image/png", "text/javascript", "text/css", "text/plain" };
struct f_data f_data[] = {
{ FILE_NAME, FILE_START, FILE_LEN, mime_HTML, 0 },
{ 0, 0, 0, mime_HTML, 0 },
};
void flash_read_bulk(uint8_t *dst)
{
for (uint16_t i = 0; i < flash_region.len; i++)
dst[i] = pattern(flash_region.addr + i);
}
void flash_init(uint8_t enable_dio) { (void)enable_dio; }
void flash_sector_erase(void) { }
void flash_write_bytes(uint8_t *ptr) { (void)ptr; }
char *get_flash_size_str(void) { return "512 kB"; }
void crc16_bank1(uint8_t *v) { (void)v; }
void reset_chip(void) { }
void delay(uint16_t t) { (void)t; }
void write_char(char c) { (void)c; }
void write_char_no_syslog(char c) { (void)c; }
void print_string(const char *p) { (void)p; }
void print_string_newline_no_syslog(const char *p) { (void)p; }
void set_sys_led_state(uint8_t state) { (void)state; }
void cmd_parser(void) { }
void execute_config(void) { }
void execute_commands(uint8_t *p) { (void)p; }
void clear_command_history(void) { }
void udp_callbacks(void) { }
void tcpip_output(void) { }
void get_random_32(void) { }
void read_reg_timer(uint32_t *tmr) { *tmr = 0; }
/* uip-conf.h asks uIP not to define the packet buffer: on the switch it lives
* at a fixed XDATA address, so the bench provides the storage itself. */
u8_t uip_buf[UIP_BUFSIZE + 2];
uint16_t strlen_x(const char *s) { return (uint16_t)strlen(s); }
uint16_t strtox(uint8_t *dst, const char *s)
{
uint16_t n = 0;
while (s[n]) { dst[n] = (uint8_t)s[n]; n++; }
return n;
}
void memcpyc(uint8_t *dst, uint8_t *src, uint16_t len) { memcpy(dst, src, len); }
bool strstart(const uint8_t *a, const uint8_t *b)
{
while (*b) { if (*a++ != *b++) return false; }
return true;
}
bool strstart_x(const uint8_t *a, const uint8_t *b) { return strstart(a, b); }
bool send_counters(uint8_t phys_port) { (void)phys_port; return false; }
void send_status(void) { }
void send_vlan(uint16_t vlan) { (void)vlan; }
void send_basic_info(void) { }
void send_bandwidth(void) { }
void send_eee(void) { }
void send_l2(uint16_t idx) { (void)idx; }
void l2_delete(uint16_t idx) { (void)idx; }
void send_mirror(void) { }
void send_mtu(void) { }
void send_config(void) { }
void send_cmd_log(void) { }
void send_lag(void) { }
void send_stp(void) { }
void send_stp_counters(void) { }
void send_vlanlist(void) { }
extern uint8_t authenticated;
/* uip.c defines the listen table but no header declares it; the bench reads it
* to confirm the port httpd_init() asked for is the one uIP is watching. */
extern u16_t uip_listenports[UIP_LISTENPORTS];
/* ---- the simulated client ----------------------------------------------- */
static uint32_t cli_seq; /* next sequence number we send */
static uint32_t cli_rcv_nxt; /* next sequence number we expect */
static uint16_t cli_window; /* what we advertise */
static uint8_t stream[STREAM_MAX]; /* bytes accepted from the server */
static int stream_len;
static uint8_t last_tx[MSS_FULL + 64]; /* payload of the last server segment */
static int last_tx_len;
static uint16_t hton16(uint16_t v)
{
return (uint16_t)((v << 8) | (v >> 8));
}
static void wr32(uint8_t *p, uint32_t v)
{
p[0] = v >> 24; p[1] = v >> 16; p[2] = v >> 8; p[3] = v;
}
static uint32_t rd32(const uint8_t *p)
{
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | p[3];
}
static void trace(const char *tag)
{
if (!verbose)
return;
printf(" [%-8s] uip_len=%-5u flags=0x%02x seq=%-6u ack=%-6u | conn: state=0x%02x len=%-5u mss=%-5u tstate=%u\n",
tag, uip_len,
uip_len ? TCPH->flags : 0,
uip_len ? rd32(TCPH->seqno) : 0,
uip_len ? rd32(TCPH->ackno) : 0,
uip_conns[0].tcpstateflags, uip_conns[0].len, uip_conns[0].mss,
uip_conns[0].appstate.tstate);
}
/* Takes in one outgoing segment, if uIP produced one. */
static int harvest(void)
{
uint8_t *payload;
uint32_t seq;
int plen, hlen;
if (uip_len == 0)
return -1;
seq = rd32(TCPH->seqno);
/* The SYNACK carries the MSS option, so the header is not always 20 B. */
hlen = (TCPH->tcpoffset >> 4) * 4;
payload = &uip_buf[UIP_LLH_LEN + 20 + hlen];
plen = (int)uip_len - 20 - hlen;
if (plen < 0)
plen = 0;
if (plen > 0) {
last_tx_len = plen > (int)sizeof(last_tx) ? (int)sizeof(last_tx) : plen;
memcpy(last_tx, payload, last_tx_len);
/* A real client keeps what continues the stream and drops the
* rest, so bytes sent twice under new sequence numbers land in
* the file just as they would in a browser. */
if (seq == cli_rcv_nxt) {
if (stream_len + plen <= STREAM_MAX) {
memcpy(stream + stream_len, payload, plen);
stream_len += plen;
}
cli_rcv_nxt += plen;
}
if (verbose)
printf(" server -> %d B, seq %u\n", plen, seq);
}
if (TCPH->flags & TCP_SYN)
cli_rcv_nxt = seq + 1;
uip_len = 0;
return plen;
}
static void client_send(uint8_t flags, const char *payload, int plen)
{
int hlen = 20;
/* A SYN carries the MSS option, as every real client does: uIP takes the
* connection MSS from it, and without one the server has nothing to send
* with. */
if (flags & TCP_SYN)
hlen = 24;
memset(uip_buf, 0, UIP_LLH_LEN + 20 + hlen + (plen > 0 ? plen : 0));
TCPH->vhl = 0x45;
TCPH->tos = 0;
TCPH->len[0] = (uint8_t)((20 + hlen + plen) >> 8);
TCPH->len[1] = (uint8_t)((20 + hlen + plen) & 0xff);
TCPH->ttl = 64;
TCPH->proto = UIP_PROTO_TCP;
TCPH->ipchksum = 0;
TCPH->srcipaddr[0] = hton16(0x0a00); TCPH->srcipaddr[1] = hton16(0x0002);
TCPH->destipaddr[0] = hton16(0x0a00); TCPH->destipaddr[1] = hton16(0x0001);
TCPH->srcport = hton16(40000);
TCPH->destport = hton16(80);
wr32(TCPH->seqno, cli_seq);
wr32(TCPH->ackno, cli_rcv_nxt);
TCPH->tcpoffset = (uint8_t)((hlen / 4) << 4);
TCPH->flags = flags;
TCPH->wnd[0] = (uint8_t)(cli_window >> 8);
TCPH->wnd[1] = (uint8_t)(cli_window & 0xff);
TCPH->tcpchksum = 0;
if (hlen == 24) {
uint8_t *opt = &uip_buf[UIP_LLH_LEN + 40];
opt[0] = 2; opt[1] = 4; /* kind = MSS, length 4 */
opt[2] = MSS_FULL >> 8; opt[3] = MSS_FULL & 0xff;
}
if (plen > 0)
memcpy(&uip_buf[UIP_LLH_LEN + 20 + hlen], payload, plen);
uip_len = 20 + hlen + plen;
uip_input();
trace("input");
cli_seq += plen;
if (flags & TCP_SYN)
cli_seq++;
harvest();
}
static void client_ack(uint16_t window)
{
cli_window = window;
client_send(TCP_ACK, NULL, 0);
}
static void run_periodic(int rounds)
{
for (int i = 0; i < rounds; i++) {
uip_periodic(0);
trace("timer");
harvest();
}
}
static void session_start(uint16_t window)
{
uip_init();
httpd_init();
authenticated = 1;
cli_seq = 1000;
cli_rcv_nxt = 0;
cli_window = window;
stream_len = 0;
last_tx_len = 0;
client_send(TCP_SYN, NULL, 0);
client_ack(window);
}
static void request_file(uint16_t window)
{
static const char get[] = "GET " FILE_NAME " HTTP/1.1\r\nHost: sw\r\n\r\n";
cli_window = window;
client_send(TCP_ACK | TCP_PSH, get, (int)strlen(get));
}
/* Drains the response, acknowledging every segment with the given window. */
static void drain(uint16_t window)
{
for (int i = 0; i < 20 && stream_len < STREAM_MAX; i++) {
client_ack(window);
run_periodic(1);
}
}
static int body_offset(void)
{
for (int i = 0; i + 4 <= stream_len; i++)
if (!memcmp(stream + i, "\r\n\r\n", 4))
return i + 4;
return -1;
}
/* Offset of the first body byte that is not the one the file holds there, or
* -1 when the body matches, or -2 when no header ever arrived. */
static int first_body_mismatch(void)
{
int off = body_offset();
if (off < 0)
return -2;
for (int i = 0; i < FILE_LEN && off + i < stream_len; i++)
if (stream[off + i] != pattern(FILE_START + i))
return i;
return -1;
}
static void report(const char *tag)
{
if (verbose)
printf(" %s: header %d B, stream %d B, first mismatch %d\n",
tag, body_offset(), stream_len, first_body_mismatch());
}
/* ---- scenarios ---------------------------------------------------------- */
/* Instrument check: with a window that never moves, the MSS at ACK time and the
* length sent earlier are the same number, so the file must arrive intact. If
* this one fails, the bench is wrong - not the firmware. */
static void scenario_steady_window(void)
{
session_start(MSS_FULL);
if (verbose)
printf(" listen port=0x%04x, initial mss=%u\n",
uip_listenports[0], uip_conns[0].initialmss);
request_file(MSS_FULL);
drain(MSS_FULL);
report("steady");
CHECK(body_offset() >= 0, "control: response carries an HTTP header");
CHECK(stream_len >= body_offset() + FILE_LEN,
"control: the whole file arrives");
CHECK(first_body_mismatch() == -1,
"control: file content without duplicates or gaps");
}
/* The client stops draining, so the window in the ACK is smaller than the
* segment that ACK covers. */
static void scenario_shrinking_window(void)
{
session_start(MSS_FULL);
request_file(MSS_FULL);
drain(SMALL_WINDOW);
report("shrinking");
CHECK(body_offset() >= 0,
"shrinking window: response carries an HTTP header");
CHECK(stream_len >= body_offset() + FILE_LEN,
"shrinking window: the whole file arrives");
CHECK(first_body_mismatch() == -1,
"shrinking window: file content without duplicates or gaps");
}
/* The client catches up, so the window in the ACK is larger than the segment
* that ACK covers. */
static void scenario_growing_window(void)
{
session_start(SMALL_WINDOW);
request_file(SMALL_WINDOW);
drain(MSS_FULL);
report("growing");
CHECK(stream_len >= body_offset() + FILE_LEN,
"growing window: the whole file arrives");
CHECK(first_body_mismatch() == -1,
"growing window: file content without duplicates or gaps");
}
/* A segment is lost, a window update shrinks the window while it is still
* unacknowledged, and the retransmission timer fires. */
static void scenario_rexmit_after_shrink(void)
{
uint8_t original[MSS_FULL + 64];
int original_len, same = 1;
session_start(MSS_FULL);
request_file(MSS_FULL);
original_len = last_tx_len;
memcpy(original, last_tx, original_len);
/* The segment never arrived: take it back out of the client's stream and
* send a pure window update, which acknowledges nothing. */
if (original_len > 0 && stream_len >= original_len) {
stream_len -= original_len;
cli_rcv_nxt -= original_len;
}
cli_window = SMALL_WINDOW;
client_send(TCP_ACK, NULL, 0);
memset(&uip_buf[UIP_LLH_LEN + 40], 0xaa, MSS_FULL);
last_tx_len = 0;
run_periodic(UIP_RTO + 2);
if (last_tx_len != original_len || memcmp(last_tx, original, original_len))
same = 0;
if (verbose)
printf(" first %d B, retransmitted %d B\n",
original_len, last_tx_len);
CHECK(original_len > 0, "retransmission: the first segment went out");
CHECK(same, "retransmission: the repeat carries the same bytes");
}
int main(int argc, char **argv)
{
if (argc > 1 && !strcmp(argv[1], "-v"))
verbose = 1;
printf("== httpd: accounting for the bytes actually sent ==\n");
scenario_steady_window();
scenario_shrinking_window();
scenario_growing_window();
scenario_rexmit_after_shrink();
printf("\n%s (%d failure%s)\n",
failures ? "BENCH: FAILURES" : "BENCH: ALL PASS",
failures, failures == 1 ? "" : "s");
return failures ? 1 : 0;
}
+216
View File
@@ -0,0 +1,216 @@
/*
* test_page_json.c - the JSON generators of httpd/page_impl.c, fed by the
* table code of rtl837x_port.c over the simulated engine. Both modules are
* compiled unmodified, so what the web page shows is checked against what
* the CLI configured, through the same bytes the switch would hold.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_port.h"
#include "machine.h"
#include "page_impl.h"
#include "support.h"
#include "hw_mock.h"
extern uint8_t outbuf[TCP_OUTBUF_SIZE];
extern uint16_t slen;
extern uint8_t vlan_names[VLAN_NAMES_SIZE];
extern uint16_t vlan_ptr;
extern uint16_t management_vlan;
/* The response body: what follows the blank line after the header. */
static const char *body(void)
{
outbuf[slen < TCP_OUTBUF_SIZE ? slen : TCP_OUTBUF_SIZE - 1] = 0;
const char *p = strstr((const char *)outbuf, "\r\n\r\n");
return p ? p + 4 : (const char *)outbuf;
}
static int has(const char *needle) { return strstr(body(), needle) != 0; }
static int count(const char *needle)
{
int n = 0;
for (const char *p = body(); (p = strstr(p, needle)); p += strlen(needle))
n++;
return n;
}
/* Brackets and braces balance outside strings, quotes pair up. */
static int balanced(void)
{
int depth = 0, instr = 0;
for (const char *p = body(); *p; p++) {
if (*p == '"') { instr = !instr; continue; }
if (instr) continue;
if (*p == '{' || *p == '[') depth++;
if (*p == '}' || *p == ']') depth--;
if (depth < 0) return 0;
}
return depth == 0 && !instr;
}
static void name_vlan(uint16_t vid, const char *name)
{
static const char h[] = "0123456789abcdef";
vlan_names[vlan_ptr++] = h[(vid >> 8) & 0xf];
vlan_names[vlan_ptr++] = h[(vid >> 4) & 0xf];
vlan_names[vlan_ptr++] = h[vid & 0xf];
while (*name) vlan_names[vlan_ptr++] = *name++;
vlan_names[vlan_ptr++] = ' ';
vlan_names[vlan_ptr] = 0;
}
static void make_vlan(uint16_t vid, uint16_t members, uint16_t tagged)
{
vlan_settings.vlan = vid;
vlan_settings.members = members;
vlan_settings.tagged = tagged;
vlan_create();
}
static void t_vlan(void)
{
printf("[test] /vlan.json shows what vlan_create and pvid wrote\n");
hw_reset(); out_reset(); vlan_ptr = 0; vlan_names[0] = 0;
make_vlan(10, 0x0015, 0x0004);
name_vlan(10, "lab");
port_pvid_set(0, 10);
port_pvid_set(2, 10);
port_pvid_set(4, 20);
send_vlan(10);
char want[32];
snprintf(want, sizeof want, "\"members\":\"0x%x\"", hw_vlan_word(10));
CHECK(has(want), "members is the entry word");
CHECK(has("\"name\":\"lab\""), "name comes from the name table");
CHECK(has("\"pvid\":\"0x0005\""), "pvid mask has ports 1 and 3, not 5");
CHECK(balanced(), "valid JSON");
}
static void t_vlanlist(void)
{
printf("[test] /vlanlist lists the VLANs that exist\n");
hw_reset(); out_reset(); vlan_ptr = 0; vlan_names[0] = 0;
make_vlan(10, 0x0015, 0x0004);
make_vlan(20, 0x00f0, 0x0000);
make_vlan(4094, 0x0001, 0x0000);
name_vlan(10, "lab");
name_vlan(4094, "top");
management_vlan = 10;
send_vlanlist();
CHECK(has("\"mgmt\":10"), "management VLAN");
CHECK(has("{\"id\":10,\"name\":\"lab\"}"), "VLAN 10 with its name");
CHECK(has("{\"id\":20,\"name\":\"\"}"), "VLAN 20 without a name");
CHECK(has("{\"id\":4094,\"name\":\"top\"}"), "VLAN 4094 is the last one listed");
CHECK(count("\"id\":") == 3, "and nothing else");
CHECK(balanced(), "valid JSON");
vlan_delete(20);
send_vlanlist();
CHECK(count("\"id\":") == 2 && !has("\"id\":20"), "a deleted VLAN drops out");
management_vlan = 1;
}
static const uint8_t base_mac[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x00 };
static void put(uint16_t idx, uint8_t last, uint16_t vid, uint8_t port, bool st)
{
uint8_t m[6];
memcpy(m, base_mac, 6);
m[5] = last;
hw_l2_put(idx, m, vid, port, st);
}
static void t_l2(void)
{
printf("[test] /l2.json walks the table\n");
hw_reset(); out_reset();
put(0x010, 0x01, 1, 4, false);
put(0x020, 0x02, 2, CPU_PORT, true);
put(0x8f0, 0x03, 1, 7, false);
send_l2(0);
CHECK(count("\"mac\":") == 4, "three entries, and the first one again: that repeat is how the page sees the wrap");
CHECK(count("\"idx\":\"0010\"") == 2 && strstr(body(), "\"idx\":\"0010\"}]"), "the repeat closes the list");
CHECK(has("{\"vlan\":\"001\",\"mac\":\"00:11:22:33:44:01\",\"type\":\"l\",\"port\":4,\"lag\":0,\"idx\":\"0010\"}"), "learned entry, VLAN, port and index");
CHECK(has("\"mac\":\"00:11:22:33:44:02\",\"type\":\"s\",\"port\":9"), "static entry on the CPU port");
CHECK(has("\"idx\":\"08f0\""), "an index above 0x800 keeps its high bit");
CHECK(balanced(), "valid JSON");
out_reset();
send_l2(0x100);
const char *b = body();
CHECK(strstr(b, "44:03") && strstr(b, "44:03") < strstr(b, "44:01"), "starting at 0x100 lists 0x8f0 first and wraps");
CHECK(count("\"mac\":") == 4 && count("44:03") == 2, "and still finds all three before repeating 0x8f0");
}
/* Walk the table the way the page does: ask for the index after the last
* entry of each page until an index comes back that was seen before. */
static void t_l2_page(void)
{
printf("[test] /l2.json pages the way the web page walks it\n");
hw_reset(); out_reset();
for (int i = 0; i < 40; i++)
put(0x100 + i * 3, 0x10 + i, 1, i % 8, false);
uint8_t seen[4096 / 8] = { 0 };
int pages = 0, unique = 0, wrapped = 0, idx = 0, biggest = 0;
while (!wrapped && pages < 10) {
out_reset();
send_l2(idx);
pages++;
if (slen > TCP_OUTBUF_SIZE || !balanced())
break;
if ((int)slen > biggest)
biggest = slen;
int n = 0, last = -1;
for (const char *p = body(); (p = strstr(p, "\"idx\":\"")); p += 7) {
int v = (int)strtol(p + 7, 0, 16);
n++;
if (seen[v >> 3] & (1 << (v & 7))) { wrapped = 1; break; }
seen[v >> 3] |= 1 << (v & 7);
unique++;
last = v;
}
if (!n)
break;
idx = last + 1;
}
CHECK(wrapped, "the walk ends on a repeated index");
CHECK(unique == 40, "and has visited every entry once");
CHECK(pages == 2, "in two pages");
CHECK(biggest <= TCP_OUTBUF_SIZE, "no page outgrows outbuf");
CHECK(balanced(), "every page is valid JSON");
}
static void t_status_counters(void)
{
printf("[test] /status.json prints 64-bit packet counters in full\n");
hw_reset(); out_reset();
hw_counter_set(0, STAT_COUNTER_TX_PKTS, 0x100000005ULL);
hw_counter_set(0, STAT_COUNTER_RX_PKTS, 0x00000000000000ffULL);
hw_counter_set(1, STAT_COUNTER_TX_PKTS, 0x00000001ffffffffULL);
send_status();
CHECK(has("\"txG\":\"0x100000005\""), "a counter past 2^32 keeps the zeros of its low word");
CHECK(has("\"rxG\":\"0xff\""), "a small counter stays short");
CHECK(has("\"txG\":\"0x1ffffffff\""), "a low word of all ones");
out_reset();
send_counters(1);
CHECK(has("\"0x100000005\"") && has("\"0x0\""), "/counters.json prints them the same way");
CHECK(slen <= TCP_OUTBUF_SIZE, "fits the buffer");
CHECK(balanced(), "valid JSON");
}
int main(void)
{
printf("== httpd/page_impl.c JSON tests ==\n");
t_vlan();
t_vlanlist();
t_l2();
t_l2_page();
t_status_counters();
printf("\n%d checks, %d failed\n", tests_run, tests_failed);
return tests_failed ? 1 : 0;
}
+181
View File
@@ -0,0 +1,181 @@
/*
* test_port_tables.c - rtl837x_port.c against the simulated table engine.
*
* What the firmware writes to the VLAN and L2 tables is checked against an
* independent statement of the entry layout (hw_mock.h), and what it reads
* back is checked against what it wrote. The module is compiled unmodified.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "rtl837x_common.h"
#include "rtl837x_regs.h"
#include "rtl837x_port.h"
#include "machine.h"
#include "support.h"
#include "hw_mock.h"
extern struct machine_runtime machine_detected;
/* VLAN entry as vlan_create() lays it out: bit 25 valid, bits 0-9 members,
* bits 10-19 the untag set, which is every port not in the tagged set (the
* bit of a non-member is meaningless to the switch and stays set). */
static uint32_t vlan_word(uint16_t members, uint16_t tagged)
{
return (1u << 25) | ((uint32_t)(~tagged & 0x3ff) << 10) | members;
}
static void t_vlan_roundtrip(void)
{
printf("[test] VLAN entry: create, read back, delete\n");
hw_reset(); out_reset();
vlan_settings.vlan = 10;
vlan_settings.members = 0x0015; /* ports 1, 3, 5 */
vlan_settings.tagged = 0x0004; /* port 3 tagged */
vlan_create();
/* the CPU port is always a tagged member */
CHECK(hw_vlan_word(10) == vlan_word(0x0015 | 0x0200, 0x0004 | 0x0200), "vlan 10 entry has the documented layout");
CHECK(hw_vlan_word(11) == 0, "the neighbouring entry is untouched");
uint32_t w = hw_vlan_word(10);
CHECK(vlan_get(10) == 0, "vlan_get finds it");
CHECK(((uint32_t)sfr_data[0] << 24 | (uint32_t)sfr_data[1] << 16 | (uint32_t)sfr_data[2] << 8 | sfr_data[3]) == w,
"and returns the word that was written");
CHECK(sfr_data[0] & 0x02, "with the valid bit the JSON generators test for");
unsigned long r = hw_reads;
CHECK(vlan_get(4095) == -1, "VLAN 4095 is refused");
CHECK(hw_reads == r, "without touching the engine");
vlan_delete(10);
CHECK(hw_vlan_word(10) == 0, "vlan_delete clears the entry");
vlan_delete(0);
vlan_delete(4095);
CHECK(hw_vlan_word(0) == 0 && hw_vlan_word(4095) == 0, "VLAN 0 and 4095 cannot be deleted");
}
static void t_vlan_rtl8372(void)
{
printf("[test] VLAN entry on an RTL8372: ports 0-2 are not there\n");
hw_reset();
machine_detected.isRTL8373 = 0;
vlan_settings.vlan = 20;
vlan_settings.members = 0x03ff;
vlan_settings.tagged = 0x0000;
vlan_create();
uint32_t w = hw_vlan_word(20);
CHECK(((w >> 10) & 0x7) == 0, "the untagged bits of ports 0-2 stay clear");
CHECK((w & 0x3ff) == 0x03ff, "the member bits are what was asked for");
machine_detected.isRTL8373 = 1;
}
static void t_pvid(void)
{
printf("[test] PVID: two ports share one register\n");
hw_reset();
port_pvid_set(2, 100);
port_pvid_set(3, 4094);
CHECK(port_pvid_get(2) == 100, "even port reads back");
CHECK(port_pvid_get(3) == 4094, "odd port reads back");
port_pvid_set(3, 7);
CHECK(port_pvid_get(2) == 100, "writing the odd port leaves the even one alone");
CHECK(port_pvid_get(3) == 7, "and takes the new value");
port_pvid_set(2, 4001);
CHECK(port_pvid_get(3) == 7, "writing the even port leaves the odd one alone");
CHECK(port_pvid_get(8) == 0, "an untouched port reads 0");
}
static void t_l2mc(void)
{
printf("[test] static multicast entry steering a group to the CPU\n");
hw_reset();
port_l2mc_set(0x0e, 2, PMASK_CPU);
const uint8_t mac[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e };
const struct hw_l2 *e = hw_l2_find(mac, 2);
CHECK(e != 0, "the entry exists for 01:80:c2:00:00:0e in VLAN 2");
if (e) {
uint8_t m[6]; uint16_t vid, pm; bool valid, st, mc;
hw_l2_decode(e, m, &vid, &pm, &valid, &st, &mc);
CHECK(valid, "valid");
CHECK(mc, "multicast");
CHECK(pm == PMASK_CPU, "member mask is the CPU port alone");
}
port_l2mc_set(0x0e, 2, PMASK_CPU | 0x0003);
e = hw_l2_find(mac, 2);
CHECK(hw_l2_count() == 1, "writing the same key again replaces, not adds");
if (e) {
uint8_t m[6]; uint16_t vid, pm; bool valid, st, mc;
hw_l2_decode(e, m, &vid, &pm, &valid, &st, &mc);
CHECK(pm == (PMASK_CPU | 0x0003), "with the new mask, low bits included");
}
}
static void t_static_mgmt(void)
{
printf("[test] the management MAC pinned to the CPU port\n");
hw_reset();
uint8_t mac[6] = { 0x02, 0x11, 0x22, 0x33, 0x44, 0x55 };
port_l2_static_mgmt(mac, 2, false);
const struct hw_l2 *e = hw_l2_find(mac, 2);
CHECK(e != 0, "entry present in VLAN 2");
if (e) {
uint8_t m[6]; uint16_t vid, p; bool valid, st, mc;
hw_l2_decode(e, m, &vid, &p, &valid, &st, &mc);
CHECK(valid && st && !mc, "valid, static, unicast");
CHECK(p == CPU_PORT, "on the CPU port");
}
port_l2_static_mgmt(mac, 0, false);
CHECK(hw_l2_find(mac, 1) != 0, "management VLAN 0 means VLAN 1");
port_l2_static_mgmt(mac, 2, true);
CHECK(hw_l2_find(mac, 2) == 0, "removal deletes the VLAN 2 entry");
CHECK(hw_l2_find(mac, 1) != 0, "and leaves the other one");
port_l2_static_mgmt(mac, 3, true);
CHECK(hw_l2_count() == 1, "removing an entry that is not there changes nothing");
}
static void t_flush(void)
{
printf("[test] flushing a port forgets its learned entries only\n");
hw_reset();
const uint8_t a[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x01 };
const uint8_t b[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x02 };
const uint8_t c[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x03 };
hw_l2_put(0x010, a, 1, 4, false);
hw_l2_put(0x020, b, 1, 4, true);
hw_l2_put(0x030, c, 1, 5, false);
port_l2_forget_port(4);
CHECK(hw_l2_find(a, 1) == 0, "learned entry on port 5 (index 4) gone");
CHECK(hw_l2_find(b, 1) != 0, "static entry on the same port stays");
CHECK(hw_l2_find(c, 1) != 0, "entry on another port stays");
port_l2_forget();
CHECK(hw_l2_find(c, 1) == 0 && hw_l2_find(b, 1) != 0, "forget-all drops every learned entry and keeps the static one");
}
static void t_lag(void)
{
printf("[test] trunk membership and its hash seed\n");
hw_reset();
port_lag_members_set(1, 0x0180);
CHECK(port_lag_members_get(1) == 0x0180, "members read back");
CHECK(port_lag_of(7) == 1 && port_lag_of(8) == 1, "both ports belong to group 2");
CHECK(port_lag_of(0) == PORT_LAG_NONE, "a port outside is in no group");
CHECK((hw_reg_get(RTL837X_TRK_HASH_CTRL_BASE + 4) & 0xff) == LAG_HASH_DEFAULT, "an unset hash gets the default");
hw_reg_set(RTL837X_TRK_HASH_CTRL_BASE + 8, 0x06);
port_lag_members_set(2, 0x0060);
CHECK((hw_reg_get(RTL837X_TRK_HASH_CTRL_BASE + 8) & 0xff) == 0x06, "a hash already set is kept");
}
int main(void)
{
printf("== rtl837x_port.c table tests ==\n");
t_vlan_roundtrip();
t_vlan_rtl8372();
t_pvid();
t_l2mc();
t_static_mgmt();
t_flush();
t_lag();
printf("\n%d checks, %d failed\n", tests_run, tests_failed);
return tests_failed ? 1 : 0;
}