Commit Graph
1308 Commits
Author SHA1 Message Date
logicog df95c544d4 Merge pull request #418 from DrDoof/fix/crc16-psbank
crc16: leave the code bank as it was found
2026-09-09 06:13:56 +02:00
d00f 53f12a42c5 crc16: banked wrapper for cross-bank callers
The lookup tables live in BANK1, so the routine switches the code bank to
reach them with movc and never switches it back. That is the contract the
__naked declaration already implied: same bank or common segment only.
The name now says it.

crc16_bank1 is the routine unchanged. crc16 becomes a __banked wrapper in
BANK1, so a caller from another bank has its own bank put back by
__sdcc_banked_ret. The two existing call sites take the direct path and
pay nothing.
2026-09-09 01:52:12 +02:00
René van Dorst dd09be9dd0 Merge pull request #412 from DrDoof/fix/httpd-init-conn
httpd: do not initialise tstate through uip_conn
2026-09-08 22:22:08 +00:00
René van Dorst b2b7f8a0c6 Merge pull request #414 from DrDoof/fix/dhcp-restart-leak
dhcp: do not leave the old socket behind when starting again
2026-09-08 22:19:18 +00:00
René van Dorst 5b28ddf80a Merge pull request #416 from DrDoof/fix/phy-100m-half-adv
phy: advertise half duplex only when half duplex was asked for
2026-09-08 22:06:11 +00:00
d00f fe3da0c7a5 phy: advertise half duplex only when half duplex was asked for
The 100M branch of phy_set_speed() was missing an else. A port set to 100M
half wrote 0x1481, walked into the next if, found duplex was not 1, took its
else and overwrote the register with 0x1581 straight away, so it advertised
100BASE-TX half and full both. That is what asking for both is meant to do,
and forcing half is something people do to match a partner that cannot
negotiate, so the link comes up looking fine and misbehaves under load.

Bit 7 is 100BASE-TX half and bit 8 is full, which makes 0x1481, 0x1501 and
0x1581 half, full and both, the same shape as 0x1421, 0x1441 and 0x1461 in
the 10M branch above.

Both branches now choose the advertisement into a variable and write it once,
and they compare against the PHY_DUPLEX_* names instead of 0, 1 and whatever
is left. Same values reach the PHY; BANK2 loses 92 bytes.
2026-09-08 23:49:11 +02:00
d00f 412098d0ed phy: advertise half duplex only when half duplex was asked for
The 100M branch of phy_set_speed() is missing an else. A port set to 100M
half writes 0x1481, falls into the next if, finds duplex is not 1 and takes
its else, and immediately overwrites the register with 0x1581. The port
ends up advertising 100BASE-TX half and full, which is what "both" is
supposed to mean.

The 10M branch directly above has the chain the 100M one meant to have, and
the values line up with it: bit 7 is 100BASE-TX half, bit 8 is full, so
0x1481, 0x1501 and 0x1581 are half, full and both, exactly as 0x1421,
0x1441 and 0x1461 are for 10M.

Forcing half duplex is something people do to match a link partner that
cannot negotiate, so quietly advertising full as well is the wrong way for
this to fail.

Not tried on hardware: every port on the switch here has a link up, and
setting the speed restarts negotiation.
2026-09-08 23:49:11 +02:00
René van Dorst 5ecce86ef1 Merge pull request #422 from DrDoof/fix/no-idle-in-isr
Do not enter Idle mode from the external interrupt 2 handler
2026-09-08 21:47:02 +00:00
d00f 247b230212 Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1, putting the core to sleep from inside an
interrupt handler.

The DW8051 leaves idle when any enabled interrupt is activated, so the core
does not stay down for long. It naps until the next enabled interrupt request
turns up, with the 200 Hz system tick putting a ceiling of 5 ms on that, and
then carries on.

