43 Commits
Author SHA1 Message Date
René van Dorst fe42b07e2b Merge pull request #481 from DrDoof/fix/rx-checksum
uip: verify TCP and UDP checksums using the RX descriptor flag
2026-09-20 07:59:46 +00:00
d00f 9984649177 uip: verify TCP and UDP checksums using the RX descriptor flag
The TODO next to the disabled TCP checksum code asks which bit of the RX tag
carries the result. It is bit 2 of byte 1: the hardware sets it when the L4
checksum is bad.

That answers the question for UDP as well, which was not being checked either
because UIP_CONF_UDP_CHECKSUMS is 0. Both paths now read the flag instead of
running a checksum over the payload on the 8051.

A zero UDP checksum, which is optional in IPv4, is not flagged by the hardware,
so DHCP keeps working and no extra condition is needed.

rx_headers lives in rtlplayground.c, which the host tests do not link, so
test_httpd_tx carries the storage itself the way it already carries uip_buf.
A zeroed descriptor reads as a good checksum, so the existing scenarios are
unaffected.

A new scenario sets the flag on a request and expects it counted as a checksum
error and left unanswered, then sends the same request with the flag clear and
expects it served. Built against uip.c without this change, those first two
checks fail and the two control checks still pass, so the scenario tells the
two versions apart rather than merely exercising the code.

Costs 156 bytes of code on SWTGW218AS and no extra XDATA.
2026-09-20 00:35:10 +02:00
René van Dorst 7d47edb649 Merge pull request #477 from bloqaudio/fix/port-speed-select
web: show the speed a port is set to, not always auto
2026-09-19 11:26:03 +00:00
René van Dorst fdb19fe0ee Merge pull request #451 from SkullyDev/main
HASIVO_S600W_4GT_2XGT_SE machine
2026-09-19 11:25:30 +00:00
SkullyDev b9b50f9a70 Merge branch 'main' into main 2026-09-19 13:07:28 +03:00
SkullyDev c31b67572b Remove led behavior & LED comments
Should work without gpio_input_setup
2026-09-19 13:03:42 +03:00
bloqaudio 93003c8cb8 web: show the speed a port is set to, not always auto
The speed selector on the Ports tab was filled with its options and never
given a value, so it read "auto" on every load whatever the port was set
to. Forcing a port to 1G and reloading showed "auto" in the selector
beside a link badge reading 1G.

The advertisement bits ports.json already reports say what the port is
set to: phy_set_speed() writes the same three registers the JSON reads
back, and every forced speed leaves exactly one of the six bits standing
while auto leaves the lot. Map those to the selector, and leave it alone
while it has focus, as the MTU field does.

Values with no entry in the table, such as the 100M setting that
advertises both duplex modes, leave the selector untouched rather than
claim a setting the list cannot express.
2026-09-18 15:13:41 -05:00
René van Dorst eaebeba528 Merge pull request #464 from DrDoof/fix/json-escape
httpd: don't let a quote in a port name break the web page
2026-09-18 18:40:16 +00:00
René van Dorst b36fd95356 Merge pull request #450 from bloqaudio/feat/idle-poll-pause
web: stop polling when the page has had no input for ten minutes
2026-09-18 18:36:34 +00:00
d00f a7fc8303e9 cmd: move name_char() above the parse_mac_cmd comment
The helper landed between that comment and the function it describes, so the
comment now reads as if it belonged to name_char(). Moving the helper up puts
the two back together. The generated code is unchanged.
2026-09-18 17:33:24 +02:00
René van Dorst a62866a93a Merge pull request #472 from DrDoof/fix/httpd-sent-len
httpd: account for the bytes actually sent
2026-09-18 16:19:46 +02:00
d00f 42bbed9661 httpd: brace the empty else body so the host tests build clean
gcc flags the else whose only statement is dbg_string(), because that macro
expands to nothing unless DEBUG is on, leaving the branch with an empty body.
The line is older than this branch; the bench is simply the first thing that
compiles httpd.c with a host compiler and -Wall -Wextra.

Turn warnings into errors in the test Makefile too, so the next one is caught
where it appears instead of scrolling past. It is the only warning the four
test binaries produce, so nothing else has to change.
2026-09-18 16:09:56 +02:00
René van Dorst 530e2683f0 Merge pull request #471 from ScApi/main
Add support for Goalake IG108 / Netcore GS9 / PB-2132 PCB
2026-09-18 11:14:53 +02:00
Sylwester Petela 9fedc6dd3d Remove unnecessary comments and fix removed empty line 2026-09-18 10:19:06 +02:00
Sylwester Petela 5e23d13771 Sort Devices in alphabetical order 2026-09-18 10:17:35 +02:00
Sylwester Petela 09d5857fdd Trim case and PCB images 2026-09-18 10:16:54 +02:00
d00f be309853c9 test: bench for the httpd transmit path
Drives the unmodified httpd.c and uip.c through a handshake and a GET
from a client that moves its receive window, and compares the bytes that
reach the wire with the file they are meant to carry. The served file
holds a position-dependent pattern, so a repeat or a gap is reported at
its offset rather than as content that differs somewhere.

