From dd7f9f7c7bbbc506f95661ec0795c90b4b19bf56 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Thu, 3 Sep 2026 10:33:53 -0500 Subject: [PATCH 1/3] httpd: refuse to delete the switch's own L2 entry The L2 page's Delete button removes any table entry by index, including the entry for the switch's own MAC on the CPU port. Without that entry unicast to the management address is no longer delivered to the CPU, so management goes dark until the CPU happens to transmit again and the entry is relearned. Compare the entry's MAC against uip_ethaddr before rewriting it and answer result 0 for the switch's own address, as for a missing entry. The compare is inline: this handler sits at the bottom of the uIP call chain, where every extra call level costs internal RAM. --- httpd/page_impl.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index d063f4c1..3addb19c 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -447,27 +447,34 @@ void l2_delete(uint16_t idx) if (!(sfr_data[0] & 0x20)) { char_to_html('0'); } else { + __bit own = sfr_data[2] == uip_ethaddr.addr[0] && sfr_data[3] == uip_ethaddr.addr[1]; sfr_data[0] &= 0x3f; // Clear SPA reg_write_m(RTL837x_TBL_DATA_IN_B); // Second half of MAC is copied reg_read_m(RTL837x_L2_DATA_OUT_A); - reg_write_m(RTL837x_TBL_DATA_IN_A); + if (own && sfr_data[0] == uip_ethaddr.addr[2] && sfr_data[1] == uip_ethaddr.addr[3] + && sfr_data[2] == uip_ethaddr.addr[4] && sfr_data[3] == uip_ethaddr.addr[5]) { + // the switch's own entry keeps management reachable + char_to_html('0'); + } else { + reg_write_m(RTL837x_TBL_DATA_IN_A); - reg_read_m(RTL837x_L2_DATA_OUT_C); - sfr_data[3] &= 0xc0; // Clear age, auth and second part of ports - sfr_data[1] &= 0xfe; // Clear nosalearn - reg_write_m(RTL837x_TBL_DATA_IN_C); + reg_read_m(RTL837x_L2_DATA_OUT_C); + sfr_data[3] &= 0xc0; // Clear age, auth and second part of ports + sfr_data[1] &= 0xfe; // Clear nosalearn + reg_write_m(RTL837x_TBL_DATA_IN_C); - reg_read_m(RTL837x_TBL_DATA_0); - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1], TBL_L2_UNICAST, sfr_data[3]); + reg_read_m(RTL837x_TBL_DATA_0); + REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1], TBL_L2_UNICAST, sfr_data[3]); - REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); - do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); + do { + reg_read_m(RTL837X_TBL_CTRL); + } while (sfr_data[3] & TBL_EXECUTE); - char_to_html('1'); + char_to_html('1'); + } } char_to_html('}'); } From 1c9bf10b871522e994f27413f0f142ed4f773afa Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Thu, 3 Sep 2026 14:33:07 -0500 Subject: [PATCH 2/3] httpd: read the table registers straight from the SFRs in l2_delete() Test the busy flag and compare the entry's MAC against the SFR data registers directly instead of going through the sfr_data copy, and rewrite TBL_DATA_0 in place rather than reassembling all four bytes. Smaller and faster on the deepest httpd path. Suggested-by: vDorst --- httpd/page_impl.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 3addb19c..7e2cd278 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -432,29 +432,31 @@ void l2_delete(uint16_t idx) __xdata uint8_t entries_left = L2_MAX_TRANSFER; do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & TBL_EXECUTE); slen += strtox(outbuf + slen, "{\"result\":"); // First, search for the entry based on the index 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]); + sfr_data[1] &= 0xfc; + sfr_data[2] |= (TBL_LUTREAD_NEXT_L2UC << 6); + reg_write_m(RTL837x_TBL_DATA_0); REG_WRITE(RTL837X_TBL_CTRL, (idx >> 8) & 0xf, idx, TBL_L2_UNICAST, TBL_EXECUTE); do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & 0x1); + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & 0x1); reg_read_m(RTL837x_L2_DATA_OUT_B); - if (!(sfr_data[0] & 0x20)) { + if (!(SFR_DATA_24 & 0x20)) { char_to_html('0'); } else { - __bit own = sfr_data[2] == uip_ethaddr.addr[0] && sfr_data[3] == uip_ethaddr.addr[1]; + __bit is_our_mac_addr = uip_ethaddr.addr[0] == SFR_DATA_8 && uip_ethaddr.addr[1] == SFR_DATA_0; sfr_data[0] &= 0x3f; // Clear SPA reg_write_m(RTL837x_TBL_DATA_IN_B); // Second half of MAC is copied reg_read_m(RTL837x_L2_DATA_OUT_A); - if (own && sfr_data[0] == uip_ethaddr.addr[2] && sfr_data[1] == uip_ethaddr.addr[3] - && sfr_data[2] == uip_ethaddr.addr[4] && sfr_data[3] == uip_ethaddr.addr[5]) { + if (is_our_mac_addr && uip_ethaddr.addr[2] == SFR_DATA_24 && uip_ethaddr.addr[3] == SFR_DATA_16 + && uip_ethaddr.addr[4] == SFR_DATA_8 && uip_ethaddr.addr[5] == SFR_DATA_0) { // the switch's own entry keeps management reachable char_to_html('0'); } else { @@ -466,12 +468,13 @@ void l2_delete(uint16_t idx) reg_write_m(RTL837x_TBL_DATA_IN_C); reg_read_m(RTL837x_TBL_DATA_0); - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1], TBL_L2_UNICAST, sfr_data[3]); + sfr_data[2] = TBL_L2_UNICAST; + reg_write_m(RTL837x_TBL_DATA_0); REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); do { - reg_read_m(RTL837X_TBL_CTRL); - } while (sfr_data[3] & TBL_EXECUTE); + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & TBL_EXECUTE); char_to_html('1'); } From f0e99530811c5e3508054caebfb6291995fdcd5b Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Thu, 3 Sep 2026 14:33:07 -0500 Subject: [PATCH 3/3] web: hide the L2 delete button for the CPU entry and drop a deleted row The firmware now refuses to delete the switch's own entry, so the button on the CPU row can only fail; leave it out. When a delete succeeds, remove the entry from the cached table and repaint instead of leaving the stale row until the next refresh. --- html/l2.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/html/l2.js b/html/l2.js index 2011c655..3d41d89a 100644 --- a/html/l2.js +++ b/html/l2.js @@ -52,8 +52,10 @@ function delL2(idx) { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { - var s = JSON.parse(xhttp.responseText); - console.log("Entry deletion result: ", s.result); + if (JSON.parse(xhttp.responseText).result == 1) { + l2All = l2All.filter(function(e) { return e.idx != idx; }); + renderL2(); + } } }; xhttp.open("GET", "/l2_del.json?idx=" + idx, true); @@ -116,6 +118,13 @@ function fillL2(s) renderL2(); } +function delL2Button(e) +{ + if (e.port == 'CPU') + return ''; + return ''; +} + function paintL2(tbl, s) { console.log("L2: ", JSON.stringify(s)); @@ -127,14 +136,14 @@ function paintL2(tbl, s) tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`; tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`; tbl.rows[i+1].cells[3].innerHTML = `${e.type}`; - tbl.rows[i+1].cells[4].innerHTML = ''; + tbl.rows[i+1].cells[4].innerHTML = delL2Button(e); } else { const tr = tbl.insertRow(); let td = tr.insertCell(); td.innerHTML = `${e.port}`; td = tr.insertCell(); td.innerHTML = `${e.mac}`; td = tr.insertCell(); td.innerHTML = `${e.vlan}`; td = tr.insertCell(); td.innerHTML = `${e.type}`; - td = tr.insertCell(); td.innerHTML = ''; + td = tr.insertCell(); td.innerHTML = delL2Button(e); } } for (let i = tbl.rows.length - 1; i > s.length; i--)