The sleep buys nothing either way, because the instruction after it is the
return. While it lasts the handler holds the priority latch, so everything at
its level or below waits, and that includes the tick, the console and the NIC
receive interrupt.

The handler still clears its flag, which is what it has to do. The bit numbers
beside the two masks were wrong: 0xef clears bit 4 and 0xdf clears bit 5,
not 7 and 6.
2026-09-08 23:35:27 +02:00
d00f c012dda029 Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1, putting the core to sleep from inside an
interrupt handler.

The DW8051 leaves idle when any enabled interrupt is activated, so the core
does not stay down for long. It naps until the next enabled interrupt request
turns up, with the 200 Hz system tick putting a ceiling of 5 ms on that, and
then carries on.

The sleep buys nothing either way, because the instruction after it is the
return. While it lasts the handler holds the priority latch, so everything at
its level or below waits, and that includes the tick, the console and the NIC
receive interrupt.

The handler still clears its flag, which is what it has to do. The two wrong
comments next to the masks go as well: 0xef clears bit 4 and 0xdf clears bit 5,
not 7 and 6, and the masks already say that without help.
2026-09-08 23:01:28 +02:00
d00f eab8c901cf Fix the EXIF bit numbers in the ext2 and ext3 handler comments
0xef clears bit 4 (IE2) and 0xdf clears bit 5 (IE3). The comments
said 7 and 6.
2026-09-08 22:43:06 +02:00
d00f 21a9fdf45a Do not enter Idle mode from the external interrupt 2 handler
isr_ext2() ends with PCON |= 1. On the DW8051 any enabled interrupt request
ends idle, so the handler sleeps until the next request arrives, the system
tick at the latest, and only then returns. Everything at the same priority
waits with it, and the instruction after the idle is RETI, so the sleep buys
nothing.

The handler still clears its flag, which is what it has to do. The other four
uses of PCON |= 1 are in task context and are left alone.
2026-09-08 22:43:05 +02:00
René van Dorst 1ed12d1101 Merge pull request #417 from DrDoof/fix/dhcp-missing-t1
dhcp: renew at half the lease when the server omits T1
2026-09-08 19:50:39 +00:00
d00f eab17f8a3f dhcp: renew at half the lease when the server omits T1
Option 58 is optional, and dhcp_state.renewal is only ever written by
parse_opts(), so a server that leaves it out keeps it at zero. The ACK
handler then sets dhcp_timer to zero, and the callback treats a zero timer
in LEASING as time to renew, so it sends a REQUEST on the next pass.

That REQUEST does set a 30 second timeout, but the ACK that answers it
comes back through the same path and zeroes the timer again, so the two
sides trade REQUEST and ACK as fast as the round trip allows, with the
console printing the whole lease each time round.

RFC 2131 has the client renew at half the lease when the server does not
say, so use that, and fall back to a fixed value if there is no lease
either. Both only fill in a value that is missing: a server that asks for a
short renewal still gets exactly what it asked for, which a blanket lower
bound would have overridden.

Not reproduced here, that needs a server configured without the option.
2026-09-07 23:10:29 +02:00
d00f 4bcbada764 dhcp: do not leave the old socket behind when starting again
dhcp_start() never looked at whether DHCP was already running, and "ip dhcp"
calls it without stopping first, so a second start took a fresh slot from
uip_udp_conns and left the previous one bound.

The leak is the smaller half of it. dhcp_callback() decides whether a call
is for it by comparing the local port, not the connection, and every
leftover socket still holds port 68, so each one runs the state machine on
every poll. The timers live in one shared dhcp_state, so ticks count down
once per socket: two starts and the retransmit and renewal timing runs at
twice the intended rate.

Stop the old one first. dhcp_stop() already does exactly that, and the
state test keeps it away from a connection that was never created.
2026-09-07 22:44:51 +02:00
d00f 9fae0f9249 httpd: do not initialise tstate through uip_conn
uip_init() does not set uip_conn, and httpd_init() runs right after it, so
the assignment went through a pointer nothing had assigned yet.