The first scenario holds the window still and must pass: it is the check
that the bench itself is sound, before the two moving-window scenarios
are believed.
2026-09-18 03:17:11 +02:00
d00f 072edb36a8 httpd: account for the bytes actually sent
uip_mss() is the limit on the next segment, not a record of the last
one: uIP recomputes it from the window the peer advertises in every
incoming segment, before the application is called. The ACK path
subtracted it from slen and added it to o_idx, and the retransmit path
handed it to uip_send(), so whenever the window moved between a segment
going out and its ACK coming back, the file being served lost or
repeated the difference. A window shrinking from 1460 to 600 between the
two repeats 860 bytes under fresh sequence numbers, which a browser
writes into the file; a window opening the same way skips 860.

Remember the length in tx_send() and use it in both places. The
retransmit path then repeats exactly the segment uIP still counts as
outstanding, which is what uip_connr->len makes it put on the wire.
2026-09-18 02:39:52 +02:00
d00f 10dd330e3c httpd: subtract through a pointer of known type
uip_appdata is void *, so p - uip_appdata subtracts pointers of
different types. SDCC accepts it, C does not, and no host compiler will
build the file for a unit test. Cast to the type the byte count is meant
in; the generated firmware code is unchanged.
2026-09-18 02:39:52 +02:00
d00f e2c4ed6be3 sfp: clean the module strings where they are read
The vendor, model and serial strings are filtered while the EEPROM read
fills the buffers, so the page copies them out as they are. NUL still
ends a string, so a field shorter than its slot reads the same as before.

The three loops in send_status() become one call each. strcpy() cannot
return the length: rtl837x_common.h hides that prototype under
RTLP_HOST_TEST so host builds take the C library version, and a call
that adds its result to slen does not compile there. A small static
helper does the copy instead, and bank 1 drops by 96 bytes.
2026-09-17 22:32:46 +02:00
d00f 56ee56af2e cmd: keep quotes and control bytes out of names when they are set
The console, POST /cmd and the saved config all go through the command
parser, so one filter there covers every way a name can be set. The
hostname already did this inline; port and VLAN names now do it too.

The SFP strings still need escaping on the way out: they come from the
module's EEPROM, not from a command.
2026-09-17 22:29:57 +02:00
d00f 8626b4c1d4 httpd: don't let a quote in a port name break the web page
A port named NAS"1 made status.json unparsable, so the page stopped
loading, and the name survives a reboot in the saved config. VLAN names
and the SFP strings have the same problem.

They now go through a helper that replaces ", \ and non-printable bytes
with a dot, the way the hostname is already sanitized on ingest. The
length does not change, so the size checks still hold.

The helper also covers the 0xa0 byte that send_sfp_info() skipped as a
special case, so that check is gone.
2026-09-17 22:29:57 +02:00
René van Dorst 0cc09e4015 Merge pull request #467 from DrDoof/test/table-engine-mock
test: run the table code and the JSON pages on the host
2026-09-17 18:56:40 +00:00
d00f 674dc1f35d port, httpd: parenthesise the mixed operators and drop a dead local
Compiling these two files for the host turns up warnings that SDCC does
not print: four places mix & with | and two mix && with ||, and l2_delete
keeps a counter it never reads. The parentheses only say what the
compiler already does, so the generated code is unchanged.

counter_to_html() reads the SFRs directly for the zero test, which saves
14 bytes of bank 1.
2026-09-17 20:49:01 +02:00
Sylwester Petela 7e6b443e07 Merge branch 'main' of https://github.com/ScApi/RTLPlayground 2026-09-17 20:01:38 +02:00
Sylwester Petela 73ec184c23 Added the port-type and led colours 2026-09-17 20:01:29 +02:00
René van Dorst bf41fbd202 Merge pull request #463 from DrDoof/fix/cmd-error-status
cmd: report a rejected command as an error and keep it out of the history
2026-09-17 17:33:56 +00:00
Sylwester Petela 04ab442c6d Merge branch 'logicog:main' into main 2026-09-17 19:28:02 +02:00
René van Dorst c02623a610 Merge pull request #468 from mintsoft/readme-fix
Updating images for new UI
2026-09-17 17:16:19 +00:00
Sylwester Petela c69bf4c5ec Add support for Goalake IG108 / Netcore GS9 / PB-2132 PCB
Support For Goalake IG108 and Netcore GS9 based on same PCB = PB-2132

