Merge pull request #467 from DrDoof/test/table-engine-mock

test: run the table code and the JSON pages on the host
This commit is contained in:
René van Dorst authored and GitHub committed 2026-09-17 18:56:40 +00:00
commit 0cc09e4015
13 files changed
+1012 -21

No files matched your search

+4
View File
@@ -7,3 +7,7 @@ version.h
tools/httpd_sim tools/httpd_sim
tools/injector tools/injector
tools/fileadder tools/fileadder
# host-test stand-ins for the two generated headers
!test/stub/html_data.h
!test/stub/version.h
+17 -8
View File
@@ -175,6 +175,20 @@ void reg_to_html_long(uint16_t reg)
} }
/* The 64-bit counter of the last STAT_GET as one number: the low word keeps
* its leading zeros whenever the high word is set. */
void counter_to_html(void)
{
reg_read_m(RTL837X_STAT_V_HIGH);
if (SFR_DATA_24 | SFR_DATA_16 | SFR_DATA_8 | SFR_DATA_0) {
sfr_data_to_html();
reg_to_html_long(RTL837X_STAT_V_LOW);
} else {
reg_to_html(RTL837X_STAT_V_LOW);
}
}
void send_sfp_info(uint8_t sfp) void send_sfp_info(uint8_t sfp)
{ {
// This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields // This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields
@@ -311,8 +325,7 @@ bool send_counters(uint8_t phys_port)
for (uint8_t counter = 0; counter < 0x37; counter++) { for (uint8_t counter = 0; counter < 0x37; counter++) {
STAT_GET(counter, log_port); STAT_GET(counter, log_port);
slen += strtox(outbuf + slen, "\"0x"); slen += strtox(outbuf + slen, "\"0x");
reg_to_html(RTL837X_STAT_V_HIGH); counter_to_html();
reg_to_html_long(RTL837X_STAT_V_LOW);
char_to_html('\"'); char_to_html('\"');
if (counter != 0x36) if (counter != 0x36)
char_to_html(','); char_to_html(',');
@@ -432,8 +445,6 @@ void l2_delete(uint16_t idx)
slen = strtox(outbuf, HTTP_RESPONCE_JSON); slen = strtox(outbuf, HTTP_RESPONCE_JSON);
dbg_string("L2 DELETE\n"); dbg_string("L2 DELETE\n");
dbg_short(idx); dbg_short(idx);
__xdata uint8_t entries_left = L2_MAX_TRANSFER;
do { do {
reg_read(RTL837X_TBL_CTRL); reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE); } while (SFR_DATA_0 & TBL_EXECUTE);
@@ -914,8 +925,7 @@ void send_status(void)
STAT_GET(STAT_COUNTER_TX_PKTS, i); STAT_GET(STAT_COUNTER_TX_PKTS, i);
slen += strtox(outbuf + slen, ",\"txG\":\"0x"); slen += strtox(outbuf + slen, ",\"txG\":\"0x");
reg_to_html(RTL837X_STAT_V_HIGH); counter_to_html();
reg_to_html(RTL837X_STAT_V_LOW);
slen += strtox(outbuf + slen, "\",\"txB\":\"0x"); slen += strtox(outbuf + slen, "\",\"txB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i); STAT_GET(STAT_COUNTER_ERR_PKTS, i);
@@ -923,8 +933,7 @@ void send_status(void)
slen += strtox(outbuf + slen, "\",\"rxG\":\"0x"); slen += strtox(outbuf + slen, "\",\"rxG\":\"0x");
STAT_GET(STAT_COUNTER_RX_PKTS, i); STAT_GET(STAT_COUNTER_RX_PKTS, i);
reg_to_html(RTL837X_STAT_V_HIGH); counter_to_html();
reg_to_html(RTL837X_STAT_V_LOW);
slen += strtox(outbuf + slen, "\",\"rxB\":\"0x"); slen += strtox(outbuf + slen, "\",\"rxB\":\"0x");
STAT_GET(STAT_COUNTER_ERR_PKTS, i); STAT_GET(STAT_COUNTER_ERR_PKTS, i);
+6 -6
View File
@@ -102,9 +102,9 @@ static void port_pvid_write(uint8_t port, __xdata uint16_t pvid)
reg_read_m(reg); reg_read_m(reg);
if (port & 0x1) { if (port & 0x1) {
REG_WRITE(reg, sfr_data[0], pvid >> 4, sfr_data[2] & 0x0f | (pvid << 4), sfr_data[3]); REG_WRITE(reg, sfr_data[0], pvid >> 4, (sfr_data[2] & 0x0f) | (pvid << 4), sfr_data[3]);
} else { } else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0 | (pvid >> 8), pvid); REG_WRITE(reg, sfr_data[0], sfr_data[1], (sfr_data[2] & 0xf0) | (pvid >> 8), pvid);
} }
} }
@@ -285,7 +285,7 @@ void vlan_setup(void) __banked
#endif #endif
reg_read_m(reg); reg_read_m(reg);
if (i & 0x1) { if (i & 0x1) {
REG_WRITE(reg, sfr_data[0], 0, sfr_data[2] & 0x0f | 0x10, sfr_data[3]); REG_WRITE(reg, sfr_data[0], 0, (sfr_data[2] & 0x0f) | 0x10, sfr_data[3]);
} else { } else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01); REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01);
} }
@@ -758,7 +758,7 @@ void port_eee_enable_all(__xdata uint8_t speed) __banked
port_eee_enable(i, speed); port_eee_enable(i, speed);
} else { } else {
if (speed & EEE_10G) if (speed & EEE_10G)
port_eee_enable(i, speed & EEE_NORESET | EEE_2G5); port_eee_enable(i, (speed & EEE_NORESET) | EEE_2G5);
else else
port_eee_enable(i, speed); port_eee_enable(i, speed);
} }
@@ -899,7 +899,7 @@ void vlan_dump(void) __banked
/** Set the ingress VLAN filtering */ /** Set the ingress VLAN filtering */
bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
{ {
if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) { if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false; return false;
} }
if (enabled) if (enabled)
@@ -913,7 +913,7 @@ bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
/** Get the ingress VLAN filtering status */ /** Get the ingress VLAN filtering status */
bool port_ingress_vlan_filter_get(uint8_t port) __banked bool port_ingress_vlan_filter_get(uint8_t port) __banked
{ {
if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) { if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false; return false;
} }
+17 -2
View File
@@ -12,9 +12,10 @@ CC = gcc
SRCROOT = .. SRCROOT = ..
# -include shim first so __xdata/__code/__banked vanish before any firmware header. # -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 -Wno-unused-parameter -Wno-unknown-pragmas \
-Wno-pointer-sign \
-DRTLP_HOST_TEST \ -DRTLP_HOST_TEST \
-fsanitize=address,undefined -fno-omit-frame-pointer \ -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)),) ifeq ($(findstring clang,$(shell $(CC) --version 2>/dev/null)),)
CFLAGS += -Wno-builtin-declaration-mismatch CFLAGS += -Wno-builtin-declaration-mismatch
endif endif
@@ -23,7 +24,14 @@ LDFLAGS = -fsanitize=address,undefined
BUILDDIR = build BUILDDIR = build
# One binary per subsystem under test. # One binary per subsystem under test.
TESTS = $(BUILDDIR)/test_cmd_editor TESTS = $(BUILDDIR)/test_cmd_editor \
$(BUILDDIR)/test_port_tables \
$(BUILDDIR)/test_page_json
# 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 all: run
@@ -34,6 +42,13 @@ $(BUILDDIR):
$(BUILDDIR)/test_cmd_editor: test_cmd_editor.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR) $(BUILDDIR)/test_cmd_editor: test_cmd_editor.c support.c $(SRCROOT)/cmd_editor.c | $(BUILDDIR)
$(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) $(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)
run: $(TESTS) run: $(TESTS)
@fail=0; for t in $(TESTS); do \ @fail=0; for t in $(TESTS); do \
echo "=== $$t ==="; \ echo "=== $$t ==="; \
+12
View File
@@ -19,6 +19,16 @@ fires — so it drops straight into CI.
- **`sdcc_shim.h`** (force-included) erases SDCC 8051 keywords (`__xdata`, - **`sdcc_shim.h`** (force-included) erases SDCC 8051 keywords (`__xdata`,
`__code`, `__banked`, …) so firmware sources compile under host gcc. It changes `__code`, `__banked`, …) so firmware sources compile under host gcc. It changes
no logic — the code under test is byte-for-byte the firmware source. 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 - **`support.c` / `support.h`** mock the hardware edges: the 16-byte serial ring
(`sbuf`), the command/history buffers, and the character-output sink (`sbuf`), the command/history buffers, and the character-output sink
(`write_char` etc.). Buffers are sized **exactly** as on target, so ASan (`write_char` etc.). Buffers are sized **exactly** as on target, so ASan
@@ -35,6 +45,8 @@ fires — so it drops straight into CI.
| Test binary | TU under test | Findings exercised | | 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_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` |
## Adding a test for another module ## Adding a test for another module
1. Write `test_<module>.c` with `main()` driving the module's entry points and 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 #define SDCC_SHIM_H
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h>
/* Memory-space qualifiers → nothing (host has one flat address space). */ /* Memory-space qualifiers → nothing (host has one flat address space). */
#define __xdata #define __xdata
@@ -22,12 +23,27 @@
/* bool, not unsigned char: SDCC's bit types normalize any assigned value /* bool, not unsigned char: SDCC's bit types normalize any assigned value
* to 0/1, which _Bool reproduces exactly. */ * to 0/1, which _Bool reproduces exactly. */
#define __bit bool #define __bit bool
#define __sbit bool
/* NOTE: the firmware ships its own 8051 memcpy/memset/strlen/strcpy/sleep with /* Special-function registers are declared in rtl837x_sfr.h as
* incompatible signatures (rtl837x_common.h). Host TUs must therefore build with * `__sfr __at(0xNN) NAME;`. With __at erased those become plain declarations,
* -fno-builtin (so gcc doesn't auto-declare the builtins) and must NOT include * so they must carry extern or every translation unit including the header
* <string.h>/<unistd.h>. Test code uses the t_* helpers in support.h instead. */ * 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). */ /* Function attributes → nothing (no banking / no 8051 calling conventions). */
#define __banked #define __banked
+9
View File
@@ -0,0 +1,9 @@
/* 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; };
#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
+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;
}