It has stayed invisible because the startup code does clear xdata: uip_conn
is NULL rather than garbage, appstate sits 28 bytes into struct uip_conn, and
the byte therefore lands at xdata 0x1C. With the current map that is inside
the command line buffer, at an offset nothing reads while the line is empty,
and cmd_editor_init() runs later anyway.

Nothing is broken today, but it is a write to an address that has nothing to
do with the state being set, and it moves the moment the struct grows or the
map shifts. Address the connection directly.
2026-09-07 22:23:12 +02:00
René van Dorst 6488433584 Merge pull request #401 from DrDoof/fix/ports-sfp-row
ports: read the right link state in the port table
2026-09-06 20:18:24 +00:00
René van Dorst 59088b4787 Merge pull request #407 from DrDoof/fix/arp-aging
Age the ARP cache by calling uip_arp_timer()
2026-09-06 20:10:54 +00:00
d00f 30547cc453 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].
2026-09-06 21:13:23 +02:00
René van Dorst 58aead38cf Merge pull request #399 from DrDoof/fix/lag-cli-and-gui
lag: fix the web page's group numbering, and the command's syntax line
2026-09-06 18:56:12 +00:00
René van Dorst 9c3433cf6b Merge pull request #406 from DrDoof/fix/dhcp-xid-check
dhcp: fix transaction-id check so it actually compares
2026-09-06 18:37:42 +00:00
d00f 97d44fbf4b 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.
2026-09-06 20:30:40 +02:00
René van Dorst 00ee3d1325 Merge pull request #408 from DrDoof/fix/isr-no-console-write
Do not call write_char() from the external interrupt handlers
2026-09-06 18:11:30 +00:00
d00f 078a446cee 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.
2026-09-06 18:41:27 +02:00
d00f efdf93a97a 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.
2026-09-06 18:07:16 +02:00
d00f 025e1a186b 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().
2026-09-06 18:05:29 +02:00
d00f dc5cbcda16 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.
2026-09-06 18:03:18 +02:00
René van Dorst 12c15603ed Merge pull request #395 from vDorst/fix_l2_cli_static_entry
Fix l2 cli static entry and use wait_table_ready() in more places
2026-09-06 14:17:41 +00:00
René van Dorst 3a7da77a99 Move wait_table_ready() up and replace all the same code for this function. 2026-09-06 10:06:02 +02:00
René van Dorst 13509d56e5 Fix l2 table, same as #355 but for CLI. 2026-09-06 10:06:02 +02:00
logicog 54b5e90976 Merge pull request #400 from DrDoof/feat/l2-show-lag
l2: name the group a learned address sits behind
2026-09-06 09:09:41 +02:00
d00f 38b8e38c04 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.
2026-09-06 00:09:22 +02:00
d00f 8b7306f995 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.
2026-09-05 17:29:13 +02:00
d00f ecac6d1934 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.
2026-09-05 13:28:15 +02:00
d00f f2966ad248 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.
2026-09-05 13:28:14 +02:00
René van Dorst 8a7b146892 Merge pull request #396 from feelfree69/fix/syslog_port
Fix printing of syslog portNumber in WebUI
2026-09-04 11:35:33 +02:00
feelfree69 0998a9dfaa Fix printing of syslog portNumber in WebUI 2026-09-04 11:19:46 +02:00
René van Dorst aded07730e Merge pull request #365 from janh/zx-swtgw215as
Add PCB V2.1 to ZX-SWTGW215AS documentation and use MAC address on flash
2026-09-04 09:07:31 +02:00
René van Dorst 58d059384d Merge branch 'main' into zx-swtgw215as 2026-09-04 09:07:08 +02:00
d00f a7692022f1 httpd: reap idle TCP connections (#390)
* uip: time out an ESTABLISHED connection whose peer went silent

uIP has no keepalive and no idle timeout for an ESTABLISHED connection, so
a peer that vanishes without FIN or RST holds it open forever. A crashed
client does it, and so does a SYN caught while the switch is still
booting. With UIP_CONF_MAX_CONNECTIONS = 1 that wedges the single slot:
the web UI stops answering, port 80 no longer even SYN-ACKs, and only a
power cycle recovers it. Every other TCP state already has an exit path
(UIP_MAXRTX, UIP_TIME_WAIT_TIMEOUT); idle ESTABLISHED was the one hole.

Close it inside uIP rather than in httpd, so any future TCP service is
covered too. The retransmission timer is unused while nothing is in
flight, so the idle count lives in it: a 1 Hz strobe derived from the
uip_periodic() rate ages it, any arriving segment clears it, and past the
limit the connection is aborted through the same path as UIP_MAXRTX. Two
new options in uipopt.h, default off; this project enables 30 s.

httpd also joins uip_timedout() to its cleanup: it only handled closed
and aborted, so both this timeout and the existing UIP_MAXRTX one would
have left the transfer state stuck on a dead connection.

* uip: keep the tuning values out of the prose

The comment named the timeout and the tick rate, which go stale the
moment either constant is edited. It now says why an idle connection
is aged out and leaves the numbers to the defines under it.
2026-09-04 07:11:49 +02:00
René van Dorst e26693826e Merge pull request #393 from bloqaudio/fix/l2-delete-own-mac
httpd: refuse to delete the switch's own L2 entry
2026-09-03 21:09:16 +00:00
René van Dorst be6bd2a5b3 Merge pull request #380 from vDorst/fix_vlan_docs
Fix VLAN command documenation. 'u'-suffix is not accepted anymore.
2026-09-03 21:03:23 +00:00
René van Dorst 6c43091b7a Merge pull request #329 from DrDoof/feat/cmd-output
httpd: answer /cmd with what the command printed, and show it in the console tab
2026-09-03 20:33:26 +00:00
d00f b57b7d1a3d httpd: answer /cmd with the shared text header
The /cmd path carried its own copy of the plain-text response header,
which was the same string page_impl.c already keeps in
HTTP_RESPONCE_TXT, minus one difference: the shared one never said
Connection: close.

This httpd closes after every response, so a reply that does not
advertise it invites the browser to reuse the socket and lose the next
request. HTTP_RESPONCE_JSON has always said so; the text one was the
odd one out, and send_config() and send_cmd_log() were answering
without it.

The copy is gone and the shared string carries the header. The check
for a command that printed nothing now compares against the length
strtox() returned instead of sizeof() on the macro.
2026-09-03 22:18:45 +02:00
René van Dorst fc86ea8be5 Merge pull request #333 from DrDoof/feat/syslog-hostname-port
syslog: send a hostname, allow a port other than 514, and stop the save from dropping the address
2026-09-03 20:02:41 +00:00
René van Dorst 91780110f2 Merge pull request #389 from DrDoof/fix/login-page-i18n
httpd: let the login page fetch its translations
2026-09-03 20:00:17 +00:00
bloqaudio f0e9953081 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.
2026-09-03 14:33:07 -05:00
bloqaudio 1c9bf10b87 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
2026-09-03 14:33:07 -05:00
d00f 2e2529be27 syslog: print the server address with print_ip()
The status line built the dotted quad by hand, which is what
print_ip() from cmd_parser.c already does. Reusing it drops seven
calls to one and takes 48 bytes off BANK2.

The prototype is not __banked because cmd_parser.c and syslog.c are
both in BANK2; a plain call is what the compiler needs here. Should
either file move to another bank, this has to become __banked with it.
2026-09-03 21:27:02 +02:00
René van Dorst f6348765b4 Merge pull request #343 from bloqaudio/feat/mgmt-static-l2
port: pin the management MAC to the CPU port with a static L2 entry
2026-09-03 18:25:11 +00:00