36 Commits
Author SHA1 Message Date
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
Pietro AmeruosoandPietro Ameruoso 6411c5157c Add Hi-K0402WS V2 hardware support (#391)
Signed-off-by: Pietro Ameruoso <p.ameruso@live.it>
Co-authored-by: Pietro Ameruoso <p.ameruso@live.it>
2026-09-03 19:47:02 +02:00
eeeple d06dc3e5f8 Added Siscolink SL-G0402F support (#392)
* Added Siscolink SL-G0402F picture

* Update PCB-K0402WS-V3.0.md

* Update supported_devices.md

* Rotated picture
2026-09-03 19:44:17 +02:00
bloqaudio dd7f9f7c7b 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.
2026-09-03 10:33:53 -05:00
bloqaudio 3b8dba6e87 port: pin the management MAC to the CPU port with a static L2 entry
The CPU's own MAC exists in the L2 table only as a learned entry,
created whenever the CPU transmits, and it ages out like any other
dynamic entry. Unknown unicast is not flooded to the CPU port, so on an
idle switch (no web session, no management traffic) the entry expires
after the aging time and frames for the management IP no longer reach
the CPU: pings die until the next CPU transmission (an ARP reply)
re-teaches the entry, then die again. Real management sessions keep the
entry alive continuously, which is why this is only visible on idle
switches.

Install a static entry for the management MAC on the CPU port after the
startup config has been replayed, so it lands in the final management
VLAN, and move it when the management VLAN is changed at runtime.

Entry mechanics, established on hardware: a table write with read-method
0 hashes the key in TBL_DATA_IN and places the entry itself (a search
returns index 0 on a miss, there is no software-visible insertion slot).
The age field in the third data word must be non-zero for the entry to
be visible to lookup at all, and the static flag (bit 0 of byte 1 of
that word) stops the aging engine from decrementing it: with the flag a
full-age entry survives untouched where an identical unflagged entry
ages to invisible within the aging period. With no management VLAN
configured the entry lives in VLAN 1.
2026-09-02 21:35:51 -05:00
d00f a259587bf9 system: do not let a failed console request look like an empty answer
fetch() does not throw on 4xx, so a request that came back 401 after the
session had timed out reached the same line as a successful one: the
window showed the prompt and nothing under it, and the input was cleared
as if the command had run. The session lasts 200 seconds, so leaving the
tab open is enough to hit it.

A 401 now goes to the login page instead. The transcript also gets a
trailing newline when the answer lacks one, so the next prompt does not
run into the last line of output, and it is capped so a long session
does not keep growing the node.
2026-09-03 01:43:30 +02:00
d00f f23d840cee httpd: make the /cmd answer usable when a command fails or floods
Three ways the answer could mislead the caller, all in the paths that
matter most when something is wrong.

The capture sat in write_char(), so anything printed through
print_string_no_syslog() escaped it. That is exactly the replies of the
syslog commands, which deliberately avoid the syslog path: "syslog on"
answered with the bare address and no sentence around it, "syslog off"
answered with nothing at all. Moving the capture one level down, into
write_char_no_syslog(), catches both paths and costs nothing, since
write_char() reaches it anyway.

On a parse error send_bad_request() rewrote the buffer from the start,
throwing away the very text that explains the error and replacing it
with the 400 HTML page. Keeping the captured text and restating only the
status keeps the parser's own message; "400 NO" is as long as "200 OK",
so the header does not have to be rebuilt.

An overlong listing was cut at the buffer end with nothing to show for
it, so a clipped answer read as a short one. It now ends in a marker,
and the reserved room is accounted for in the guard.

While here: a command that configures something prints nothing, and an
empty body reads the same as "nothing ran", so a successful silent
command now answers "OK".

No internal RAM and no xdata; 174 bytes of BANK1, which has 7356 left.
2026-09-03 01:43:28 +02:00
d00f f46ef3a43c system: show what the console command printed
The console tab posts to /cmd and throws the response away, so the page stayed
silent whether the command worked, was refused or was just misspelt. Now that
/cmd answers with the command's output, the page keeps a running transcript:
each command echoed after a prompt, then whatever it printed.

The box is a pre element filled through textContent, so nothing the device
prints is taken for markup. It scrolls to the newest line and the input clears
after a send.
2026-09-03 01:43:09 +02:00
d00f 81af3a29a2 httpd: return what a command printed over /cmd
POST /cmd runs the command and answers with an empty 200, so a caller cannot
tell "done" from "nothing happened", and whatever the command prints only
reaches the serial console. Reading a register over the network needs a second
channel just to see the answer.

write_char() is the one funnel every console helper goes through, and it already
tees into the syslog buffer. A third sink, live only while the httpd runs a
command that arrived over the network, puts the same bytes into the response
behind the header. Serial and syslog keep getting their copy.

The capture stops at the end of the response buffer, so a long listing is
truncated rather than overrunning it.

BANK1 grows 39 bytes and the common area 52. One byte of xdata, BANK2 unchanged.
2026-09-03 01:43:07 +02:00
d00f 5a55524115 httpd: let the login page fetch its translations
login.html asks for i18n.js, but the file is not on the short list a
client may have before it authenticates, so the request is answered with
a redirect back to the login page and the strings stay as the English
that is baked into the markup.

The file carries only the interface text, the same text the page already
ships untranslated, so serving it before login gives nothing away.
2026-09-03 01:12:56 +02:00
René van Dorst 6b39bcc448 JS: vlan don't accept u-suffic anymore.
Als update CHANGELOG about CPU_PORT
2026-09-02 19:44:41 +02:00
d00f 40ce742d04 syslog: take the review notes on the state flag and the header length
The enabled flag is a flag, so say bool. The header length no longer needs
to live in xdata: it was put there when a local would not link, and there
is room for it here.

The overview names the row after the JSON key, so the syslog row said
"syslog_server_ip" while carrying only an address. It now says
"syslog_server" and carries the port with it.
2026-09-01 23:08:42 +02:00
René van Dorst 373a21f4a1 Add CHANGELOG.md and vlan command breaking change. 2026-09-01 19:05:21 +02:00
René van Dorst 73d9a8ccb1 Fix VLAN command documentation. 'u'-suffix is not accepted anymore. 2026-08-31 23:04:57 +02:00
d00f e50d3c6950 syslog: parse the port with atoi_short()
The hand rolled loop was here because atoi_short() reported "no digits"
but not overflow, so 65540 would have wrapped to 4 and opened a
connection to a port nobody asked for. #326 gave it the same bound this
loop was carrying, so the reason is gone.

Zero still covers all three ways to be wrong, except that "no digits"
and overflow now come back as a zero return and port zero as a zero
value, so both are tested.

Three xdata scratch variables go with the loop.
2026-08-30 22:38:31 +02:00
d00f 4db1ccbbd9 syslog: fold the port parser's split conditions back together
The loop tested the digit range and the overflow bound with separate ifs,
on the strength of a comment saying the joined form makes sdcc park a
boolean in the bit area, and that an image carrying LACP and STP as well
is one bit short of needing a third byte of it. Measured on this branch
that is not what happens: the joined form links, the bit segment is
unchanged at fourteen bits overall and nine in cmd_parser, and BANK2
comes out ten bytes smaller.

The claim was about an image this branch does not build, and no branch
currently carries LACP, STP and this parser at once, so there is nothing
left to check it against. A justification nobody can verify, for a shape
nobody would otherwise write, is worse than no comment at all.

Behaviour is unchanged. The two forms agree on every input from 0 to
99999 and on the edges: empty, non digits, trailing junk, leading zeros,
65535 and 65536.

The note about atoi_short() stays, because that one still reports "no
digits" without reporting overflow. #326 replaces it with a version that
does both, and this should move onto it once that lands.
2026-08-30 22:38:31 +02:00
d00f ccafa01c74 system: stop "syslog on" from wiping the server address
Saving the configuration drops the syslog server address whenever the log
of executed commands also contains "syslog on", which it almost always
does, since you have to enable the thing after pointing it somewhere.

parseConf() deduplicates by taking the matched text of a conf_overwrite
entry and removing every stored line that starts with it. For "syslog on"
the entry that matches is the catch-all ^syslog\b, so the matched text is
just "syslog" and the filter removes anything beginning with "syslog ",
including the "syslog ip ..." line saved a moment earlier. The address then
falls back to 0.0.0.0 on the next boot.

It fails quietly: nothing is reported, the running switch keeps logging
until it is restarted, and the loss looks like the firmware forgetting a
setting rather than the page dropping it.

Narrow the entry to the commands it is meant to collapse, so the address and
the port keep their own entries and are no longer collateral.

That alone would stop "syslog on" and "syslog off" from replacing each other,
because the list drops lines starting with the text it matched and "syslog off"
does not start with "syslog on". A separate list names the stem of such a pair
and collapses the two, which is what the catch-all used to do by accident.
2026-08-30 22:38:31 +02:00
d00f cb43b3cbd6 syslog: make the destination port configurable
The port was frozen at 514 in the call that opens the connection, so a
collector listening anywhere else was simply out of reach, and plenty do,
because 514 is privileged and containerised collectors usually get a high
port instead. Working around it meant putting a proxy in front, which then
rewrites the source address and hides which device actually sent the line.

Add `syslog port <n>`. The default stays 514, so nothing changes for an
existing configuration. Setting it tears the connection down and builds a
new one, the same dance the address already does, because uip_udp_new()
freezes the port into the connection.

The value is parsed here rather than through atoi_short(), which reports
"no digits" but not overflow: 65540 would quietly wrap to 4 and we would
open a connection to a port nobody asked for. Checking before each multiply
keeps it in 16-bit arithmetic. Zero then covers all three ways to be wrong -
no digits, past 65535, or an explicit "0", which is not a port either.

Whitelist the new command in config.js as well, next to `syslog ip`, or the
line is dropped without a word when the configuration is saved and the
setting quietly fails to survive a reboot. The dedup entry has to precede
the general `^syslog\b` one, otherwise it would deduplicate against
`syslog on`.

itoa_short() is new: itoa() only handles a byte and print_short() prints
hex, so there was no way to show a port as a decimal number. Kept separate
from itoa() rather than widening it, because every existing caller passes a
byte and would start paying for 16-bit divisions it does not need.

The scratch variables for the parser live in xdata. Locals there would take
internal RAM, and this image has none left: data ends at 0x77, the overlay
occupies 0x78-0x7f and the stack starts at 0x80. Measured: no module's DSEG
grew.
2026-08-30 22:38:31 +02:00
d00f 9d4e531306 syslog: put our hostname where the receiver expects it
RFC 3164 has a hostname between the priority and the message text, and we
were sending nothing there. A receiver still has to fill that field, so it
takes the first word of the text instead, so every line arrives attributed to
"STP:", or "REGGET:", or whatever the log happens to start with. The sender
is then impossible to select on: the name changes with the content, and
anything in front of us on the path replaces the source address with its
own.

Observed on a collector behind a UDP proxy: the same switch showed up as two
different hosts depending on which subsystem had logged last.

Send the configured hostname. Skipped when it is empty, so we never emit a
lone separator.

The header length lives in xdata rather than on the stack. syslog_callback()
is __banked, so a local there cannot be overlaid, and while the variable
itself is only two bytes it costs more than that: it takes a register, which
pushes log_start out of one and into internal RAM. Those two bytes are
enough to stop an image carrying LACP and STP from linking at all.
2026-08-30 22:38:30 +02:00
Jan Hoffmann f00dbab0c4 Use MAC address from flash for ZX-SWTGW215AS 2026-08-27 16:53:29 +02:00
Jan Hoffmann bc46646398 Document PCB version 2.1 for ZX-SWTGW215AS
This PCB appears only slightly different to the already supported and
documented PCB-SWTG115AS-V2.0. It works without any changes to the
machine definition.

While at it, also fix a typo (8272 instead of 8372), and include the
existing unused pictures on the device page.
2026-08-27 16:53:29 +02:00
Jan Hoffmann 5fa6c36b73 Restructure headings in ZX-SWTGW215AS documentation
Use a proper hierarchical heading structure.
2026-08-27 16:53:29 +02:00
36 changed files with 533 additions and 59 deletions

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
# Change Log
## [0.x] - 2026-xx-XX
## Added
## Changed
## Fixed
## Breaking changes
- Config
- VLAN don't accept port `u`-suffix anymore.
So `vlan 1 4u` is not valid anymore.
Replace it with `vlan 1 4`.
- Commands
- port zero/`0` is treated as the `CPU_PORT`. #326
- Many commands don't accept the CPU_PORT anymore. See #334.
When the CPU_PORT is needed, the command/service will add the CPU_PORT automaticly.
Only `isolate` accept the CPU_PORT as destination port.
## [v0.1_aplha] - 2025-11-03
First release.
+34 -3
View File
@@ -48,6 +48,7 @@ __xdata uint16_t vlan_ptr;
__xdata char port_names[9][PORT_NAME_SIZE];
extern __xdata uint16_t management_vlan;
extern __xdata struct uip_eth_addr uip_ethaddr;
extern __xdata uint8_t sfp_speed[2];
extern __xdata uint8_t sfp_pins_last;
extern __xdata uint8_t sfp_options[2];
@@ -66,6 +67,10 @@ __xdata char save_cmd;
__xdata uint8_t ip[4];
/* Scratch for parse_syslog(), in xdata: a local here would take internal RAM
* the linker has none of. */
__xdata uint8_t syslog_port_was_on;
// These variables combined create a Fixed-capacity vector/bounded buffer.
// `N_WORDS`: The total number of command arguments that can be tracked.
// `cmd_words_len` stores the number of arguments found inside `cmd_buffer`
@@ -476,7 +481,9 @@ void parse_vlan(void)
vlan_settings.vlan = atoi_results_short;
if (cmd_compare(2, "mgmt")) {
port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, true);
management_vlan = vlan_settings.vlan;
port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, false);
if (!vlan_settings.vlan)
print_string("Management VLAN disabled\n");
else
@@ -542,7 +549,7 @@ void parse_vlan(void)
}
return;
err:
print_string("Error: vlan (<vlan-id>|show) [port][t/u]...\n");
print_string("Error: vlan (<vlan-id>|show) [port][t]...\n");
}
@@ -1412,6 +1419,7 @@ void parse_syslog(void)
if (syslog_state.enabled) {
print_string("enabled, sending to ");
print_ip(syslog_state.server_ip);
write_char(':'); itoa_short(syslog_state.server_port);
write_char('\n');
} else {
print_string("disabled\n");
@@ -1440,11 +1448,34 @@ void parse_syslog(void)
} else {
print_string("Invalid IP address\n");
}
} else if (cmd_compare(1, "port")) {
if (cmd_words_len < 3) { // no additional argument -> print current port
print_string("Current syslog port: ");
itoa_short(syslog_state.server_port); write_char('\n');
return;
}
/* atoi_short() returns zero for no digits and for a value past
* 65535; port zero is not a port either, so both tests are needed. */
if (!atoi_short(cmd_words_b[2]) || !atoi_results_short) {
print_string("Invalid port\n");
return;
}
/* The port is frozen into the connection by uip_udp_new(), so
* changing it has to tear the connection down and build a new one -
* same dance as setting the address above. */
syslog_port_was_on = syslog_state.enabled;
if (syslog_port_was_on)
syslog_stop();
print_string("Setting new syslog port.\n");
syslog_state.server_port = atoi_results_short;
if (syslog_port_was_on)
syslog_start();
}
else
{
print_string("Error: syslog [on|off|ip [ip-address]]\n");
print_string(" on/off enables or disables syslog, ip sets the syslog server IP address\n");
print_string("Error: syslog [on|off|ip [ip-address]|port [number]]\n");
print_string(" on/off enables or disables syslog, ip sets the syslog server IP address,\n");
print_string(" port sets the destination UDP port (default 514)\n");
}
}
+1
View File
@@ -13,6 +13,7 @@ void cmd_tokenize(void) __banked;
void cmd_parser(void) __banked;
void execute_config(void) __banked;
void execute_commands(__xdata uint8_t *p) __banked;
void print_ip(__xdata uint8_t *ptr);
void print_sw_version(void) __banked;
void clear_command_history(void) __banked;
+97
View File
@@ -0,0 +1,97 @@
Hisource Hi-K0402WS V2
RTL8372-based 4×2.5G + 2×SFP+ unmanaged switch.
Using SPI clamp in-board is the only method for initial installation.
### Label specifications
- **Manufacturer**: HISOURCE
- **Model**: Hi-K0402WS V2
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1G / 2.5G / 10G
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
- Both SFP+ ports supporting 1G, 2.5G and 10G modules
- LEDs: amber (2.5G) and green (1G/100M/10M) per copper port; combined link/act on SFP ports
### PCB overview
**Board markings**
- PCB-K0402WS-V2.0
- DIP-K0402WS-V2.0
**Key components**
- U1: SPI NOR flash, 2 MiB
- S1: unpopulated slide switch footprint (three through-holes used as serial console)
Front panel
<img src="photos/PCB-K0402WS-V2.0/chassis-front.jpg" width="400" />
Bottom label
<img src="photos/PCB-K0402WS-V2.0/chassis-bottom.jpg" width="400" />
Top side (PCB)
<img src="photos/PCB-K0402WS-V2.0/PCB-top.jpg" width="300" />
### Port layout
| Front panel position | Logical port | Physical port | Type |
|----------------------|--------------|---------------|---------|
| SFP left | 8 | 5 | SFP+ |
| RJ45 1 | 4 | 1 | Copper |
| RJ45 2 | 5 | 2 | Copper |
| RJ45 3 | 6 | 3 | Copper |
| RJ45 4 | 7 | 4 | Copper |
| SFP right | 3 | 6 | SFP+ |
### Serial console
The PCB has three unpopulated through-holes intended for a slide switch, directly connected to UART0.
Numbered from the left (SFP port side), the pinout is:
| Position (left→right) | Signal | GPIO |
|-----------------------|--------|--------------------------|
| 1 (leftmost) | RX | GPIO32\_UART0\_RX (32) |
| 2 (middle) | GND | GND |
| 3 (rightmost) | TX | GPIO31\_UART0\_TX (31) |
- **Settings**: 115200 baud / 8N1 / 3.3V TTL
- Connect a USB-TTL adapter: adapter TX → pin 1, GND → pin 2, adapter RX → pin 3
### LED configuration
Copper ports use LED SET0, SFP ports use LED SET1.
| SET | LED0 | LED2 |
|------|--------------------------------------------------|---------------------------------------------------|
| SET0 | Amber — lights on 2.5G link | Green — lights on 1G / 100M / 10M link |
| SET1 | All speeds — lights on any link with activity | — |
LED pad to physical port mapping:
| GPIO pads | Port |
|-----------|-------------------------|
| GPIO8–11 | Physical port 5 (left SFP) |
| GPIO12–14 | Physical port 1 (RJ45 1) |
| GPIO15–17 | Physical port 2 (RJ45 2) |
| GPIO18–20 | Physical port 3 (RJ45 3) |
| GPIO21–23 | Physical port 4 (RJ45 4) |
| GPIO24–27 | Physical port 6 (right SFP) |
### SFP GPIO assignments
| SFP | pin\_detect (ModAbs) | pin\_los | SerDes | I2C SDA | I2C SCL |
|------------------|-----------------------------|------------------------|--------|----------------------|--------------------------|
| Left (logical 8) | GPIO30\_ACL\_BIT3\_EN | GPIO37 | SDS1 | GPIO39\_I2C\_SDA4 | GPIO40\_I2C\_SCL3\_MDC1 |
| Right (logical 3)| GPIO50\_I2C\_SCL2\_UART1\_TX | GPIO51\_I2C\_SDA2\_UART1\_RX | SDS0 | GPIO41\_I2C\_SDA3\_MDIO1 | GPIO40\_I2C\_SCL3\_MDC1 |
GPIO assignments were verified by observing GPIO state changes during SFP module insertion/removal
and cross-checked against an original firmware register dump.
`pin_tx_disable` is GPIO\_NA on both ports (original firmware keeps all GPIOs as inputs).
+3
View File
@@ -19,6 +19,9 @@ of the device just above the big label, might be covered by a QC sticker.
<img src="photos/PCB-K0402WS-V3.0/Ztyuav_Z-QWYT0402_label.jpg" width="300" />
* Siscolink SL-G0402F
<img src="photos/PCB-K0402WS-V3.0/Siscolink SL-G0402F.jpg" width="300"/>
### Programming
Using SPI clamp in-board is the only method for initial installation.
+36 -17
View File
@@ -1,9 +1,11 @@
### ZX-SWTGW215AS
# ZX-SWTGW215AS
## Brands
|Brand|Type|Managed|PCB|Flash|Chip RTL|
|---|---|---|---|---|---|
| Lianguo | ZX-SWTGW215AS | Yes | PCB-SWTG115AS-V2.0 | FM25Q16A | 8272 |
| Lianguo | ZX-SWTGW215AS | Yes | PCB-SWTG115AS-V2.0 | FM25Q16A | 8372 |
| sold as Lianguo, actual device unbranded | ZX-SWTGW215AS | Yes | PCB-SWTG115AS-V2.1 | W25Q16JVSIQ (2MiB) | 8372 |
## RTLPlayground target
@@ -12,13 +14,23 @@ Use machine target `MACHINE_LIANGUO_ZX_SWTGW215AS` for this device.
Physical hardware verification: 5x RJ45 ports + 1x SFP port.
Port 5 RJ45 is interfaced through a RTL8221B IC.
## PCB
## Label (V2.0)
<img src="photos/ZX-SWTGW215AS/pcb_top.jpg" width="300" />
<img src="photos/ZX-SWTGW215AS-V2.0/label.jpg" width="300" />
# Connectors
## PCB (V2.0)
## Port overview
<img src="photos/ZX-SWTGW215AS-V2.0/pcb_top.jpg" width="300" />
<img src="photos/ZX-SWTGW215AS-V2.0/pcb_bottom.jpg" width="300" />
## PCB (V2.1)
<img src="photos/ZX-SWTGW215AS-V2.1/pcb-top.jpg" width="300" />
<img src="photos/ZX-SWTGW215AS-V2.1/pcb-bottom.jpg" width="300" />
## Connectors
### Port overview
```
┌──────────────────────────────────────────────────────────────────────────────────┐
@@ -36,7 +48,7 @@ Port 5 RJ45 is interfaced through a RTL8221B IC.
| RJ45 | 3, 4, 5, 6, 7 | 1-5 |
| SFP | 8 | 6 |
## J4
### J4
* Location: SFP connector `J4`.
* Connected to: 10GMAC number 8, SerDes 1.
@@ -48,10 +60,11 @@ Port 5 RJ45 is interfaced through a RTL8221B IC.
|5| MODDEF1 – SCL | GPIO40 | I2C SCL |
|6| MODDEF0 – PRESENT | GPIO30 | Detect |
|8| LOS | GPIO37 | RX Loss of Signal |
### Notes
#### Notes
* Not all signals were mapped mechanically, hence they've been left out of documentation.
## T3, Slave Interface
### T3, Slave Interface
This connector goes to U4 `I2C EEPROM` and U10 `SPI FLASH` (mappings identical to SWTG024AS).
@@ -65,11 +78,12 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
|4| VCC |
|5| 33R -> U10-P2 | SPI-DO/D1 |
|6| U10-P1 | SPI-CS |
### Notes
#### Notes
* 1 pin is square shaped.
## T5, Serial Console
### T5, Serial Console
|`T5` pin|GPIO|Signal|
|---|---|---|
@@ -77,10 +91,11 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
| 2 | GND | |
| 3 | GPIO32 | U0RXD (Input) |
| 4 | 3V3 | |
### Notes
#### Notes
* 1 pin is square shaped.
## T8
### T8
|`T8` pin|GPIO|Signal|
|---|---|---|
@@ -91,18 +106,19 @@ For detailed Slave Interface functionality and protocol information, see [T3 doc
| 5 | GPIO47 | |
| 6 | GPIO49 | |
### Notes
#### Notes
* 1 pin is square shaped.
* Mapping unverified but assumed the same as [LIANGUO SWTG024AS](SWTG024AS.md#t8).
# Reset Circuit
## Reset Circuit
| Function | GPIO |
|---|---|
| Reset button | GPIO54 |
### Notes
* Circuit is active-low
# GPIO
## GPIO
Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG024AS T3 section](SWTG024AS.md#t3-slave-interface) as well as T8 port from [LIANGUO SWTG024AS T8 section](SWTG024AS.md#t8). They should be treated as assumed identical for ZX-SWTGW215AS as it has not been 100% confirmed true at the moment.
@@ -141,7 +157,7 @@ Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG02
| 40000000 | GPIO30 | J4-6 | SFP DETECT | | GPIO62 | | |
| 80000000 | GPIO31 | T5-1 | U0TXD | | GPIO63 | | |
# LEDs
## LEDs
| NAME | GPIO | Port(s) | Function | Notes |
| ---- | ---- | ---- | ---- | ---- |
@@ -162,3 +178,6 @@ Note: T3/U4/U10-related signal annotations below are copied from [LIANGUO SWTG02
While [SWTG024AS.md](SWTG024AS.md) can be used as a general reference for hardware concepts and interface specifications, this device should not be assumed to be identical beside the difference implicitely highlighted below. Not all information has been validated for compatibility with the SWTG215AS. Consult the SWTG024AS documentation with caution and verify any critical details against this device's.
## Flashing
For devices with Winbond 25Q16JVSIQ, flashing is possible for example using flashprog with device "W25Q16.V". Note that you should not overwrite the whole flash to keep device-specific data (like the MAC address at 0x1fc000). Also, creating a backup of the entire falsh before is of course a very good idea.
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

+3 -1
View File
@@ -7,6 +7,7 @@ The following devices have been tested and are fully working:
| Davuaz | Da-K6501W | No | [PCB-K0501W-V2.0](devices/K0501W_V2_0.md) | | 5 + 1 |
| FOXNEO | FNS-1200P | No | [PCB-K0402W-U13-V2.0](devices/FNS-1200P.md) | 2M | 4 + 2 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Hisource | Hi-K0402WS | No | [PCB-K0402WS-V2.0](devices/PCB-K0402WS-V2.0.md) | 2M | 4 + 2 |
| Hisource | Hi-K0801WS | No | [PCB-KO801W-V2.0](devices/HI-K0801WS.md) | | 8 + 1 |
| hongyavision | LG-SG5T1 | No | [PCB-SWTG024AS-V2.0_16895](devices/SWTG024AS-V2.0.md) | 0.5M | 5 + 1 |
| Horaco | HC-SWTGW215AS | Yes | [SWTG024AS-A-V2.0.1_19650 5C 1SFP](devices/SWTG024AS-A-V2.0.1_5C_1SFP.md) | ? | 5 + 1 |
@@ -19,11 +20,12 @@ The following devices have been tested and are fully working:
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V2.2](devices/2M-PCB23-V2_2.md) | 2M | 8 + 1 |
| keepLINK | KP-9000-9XHML-X | Yes | [2M-PCB23-V3.1](devices/2M-PCB23-V3_1.md) | 2M | 8 + 1 |
| LIANGUO | SWTG024AS | No | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 0.5M | 4 + 2 |
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| Lianguo | ZX-SWTGW215AS | Yes | [PCB-SWTG115AS-V2.0 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 |
| Mokerlink| 2G040210GSM | Yes | [2M-PCB43-V1.1](devices/2M-PCB43-V1.1.md) | | 4 + 2 |
| Mokerlink| ZX-SWTGW218AS | Yes | [SWTG118AS-V2.0-16029](devices/SWTGW218AS.md) | 2M | 8 + 1 |
| Ruiying | RY-4GT-2SX | No | [FG-4GT-2SX_V2.0](devices/FG-4GT-2SX_V2.0.md) | 4M | 4 + 2 |
| Ruiying | RY-8GT-1SX | No | [FG-8GT-1SX](devices/FG-8GT-1SX.md) | 1M | 8 + 1 |
| SiscoLink | SL-G0402F | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 |
| Sodola | SL-SWTG124AS-D | Yes | [SWTG024AS-v2.0-17452](devices/SWTG024AS.md) | 2M | 4 + 2 |
| Steamemo | IG204-V1 | No | [PB-2131](devices/STEAMEMO_IG204_V1.md) | | 4 + 2 |
| TrendNet | TEG-S562 | No | [TEG-S563/EU H/W: V1.0R](devices/TEG-S562.md) | 2M | 4 + 2 |
+3 -3
View File
@@ -69,9 +69,9 @@ void vlan_delete(uint16_t vlan) __banked;
# VLAN configuration on the Serial Console
For testing the following commands are provided on the serial console:
```
vlan <VLAN-ID> p[t/u]...
create or set vlan with given ID and the list of ports as members, a t
behind a port defines the port as a tagged member, the u is optional
vlan <VLAN-ID> p[t]...
create or set vlan with given ID and the list of ports as members, a `t`
behind a port defines the port as a tagged member.
vlan <VLAN-ID> d
deletes the VLAN
+19 -2
View File
@@ -7,10 +7,11 @@ const conf_cmds = [
/^netmask\s+(\d{1,3}\.){3}\d{1,3}$/,
/^syslog\s+(on|off)$/,
/^syslog\s+ip\s+(\d{1,3}\.){3}\d{1,3}$/,
/^syslog\s+port\s+\d{1,5}$/,
/^passwd\s+\S+$/,
/^vlan\s+\d{1,4}\s+d$/,
/^vlan\s+\d{1,4}\s+mgmt$/,
/^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+\d{1,2}[tu]?)+$/,
/^vlan\s+\d{1,4}(\s+[a-zA-Z]\w*)?(\s+[1-9]t?)+$/,
/^pvid\s+\d{1,2}\s+\d{1,4}$/,
/^ingress(\s+\d{1,2}[tua])+$/,
/^ingress\s+[tua]$/,
@@ -36,12 +37,21 @@ const conf_cmds = [
/^bw\s+(in|out)\s+\d{1,2}\s+\S+$/,
/^hostname\s+.{1,23}$/,
];
/* Commands that come in an on/off pair replace each other, which the list
* below cannot express: it drops lines starting with the text it matched, and
* "syslog off" does not start with "syslog on". Naming the stem separately
* keeps the pair collapsed without widening the match to the whole family. */
const conf_toggle = [
/^(syslog)\s+(?:on|off)$/,
];
const conf_overwrite = [
/^ip\b/,
/^gw\b/,
/^netmask\b/,
/^syslog\s+ip\b/,
/^syslog\b/,
/^syslog\s+port\b/,
/^syslog\s+(on|off)$/,
/^passwd\b/,
/^vlan\s+\d{1,4}\s+mgmt$/,
/^vlan\s+\d{1,4}(?!\s+mgmt\b)/,
@@ -79,6 +89,13 @@ function parseConf(s){
for (const x of conf_cmds)
if (x.test(line)) { ignore = false; break; }
if (ignore) continue;
for (const x of conf_toggle) {
const t = line.match(x);
if (t) {
configuration = configuration.filter(item => item !== t[1] + " on" && item !== t[1] + " off");
break;
}
}
for (const x of conf_overwrite) {
if (x.test(line)) {
let m = line.match(x);
+13 -4
View File
@@ -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 '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
}
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 = '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
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 = '<button type="button" onclick="delL2(' + e.idx + ');">' + t('l2_delete') + '</button>';
td = tr.insertCell(); td.innerHTML = delL2Button(e);
}
}
for (let i = tbl.rows.length - 1; i > s.length; i--)
+2
View File
@@ -88,6 +88,8 @@
<input style="width:20%;" class="action" id="cmd_sub" onclick="cmdSub();" type="button" data-i18n="sys_send_cmd" value="Send Command"><br/>
<br/><br/>
<span data-i18n="sys_console_warn">Be careful when entering console commands, you can lock yourself out!</span><br/>
<br/>
<pre id="console_out" style="height:22em;overflow:auto;white-space:pre-wrap;border:1px solid;padding:0.5em;"></pre>
</div>
+16 -2
View File
@@ -35,14 +35,28 @@ async function ipSub() {
}
async function cmdSub() {
var cmd = document.getElementById('console_cmd').value;
const input = document.getElementById('console_cmd');
const out = document.getElementById('console_out');
const cmd = input.value;
try {
const response = await fetch('/cmd', {
method: 'POST',
body: cmd
});
console.log('Completed!', response);
if (response.status == 401) {
window.location.href = 'login.html';
return;
}
let text = await response.text();
if (text != "" && !text.endsWith("\n"))
text += "\n";
if (out.textContent.length > 20000)
out.textContent = out.textContent.slice(-16000);
out.textContent += "> " + cmd + "\n" + text;
out.scrollTop = out.scrollHeight;
input.value = "";
} catch(err) {
out.textContent += "> " + cmd + "\n" + err + "\n";
console.error(`Error: ${err}`);
}
}
+23 -4
View File
@@ -21,6 +21,7 @@
extern volatile __xdata uint8_t sfr_data[4];
extern volatile __xdata uint32_t ticks;
extern __xdata uint8_t cmd_capture; /* owned by rtlplayground.c, see write_char_no_syslog() */
extern __code uint8_t * __code hex;
extern __code struct f_data f_data[];
extern __code char * __code mime_strings[];
@@ -625,12 +626,29 @@ static void handle_firmware_fragment(__xdata uint8_t *p)
static void run_cmd_body(__xdata uint8_t *body)
{
uint16_t hdr_len = strtox(outbuf, HTTP_RESPONCE_TXT);
slen = hdr_len;
cmd_capture = 1;
execute_commands(body);
if (cmd_capture == 2)
slen += strtox(outbuf + slen, CMD_TRUNCATED);
cmd_capture = 0;
/* Commands that configure something print nothing at all. Saying
* so beats an empty body, which reads the same as "nothing ran".
* Only on success: a silent failure must not answer with "OK". */
if (err_status == ERR_OK && slen == hdr_len)
slen += strtox(outbuf + slen, "OK\n");
/* On a parse error keep what the parser printed, because that text
* is the explanation, and only restate the status. "400 NO" is as
* long as "200 OK", so the header does not have to be rebuilt. */
if (err_status != ERR_OK) {
send_bad_request();
return;
outbuf[9] = '4';
outbuf[10] = '0';
outbuf[11] = '0';
outbuf[13] = 'N';
outbuf[14] = 'O';
}
send_ok();
}
@@ -824,7 +842,7 @@ void httpd_appcall(void)
} else if (uip_closed()) {
dbg_string("Connection closed\n");
s->tstate = TSTATE_CLOSED;
} else if (uip_aborted()) {
} else if (uip_aborted() || uip_timedout()) {
dbg_string("Connection aborted\n");
uip_close();
s->tstate = TSTATE_CLOSED;
@@ -994,6 +1012,7 @@ void httpd_appcall(void)
if (!authenticated && !(f_data[entry].start == FDATA_START_login_html
|| f_data[entry].start == FDATA_START_port_svg
|| f_data[entry].start == FDATA_START_sfp_svg
|| f_data[entry].start == FDATA_START_i18n_js
|| f_data[entry].start == FDATA_START_style_css)) {
send_to_login();
goto do_send;
+23 -12
View File
@@ -48,7 +48,7 @@ extern __xdata char sfp_module_serial[2][17];
extern __xdata uint8_t sfp_options[2];
__code uint8_t * __code HTTP_RESPONCE_JSON = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: application/json\r\n\r\n";
__code uint8_t * __code HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n";
__code uint8_t * __code HTTP_RESPONCE_TXT = "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\n\r\n";
// Convert uint8_t to ascii HEX char push on html-buffer.
void charhex_to_html(char c)
@@ -223,11 +223,12 @@ void send_basic_info(void)
itoa_html(uip_netmask[0] >> 8); char_to_html('.');
itoa_html(uip_netmask[1]); char_to_html('.');
itoa_html(uip_netmask[1] >> 8);
slen += strtox(outbuf + slen, "\",\"syslog_server_ip\":\"");
slen += strtox(outbuf + slen, "\",\"syslog_server\":\"");
itoa_html(syslog_state.server_ip[0]); char_to_html('.');
itoa_html(syslog_state.server_ip[1]); char_to_html('.');
itoa_html(syslog_state.server_ip[2]); char_to_html('.');
itoa_html(syslog_state.server_ip[3]);
itoa_html(syslog_state.server_ip[3]); char_to_html(':');
itoa16_html(syslog_state.server_port);
slen += strtox(outbuf + slen, "\",\"mac_address\":\"");
byte_to_html(uip_ethaddr.addr[0]); char_to_html(':');
byte_to_html(uip_ethaddr.addr[1]); char_to_html(':');
@@ -432,26 +433,34 @@ 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 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 (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 {
reg_write_m(RTL837x_TBL_DATA_IN_A);
reg_read_m(RTL837x_L2_DATA_OUT_C);
@@ -460,15 +469,17 @@ 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');
}
}
char_to_html('}');
}
+2
View File
@@ -3,6 +3,8 @@
#include <stdbool.h>
extern __code uint8_t * __code HTTP_RESPONCE_TXT;
bool send_counters(uint8_t phys_port);
void send_status(void);
void send_vlan(uint16_t vlan);
+6 -1
View File
@@ -487,6 +487,7 @@ __code const struct machine machine = {
__code const struct machine machine = {
.machine_name = "Lianguo ZX-SWTGW215AS",
.isRTL8373 = 0,
.mac_flash_offset = 0x1FC000,
.min_port = 3,
.max_port = 8,
.n_sfp = 1,
@@ -801,9 +802,13 @@ __code const struct machine machine = {
},
};
#elif defined MACHINE_FNS1200P
#elif defined(MACHINE_PCB_K0402WS_V2) || defined(MACHINE_FNS1200P)
__code const struct machine machine = {
#if defined(MACHINE_PCB_K0402WS_V2)
.machine_name = "PCB-K0402WS-V2.0",
#else
.machine_name = "FNS-1200P",
#endif
.isRTL8373 = 0,
.min_port = 3,
.max_port = 8,
+1
View File
@@ -30,6 +30,7 @@
// #define MACHINE_SWTGW218AS
// #define MACHINE_PCB_SWTG018AS_V2_1_0
// #define MACHINE_PCB_K0402WS_V3
// #define MACHINE_PCB_K0402WS_V2
// #define MACHINE_K0501W_V2_0
// #define MACHINE_LIANGUO_ZX_SWTGW215AS
// #define MACHINE_ZX310S_4T2XH
+1 -1
View File
@@ -48,7 +48,7 @@ void machine_custom_init(void) __banked
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
}
#elif defined MACHINE_FNS1200P
#elif defined(MACHINE_PCB_K0402WS_V2) || defined(MACHINE_FNS1200P)
void machine_custom_init(void) __banked
{
reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6);
+5
View File
@@ -37,6 +37,10 @@ extern __xdata uint8_t sbuf[SBUF_SIZE];
// Size of the TCP Output buffer
#define TCP_OUTBUF_SIZE 2500
// Appended when a captured command wrote more than the output buffer holds,
// so a clipped listing is visibly clipped instead of silently short.
#define CMD_TRUNCATED "\n[output truncated]\n"
// Size of the port name, including the terminating null byte
#define PORT_NAME_SIZE 32
@@ -138,6 +142,7 @@ void print_long(uint32_t a);
void print_short(uint16_t a);
void print_byte(uint8_t a);
void itoa(uint8_t v);
void itoa_short(uint16_t v);
void print_sfr_data(void);
void print_phy_data(void);
void print_cmd_prompt(void);
+54
View File
@@ -911,3 +911,57 @@ bool port_ingress_vlan_filter_get(uint8_t port) __banked
return reg_bit_test(RTL837X_VLAN_PORT_IGR_FLTR, port);
}
// C1 bit 0: static (no aging); age at C3[4:2] must be non-zero or the
// entry is invisible to lookup
static void l2_mgmt_entry_fill(__xdata uint8_t *mac, __xdata uint16_t vlan)
{
REG_WRITE(RTL837x_TBL_DATA_IN_A, mac[2], mac[3], mac[4], mac[5]);
REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | ((CPU_PORT & 0x3) << 6) | (vlan >> 8),
vlan & 0xff, mac[0], mac[1]);
REG_WRITE(RTL837x_TBL_DATA_IN_C, 0x00, 0x01, 0x00, (7 << 2) | (CPU_PORT >> 2));
}
static void l2_mgmt_tbl_prepare(void)
{
reg_read_m(RTL837x_TBL_DATA_0);
sfr_data[2] &= 0x3f;
sfr_data[1] &= 0xf8;
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
{
wait_table_ready();
// vlan 0 = management VLAN disabled: untagged management resolves in
// VLAN 1; clients in other VLANs keep their learned entry and age as before
if (!vlan)
vlan = 1;
l2_mgmt_entry_fill(mac, vlan);
l2_mgmt_tbl_prepare();
if (remove_entry) {
// a search miss returns index 0, not a slot
REG_WRITE(RTL837X_TBL_CTRL, 0x00, 0x00, TBL_L2_UNICAST, TBL_EXECUTE);
wait_table_ready();
reg_read_m(RTL837x_TBL_DATA_0);
if (!(sfr_data[2] & 0x10))
return;
sfr_data[1] |= 0x04; // CLEAR-entry
reg_write_m(RTL837x_TBL_DATA_0);
__xdata uint16_t idx = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3];
REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx & 0xff, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE);
} else {
// 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();
}
+1
View File
@@ -81,5 +81,6 @@ bool port_ingress_vlan_filter_get(uint8_t port) __banked;
vlan_ingress_mode_t port_ingress_filter_get(__xdata uint8_t port) __banked;
void port_isolate(uint8_t port, __xdata uint16_t pmask) __banked;
uint16_t port_isolation_get(uint8_t port) __banked;
void port_l2_static_mgmt(__xdata uint8_t *mac, __xdata uint16_t vlan, __xdata bool remove_entry) __banked;
#endif
+45
View File
@@ -240,8 +240,25 @@ void isr_serial(void) __interrupt(4)
}
/* Set by the httpd while it runs a command that arrived over the network, so
* that everything the command prints lands in the response as well. */
__xdata uint8_t cmd_capture;
extern __xdata uint8_t outbuf[TCP_OUTBUF_SIZE];
extern __xdata uint16_t slen;
void write_char_no_syslog(char c)
{
/* Capturing sits here rather than in write_char() so that the messages
* printed through print_string_no_syslog() are captured too: those are
* the replies of the syslog commands, which must not generate a syslog
* packet but do belong in the answer to a command sent over HTTP. */
if (cmd_capture) {
if (slen < TCP_OUTBUF_SIZE - sizeof(CMD_TRUNCATED))
outbuf[slen++] = c;
else
cmd_capture = 2; /* out of room, httpd says so */
}
do {
} while (tx_buf_empty == 0);
if (c =='\n') {
@@ -282,6 +299,32 @@ void itoa(uint8_t v)
}
/* Same as itoa(), one decade wider: enough for a port number. Kept separate
* rather than widening itoa() itself, because every existing caller passes a
* byte and would start paying for 16-bit divisions it does not need. */
void itoa_short(uint16_t v)
{
uint8_t t = v / 10000;
uint8_t print_zeros = t;
if (print_zeros)
write_char('0' + t);
t = (v / 1000) % 10;
print_zeros |= t;
if (print_zeros)
write_char('0' + t);
t = (v / 100) % 10;
print_zeros |= t;
if (print_zeros)
write_char('0' + t);
t = (v / 10) % 10;
print_zeros |= t;
if (print_zeros)
write_char('0' + t);
write_char('0' + (v % 10));
}
void print_string(__code char *p)
{
while (*p)
@@ -2306,6 +2349,8 @@ void main(void)
early_boot_handle_button();
execute_config();
// After the config so the entry lands in the final management VLAN
port_l2_static_mgmt(uip_ethaddr.addr, management_vlan, false);
/* After the config: a name from it wins, otherwise derive one. */
set_hostname_default();
print_cmd_prompt();
+28 -8
View File
@@ -1,5 +1,6 @@
#include "machine.h"
#include "syslog.h"
#include "cmd_parser.h"
#include "uip/uip.h"
#include "rtl837x_common.h"
@@ -22,20 +23,21 @@ void syslog_init(void) __banked
state.readptr = 0;
state.line_available = 0;
state.server_ip[0] = 0; state.server_ip[1] = 0; state.server_ip[2] = 0; state.server_ip[3] = 0;// Default to 0.0.0.0
state.server_port = SYSLOG_PORT_DEFAULT;
}
void syslog_start(void) __banked
{
if (state.syslog_conn == 0) {
uip_ipaddr(server_ip, state.server_ip[0], state.server_ip[1], state.server_ip[2], state.server_ip[3]);
state.syslog_conn = uip_udp_new(&server_ip, HTONS(514));
state.syslog_conn = uip_udp_new(&server_ip, HTONS(state.server_port));
if (state.syslog_conn == 0) {
print_string_newline_no_syslog("Failed to create a new UDP client");
return;
}
print_string_newline_no_syslog("Started syslog to IP ");
itoa(state.server_ip[0]); write_char('.'); itoa(state.server_ip[1]); write_char('.');
itoa(state.server_ip[2]); write_char('.'); itoa(state.server_ip[3]); write_char('\n');
print_ip(state.server_ip);
write_char(':'); itoa_short(state.server_port); write_char('\n');
state.enabled = 1;
}
else {
@@ -57,6 +59,7 @@ void syslog_stop(void) __banked
void syslog_callback(uint16_t lport) __banked
{
uint16_t syslog_hdr;
if (lport != state.syslog_conn->lport)
return;
@@ -91,14 +94,31 @@ void syslog_callback(uint16_t lport) __banked
memcpyc(SYSLOG_P, "<14>", 4); // Syslog priority prefix
if (log_end < log_start) {
memcpy(SYSLOG_P + 4, logbuf + log_start, LOGBUF_SIZE - log_start);
memcpy(SYSLOG_P + 4 + LOGBUF_SIZE - log_start, logbuf, log_end);
/* RFC 3164 puts a hostname between the priority and the text, and
* we were leaving that slot empty. A receiver still has to fill
* the field, so it takes the first word of the message instead:
* every line arrives attributed to "STP:" or "REGGET:" or
* whatever the log happens to start with, and the sender cannot
* be selected on at all. Send our own name and the field means
* something. Skipped when the name is empty, so we never emit a
* lone separator. */
syslog_hdr = strlen_x(hostname);
if (syslog_hdr) {
memcpy(SYSLOG_P + 4, hostname, syslog_hdr);
SYSLOG_P[4 + syslog_hdr] = ' ';
syslog_hdr += 5;
} else {
memcpy(SYSLOG_P + 4, logbuf + log_start, log_end - log_start);
syslog_hdr = 4;
}
uip_udp_send(log_size+4);
if (log_end < log_start) {
memcpy(SYSLOG_P + syslog_hdr, logbuf + log_start, LOGBUF_SIZE - log_start);
memcpy(SYSLOG_P + syslog_hdr + LOGBUF_SIZE - log_start, logbuf, log_end);
} else {
memcpy(SYSLOG_P + syslog_hdr, logbuf + log_start, log_end - log_start);
}
uip_udp_send(log_size + syslog_hdr);
state.readptr = state.writeptr;
state.line_available = 0;
}
+5 -1
View File
@@ -1,16 +1,20 @@
#ifndef _SYSLOG_H_
#define _SYSLOG_H_
#include <stdbool.h>
#include <stdint.h>
#define LOGBUF_SIZE 512
#define SYSLOG_PORT_DEFAULT 514
struct syslog_state {
uint8_t enabled;
bool enabled;
uint8_t line_available;
uint16_t writeptr ;
uint16_t readptr;
uint8_t server_ip[4];
uint16_t server_port;
struct uip_udp_conn *syslog_conn;
};
+15
View File
@@ -96,6 +96,21 @@ typedef unsigned short uip_stats_t;
*/
#define UIP_CONF_MAX_CONNECTIONS 1
/**
* This httpd closes after every response, so an ESTABLISHED connection
* that stays idle is a peer that died or never sent its request. With a
* single connection slot it would hold the web UI until the next power
* cycle, so it is aged out instead.
*
* uip_periodic() runs from idle() once per system tick; interrupt
* wake-ups only add sweeps, so the timeout can fire early but never
* late.
*
* \hideinitializer
*/
#define UIP_CONF_IDLE_PERIODS 200
#define UIP_CONF_IDLE_TIMEOUT 30
/**
* Maximum number of listening TCP ports. TODO: increase this!
*
+43
View File
@@ -171,6 +171,11 @@ __xdata struct uip_udp_conn * __xdata uip_udp_conn;
__xdata struct uip_udp_conn uip_udp_conns[UIP_UDP_CONNS];
#endif /* UIP_UDP */
#if UIP_IDLE_TIMEOUT
__xdata static u8_t uip_idle_prescale;
__xdata static u8_t uip_idle_age;
#endif /* UIP_IDLE_TIMEOUT */
__xdata static u16_t ipid; /* Ths ipid variable is an increasing
number that is used for the IP ID
field. */
@@ -702,6 +707,15 @@ uip_process(u8_t flag) __banked
/* Check if we were invoked because of the perodic timer fireing. */
} else if(flag == UIP_TIMER) {
// print_string("T1\n");
#if UIP_IDLE_TIMEOUT
if(uip_connr == uip_conns) {
uip_idle_age = 0;
if(++uip_idle_prescale >= UIP_IDLE_PERIODS) {
uip_idle_prescale = 0;
uip_idle_age = 1;
}
}
#endif /* UIP_IDLE_TIMEOUT */
#if UIP_REASSEMBLY
if(uip_reasstmr != 0) {
--uip_reasstmr;
@@ -733,6 +747,21 @@ uip_process(u8_t flag) __banked
/* If the connection has outstanding data, we increase the
connection's timer and see if it has reached the RTO value
in which case we retransmit. */
#if UIP_IDLE_TIMEOUT
if(!uip_outstanding(uip_connr)) {
/* The retransmission timer is unused while nothing is in flight,
so idle time is counted in it instead. */
if(uip_idle_age &&
(uip_connr->tcpstateflags & UIP_TS_MASK) == UIP_ESTABLISHED &&
++(uip_connr->timer) >= UIP_IDLE_TIMEOUT) {
uip_connr->tcpstateflags = UIP_CLOSED;
uip_flags = UIP_TIMEDOUT;
UIP_APPCALL();
BUF->flags = TCP_RST | TCP_ACK;
goto tcp_send_nodata;
}
}
#endif /* UIP_IDLE_TIMEOUT */
if(uip_outstanding(uip_connr)) {
if(uip_connr->timer-- == 0) {
if(uip_connr->nrtx == UIP_MAXRTX ||
@@ -1550,6 +1579,11 @@ uip_process(u8_t flag) __banked
#endif /* UIP_ACTIVE_OPEN */
case UIP_ESTABLISHED:
#if UIP_IDLE_TIMEOUT
if(!uip_outstanding(uip_connr)) {
uip_connr->timer = 0;
}
#endif /* UIP_IDLE_TIMEOUT */
/* In the ESTABLISHED state, we call upon the application to feed
data into the uip_buf. If the UIP_ACKDATA flag is set, the
application should put new data into the buffer, otherwise we are
@@ -1576,6 +1610,9 @@ uip_process(u8_t flag) __banked
uip_connr->len = 1;
uip_connr->tcpstateflags = UIP_LAST_ACK;
uip_connr->nrtx = 0;
#if UIP_IDLE_TIMEOUT
uip_connr->timer = uip_connr->rto;
#endif /* UIP_IDLE_TIMEOUT */
tcp_send_finack:
BUF->flags = TCP_FIN | TCP_ACK;
goto tcp_send_nodata;
@@ -1665,6 +1702,9 @@ uip_process(u8_t flag) __banked
uip_connr->len = 1;
uip_connr->tcpstateflags = UIP_FIN_WAIT_1;
uip_connr->nrtx = 0;
#if UIP_IDLE_TIMEOUT
uip_connr->timer = uip_connr->rto;
#endif /* UIP_IDLE_TIMEOUT */
BUF->flags = TCP_FIN | TCP_ACK;
goto tcp_send_nodata;
}
@@ -1693,6 +1733,9 @@ uip_process(u8_t flag) __banked
/* Remember how much data we send out now so that we know
when everything has been acknowledged. */
uip_connr->len = uip_slen;
#if UIP_IDLE_TIMEOUT
uip_connr->timer = uip_connr->rto;
#endif /* UIP_IDLE_TIMEOUT */
} else {
/* If the application already had unacknowledged data, we
+29
View File
@@ -324,6 +324,35 @@
*/
#define UIP_TIME_WAIT_TIMEOUT 120
/**
* The number of UIP_TIMER sweeps that make one idle-aging period,
* i.e. how many times uip_periodic() runs per second in this project.
* Counted in an 8-bit variable, so 255 is the maximum.
*
* \hideinitializer
*/
#ifndef UIP_CONF_IDLE_PERIODS
#define UIP_IDLE_PERIODS 2
#else /* UIP_CONF_IDLE_PERIODS */
#define UIP_IDLE_PERIODS UIP_CONF_IDLE_PERIODS
#endif /* UIP_CONF_IDLE_PERIODS */
/**
* The number of idle-aging periods after which an ESTABLISHED
* connection with a silent peer is aborted, or 0 to keep such
* connections forever (the classic uIP behaviour). TCP itself has no
* idle timeout, so a peer that vanished without FIN or RST would
* otherwise occupy its connection slot until a reboot. Counted in the
* connection's 8-bit retransmission timer, so 255 is the maximum.
*
* \hideinitializer
*/
#ifndef UIP_CONF_IDLE_TIMEOUT
#define UIP_IDLE_TIMEOUT 0
#else /* UIP_CONF_IDLE_TIMEOUT */
#define UIP_IDLE_TIMEOUT UIP_CONF_IDLE_TIMEOUT
#endif /* UIP_CONF_IDLE_TIMEOUT */
/** @} */
/*------------------------------------------------------------------------------*/