From f2966ad248e3fde26f53c5530129a597f4d150e5 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 5 Sep 2026 13:28:14 +0200 Subject: [PATCH 01/12] lag gui: create the group the page says it is creating The LAG page draws four sections headed LAG 1 to LAG 4 and wires their buttons to lagSub(0) through lagSub(3), which then sends the index straight through as the group number. So the first button asks for "lag 0", which the parser rejects, and the page shows nothing at all because the error only goes to the console. Every other button is off by one: the section headed LAG 2 creates group 1. The index stays 0-based for the element ids and only the command gets the 1-based number the CLI documents. --- html/lag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html/lag.js b/html/lag.js index 2c56d1a5..2a4e4f26 100644 --- a/html/lag.js +++ b/html/lag.js @@ -57,7 +57,7 @@ function fetchLag() { sendXHTTP(xhttp); } async function lagSub(l) { - var cmd = "lag " + l; + var cmd = "lag " + (l + 1); for (let i = 1; i <= numPorts; i++) { if (document.getElementById("p_mLAG"+l+"_"+i).checked) cmd = cmd + ` ${i}`; From ecac6d1934cbfe68bbe0cc5fe5d72cf8c2af5aa8 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 5 Sep 2026 13:28:15 +0200 Subject: [PATCH 02/12] lag: say what the command takes, and delete only when asked Two small things about the same command. The usage line offered "lag <1-4> [port]...", which leaves out "lag show" even though that is the only way to see the groups, so the one subcommand a reader most needs is the one the error does not mention. And a group number with no ports after it quietly wiped the group. That is a reasonable thing to want and an unreasonable thing to do by accident, as it is one fat-fingered return away from a bare "lag 1". It now takes "lag 1 delete" or "lag 1 d", and a bare group number prints the usage instead. Nothing changes for a saved configuration: "lag 1 5 6" is what gets written and what gets replayed, and neither the old bare form nor the new delete matches the pattern that decides what is persisted. --- cmd_parser.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index bd2fc544..8798e8be 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -406,10 +406,16 @@ void parse_lag(void) if (group > 3) goto err; + if (cmd_words_len == 2) + goto err; + + if (cmd_compare(2, "delete") || cmd_compare(2, "d")) { + port_lag_members_set(group, 0); + return; + } + uint8_t w = 2; while (w < cmd_words_len) { -// write_char('|'); print_byte(w); write_char(':'); write_char(cmd_buffer[cmd_words_b[w]]); write_char('-'); - // Parse port. if (cmd_parse_port_separator(cmd_words_b[w++]) == 0) goto err; @@ -419,7 +425,7 @@ void parse_lag(void) port_lag_members_set(group, members); return; err: - print_string("Error: lag <1-4> [port]...\n"); + print_string("Error: lag (show | <1-4> (delete | ...))\n"); } From 8b7306f995f7efc5ef43e46d5f834f1af972db28 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:01:08 +0200 Subject: [PATCH 03/12] l2: name the group a learned address sits behind A device on an aggregate is learned against whichever member the frame came in on, and the hash moves that around: the same address changes port every time the flow mix changes. The table was telling the truth and there was no way to read it, since nothing on the page or the console says those two ports are one link. port_lag_of() already exists and already knows the answer, so both views ask it. The console prints LAG1 in place of the member, and /l2.json carries a "lag" field beside the port, 0 when the port stands alone, which is the shape /stp.json already uses. Costs 151 bytes of BANK1. --- html/l2.js | 12 ++++++++++-- httpd/page_impl.c | 4 ++++ rtl837x_port.c | 9 ++++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/html/l2.js b/html/l2.js index 3d41d89a..9df0c75d 100644 --- a/html/l2.js +++ b/html/l2.js @@ -68,7 +68,11 @@ var l2SortCol = 'port'; var l2SortDir = 1; function l2Key(e, col) { - if (col === 'port') return e.port === 'CPU' ? Number.MAX_SAFE_INTEGER : Number(e.port); + if (col === 'port') { + if (e.port === 'CPU') return Number.MAX_SAFE_INTEGER; + if (e.lag) return 100 + e.lag; + return Number(e.port); + } if (col === 'vlan') return Number(e.vlan); return String(e[col]).toLowerCase(); } @@ -114,7 +118,11 @@ function fillL2(s) return; s.sort(l2CMP); s = uniq(s); - l2All = s; + l2All = s.map(function(e) { + if (e.lag) + e.port = 'LAG' + e.lag; + return e; + }); renderL2(); } diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 313a652e..b99f0b9b 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -359,6 +359,7 @@ void send_l2(uint16_t idx) while (1) { entries_left--; uint8_t port = 0; + uint8_t lag; reg_read_m(RTL837x_TBL_DATA_0); REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1] & 0xfc, sfr_data[2] | (TBL_LUTREAD_NEXT_L2UC << 6), sfr_data[3]); @@ -402,6 +403,9 @@ void send_l2(uint16_t idx) port |= (sfr_data[3] & 0x3) << 2; itoa_html(port); + slen += strtox(outbuf + slen, ",\"lag\":"); + lag = port_lag_of(port); + itoa_html(lag == PORT_LAG_NONE ? 0 : lag + 1); } // Index diff --git a/rtl837x_port.c b/rtl837x_port.c index 1c966728..950766eb 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -381,6 +381,7 @@ void port_l2_learned(void) __banked while (1) { uint8_t port = 0; + uint8_t lag; reg_read_m(RTL837x_TBL_DATA_0); REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]); @@ -422,7 +423,13 @@ void port_l2_learned(void) __banked print_string("\tlearned\t"); port |= (sfr_data[3] & 0x3) << 2; - print_phys_port(port); + lag = port_lag_of(port); + if (lag == PORT_LAG_NONE) { + print_phys_port(port); + } else { + print_string("LAG"); + itoa(lag + 1); + } } entry++; From 38b8e38c04d08b4f5d6702f428846e2e34b6e4c9 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:09:22 +0200 Subject: [PATCH 04/12] ports: show the SFP port in the table, and read the right link state The port table skipped every SFP port outright, so on a SWTGW218AS the uplink simply was not there. Of the seven columns only Set Speed is meaningless for a module: the name, the link speed and the devices behind the port all are worth showing, and the name in particular was accepted on another page and then never displayed anywhere. The MTU table further down the same page has always drawn the port, so the page disagreed with itself. Give an SFP port its row and leave the three control cells empty. Whether speed and disable should work for a module is a separate question and untested, so this does not offer a button for it. The link state was read one port too far. main.js fills pState by portNum - 1, updatePortTable() reads pState[i-1], and createPortTable() read pState[i], so every row started out showing the next port's link speed until the first refresh corrected it. On the last copper port that meant showing the uplink's speed. Feeding the real status.json of a nine port switch through both versions: expected 7 7 7 1 7 1 7 7 6 before 7 7 1 7 1 7 7 6 after 7 7 7 1 7 1 7 7 6 Dropping the skip also removes a trap rather than papering over it. updatePortTable() addresses rows as tbl.rows[i], which only lined up with port i because the SFP port happens to be last on this board. With a module in the middle every row after it would have been off by one. --- html/ports.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/html/ports.js b/html/ports.js index cf99d47a..f84986b1 100644 --- a/html/ports.js +++ b/html/ports.js @@ -14,15 +14,16 @@ function createPortTable() { + ''; const dSwitch = '' for (let i = 1; i <= numPorts; i++) { - if (pIsSFP[i-1]) - continue; - console.log("Table row: " + i + "pState: " + pState[i-2]); const tr = tbl.insertRow(); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + i)); let portName = portNames[physToLogPort[i-1]] || ''; td = tr.insertCell(); td.appendChild(document.createTextNode(portName)); - td = tr.insertCell(); td.innerHTML = linkText(pState[i] + 1); + td = tr.insertCell(); td.innerHTML = linkText(pState[i-1] + 1); tr.insertCell(); // filled by devRender() + if (pIsSFP[i-1]) { + tr.insertCell(); tr.insertCell(); tr.insertCell(); + continue; + } td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i); td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i) .replace("portOnOff()", "portOnOff(" + i + ")"); @@ -67,9 +68,9 @@ function updatePortTable() { if (tbl.rows.length <= 2 || !numPorts) return; for (let i = 1; i <= numPorts ; i++) { + tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1); if (pIsSFP[i-1]) continue; - tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1); if (!clicked[i] && pState[i - 1] < 0) { document.getElementById('speed_sel_' + i).disabled = true; document.getElementById('disable_port_' + i).checked = true; @@ -133,8 +134,6 @@ function devRender(entries) { perPort[e.port].push(e.mac); } for (let i = 1; i <= numPorts; i++) { - if (pIsSFP[i-1]) - continue; var cell = tbl.rows[i].cells[3]; var macs = perPort[i] || []; if (macs.length == 1) { From 13509d56e5d3651ba5a4648f6e2700da6a727b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Thu, 3 Sep 2026 22:40:49 +0200 Subject: [PATCH 05/12] Fix l2 table, same as #355 but for CLI. --- rtl837x_port.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtl837x_port.c b/rtl837x_port.c index 950766eb..5a764750 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -417,7 +417,7 @@ void port_l2_learned(void) __banked // type reg_read_m(RTL837x_L2_DATA_OUT_C); - if (sfr_data[2] & 0x1) + if (sfr_data[1] & 0x1) print_string("\tstatic\t"); else print_string("\tlearned\t"); From 3a7da77a998f33accb16c46e1cc5b388fdfee06c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20van=20Dorst?= Date: Thu, 3 Sep 2026 23:02:07 +0200 Subject: [PATCH 06/12] Move wait_table_ready() up and replace all the same code for this function. --- rtl837x_port.c | 57 +++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/rtl837x_port.c b/rtl837x_port.c index 5a764750..a640fb5d 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -30,6 +30,14 @@ __xdata uint32_t l2_head; __xdata struct vlan_settings vlan_settings; +// Wait for the TABLE ready +static void wait_table_ready(void) +{ + do { + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & TBL_EXECUTE); +} + void port_mirror_set(uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked { print_string("\nport_mirror_set called \n"); @@ -183,9 +191,9 @@ int8_t vlan_get(uint16_t vlan) __banked return -1; REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_EXECUTE); - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + + wait_table_ready(); + reg_read_m(RTL837x_L2_DATA_OUT_A); return 0; @@ -239,9 +247,9 @@ void vlan_create(void) __banked // Initialize VLAN table with VLAN 1 REG_WRITE(RTL837x_TBL_DATA_IN_A, 0x02, (a >> 6) & 0x0f, (a << 2) | (vlan_settings.members >> 8), vlan_settings.members); REG_WRITE(RTL837X_TBL_CTRL, vlan_settings.vlan >> 8, vlan_settings.vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE); - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + + wait_table_ready(); + print_string("vlan_create done \n"); } @@ -265,9 +273,8 @@ void vlan_setup(void) __banked REG_SET(RTL837x_TBL_DATA_IN_A, machine_detected.isRTL8373? 0x0007ffff : 0x0007e3f8); REG_SET(RTL837X_TBL_CTRL, 0x00010303); - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + + wait_table_ready(); // Set PVID 1 for every port. TODO: Skip unused ports! for (uint8_t i = machine.min_port; i <= machine.max_port + 1; i++) { // Do this also for the CPU port (+1) @@ -310,9 +317,8 @@ void vlan_setup(void) __banked REG_SET(RTL837x_TBL_DATA_IN_A, machine_detected.isRTL8373? 0x0207ffff : 0x0207e3f8); // 02: Entry valid, 7...: membership REG_SET(RTL837X_TBL_CTRL, 0x00010303); // Write VLAN 1 - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + + wait_table_ready(); #ifdef DEBUG print_string("\nvlan_setup, REG 0x6738: "); print_reg(0x6738); @@ -372,9 +378,8 @@ uint8_t port_l2_forget(void) __banked void port_l2_learned(void) __banked { // Whait for any table action to be finished - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & 0x01); + wait_table_ready(); + print_string("\n\tMAC\t\tVLAN\ttype\tport\n"); __xdata uint16_t entry = 0x0000; __xdata uint16_t first_entry = 0xffff; // Table does not have that many entries @@ -386,9 +391,8 @@ void port_l2_learned(void) __banked REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]); REG_WRITE(RTL837X_TBL_CTRL, (entry >> 8) & 0xf, entry, TBL_L2_UNICAST, TBL_EXECUTE); - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + + wait_table_ready(); reg_read_m(RTL837x_TBL_DATA_0); entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3]; @@ -445,17 +449,14 @@ void port_l2_learned(void) __banked void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked { - do { - reg_read(RTL837X_TBL_CTRL); - } while (SFR_DATA_0 & TBL_EXECUTE); + wait_table_ready(); REG_WRITE(RTL837x_TBL_DATA_IN_A, 0xc2, 0x00, 0x00, mac_last); REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | (vid >> 8) | ((pmask & 0x3) << 6), vid, 0x01, 0x80); REG_WRITE(RTL837x_TBL_DATA_IN_C, 0, 0, 0, pmask >> 2); REG_WRITE(RTL837X_TBL_CTRL, 0, 0, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); - do { - reg_read(RTL837X_TBL_CTRL); - } while (SFR_DATA_0 & TBL_EXECUTE); + + wait_table_ready(); } @@ -937,13 +938,6 @@ static void l2_mgmt_tbl_prepare(void) reg_write_m(RTL837x_TBL_DATA_0); } -static void wait_table_ready(void) -{ - do { - reg_read(RTL837X_TBL_CTRL); - } while (SFR_DATA_0 & TBL_EXECUTE); -} - /** Pin the management MAC to the CPU port; remove_entry deletes it */ void port_l2_static_mgmt(__xdata uint8_t *mac, __xdata uint16_t vlan, __xdata bool remove_entry) __banked { @@ -970,5 +964,6 @@ void port_l2_static_mgmt(__xdata uint8_t *mac, __xdata uint16_t vlan, __xdata bo // method-0 write: the hardware hashes the key and places the entry REG_WRITE(RTL837X_TBL_CTRL, 0x00, 0x00, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); } + wait_table_ready(); } From dc5cbcda16a9ac1c20ab7207b8060a99983adcaa Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:03:18 +0200 Subject: [PATCH 07/12] dhcp: fix transaction-id check so it actually compares The guard read `if (!DHCP_P->tid == HTONS(...))`. Unary `!` binds tighter than `==`, so this evaluated `(!tid) == HTONS(id)`, i.e. `(tid ? 0 : 1) == HTONS(id)`, which is false for any non-zero id and never returns. The reply was therefore accepted regardless of its transaction id, so a broadcast reply meant for another client on the segment was parsed as ours. Compare the field directly. The id is stored network-order in the packet and host-order in the state, matching the HTONS() used when the request is built, so `DHCP_P->tid != HTONS(dhcp_state.transaction_id)` is the intended test. --- dhcp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dhcp.c b/dhcp.c index d821f805..9d92acb5 100644 --- a/dhcp.c +++ b/dhcp.c @@ -300,7 +300,7 @@ void parse_opts(void) void parse_dhcp(void) { - if (!DHCP_P->tid == HTONS(dhcp_state.transaction_id)) + if (DHCP_P->tid != HTONS(dhcp_state.transaction_id)) return; if (DHCP_P->cookie[0] != 0x63 || DHCP_P->cookie[1] != 0x82 || DHCP_P->cookie[2] != 0x53 || DHCP_P->cookie[3] != 0x63) return; From 025e1a186b94bfae24d976a7d6da7f62283f90f7 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:05:29 +0200 Subject: [PATCH 08/12] Call uip_arp_timer() so the ARP cache ages uip_arp_timer() was defined but never called, so ARP entries were only refreshed on new learning and only removed under table pressure. A cached entry for a peer whose MAC later changes (a gateway failover, a replaced device) is then used until it is evicted, and traffic to anything reached through it is sent to the old MAC and lost, while on-link traffic is unaffected. uIP expects the timer roughly every ten seconds; drive it from the once-per-second housekeeping in idle(). --- rtlplayground.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rtlplayground.c b/rtlplayground.c index b3710c76..f9da5839 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -89,6 +89,7 @@ volatile __xdata uint32_t ticks; volatile __xdata uint8_t sec_counter; volatile __xdata uint16_t sleep_ticks; __xdata uint8_t stp_clock; +__xdata uint8_t arp_age_secs; extern __xdata struct dhcp_state dhcp_state; #define STP_TICK_DIVIDER 3 @@ -1565,6 +1566,11 @@ void idle(void) // Check for button presses once a second handle_button(); + // Age the ARP cache: uip_arp_timer() expects a 10 s cadence + if (++arp_age_secs >= 10) { + arp_age_secs = 0; + uip_arp_timer(); + } #ifdef DEBUG print_sfr_data(); From efdf93a97a8869271cde748600fbf3b895071d97 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:07:16 +0200 Subject: [PATCH 09/12] Do not call write_char() from the external interrupt handlers The EX0..EX3 handlers each emitted a single debug character through write_char(), which busy-waits on the serial TX-empty flag that is set by the serial interrupt. If one of these handlers runs while a byte is being transmitted, it waits for a flag that the equal-or-lower-priority serial interrupt cannot deliver, hanging the handler with the system tick stopped. The characters were only ever debug markers, so drop them; the handlers now just acknowledge the interrupt. No interrupt service routine should call the blocking console path. --- rtlplayground.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rtlplayground.c b/rtlplayground.c index b3710c76..5a7b8709 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -493,7 +493,6 @@ void print_cmd_prompt(void) void isr_ext0(void) __interrupt(0) { EX0 = 0; // Disable interrupt for the moment - write_char('X'); IT0 = 1; // Trigger on falling edge of external interrupt EX0 = 1; // Re-enable interrupt } @@ -501,14 +500,11 @@ void isr_ext0(void) __interrupt(0) /* * External IRQ 1 Service Routine, triggered by the NIC recieving a packet - * Note that all registers are being put on the STACK because of calling - * a subroutine (write_char), we shold do better... */ void isr_ext1(void) __interrupt(2) { // This flag should only be reset after all packets have been read EX1 = 0; - write_char('Y'); EX1 = 1; } @@ -519,7 +515,6 @@ void isr_ext1(void) __interrupt(2) void isr_ext2(void) __interrupt(8) { EXIF &= 0xef; // Clear IRQ flag (bit 7) in EXIF - write_char('Z'); PCON |= 1; // Enter Idle mode until interrupt occurs } @@ -530,7 +525,6 @@ void isr_ext2(void) __interrupt(8) void isr_ext3(void) __interrupt(9) { EXIF &= 0xdf; // Clear IRQ flag (bit 6) in EXIF - write_char('W'); } // Timer2: handles system tick. From 078a446ceea2bbf15d9c394419857a21a3f0b5e8 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:41:27 +0200 Subject: [PATCH 10/12] lag: address review, single arg-count check and "d" only Fold the "group with no ports" case into the argument-count guard (cmd_words_len < 3) instead of a separate check, and accept only "d" for delete, dropping the longer "delete" spelling. Update the serial-console docs to match. --- cmd_parser.c | 9 +++------ doc/link_aggregation.md | 8 +++++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 8798e8be..4fc2b0cf 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -395,7 +395,7 @@ void parse_lag(void) return; } - if (cmd_words_len < 2) + if (cmd_words_len < 3) goto err; // Parse group, expect only one number 0-9; @@ -406,10 +406,7 @@ void parse_lag(void) if (group > 3) goto err; - if (cmd_words_len == 2) - goto err; - - if (cmd_compare(2, "delete") || cmd_compare(2, "d")) { + if (cmd_compare(2, "d")) { port_lag_members_set(group, 0); return; } @@ -425,7 +422,7 @@ void parse_lag(void) port_lag_members_set(group, members); return; err: - print_string("Error: lag (show | <1-4> (delete | ...))\n"); + print_string("Error: lag (show | <1-4> (d | ...))\n"); } diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index 6bde787e..b725e419 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -48,9 +48,11 @@ void port_lag_hash_set(__xdata uint8_t lag, __xdata uint8_t hash_bits) __banked; ## LAG configuration on the Serial Console For testing the following commands are provided on the serial console: ``` -> lag [p1] [p2]... - Create or set a LAG. Trunk-ID is 1 or 2. Ports are physical ports - If only the LAG-ID is given but no members, the LAG is deleted +> lag [p2]... + Create or set a LAG. LAG-ID is 1 to 4. Ports are physical ports. + +> lag d + Delete the LAG. > lag show Shows information on all 4 lags From 97d44fbf4b379c0fd196256689227ae6e980081e Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:30:40 +0200 Subject: [PATCH 11/12] dhcp: drop the 16-bit swap on the 32-bit transaction id The id was built and checked with HTONS(), which casts to 16 bits, so the top half of the 32-bit transaction id was thrown away and the rest byte swapped. The id is opaque: the client only has to receive its own value back, so there is no byte order to honour and no reason to narrow it. Assign and compare the field directly, so the full 32-bit id round-trips. --- dhcp.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dhcp.c b/dhcp.c index 9d92acb5..6cfa373a 100644 --- a/dhcp.c +++ b/dhcp.c @@ -94,7 +94,7 @@ void dhcp_prepare_request(void) DHCP_P->hw_len = 6; DHCP_P->hops = 0; - DHCP_P->tid = HTONS(dhcp_state.transaction_id); + DHCP_P->tid = dhcp_state.transaction_id; DHCP_P->delay = HTONS(0); DHCP_P->flags = 0; // Clear fields client_ip to bootp_file @@ -300,7 +300,7 @@ void parse_opts(void) void parse_dhcp(void) { - if (DHCP_P->tid != HTONS(dhcp_state.transaction_id)) + if (DHCP_P->tid != dhcp_state.transaction_id) return; if (DHCP_P->cookie[0] != 0x63 || DHCP_P->cookie[1] != 0x82 || DHCP_P->cookie[2] != 0x53 || DHCP_P->cookie[3] != 0x63) return; From 30547cc453d7b99099b7913f1c2d7c1a978b86b6 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:13:23 +0200 Subject: [PATCH 12/12] ports: drop the SFP row, keep the link-state index fix Leaving SFP ports out of the table is deliberate for now, and being able to force a module's speed belongs in a feature of its own rather than arriving inside a fix, so that half goes again. What remains is the off-by-one: createPortTable() read pState[i] where the array is filled by portNum - 1, so every row started out showing the next port's link speed until the first refresh corrected it. The debug line above it goes too; it printed a third index, pState[i-2]. --- html/ports.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/html/ports.js b/html/ports.js index f84986b1..f76534f2 100644 --- a/html/ports.js +++ b/html/ports.js @@ -14,16 +14,14 @@ function createPortTable() { + ''; const dSwitch = '' for (let i = 1; i <= numPorts; i++) { + if (pIsSFP[i-1]) + continue; const tr = tbl.insertRow(); let td = tr.insertCell(); td.appendChild(document.createTextNode(t('common_port') + i)); let portName = portNames[physToLogPort[i-1]] || ''; td = tr.insertCell(); td.appendChild(document.createTextNode(portName)); td = tr.insertCell(); td.innerHTML = linkText(pState[i-1] + 1); tr.insertCell(); // filled by devRender() - if (pIsSFP[i-1]) { - tr.insertCell(); tr.insertCell(); tr.insertCell(); - continue; - } td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i); td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i) .replace("portOnOff()", "portOnOff(" + i + ")"); @@ -68,9 +66,9 @@ function updatePortTable() { if (tbl.rows.length <= 2 || !numPorts) return; for (let i = 1; i <= numPorts ; i++) { - tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1); if (pIsSFP[i-1]) continue; + tbl.rows[i].cells[2].innerHTML = linkText(pState[i-1]+1); if (!clicked[i] && pState[i - 1] < 0) { document.getElementById('speed_sel_' + i).disabled = true; document.getElementById('disable_port_' + i).checked = true; @@ -134,6 +132,8 @@ function devRender(entries) { perPort[e.port].push(e.mac); } for (let i = 1; i <= numPorts; i++) { + if (pIsSFP[i-1]) + continue; var cell = tbl.rows[i].cells[3]; var macs = perPort[i] || []; if (macs.length == 1) {