SFP port untested
2026-09-17 19:13:27 +02:00
Rob Emery 7f4f6dae03 Fixing typo's and image sizes 2026-09-17 09:25:35 +00:00
Rob Emery 4eeb12c020 Merge branch 'logicog:main' into readme-fix 2026-09-17 12:17:09 +03:00
Rob Emery 7d81b15b6e Updating images to match new UI 2026-09-17 10:13:14 +01:00
d00f d6253ddc25 test: the JSON generators against the tables the CLI wrote
page_impl.c and rtl837x_port.c are linked together over the simulated
engine, so /vlan.json, /vlanlist and /l2.json are checked against what
vlan_create, pvid and the L2 table hold, through the same bytes the switch
would. The L2 walk is driven the way the web page drives it, page after
page until an index repeats, which is also where the repeat of the first
entry is pinned down as the wrap marker the page relies on. The counter
test fails on the code before the previous commit.
2026-09-17 04:22:22 +02:00
d00f 8cac67cdf7 httpd: print 64-bit packet counters in full on the status page
send_status wrote the high and the low word of a packet counter one after
the other, each without its leading zeros. That reads fine until a port
passes four billion packets; from then on a low word of 5 prints as "0x15"
next to a high word of 1, and the rates the page derives from the
difference between two readings jump around. send_counters already padded
the low word, so both now share one helper that pads it only when the high
word is set, which also drops the leading "0" the counters page used to
print in front of every small value.
2026-09-17 04:22:22 +02:00
d00f 5620e950ce test: simulate the register file and the table engine, and test the tables
The switch is reached through four register functions and the SFR_DATA
registers, so a flat register file behind them is enough to run the table
code on the host. A few addresses are live: writing TBL_CTRL with EXECUTE
performs the VLAN or L2 operation at once and clears the bit, STAT_GET
fetches a counter, L2_TBL_FLUSH_CTRL drops entries. Nothing ever reports
busy, so a polling loop runs once and the tests say nothing about what
happens when hardware stalls; that question was settled elsewhere.

The header states the VLAN and L2 entry layouts on its own, so a test can
hold what rtl837x_port.c writes against a second reading of the same bits
rather than against itself. The first suite does that for the VLAN entry,
the PVID register two ports share, the static multicast and management
entries, the per-port flush and the trunk registers. Two stub headers stand
in for the ones a firmware build generates, so the harness needs no SDCC
build first.
2026-09-17 04:22:22 +02:00
d00f 83491bc9e3 test: let the shim compile sources that touch the SFRs and libc
Two things kept every register-driven module out of the host harness. The
SFR keywords were unknown, so rtl837x_sfr.h did not compile at all, and once
it did, each translation unit would have defined its own copy of the
registers. They map to extern declarations now; a mock emits the storage
once by re-including the header with the keywords redefined.

The other was memset. rtl837x_common.h hides the firmware's own prototypes
under RTLP_HOST_TEST so host builds use the C library, but nothing declared
the library versions, and the modules do call them. The shim pulls in
string.h ahead of everything, where nothing can clash with it.
2026-09-17 03:55:30 +02:00
d00f bece4a20a1 cmd: let the helper print the Error prefix
The messages said Error: themselves, or usage:, or nothing at all.
cmd_error() prints the prefix now, so every rejected command answers the
same way and the strings only carry what is specific to them.

Two messages that started with Error or refusing are reworded so the
prefix does not read twice. The syslog usage text becomes one string
instead of three calls.
2026-09-16 23:55:11 +02:00
d00f c39cc421a7 print: take the message as a pointer to const
The strings are literals in code space, so the parameter can say so.
Drops six warning 357 from the build.
2026-09-16 16:17:46 +02:00
d00f c1df7c23d1 cmd: set the error status and print the message in one helper
Every rejecting path repeated the same two lines. cmd_error() does both,
which also makes it harder to add a message and forget the status.
2026-09-16 16:16:10 +02:00
d00f 13cc9db1ce cmd: report a rejected command as an error and keep it out of the history
run_cmd_body() answers POST /cmd with 400 when err_status is set, but
only the tokenizer set it. Every "Error:", "Invalid ..." and "Unknown
command" from the parsers went back as 200, so the web page reported the
command as applied, and save_cmd put the rejected line into the command
history, from where Save to flash wrote it into the startup config.

Set ERR_INVALID_ARGUMENT on every path that rejects a command: the err:
labels of the parsers, the parsers that print their own error, the
stp parser and the unknown command fallback. Warnings that still apply
the command, such as a full VLAN name table, are left alone.

Record a command in the history only when it was accepted, and let
execute_commands() report the first failure of a multi-line body, since
cmd_tokenize() clears err_status again for every following line.
2026-09-15 21:17:47 +02:00
SkullyDev 974052877d Added config for HASIVO_S600W_4GT_2XGT_SE (4x2.5G, 2x10G)
SysLed behavior logic added so it can mimic any original style
2026-09-15 00:37:27 +03:00
bloqaudio ffde4eaf60 web: stop polling when the page has had no input for ten minutes
The firmware counts every authenticated request as session activity, and
the page polls the status every 2.5 s, so an open tab never let the
session expire. Track the last mouse, key, touch or wheel event and treat
the page as hidden once ten minutes have passed without one: the pollers
keep ticking but send nothing, the session times out in the firmware as
it did before, and the next input resumes polling, which meets the 401
and returns to the login page.
2026-09-14 14:06:41 -05:00
36 changed files with 1850 additions and 139 deletions

No files matched your search

