While the firmware provides already considerable improvements over the original managed firmware,
-the firmware still lacks support for STP and the proprietary loop prevention
-protocols as well as DHCP. If you need these features, do not install the playground on your managed
+the firmware still lacks support for the proprietary loop prevention
+protocols as well as DHCP. Spanning Tree is available (see doc/stp.md), but is
+a simplified implementation - read that document before enabling it on a
+switch you administer over the network. If you need these features, do not install the playground on your managed
devices. In any case, installation is strongly discouraged unless you can at least make
a backup of the original flash content via a SOIC clamp such as also used for BIOS
backups and can re-install that firmware in case something is wrong. For this no soldering
diff --git a/cmd_editor.c b/cmd_editor.c
index 866cf403..aebfe7f3 100644
--- a/cmd_editor.c
+++ b/cmd_editor.c
@@ -193,7 +193,7 @@ void cmd_edit(void) __banked
// Check whether return was pressed:
if (sbuf[l] == '\n' || sbuf[l] == '\r') {
write_char('\n');
- cmd_buffer[cmd_line_len] = '\0';
+ cmd_buffer[cmd_line_len] = NUL;
// write_char('>'); print_string_x(cmd_buffer); write_char('<');
// If there is a command we print the prompt after execution
// otherwise immediately because there is nothing to execute
diff --git a/cmd_parser.c b/cmd_parser.c
index 996e743c..bd2fc544 100644
--- a/cmd_parser.c
+++ b/cmd_parser.c
@@ -26,7 +26,7 @@
#pragma constseg BANK2
extern __code struct machine machine;
-extern __xdata uint8_t stpEnabled;
+extern __xdata bool stp_enabled;
extern __code uint8_t log_to_phys_port[9];
extern volatile __xdata uint32_t ticks;
@@ -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`
@@ -80,6 +85,9 @@ __xdata uint16_t cmd_history_ptr;
// Error set by commands
__xdata uint8_t err_status;
+__xdata uint16_t atoi_results_short;
+__xdata uint8_t atoi_results_u8;
+
inline uint8_t isletter(uint8_t l)
{
// return (l >= 'a' && l <= 'z') || (l >= 'A' && l <= 'Z');
@@ -111,9 +119,9 @@ uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd)
uint8_t c = cmd[j];
uint8_t b = cmd_buffer[i];
- // cmd is garanteerd to be NULL-terminated.
- if (c == '\0') {
- if ((b == ' ') || (b == '\0')) {
+ // cmd is guaranteed to be NUL-terminated.
+ if (c == NUL) {
+ if ((b == ' ') || (b == NUL)) {
// Match
return 1;
}
@@ -145,7 +153,7 @@ uint8_t atoi_hex(uint8_t idx)
while(1) {
c = cmd_buffer[idx];
- if (c == '\0' || c == ' ') {
+ if (c == NUL || c == ' ') {
break;
}
@@ -185,61 +193,177 @@ uint8_t atoi_hex(uint8_t idx)
}
-uint8_t atoi_byte(__xdata uint8_t *out, uint8_t idx)
+// Returns 0 or the number of digits taken into account for conversion.
+// Stops at any non-digit '0'-'9' char or more than 3 bytes.
+uint8_t atoi_byte(uint8_t idx)
{
- uint8_t err = 1;
+ uint8_t cnt = 0;
uint8_t num = 0;
- while (isnumber(cmd_buffer[idx])) {
- uint8_t val = cmd_buffer[idx] - '0';
- err = 0;
- if (num > 25 || (num == 25 && val > 5))
- return 1;
+ uint8_t * ptr = &cmd_buffer[idx];
+
+ while (1) {
+ uint8_t val = *ptr++ - '0';
+ if (val > 9)
+ break;
+ if (num > 25 || (num == 25 && val > 5) || cnt >= 3)
+ return 0;
num = (num * 10) + val;
- idx++;
+ cnt++;
}
- *out = num;
- return err;
+ atoi_results_u8 = num;
+ return cnt;
}
-uint8_t atoi_short(__xdata uint16_t *vlan, uint8_t idx)
+// Returns 0 or the number of digits taken into account for conversion.
+// Stops at any non-digit '0'-'9' char or bytes is more then 5.
+uint8_t atoi_short(uint8_t idx)
{
- uint8_t err = 1;
- *vlan = 0;
+ uint8_t cnt = 0;
+ atoi_results_short = 0;
- while (isnumber(cmd_buffer[idx])) {
- err = 0;
- uint8_t val = cmd_buffer[idx] - '0';
- if (*vlan > 6553 || (*vlan == 6553 && val > 5))
- return 1;
- *vlan = (*vlan * 10) + val;
- idx++;
+ uint8_t *ptr = &cmd_buffer[idx];
+
+ while (1) {
+ uint8_t val = *ptr++ - '0';
+ if (val > 9)
+ break;
+ if (atoi_results_short > 6553 || (atoi_results_short == 6553 && val > 5) || cnt >= 5)
+ return 0;
+ atoi_results_short = (atoi_results_short * 10) + val;
+ cnt++;
}
- return err;
+ return cnt;
+}
+
+/* Parse, validate and translate phys_to_log_port physical port argument.
+ * The CPU-port, i.e. port 0, is not a valid argument.
+ * returns 0 when on parser error or invalid value.
+ * returns non-zero number of characters consumed.
+ * Store the value in atoi_results_u8.
+ */
+uint8_t cmd_parse_port(uint8_t idx) {
+ uint8_t port = cmd_buffer[idx] - '0' - 1;
+ if (port > 8)
+ return 0;
+
+ port = machine.phys_to_log_port[port];
+ if (port < machine.min_port || port > machine.max_port)
+ return 0;
+
+ atoi_results_u8 = port;
+ return 1;
}
+// Same as cmd_parse_port() but additionally check for trailing SPACE or NUL.
+// returns 0 when on parser error or invalid value or no SPACE or no NUL.
+// returns non-zero number of characters consumed including the SPACE.
+uint8_t cmd_parse_port_separator(uint8_t idx) {
+ uint8_t ret = cmd_parse_port(idx);
+ if (ret != 0) {
+ idx += ret;
+ uint8_t c = cmd_buffer[idx];
+ if (c == ' ') {
+ ret++;
+ } else if (c != NUL)
+ ret = 0;
+ }
+ return ret;
+}
+
+// Same as cmd_parse_port_separator() but additionally allow CPU-port.
+// returns 0 when on parser error or invalid value or no SPACE or no NUL.
+// returns number of characters consumed including the SPACE.
+uint8_t cmd_parse_port_cpu_separator(uint8_t idx) {
+ uint8_t ret = cmd_parse_port(idx);
+
+ if (ret == 0 && cmd_buffer[idx] == '0') {
+ ret = 1;
+ atoi_results_u8 = CPU_PORT;
+ }
+
+ if (ret != 0) {
+ idx += ret;
+ uint8_t c = cmd_buffer[idx];
+ if (c == ' ') {
+ ret++;
+ } else if (c != NUL)
+ ret = 0;
+ }
+ return ret;
+}
+
+
+// check if the cmd_buffer[idx] is a SPACE.
+__bit cmd_is_space(uint8_t idx) {
+ return cmd_buffer[idx] == ' ';
+}
+
+// check if the cmd_buffer[idx] is a SPACE or NUL.
+__bit cmd_is_space_or_nul(uint8_t idx) {
+ uint8_t c = cmd_buffer[idx];
+ return c == ' ' || c == NUL;
+}
+
+// Parse an IPv4 address
+// returns 0 when on parse error or invalid value or it don't ends with SPACE or NUL.
+// returns non-zero number of characters consumed including the SPACE.
uint8_t parse_ip(uint8_t idx)
{
- __xdata uint8_t b;
+ uint8_t b = 0;
+ uint8_t ret;
+ uint8_t idx_start = idx;
- for (b = 0; b < 4; b++) {
- ip[b] = 0;
- while (isnumber(cmd_buffer[idx])) {
- ip[b] = (ip[b] * 10) + cmd_buffer[idx] - '0';
- idx++;
- }
- if (b < 3 && cmd_buffer[idx++] != '.') {
- print_string("Error in IP format, expecting '.'\n");
- return -1;
+ while(1) {
+ ret = atoi_byte(idx);
+ if (ret == 0)
+ goto err;
+
+ idx += ret;
+ ip[b++] = atoi_results_u8;
+
+ ret = cmd_buffer[idx];
+ if (b == 4) {
+ if (ret == ' ') {
+ idx++;
+ break;
+ }
+ if (ret == NUL)
+ break;
+ goto err;
}
+ idx++;
+
+ if (ret != '.')
+ goto err;
}
+ return idx - idx_start;
+
+err:
+ print_string("Error in IP format\n");
return 0;
}
+// Prints an IPv4 address.
+void print_ip(__xdata uint8_t * ptr)
+{
+ uint8_t idx = 0;
+ uint8_t num;
+
+ while(1) {
+ num = *ptr++;
+ itoa(num);
+ if (++idx == 4)
+ break;
+
+ write_char('.');
+ }
+}
+
void parse_lag(void)
{
@@ -258,7 +382,7 @@ void parse_lag(void)
print_string(" member ports: ");
for (uint8_t j = 0; j < 10; j++) {
if (members & 1) {
- write_char('0' + machine.log_to_phys_port[j]);
+ print_phys_port(j);
write_char(' ');
}
members >>= 1;
@@ -271,30 +395,26 @@ void parse_lag(void)
return;
}
- if (cmd_words_len < 2 || !isnumber(cmd_buffer[cmd_words_b[1]]))
+ if (cmd_words_len < 2)
goto err;
- group = cmd_buffer[cmd_words_b[1]] - '1';
- if (group > 3) /* '0' wraps well past three, so one test does both ends */
+
+ // Parse group, expect only one number 0-9;
+ if (atoi_byte(cmd_words_b[1]) != 1)
+ goto err;
+
+ group = atoi_results_u8 - 1;
+ if (group > 3)
goto err;
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('-');
- uint8_t port;
- if (isnumber(cmd_buffer[cmd_words_b[w]])) {
- port = cmd_buffer[cmd_words_b[w]] - '1';
- if (isnumber(cmd_buffer[cmd_words_b[w] + 1]))
- port = (port + 1) * 10 + cmd_buffer[cmd_words_b[w] + 1] - '1';
- if (port > 8) /* phys_to_log_port holds nine entries */
- goto err;
- port = machine.phys_to_log_port[port];
- } else {
+
+ // Parse port.
+ if (cmd_parse_port_separator(cmd_words_b[w++]) == 0)
goto err;
- }
- if (port > machine.max_port)
- goto err;
- members |= ((uint16_t)1) << port;
- w++;
+
+ members |= ((uint16_t)1) << atoi_results_u8;
}
port_lag_members_set(group, members);
return;
@@ -305,15 +425,15 @@ err:
void parse_lag_hash(void)
{
- __xdata uint8_t group;
- __xdata uint8_t hash = 0;
-
- if (cmd_words_len < 2 || !isnumber(cmd_buffer[cmd_words_b[1]]))
- goto err;
- group = cmd_buffer[cmd_words_b[1]] - '1';
- if (group > 3) /* '0' wraps well past three, so one test does both ends */
+ // Parse group, expect only one number 0-9.
+ if (cmd_words_len < 3 || atoi_byte(cmd_words_b[1]) != 1)
goto err;
+ uint8_t group = atoi_results_u8 - 1;
+ if (group > 3)
+ goto err;
+
+ uint8_t hash = 0;
uint8_t w = 2;
while (w < cmd_words_len) {
if (cmd_compare(w, "spa"))
@@ -334,13 +454,14 @@ void parse_lag_hash(void)
print_string("Error: invalid hash type:");
print_string_x(&cmd_buffer[cmd_words_b[w]]);
write_char('\n');
+ goto err;
}
w++;
}
port_lag_hash_set(group, hash);
return;
err:
- print_string("Error: lag hash <1-4> [type]...\n");
+ print_string("Error: laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n");
}
@@ -349,58 +470,71 @@ void parse_vlan(void)
vlan_settings.vlan = 0;
vlan_settings.members = 0;
vlan_settings.tagged = 0;
+
if (cmd_words_len < 2)
goto err;
- if (!atoi_short(&vlan_settings.vlan, cmd_words_b[1])) {
- if (cmd_words_len == 3 && cmd_buffer[cmd_words_b[2]] == 'd') {
- vlan_delete(vlan_settings.vlan);
- return;
- }
+
+ // Parse the VLAN number
+ if (atoi_short(cmd_words_b[1]) != 0) {
+ if (atoi_results_short > 4094)
+ goto err;
+ vlan_settings.vlan = atoi_results_short;
+
if (cmd_compare(2, "mgmt")) {
- if (vlan_settings.vlan > 4094)
- goto err;
+ 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
print_string("Management VLAN set to "); print_short(management_vlan); write_char('\n');
return;
}
- if (!vlan_settings.vlan || vlan_settings.vlan > 4094)
+
+ // Other commands vlan 0 is invalid
+ if (!vlan_settings.vlan)
goto err;
+
+ if (cmd_words_len == 3 && cmd_buffer[cmd_words_b[2]] == 'd') {
+ vlan_delete(vlan_settings.vlan);
+ return;
+ }
+
uint8_t w = 2;
if (cmd_words_len > w && isletter(cmd_buffer[cmd_words_b[w]])) {
- register uint8_t i = 0;
+ uint8_t i = 0;
vlan_name_remove(vlan_settings.vlan);
vlan_names[vlan_ptr++] = hex[(vlan_settings.vlan >> 8) & 0xf];
vlan_names[vlan_ptr++] = hex[(vlan_settings.vlan >> 4) & 0xf] ;
vlan_names[vlan_ptr++] = hex[vlan_settings.vlan & 0xf];
- while(cmd_buffer[cmd_words_b[w] + i] != ' ' && cmd_buffer[cmd_words_b[w] + i] != '\0') {
+ while(!cmd_is_space_or_nul(cmd_words_b[w] + i)) {
write_char(cmd_buffer[cmd_words_b[w] + i]);
vlan_names[vlan_ptr++] = cmd_buffer[cmd_words_b[w] + i++];
}
- vlan_names[vlan_ptr++] = ' '; vlan_names[vlan_ptr] = '\0';
+ vlan_names[vlan_ptr++] = ' '; vlan_names[vlan_ptr] = NUL;
w++;
print_string("<\n");
}
+
+ uint8_t ret;
+ uint8_t idx;
while (cmd_words_len > w) {
- __xdata uint8_t port;
- if (isnumber(cmd_buffer[cmd_words_b[w]])) {
- port = cmd_buffer[cmd_words_b[w]] - '1';
- if (isnumber(cmd_buffer[cmd_words_b[w] + 1])) {
- port = (port + 1) * 10 + cmd_buffer[cmd_words_b[w] + 1] - '1';
- if (cmd_buffer[cmd_words_b[w] + 2] == 't')
- vlan_settings.tagged |= ((uint16_t)1) << port;
- } else {
- port = machine.phys_to_log_port[port];
- if (cmd_buffer[cmd_words_b[w] + 1] == 't')
- vlan_settings.tagged |= ((uint16_t)1) << port;
- }
- if (port > machine.max_port)
- goto err;
- vlan_settings.members |= ((uint16_t)1) << port;
+ idx = cmd_words_b[w++];
+ ret = cmd_parse_port(idx);
+ if (ret == 0)
+ goto err;
+
+ idx += ret;
+ uint16_t pmask = ((uint16_t)1) << atoi_results_u8;
+ vlan_settings.members |= pmask;
+
+ if (cmd_buffer[idx] == 't') {
+ vlan_settings.tagged |= pmask;
+ idx++;
}
- w++;
+
+ if (!cmd_is_space_or_nul(idx))
+ goto err;
}
vlan_create();
} else if (cmd_compare(1, "show")) {
@@ -415,25 +549,22 @@ void parse_vlan(void)
}
return;
err:
- print_string("Error: vlan (
+
+Bottom:
+
+
+
+### Serial console
+
+The PCB has five unpopulated through-holes near the SoC, with a white rectangle surrouding them, labeled as `J18` and a triangle points to the square shaped first pin.
+
+This is where the "expected" UART header should be soldered at, with redundant 3V3 VCC, but also with missing 0Ω resistors between TX/RX and SoC, so simply soldering a header would not work. One need also add the missing resistors or solder the pads together, while making sure not connecting unrelated pads.
+
+Numbered from the triangle, the header pinout is:
+
+| Position | Signal | GPIO | Status |
+|----------|--------|--------|-------------|
+| 1 | GND | GND | Internal |
+| 2 | TX | GPIO31 | Unconnected |
+| 3 | RX | GPIO32 | Unconnected |
+| 4 | 3V3 | - | Internal |
+| 5 | 3V3 | - | Internal |
+
+There're four resistor pads near the header.
+
+| Resistor ID | SoC Side | Header Side |
+|-------------|-------------|-------------|
+| R1240 | TX / GPIO31 | Pin 2 |
+| R1243 | 3V3 | - |
+| R1242 | 3V3 | Pin 3 |
+| R1241 | RX / GPIO32 | - |
+
+To get TX working, solder a 0Ω resistor between the pads for R1240 or solder them together.
+
+For RX However, the designer certainly made a mistake, as the SoC-side lines to the resistor expected for Rx (R1242) and 3V3 (R1241) are swapped. For RX to work, the R1241 SoC side and R1242 header side shall be connected, so either:
+
+- Solder: R1241 SoC side -> R1241 header side -> R1242 header side
+- Jump wire: R1241 SoC side -> R1242 header side
+
+Be sure not to bring R1242 SoC side to the connection as that would wire Rx to 3v3.
+
+A complete working serial header should look like following on this PCB:
+
+
+
+- **Settings**: 115200 baud / 8N1 / 3.3V TTL
+- Connect a USB-TTL adapter: adapter GND → pin 1, RX → pin 2, TX → pin 3
+
+### Power supply
+
+Input power is delivered via barell plug, `12V 1A` adapter was provided.
+
diff --git a/doc/devices/KP-9000-6XH-X2.md b/doc/devices/KP-9000-6XH-X2.md
index 4cc21d71..d0f19b96 100644
--- a/doc/devices/KP-9000-6XH-X2.md
+++ b/doc/devices/KP-9000-6XH-X2.md
@@ -1,17 +1,36 @@
-# Keeplink KP-9000-6XH-X2
+# Keeplink KP-9000-6XH-X2 / KP-9000-6XHML-X2
-Following is documentation for unmanaged switch marked as `KP-9000-6XH-X2`.
+Following is documentation for switches marked as `KP-9000-6XH-X2` or
+`KP-9000-6XHML-X2`.
Using SPI clamp in-board is the only method for initial installation.
### Label specifications
- **Name**: 4X 2.5G RJ45 Port + 2 X 10G SFP+ Port
-- **Model**: KP-9000-6XH-X2
+- **Model**: KP-9000-6XH-X2 / KP-9000-6XHML-X2
- **Ports**:
- 4 × RJ45: 10/100/1000/2500 Mbps
- 2 × SFP+: 1000 / 2500 / 10000 Mbps
+### Machine target
+
+These devices exist with different PCB revisions. Select the machine target by
+the PCB silkscreen, not by the label on the case. The `6XH` / `6XHML`
+distinction appears to be a stock firmware, SKU or label difference, not a
+reliable indicator of PCB wiring. The PCB revision defines the hardware layout.
+
+| PCB silkscreen | Known labels / devices | Recommended machine target | Equivalent target | Legacy target |
+| --- | --- | --- | --- | --- |
+| `2M-PCB43-V1.1` | Mokerlink 2G040210GSM web-managed 4+2 switch | `MACHINE_KP_9000_6XHML_X2_V1_1` | `MACHINE_KP_9000_6XH_X2_V1_1` | `MACHINE_KP_9000_6XHML_X2` |
+| `2M-PCB43-V1.2` | `KP-9000-6XHML-X2` | `MACHINE_KP_9000_6XHML_X2_V1_2` | `MACHINE_KP_9000_6XH_X2_V1_2` | `MACHINE_KP_9000_6XHML_X2` |
+| `2M-PCB43-V2.1` | `KP-9000-6XH-X2`, `KP-9000-6XHML-X2` | `MACHINE_KP_9000_6XH_X2_V2_1` or `MACHINE_KP_9000_6XHML_X2_V2_1` | same V2.1 layout | `MACHINE_KP_9000_6XH_X2` |
+
+The V1.1 and V1.2 boards currently use the same V1.x GPIO, SFP, port and LED
+layout. The V2.1 board uses a different V2.1 layout with custom LED muxing.
+Legacy targets are preserved for compatibility with already-tested devices, but
+new builds should prefer the explicit PCB-revision targets.
+
### What works
- All four 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps
@@ -19,7 +38,7 @@ Using SPI clamp in-board is the only method for initial installation.
- LEDs
- untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs.
-### Hardware overview
+### Hardware overview: 2M-PCB43-V2.1
Front side:
@@ -44,6 +63,24 @@ Bottom
+### Hardware overview: 2M-PCB43-V1.1 / V1.2
+
+The V1.1 board has been reported in the Mokerlink 2G040210GSM web-managed 4+2
+switch. The V1.2 board has been seen in managed `KP-9000-6XHML-X2` devices.
+Both use the same V1.x layout.
+
+Label:
+
+
+
+Top side:
+
+
+
+Bottom:
+
+
+
## Reset Button
There's an unpopulated Reset button on the front left side of the PCB.
@@ -54,4 +91,3 @@ The front case has already the hole in the metal case, you just have to punch a
## Power supply
Input power is delivered via barell plug, `12V 1A` adapter was provided.
-
diff --git a/doc/devices/PCB-K0402WS-V2.0.md b/doc/devices/PCB-K0402WS-V2.0.md
new file mode 100644
index 00000000..36dea4d8
--- /dev/null
+++ b/doc/devices/PCB-K0402WS-V2.0.md
@@ -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
+
+
+
+Bottom label
+
+
+
+Top side (PCB)
+
+
+
+### 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).
diff --git a/doc/devices/PCB-K0402WS-V3.0.md b/doc/devices/PCB-K0402WS-V3.0.md
index 759fdd14..0da0f0d5 100644
--- a/doc/devices/PCB-K0402WS-V3.0.md
+++ b/doc/devices/PCB-K0402WS-V3.0.md
@@ -19,6 +19,9 @@ of the device just above the big label, might be covered by a QC sticker.
+* Siscolink SL-G0402F
+
+
### Programming
Using SPI clamp in-board is the only method for initial installation.
diff --git a/doc/devices/SWTGW215AS.md b/doc/devices/SWTGW215AS.md
index 6319cb3a..0f986986 100644
--- a/doc/devices/SWTGW215AS.md
+++ b/doc/devices/SWTGW215AS.md
@@ -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)
-
+
-# Connectors
+## PCB (V2.0)
-## Port overview
+
+
+
+## PCB (V2.1)
+
+
+
+
+## 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.
diff --git a/doc/devices/photos/FG-8GT-1SX/PCB-bottom.jpg b/doc/devices/photos/FG-8GT-1SX/PCB-bottom.jpg
new file mode 100644
index 00000000..b266d29f
Binary files /dev/null and b/doc/devices/photos/FG-8GT-1SX/PCB-bottom.jpg differ
diff --git a/doc/devices/photos/FG-8GT-1SX/PCB-top.jpg b/doc/devices/photos/FG-8GT-1SX/PCB-top.jpg
new file mode 100644
index 00000000..ca640cd1
Binary files /dev/null and b/doc/devices/photos/FG-8GT-1SX/PCB-top.jpg differ
diff --git a/doc/devices/photos/FG-8GT-1SX/Serial.png b/doc/devices/photos/FG-8GT-1SX/Serial.png
new file mode 100644
index 00000000..7d255a9f
Binary files /dev/null and b/doc/devices/photos/FG-8GT-1SX/Serial.png differ
diff --git a/doc/devices/photos/PCB-K0402WS-V2.0/PCB-top.jpg b/doc/devices/photos/PCB-K0402WS-V2.0/PCB-top.jpg
new file mode 100644
index 00000000..2378dd7a
Binary files /dev/null and b/doc/devices/photos/PCB-K0402WS-V2.0/PCB-top.jpg differ
diff --git a/doc/devices/photos/PCB-K0402WS-V2.0/chassis-bottom.jpg b/doc/devices/photos/PCB-K0402WS-V2.0/chassis-bottom.jpg
new file mode 100644
index 00000000..c30a3b70
Binary files /dev/null and b/doc/devices/photos/PCB-K0402WS-V2.0/chassis-bottom.jpg differ
diff --git a/doc/devices/photos/PCB-K0402WS-V2.0/chassis-front.jpg b/doc/devices/photos/PCB-K0402WS-V2.0/chassis-front.jpg
new file mode 100644
index 00000000..47bf168a
Binary files /dev/null and b/doc/devices/photos/PCB-K0402WS-V2.0/chassis-front.jpg differ
diff --git a/doc/devices/photos/PCB-K0402WS-V3.0/Siscolink SL-G0402F.jpg b/doc/devices/photos/PCB-K0402WS-V3.0/Siscolink SL-G0402F.jpg
new file mode 100644
index 00000000..86f34b7b
Binary files /dev/null and b/doc/devices/photos/PCB-K0402WS-V3.0/Siscolink SL-G0402F.jpg differ
diff --git a/doc/devices/photos/ZX-SWTGW215AS/label.jpg b/doc/devices/photos/ZX-SWTGW215AS-V2.0/label.jpg
similarity index 100%
rename from doc/devices/photos/ZX-SWTGW215AS/label.jpg
rename to doc/devices/photos/ZX-SWTGW215AS-V2.0/label.jpg
diff --git a/doc/devices/photos/ZX-SWTGW215AS/pcb_bottom.jpg b/doc/devices/photos/ZX-SWTGW215AS-V2.0/pcb_bottom.jpg
similarity index 100%
rename from doc/devices/photos/ZX-SWTGW215AS/pcb_bottom.jpg
rename to doc/devices/photos/ZX-SWTGW215AS-V2.0/pcb_bottom.jpg
diff --git a/doc/devices/photos/ZX-SWTGW215AS/pcb_top.jpg b/doc/devices/photos/ZX-SWTGW215AS-V2.0/pcb_top.jpg
similarity index 100%
rename from doc/devices/photos/ZX-SWTGW215AS/pcb_top.jpg
rename to doc/devices/photos/ZX-SWTGW215AS-V2.0/pcb_top.jpg
diff --git a/doc/devices/photos/ZX-SWTGW215AS-V2.1/pcb-bottom.jpg b/doc/devices/photos/ZX-SWTGW215AS-V2.1/pcb-bottom.jpg
new file mode 100644
index 00000000..78babe5e
Binary files /dev/null and b/doc/devices/photos/ZX-SWTGW215AS-V2.1/pcb-bottom.jpg differ
diff --git a/doc/devices/photos/ZX-SWTGW215AS-V2.1/pcb-top.jpg b/doc/devices/photos/ZX-SWTGW215AS-V2.1/pcb-top.jpg
new file mode 100644
index 00000000..d9f6c1f9
Binary files /dev/null and b/doc/devices/photos/ZX-SWTGW215AS-V2.1/pcb-top.jpg differ
diff --git a/doc/l2.md b/doc/l2.md
index c0aaf610..0a785a8f 100644
--- a/doc/l2.md
+++ b/doc/l2.md
@@ -65,3 +65,36 @@ ASIC and flushing the table in order to quickly forget the learned entries.
3c:18:a0:7e:11:00 0x0001 learned 5
1c:2a:a3:23:00:02 0x0001 learned 7
```
+
+## Static multicast entries
+
+Slow-protocol frames such as LACPDUs and STP BPDUs have to reach the CPU
+without being flooded to the other ports. No bridge relays these frames:
+their addresses are in the set that 802.1D-2004 clause 7.12.6 forbids a
+bridge to forward, and what travels the network is the information, with
+every bridge regenerating BPDUs of its own on its designated ports. The reserved-multicast *trap* action
+cannot do that on this hardware, because its destination is an external CPU
+attached to a physical port, which these boards do not populate. The protocol
+modules therefore leave the reserved-multicast action at *forward* and constrain
+the egress with a static L2 multicast entry instead: the lookup hits the entry's
+own port mask rather than the VLAN flood mask. Verified on a SWTGW218AS both
+ways, with the CPU bit cleared, where delivery stops, and with the CPU bit alone,
+where nothing egresses.
+
+`port_l2mc_set()` writes one such entry. The SMI layout is the L2 multicast
+variant of the table entry:
+
+```
+DATA_IN_A = MAC bytes 5..2 -> c2 00 00 | Port | Name | Current Link Speed | Set Speed | Disabled | Apply | |
|---|---|---|---|---|---|---|
| Port | Name | Current Link Speed | Connected devices | Set Speed | Disabled | Apply |
| Priority | Version | Hello [s] | Max age [s] | Fwd delay [s] | Tx hold | +
|---|---|---|---|---|---|
| + | + | + | + | + | + |
Changes apply immediately. Edge ports skip the listen period; guard/filter act on received BPDUs.
+| Port | State | Path Cost (0 = Auto) | Priority | Edge Port | BPDU Filter | Guard | Point-to-Point | +
|---|
| Port | Port State | Role | Designated Bridge | Designated Port ID | Designated Cost | Oper. Edge | Oper. P2P | +
|---|