+4
View File
@@ -7,3 +7,7 @@ version.h
tools/httpd_sim
tools/injector
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.
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,
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:
- A modern web-interface with mouse-over to display further information
- A serial console interface to configure all features
- IGMP to configure Multicast streaming
- Port configuration showing detailed informtion about own and Link-partner advertised
Speed settins and configuration of these settings on the local side
- Port configuration showing detailed information about own and Link-partner advertised
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
for particular devices
- 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
- 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,
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
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
- 5 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]
> 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)
@@ -179,7 +179,7 @@ ghidra see this information about [Ghidra images](doc/ghidra.md).
## (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).
Go to "Firmware update" tab, select the correct file.
@@ -190,7 +190,7 @@ Go to "Firmware update" tab, select the correct file.
> [!CAUTION]
> 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 !
@@ -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)
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]
> 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) !
- Load the firmware into IMSProg.
- 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.
## (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
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]
> 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
```
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
gw yyy.yyy.yyy.yyy = IP adress of the gateway
ip xxx.xxx.xxx.xxx = IP address of the switch
gw yyy.yyy.yyy.yyy = IP address of the gateway
netmask zzz.zzz.zzz.zzz = Network mask of the switch
port x name xxx = Name xxx the port number x
port z 1g = Set 1g speed for port z
+73 -52
View File
@@ -426,6 +426,22 @@ err:
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
// the current one with a bare "mac"). Refuses blank/multicast/locally
// administered/all-zero-OUI addresses so the running MAC stays reachable, and
@@ -441,11 +457,11 @@ void parse_mac_cmd(void)
return;
}
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;
}
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;
}
if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) {
@@ -523,7 +539,7 @@ void parse_lag(void)
port_lag_members_set(group, members);
return;
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);
return;
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 & 0xf];
while(!cmd_is_space_or_nul(pos)) {
uint8_t c = cmd_buffer[pos++];
uint8_t c = name_char(cmd_buffer[pos++]);
write_char(c);
*vlan_str++ = c;
}
@@ -663,7 +679,7 @@ void parse_vlan(void)
}
return;
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;
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
for (log_port = machine.min_port; log_port <= machine.max_port; log_port++) {
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;
}
print_string("All ports ingress filter set to: ");
@@ -762,18 +778,18 @@ void parse_ingress(void)
idx = cmd_words_b[w];
uint8_t ret = cmd_parse_port(idx);
if (ret != 1) {
print_string("Invalid physical port number\n");
cmd_error("Invalid physical port number\n");
continue;
}
log_port = atoi_results_u8;
idx += ret;
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;
}
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;
}
print_string("Port "); print_phys_port(log_port);
@@ -783,7 +799,7 @@ void parse_ingress(void)
}
return;
err:
print_string("Error: ingress [p]<u/t/a>...\n");
cmd_error("ingress [p]<u/t/a>...\n");
}
void parse_mirror(void)
@@ -848,7 +864,7 @@ void parse_mirror(void)
return;
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;
}
@@ -856,7 +872,7 @@ err:
void parse_port(void)
{
if (cmd_words_len < 3) {
print_string("\nUsage:" \
cmd_error("\nUsage:" \
"\nport <port> [show|on|off]" \
"\nport <port> [10m|100m|1g|2g5|duplex] [half|full]" \
"\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) {
print_string("Invalid port number\n");
cmd_error("Invalid port number\n");
return;
}
phy_settings.port = atoi_results_u8;
@@ -881,7 +897,7 @@ void parse_port(void)
} else if (cmd_compare(2, "name")) {
uint8_t i = 0;
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++;
}
port_names[phy_settings.port][i] = NUL;
@@ -942,7 +958,7 @@ void parse_port(void)
phy_settings.duplex = PHY_DUPLEX_HALF;
phy_set_duplex();
} else {
print_string("Unknown port command\n");
cmd_error("Unknown port command\n");
}
}
@@ -970,7 +986,7 @@ void parse_mtu(void)
print_byte(p);
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;
}
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;
err:
print_string("mtu [port] [size]\n");
cmd_error("mtu [port] [size]\n");
return;
}
@@ -1036,11 +1052,11 @@ void parse_sfp(void)
idx += ret;
slot = atoi_results_u8 - 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;
}
if (slot >= machine.n_sfp) {
print_string("SFP slot not present\n");
cmd_error("SFP slot not present\n");
return;
}
@@ -1066,7 +1082,7 @@ void parse_sfp(void)
handle_sfp();
return;
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;
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;
}
@@ -1145,7 +1161,7 @@ void parse_regset(void)
return;
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;
err:
print_string("usage: sdsget <sds-id> <hex:page> <hex:reg>\n");
cmd_error("sdsget <sds-id> <hex:page> <hex:reg>\n");
return;
}
@@ -1244,7 +1260,7 @@ void parse_sdsset(void)
return;
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;
}
@@ -1293,7 +1309,7 @@ void parse_phyget(void)
return;
err:
print_string("usage: phyget <phy-id> <dev-id> <hex:reg>\n");
cmd_error("phyget <phy-id> <dev-id> <hex:reg>\n");
return;
}
@@ -1353,7 +1369,7 @@ void parse_physet(void)
return;
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;
}
@@ -1388,7 +1404,7 @@ void parse_passwd(void)
passwd[j] = NUL;
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)) {
// Word 2 is a port number
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;
}
port = atoi_results_u8;
@@ -1431,7 +1447,7 @@ void parse_eee(void)
speed = EEE_2G5;
else
{
print_string("Speed word invalid, use: [100m|1g|2g5]\n");
cmd_error("Speed word invalid, use: [100m|1g|2g5]\n");
return;
}
}
@@ -1451,7 +1467,7 @@ void parse_eee(void)
else
port_eee_status_all();
} 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;
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)
@@ -1560,7 +1576,7 @@ void parse_syslog(void)
if (was_enabled)
syslog_start();
} else {
print_string("Invalid IP address\n");
cmd_error("Invalid IP address\n");
}
} else if (cmd_compare(1, "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
* 65535; port zero is not a port either, so both tests are needed. */
if (!atoi_short(cmd_words_b[2]) || !atoi_results_short) {
print_string("Invalid port\n");
cmd_error("Invalid port\n");
return;
}
/* The port is frozen into the connection by uip_udp_new(), so
@@ -1587,9 +1603,9 @@ void parse_syslog(void)
}
else
{
print_string("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");
print_string(" port sets the destination UDP port (default 514)\n");
cmd_error("syslog [on|off|ip [ip-address]|port [number]]\n"
" on/off enables or disables syslog, ip sets the syslog server IP address,\n"
" port sets the destination UDP port (default 514)\n");
}
}
@@ -1757,7 +1773,7 @@ void cmd_parser(void) __banked
print_string("Setting ip: ");
print_ip(ip); write_char('\n');
} else {
print_string("Invalid IP address\n" \
cmd_error("Invalid IP address\n" \
"Error: ip [<ip-address>|dhcp]\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");
@@ -1773,7 +1789,7 @@ void cmd_parser(void) __banked
print_string("Setting gw: ");
print_ip(ip); write_char('\n');
} else {
print_string("Invalid IP address\n" \
cmd_error("Invalid IP address\n" \
"Error: gw <ip-address>\n");
}
}
@@ -1787,7 +1803,7 @@ void cmd_parser(void) __banked
print_string("Setting netmask: ");
print_ip(ip); write_char('\n');
} else {
print_string("Invalid IP address\n");
cmd_error("Invalid IP address\n");
}
}
write_char('\n');
@@ -1803,8 +1819,9 @@ void cmd_parser(void) __banked
igmp_setup();
else if (cmd_compare(1, "show"))
igmp_show();
else
print_string("Error: igmp on|off|show\n");
else {
cmd_error("igmp on|off|show\n");
}
} else if (cmd_compare(0, "mac")) {
parse_mac_cmd();
} else if (cmd_compare(0, "hostname")) {
@@ -1822,13 +1839,11 @@ void cmd_parser(void) __banked
uint8_t c = *hp++;
if (c == NUL || c == '\r' || c == '\n')
break;
if (c < 0x20 || c > 0x7e || c == '"' || c == '\\')
c = '.';
*dst++ = c;
*dst++ = name_char(c);
}
*dst = NUL;
} 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")) {
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
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
port_pvid_set(atoi_results_u8, atoi_results_short);
else
print_string("Error: pvid <port> <1-4094>\n");
else {
cmd_error("pvid <port> <1-4094>\n");
}
} else if (cmd_compare(0, "vlan")) {
parse_vlan();
} else if (cmd_compare(0, "isolate")) {
@@ -1897,11 +1913,11 @@ void cmd_parser(void) __banked
parse_ingress();
}
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.
uint8_t i = cmd_words_b[cmd_words_len - 1];
do {
@@ -1995,6 +2011,7 @@ config_done:
// If a command is too long or can't be tokenized, remaining commands are not executed
// Returns the status via `err_status`-variable.
void execute_commands(__xdata uint8_t *p) __banked {
__xdata uint8_t failed = ERR_OK;
err_status = ERR_OK;
uint8_t cmd_idx = 0;
while (1) {
@@ -2005,9 +2022,13 @@ void execute_commands(__xdata uint8_t *p) __banked {
if (err_status != ERR_OK)
return;
cmd_parser();
if (failed == ERR_OK)
failed = err_status;
}
if (*p == 0)
if (*p == 0) {
err_status = failed;
return;
}
cmd_idx = 0;
} else {
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 |
| 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 |
| 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-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 |
@@ -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 |
| 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 | 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| 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 |
| 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-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 |
@@ -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 |
| 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 |
| 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
the machine target by PCB revision. The ML/non-ML or managed/unmanaged label
+9 -1
View File
@@ -533,13 +533,18 @@ TABS.forEach(function(tb){
});
$("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}
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.tick=function(){
var self=this;
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); });
};
@@ -723,6 +728,7 @@ tabHooks.dash={
var SPEEDS=[["auto","c_auto"],["2g5","2.5G"],["1g","1G"],["100m full","100M full"],
["100m half","100M half"],["10m full","10M full"],["10m half","10M half"]];
var SFPRATES=[["auto","c_auto"],["10g","10G"],["2g5","2.5G"],["1g","1G"],["100m","100M"]];
var ADVSEL={63:"auto",31:"auto",32:"2g5",16:"1g",8:"100m full",4:"100m half",2:"10m full",1:"10m half"};
function speedLabel(s){
if(s==="c_auto")return t("c_auto");
return s.replace(" full"," "+t("c_full")).replace(" half"," "+t("c_half"));
@@ -785,6 +791,8 @@ function portsStatus(){
S.ports.forEach(function(p){
var i=p.portNum-1,el=$("plink"+i);
if(el)el.innerHTML=linkBadge(p);
var sel=$("pspd"+i),v=p.isSFP?0:ADVSEL[parseInt(p.adv,2)];
if(sel&&v&&document.activeElement!==sel)sel.value=v;
});
}
function applyPort(i){
+24 -19
View File
@@ -37,6 +37,7 @@ __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
__xdata uint8_t entry;
__xdata uint16_t slen;
__xdata uint16_t o_idx;
__xdata uint16_t sent_len;
__xdata uint16_t len_left;
__xdata uint16_t cont_len;
__xdata uint32_t cont_addr;
@@ -347,8 +348,9 @@ __xdata uint8_t *scan_header(__xdata uint8_t * __xdata p)
if (is_word_x(session, session_id)) {
authenticated = 1;
last_session_use = now;
} else
} else {
dbg_string("Invalid session cookie!\n");
}
}
}
return p;
@@ -559,7 +561,7 @@ static void handle_config_fragment(__xdata uint8_t *p)
__xdata uint16_t frag_len;
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) {
print_string("Configuration too large, aborting.\n");
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 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) {
print_string("Firmware upload header too large, aborting.\n");
config_upload = 0;
@@ -684,7 +686,7 @@ static uint8_t post_body_take(__xdata uint8_t *p)
send_bad_request();
return 0;
}
have = uip_len - (p - uip_appdata);
have = uip_len - (p - (__xdata uint8_t *)uip_appdata);
if (have >= content_length) {
p[content_length] = NUL;
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)
{
__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) {
dbg_string("ACK\n");
if (slen > uip_mss()) {
slen -= uip_mss();
o_idx += uip_mss();
if (slen > sent_len) {
slen -= sent_len;
o_idx += sent_len;
} else {
slen = 0;
}
@@ -872,11 +881,11 @@ void httpd_appcall(void)
if (slen > uip_mss()) {
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;
} else if (slen > 0) {
dbg_string("Sending B: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, slen);
tx_send(slen);
s->tstate = TSTATE_TX;
} else if (cont_len) {
dbg_string("CONT cont_len: "); dbg_short(cont_len);
@@ -887,7 +896,7 @@ void httpd_appcall(void)
flash_region.len = slen;
flash_read_bulk(outbuf);
o_idx = 0;
uip_send(outbuf + o_idx, slen);
tx_send(slen);
cont_len -= slen;
cont_addr += slen;
s->tstate = TSTATE_TX;
@@ -1045,24 +1054,20 @@ do_send:
o_idx = 0;
if (slen > uip_mss()) {
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");
} else {
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");
}
s->tstate = TSTATE_TX;
} else if (uip_rexmit()) { // Connection established, need to rexmit?
dbg_string("RETRANSMIT requested\n");
if (slen > uip_mss()) {
dbg_string("Sending C: "); dbg_short(slen); dbg_char('\n');
uip_send(outbuf + o_idx, uip_mss());
if (sent_len) {
dbg_string("Sending C: "); dbg_short(sent_len); dbg_char('\n');
tx_send(sent_len);
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;
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.
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)
{
// 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
continue;
uint8_t c = sfp_buf[i & 0xf];
if (c && c != 0xa0) // a0 is the byte read from a non-existant I2C EEPROM
char_to_html(c);
if (c)
json_char_to_html(c);
}
}
@@ -279,7 +308,7 @@ void send_vlan(uint16_t vlan)
dbg_string("VLAN has no name\n");
} else {
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");
uint16_t pvid_mask = 0;
@@ -311,8 +340,7 @@ bool send_counters(uint8_t phys_port)
for (uint8_t counter = 0; counter < 0x37; counter++) {
STAT_GET(counter, log_port);
slen += strtox(outbuf + slen, "\"0x");
reg_to_html(RTL837X_STAT_V_HIGH);
reg_to_html_long(RTL837X_STAT_V_LOW);
counter_to_html();
char_to_html('\"');
if (counter != 0x36)
char_to_html(',');
@@ -432,8 +460,6 @@ void l2_delete(uint16_t idx)
slen = strtox(outbuf, HTTP_RESPONCE_JSON);
dbg_string("L2 DELETE\n");
dbg_short(idx);
__xdata uint8_t entries_left = L2_MAX_TRANSFER;
do {
reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE);
@@ -814,7 +840,7 @@ void send_status(void)
itoa_html(i);
slen += strtox(outbuf + slen, ",\"name\":\"");
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, "\"");
@@ -853,14 +879,11 @@ void send_status(void)
sfp_send_data(sfp, 238, 1);
}
slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_vendor[sfp][s]; s++)
outbuf[slen++] = sfp_module_vendor[sfp][s];
str_x_to_html(sfp_module_vendor[sfp]);
slen += strtox(outbuf + slen,"\",\"sfp_model\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_model[sfp][s]; s++)
outbuf[slen++] = sfp_module_model[sfp][s];
str_x_to_html(sfp_module_model[sfp]);
slen += strtox(outbuf + slen,"\",\"sfp_serial\":\"");
for (uint8_t s = 0; s < 16 && sfp_module_serial[sfp][s]; s++)
outbuf[slen++] = sfp_module_serial[sfp][s];
str_x_to_html(sfp_module_serial[sfp]);
slen += strtox(outbuf + slen,"\",\"sfp_los\":");
if (machine.sfp_port[sfp].pin_los == GPIO_NA) {
slen += strtox(outbuf + slen,"null");
@@ -914,8 +937,7 @@ void send_status(void)
STAT_GET(STAT_COUNTER_TX_PKTS, i);
slen += strtox(outbuf + slen, ",\"txG\":\"0x");
reg_to_html(RTL837X_STAT_V_HIGH);
reg_to_html(RTL837X_STAT_V_LOW);
counter_to_html();
slen += strtox(outbuf + slen, "\",\"txB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -923,8 +945,7 @@ void send_status(void)
slen += strtox(outbuf + slen, "\",\"rxG\":\"0x");
STAT_GET(STAT_COUNTER_RX_PKTS, i);
reg_to_html(RTL837X_STAT_V_HIGH);
reg_to_html(RTL837X_STAT_V_LOW);
counter_to_html();
slen += strtox(outbuf + slen, "\",\"rxB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -1034,7 +1055,7 @@ void send_vlanlist(void)
n = vlan_name(i);
if (n != 0xffff) {
while (vlan_names[n] && vlan_names[n] != ' ')
char_to_html(vlan_names[n++]);
json_char_to_html(vlan_names[n++]);
}
slen += strtox(outbuf + slen, "\"}");
+82
View File
@@ -1226,7 +1226,89 @@ __code const struct machine machine = {
0x1a, 0x19, 0x1d, 0x1e, 0x1c,
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
__code const struct machine machine = {
.machine_name = "Hasivo S600W-4GT-2XGT-SE",
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
.n_sfp = 0,
.n_10g = 2,
.log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6},
.phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0},
.is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 0},
.reset_pin = GPIO54_ACL_BIT2_EN,
.high_leds = { .mux = LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 },
.port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1 },
/* Ports 1-4: Green: 2.5GBit, Orange: 10/100/1000MBit
* Ports 5-6: Green: 10GBit, Orange: 5/2.5GBit, None: 10/100/1000MBit
*/
.led_sets = {
{
LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow
LEDS_2G5 | LEDS_LINK, // Green
LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK, // Orange
0, // Unused
},
{
LEDS_10G | LEDS_5G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, // Yellow
LEDS_10G | LEDS_LINK, // Green
LEDS_5G | LEDS_2G5 | LEDS_LINK, // Orange
0, // Unused
},
},
.led_mux_custom = 1,
.led_mux = {
0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x3f, 0x0f, 0x20,
0x21, 0x22, 0x10, 0x11, 0x12,
0x14, 0x15, 0x16, 0x18, 0x19,
0x1a, 0x1c, 0x1d, 0x1e, 0x0c,
0x0d, 0x0e, 0x23
},
};
#else
#error "Please select a machine type in machine.h"
#endif
+2
View File
@@ -46,6 +46,8 @@
// #define MACHINE_FG_8GT_1SX
// #define MACHINE_LIANGUO_HYWS_SGT0108S
// #define MACHINE_POE_2G080110GS
// #define MACHINE_PB_2132
// #define MACHINE_HASIVO_S600W_4GT_2XGT_SE
typedef struct {
// GPIO pins for SDA/SCL
+4 -3
View File
@@ -54,6 +54,7 @@ extern __xdata uint8_t sbuf[SBUF_SIZE];
#define ERR_OK 0
#define ERR_TOO_MANY_ARGUMENTS 1
#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,
// 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;
// Headers for calls in the common code area (HOME/BANK0)
void print_string_no_syslog(__code char *p);
void print_string_newline_no_syslog(__code char *p);
void print_string(__code char *p);
void print_string_no_syslog(__code const char *p);
void print_string_newline_no_syslog(__code const char *p);
void print_string(__code const char *p);
void print_string_x(__xdata char *p);
void print_long(uint32_t a);
void print_short(uint16_t a);
+6 -6
View File
@@ -102,9 +102,9 @@ static void port_pvid_write(uint8_t port, __xdata uint16_t pvid)
reg_read_m(reg);
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 {
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
reg_read_m(reg);
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 {
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);
} else {
if (speed & EEE_10G)
port_eee_enable(i, speed & EEE_NORESET | EEE_2G5);
port_eee_enable(i, (speed & EEE_NORESET) | EEE_2G5);
else
port_eee_enable(i, speed);
}
@@ -899,7 +899,7 @@ void vlan_dump(void) __banked
/** Set the ingress VLAN filtering */
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;
}
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 */
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;
}
+3
View File
@@ -144,6 +144,9 @@
#define RTL837X_REG_CPU_TAG_AWARE_PMASK 0x603C
#define RTL837X_REG_MAC_FORCE_MODE 0x6344
/* Flags in byte 1 of the RX frame descriptor */
#define RX_TAG_L4_CSUM_BAD 0x04
/*
* Statistics related registers
*/
+3
View File
@@ -15,6 +15,8 @@
#include "uip.h"
#include "machine.h"
extern __xdata uint8_t err_status;
// All entry points are __banked and nothing here runs from an interrupt,
// so the module does not need to stay in the resident bank
#pragma codeseg BANK2
@@ -1134,5 +1136,6 @@ void stp_parse(void) __banked __reentrant
}
return;
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");
}
+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)
write_char(*p++);
}
void print_string_no_syslog(__code char *p)
void print_string_no_syslog(__code const char *p)
{
while (*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');
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;
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] == ' ')
dst[length] = NUL;
+24 -3
View File
@@ -11,10 +11,11 @@
CC = gcc
SRCROOT = ..
# -include shim first so __xdata/__code/__banked vanish before any firmware header.
CFLAGS = -std=gnu11 -g -O1 -Wall -Wextra -Wno-unused-parameter -Wno-unknown-pragmas \
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 -I. -I$(SRCROOT) -I$(SRCROOT)/httpd -I$(SRCROOT)/uip
-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
@@ -23,7 +24,15 @@ LDFLAGS = -fsanitize=address,undefined
BUILDDIR = build
# One binary per subsystem under test.
TESTS = $(BUILDDIR)/test_cmd_editor
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
@@ -34,6 +43,18 @@ $(BUILDDIR):
$(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 ==="; \
+13
View File
@@ -19,6 +19,16 @@ fires — so it drops straight into CI.
- **`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
@@ -35,6 +45,9 @@ fires — so it drops straight into CI.
| 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
+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 */
+21 -5
View File
@@ -12,6 +12,7 @@
#define SDCC_SHIM_H
#include <stdbool.h>
#include <stdint.h>
/* Memory-space qualifiers → nothing (host has one flat address space). */
#define __xdata
@@ -22,12 +23,27 @@
/* bool, not unsigned char: SDCC's bit types normalize any assigned value
* to 0/1, which _Bool reproduces exactly. */
#define __bit bool
#define __sbit bool
/* NOTE: the firmware ships its own 8051 memcpy/memset/strlen/strcpy/sleep with
* incompatible signatures (rtl837x_common.h). Host TUs must therefore build with
* -fno-builtin (so gcc doesn't auto-declare the builtins) and must NOT include
* <string.h>/<unistd.h>. Test code uses the t_* helpers in support.h instead. */
/* 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
+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
+1 -1
View File
@@ -47,7 +47,7 @@ static void out_putc(char c) {
/* --- mocked print helpers (signatures match rtl837x_common.h post-shim) ---- */
void write_char(char c) { out_putc(c); }
void print_string(char *p) { while (*p) out_putc(*p++); }
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 */
+508
View File
@@ -0,0 +1,508 @@
/*
* 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_regs.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];
uint8_t rx_headers[16];
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");
}
static void scenario_bad_l4_checksum(void)
{
uip_stats_t chkerr_before;
uint32_t seq_before;
int len_before;
session_start(MSS_FULL);
chkerr_before = uip_stat.tcp.chkerr;
len_before = stream_len;
seq_before = cli_seq;
rx_headers[1] = RX_TAG_L4_CSUM_BAD;
request_file(MSS_FULL);
rx_headers[1] = 0;
cli_seq = seq_before;
CHECK(uip_stat.tcp.chkerr == chkerr_before + 1,
"bad checksum: the segment is counted as a checksum error");
CHECK(stream_len == len_before,
"bad checksum: the request draws no reply");
request_file(MSS_FULL);
drain(MSS_FULL);
report("bad checksum");
CHECK(body_offset() >= 0,
"bad checksum control: the same request with a good flag is served");
CHECK(first_body_mismatch() == -1,
"bad checksum control: the file that follows is intact");
}
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();
scenario_bad_l4_checksum();
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;
}
+15 -9
View File
@@ -95,6 +95,9 @@
#endif /* UIP_CONF_IPV6 */
#include "../rtl837x_common.h"
#include "../rtl837x_regs.h"
extern __xdata uint8_t rx_headers[16];
/*---------------------------------------------------------------------------*/
/* Variable definitions. */
@@ -1120,6 +1123,12 @@ uip_process(u8_t flag) __banked
goto drop;
}
#else /* UIP_UDP_CHECKSUMS */
if (rx_headers[1] & RX_TAG_L4_CSUM_BAD) {
UIP_STAT(++uip_stat.udp.drop);
UIP_STAT(++uip_stat.udp.chkerr);
UIP_LOG("udp: bad checksum.");
goto drop;
}
uip_len = uip_len - UIP_IPUDPH_LEN;
#endif /* UIP_UDP_CHECKSUMS */
@@ -1200,15 +1209,12 @@ uip_process(u8_t flag) __banked
/* Start of TCP input header processing code. */
// BUG: We need to reimplement this with RTL837x TCP checksum checking
// i.e. figure out which bit in the RX-tag states the checksum is correct
// if(uip_tcpchksum() != 0xffff) { /* Compute and check the TCP
// checksum. */
// UIP_STAT(++uip_stat.tcp.drop);
// UIP_STAT(++uip_stat.tcp.chkerr);
// UIP_LOG("tcp: bad checksum.");
// goto drop;
// }
if (rx_headers[1] & RX_TAG_L4_CSUM_BAD) {
UIP_STAT(++uip_stat.tcp.drop);
UIP_STAT(++uip_stat.tcp.chkerr);
UIP_LOG("tcp: bad checksum.");
goto drop;
}
/* Demultiplex this segment. */