diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..168bd34f --- /dev/null +++ b/CHANGELOG.md @@ -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. \ No newline at end of file diff --git a/Makefile b/Makefile index 2c9a7c98..6f369d78 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,7 @@ create_build_dir: # Keep machine.c in first position to fail immediately on invalid $MACHINE value SRCS = \ machine.c \ + machine_init.c \ cmd_editor.c \ cmd_parser.c \ dhcp.c \ @@ -112,14 +113,17 @@ httpd: html_data.h $(SUBDIRS): $(MAKE) -C $@ -clean: +clean: $(SUBDIRSCLEAN) -rm -f html_data.c html_data.h $(VERSION_HEADER) -if [ -d $(BUILDDIR) ]; then find $(BUILDDIR) -type f ! -name "*.bin" -delete; fi -distclean: +distclean: $(SUBDIRSCLEAN) -rm -f html_data.c html_data.h $(VERSION_HEADER) -rm -rf $(BUILDDIR) +$(SUBDIRSCLEAN): + $(MAKE) -C $(@:clean=) clean + $(BUILDDIR)/%.rel: %.c | create_build_dir html_data.h $(CC) -MMD $(CC_FLAGS) -o $@ -c $< @@ -142,16 +146,17 @@ $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.i tools/output/crc_calculator -u $@ ln -sf $(MACHINE)/rtlplayground-$(FILENAME_EXTENSION).bin output/rtlplayground.bin -.PHONY: clean all $(SUBDIRS) $(VERSION_HEADER) create_build_dir +.PHONY: clean distclean all $(SUBDIRS) $(SUBDIRSCLEAN) $(VERSION_HEADER) create_build_dir .PHONY: machine_check: @mkdir -p $(BUILDDIR)/tmp @set -eo pipefail; \ - for MACHINE in `grep -e ' MACHINE_' machine.c | sed -e 's%^.* MACHINE_%%' -e 's%[ ]*//.*$$%%' | sort -u`; \ + for MACHINE in `grep -E '^[[:space:]]*(//[[:space:]]*)?#define MACHINE_' machine.h | sed -E 's%^[[:space:]]*(//[[:space:]]*)?#define MACHINE_%%' | awk '{print $$1}' | sort -u`; \ do \ echo "Checking $${MACHINE}"; \ $(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine.c; \ + $(CC) $(CC_FLAGS) -DMACHINE_$${MACHINE} -MMD -o $(BUILDDIR)/tmp/machine_check -c machine_init.c; \ done @rm -rf $(BUILDDIR)/tmp diff --git a/README.md b/README.md index e149058f..acf787e9 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,10 @@ only the following features are provided: GUI 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 (|show) [port][t/u]...\n"); + print_string("Error: vlan (|show) [port][t]...\n"); } void parse_isolate(void) { - __xdata uint16_t members = 0; + uint16_t members = 0; if (cmd_words_len < 3) goto err; print_string("\nISOLATE "); - __xdata int8_t port_configured = cmd_buffer[cmd_words_b[1]] - '1'; - port_configured = machine.phys_to_log_port[port_configured]; - if (isnumber(cmd_buffer[cmd_words_b[1] + 1])) // CPU-port, logical port 9 - port_configured = (port_configured + 1) * 10 + cmd_buffer[cmd_words_b[1] + 1] - '1'; - if (port_configured < 0 || port_configured > 9) + if (cmd_parse_port_separator(cmd_words_b[1]) == 0) goto err; + uint8_t port_configured = atoi_results_u8; print_byte(port_configured); write_char('\n'); @@ -441,19 +572,16 @@ void parse_isolate(void) members = port_isolation_get(port_configured); for (uint8_t i = 0; i < 10; i++) { if (members & 1) { - if (i < 9) - write_char(machine.log_to_phys_port[i] + '0'); - else - print_string("CPU"); + print_phys_port(i); write_char(' '); } members >>= 1; } return; - } + } if (cmd_compare(2, "off")) { - for (uint8_t i = machine.min_port; i < machine.max_port; i++) + for (uint8_t i = machine.min_port; i <= machine.max_port; i++) members |= ((uint16_t)1) << i; members |= 0x200; // CPU-port port_isolate(port_configured, members); @@ -462,22 +590,12 @@ void parse_isolate(void) uint8_t w = 2; while (w < cmd_words_len) { - __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'; // logical port - if (port != 9) // CPU port is logical port 9 - goto err; - } else { - port = machine.phys_to_log_port[port]; - if (port < machine.min_port || port > machine.max_port) - goto err; - } - members |= ((uint16_t)1) << port; - } - w++; + if (cmd_parse_port_cpu_separator(cmd_words_b[w++]) == 0) + goto err; + uint8_t port = atoi_results_u8; + members |= ((uint16_t)1) << port; } + port_isolate(port_configured, members); return; @@ -509,52 +627,54 @@ void parse_ingress(void) if (cmd_words_len < 2) { goto err; } - __xdata uint8_t log_port = 0; + uint8_t log_port = 0; __xdata vlan_ingress_mode_t mode = VLAN_INVALID; + uint8_t idx = cmd_words_b[1]; - if (vlan_ingress_mode_parse(cmd_buffer[cmd_words_b[1]], &mode)) { + if (vlan_ingress_mode_parse(cmd_buffer[idx++], &mode)) { + if (!cmd_is_space_or_nul(idx)) + goto err; // Setting mode for all ports at once for (log_port = machine.min_port; log_port <= machine.max_port; log_port++) { if (!port_ingress_filter(log_port, mode)) { - print_string("Error setting ingress filter for port "); print_byte(machine.log_to_phys_port[log_port]); write_char('\n'); + print_string("Error setting ingress filter for port "); print_phys_port(log_port); write_char('\n'); return; } print_string("All ports ingress filter set to: "); print_port_ingress_filter_mode(mode); write_char('\n'); } - return; } else { for(uint8_t w = 1; w < cmd_words_len; w++) { - uint8_t p = cmd_buffer[cmd_words_b[w]]; - if (!isnumber(p)) { + idx = cmd_words_b[w]; + uint8_t ret = cmd_parse_port(idx); + if (ret != 1) { + print_string("Invalid physical port number\n"); continue; } - if (p - '1' > 9) { - print_string("Invalid physical port number: "); write_char(p); write_char('\n'); - continue; - } - log_port = machine.phys_to_log_port[p - '1']; - if (!vlan_ingress_mode_parse(cmd_buffer[cmd_words_b[w] + 1], &mode)) { - print_string("Invalid ingress mode for port "); write_char(p); print_string(" in ingress command\n"); + log_port = atoi_results_u8; + idx += ret; + + if (!vlan_ingress_mode_parse(cmd_buffer[idx++], &mode) || !cmd_is_space_or_nul(idx)) { + print_string("Invalid ingress mode for port "); print_phys_port(log_port); print_string(" in ingress command\n"); goto err; } if (!port_ingress_filter(log_port, mode)) { - print_string("Error setting ingress filter for port "); write_char(p); write_char('\n'); + print_string("Error setting ingress filter for port "); print_phys_port(log_port); write_char('\n'); return; } - print_string("Port "); write_char(p); + print_string("Port "); print_phys_port(log_port); print_string(" ingress filter set to: "); print_port_ingress_filter_mode(mode); write_char('\n'); } - return; } + return; err: - print_string("Error: ingress [p]... \n"); + print_string("Error: ingress [p]...\n"); } void parse_mirror(void) { - __xdata uint8_t mirroring_port; + __xdata uint8_t mirroring_port = 0; __xdata uint16_t rx_pmask = 0; __xdata uint16_t tx_pmask = 0; @@ -567,7 +687,7 @@ void parse_mirror(void) print_string("NOT Enabled: "); } print_string("Mirroring port: "); - write_char('0' + machine.log_to_phys_port[mPort >> 1]); + print_phys_port(mirroring_port); reg_read_m(RTL837x_MIRROR_CONF); uint16_t m = sfr_data[0]; m = (m << 8) | sfr_data[1]; @@ -584,48 +704,38 @@ void parse_mirror(void) return; } - if (cmd_words_len < 2 || !isnumber(cmd_buffer[cmd_words_b[1]])) { - print_string("Port/command missing: mirror [status/off/ [port][t/r]]...\n"); - return; - } - - mirroring_port = cmd_buffer[cmd_words_b[1]] - '1'; - if (isnumber(cmd_buffer[cmd_words_b[1] + 1])) - mirroring_port = (mirroring_port + 1) * 10 + cmd_buffer[cmd_words_b[1] + 1] - '1'; - mirroring_port = machine.phys_to_log_port[mirroring_port]; - + if (cmd_words_len < 2) + goto err; uint8_t w = 2; + uint8_t port; + uint8_t ret; while (w < cmd_words_len) { - 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'; - port = machine.phys_to_log_port[port]; - if (cmd_buffer[cmd_words_b[w] + 2] == 'r') - rx_pmask |= ((uint16_t)1) << port; - else if (cmd_buffer[cmd_words_b[w] + 2] == 't') - tx_pmask |= ((uint16_t)1) << port; - else { - rx_pmask |= ((uint16_t)1) << port; - tx_pmask |= ((uint16_t)1) << port; - } - } else { - port = machine.phys_to_log_port[port]; - if (cmd_buffer[cmd_words_b[w] + 1] == 'r') - rx_pmask |= ((uint16_t)1) << port; - else if (cmd_buffer[cmd_words_b[w] + 1] == 't') - tx_pmask |= ((uint16_t)1) << port; - else { - rx_pmask |= ((uint16_t)1) << port; - tx_pmask |= ((uint16_t)1) << port; - } - } - } - w++; + uint8_t idx = cmd_words_b[w++]; + ret = cmd_parse_port(idx); + if (ret == 0) + goto err; + + idx += ret; + port = atoi_results_u8; + + // Use the first port argument as mirroring_port + if (w == 2) + mirroring_port = port; + + ret = cmd_buffer[idx]; + uint16_t pmask = ((uint16_t)1) << port; + if (ret != 't') + rx_pmask |= pmask; + if (ret != 'r') + tx_pmask |= pmask; } port_mirror_set(mirroring_port, rx_pmask, tx_pmask); + return; + +err: + print_string("Port/command missing: mirror [status/off/ [port][t/r]]...\n"); + return; } @@ -639,16 +749,11 @@ void parse_port(void) return; } - if (cmd_buffer[cmd_words_b[1]] < '1' || cmd_buffer[cmd_words_b[1]] > '9' || cmd_buffer[cmd_words_b[1] + 1] != ' ' ) { - print_string("Illegal port number\n"); - return; - } - phy_settings.port = cmd_buffer[cmd_words_b[1]] - '1'; - phy_settings.port = machine.phys_to_log_port[phy_settings.port]; - if (phy_settings.port > machine.max_port || phy_settings.port < machine.min_port) { - print_string("This machine has no port with the specified number\n"); + if (cmd_parse_port_separator(cmd_words_b[1]) == 0) { + print_string("Invalid port number\n"); return; } + phy_settings.port = atoi_results_u8; print_string("Logical Port: "); print_byte(phy_settings.port); write_char('\n'); phy_settings.duplex = PHY_DUPLEX_BOTH; @@ -661,11 +766,11 @@ void parse_port(void) } } else if (cmd_compare(2, "name")) { uint8_t i = 0; - while ( (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != '\0') ) { + while ( (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != NUL) ) { port_names[phy_settings.port][i] = cmd_buffer[cmd_words_b[3] + i]; i++; } - port_names[phy_settings.port][i] = '\0'; + port_names[phy_settings.port][i] = NUL; print_string("\nName set to: \""); print_string_x(port_names[phy_settings.port]); print_string("\"\n"); @@ -730,47 +835,59 @@ void parse_port(void) void parse_mtu(void) { - __xdata uint16_t mtu; uint8_t p; if (cmd_compare(1, "show")) { for (p = machine.min_port; p <= machine.max_port; p++) { reg_read_m(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8)); - mtu = SFR_DATA_U16 & 0x3fff; - print_string("Port "); print_byte(machine.log_to_phys_port[p]); + uint16_t mtu = SFR_DATA_U16 & 0x3fff; + print_string("Port "); print_phys_port(p); write_char(' '); print_short(mtu); write_char('\n'); } return; } - if (cmd_words_len != 3 || cmd_buffer[cmd_words_b[1]] < '1' - || cmd_buffer[cmd_words_b[1]] > '9' - || cmd_buffer[cmd_words_b[1] + 1] > ' ') { - print_string("mtu [port] [size]\n"); - return; - } - p = machine.phys_to_log_port[cmd_buffer[cmd_words_b[1]] - '1']; + if (cmd_words_len != 3) + goto err; + + if (cmd_parse_port_separator(cmd_words_b[1]) == 0) + goto err; + + p = atoi_results_u8; print_byte(p); - if (atoi_short(&mtu, cmd_words_b[2]) || mtu < 64 || mtu > 0x3fff) { + + if (atoi_short(cmd_words_b[2]) == 0 || atoi_results_short < 64 || atoi_results_short > 0x3fff) { print_string("MTU must be 64..16383\n"); return; } - REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (mtu >> 10) & 0xf, (mtu >> 2) & 0xff, - ((mtu & 0x3) << 6) | ((mtu >> 8) & 0x3f), mtu & 0xff); + REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (atoi_results_short >> 10) & 0xf, (atoi_results_short >> 2) & 0xff, + ((atoi_results_short & 0x3) << 6) | ((atoi_results_short >> 8) & 0x3f), atoi_results_short & 0xff); write_char('\n'); + return; + +err: + print_string("mtu [port] [size]\n"); + return; } -void sfp_print_measurements(uint8_t sfp) +bool sfp_print_measurements(uint8_t sfp) { - print_string("Options: "); print_byte(sfp_read_reg(sfp, 92)); write_char('\n'); + if (!sfp_read_block(sfp, 92, 1)) + return false; + + print_string("Options: "); print_byte(sfp_buf[0]); write_char('\n'); if (!(sfp_options[sfp] & 0x40)) - return; - print_string("Temp: "); print_byte(sfp_read_reg(sfp, 224)); print_byte(sfp_read_reg(sfp, 225)); write_char('\n'); - print_string("Vcc: "); print_byte(sfp_read_reg(sfp, 226)); print_byte(sfp_read_reg(sfp, 227)); write_char('\n'); - print_string("TX Bias: "); print_byte(sfp_read_reg(sfp, 228)); print_byte(sfp_read_reg(sfp, 229)); write_char('\n'); - print_string("TX Power: "); print_byte(sfp_read_reg(sfp, 230)); print_byte(sfp_read_reg(sfp, 231)); write_char('\n'); - print_string("RX Power: "); print_byte(sfp_read_reg(sfp, 232)); print_byte(sfp_read_reg(sfp, 233)); write_char('\n'); - print_string("Laser: "); print_byte(sfp_read_reg(sfp, 234)); print_byte(sfp_read_reg(sfp, 235)); write_char('\n'); - print_string("State: "); print_byte(sfp_read_reg(sfp, 238)); write_char('\n'); + return true; + if (!sfp_read_block(sfp, 224, 16)) + return false; + print_string("Temp: "); print_byte(sfp_buf[0]); print_byte(sfp_buf[1]); write_char('\n'); + print_string("Vcc: "); print_byte(sfp_buf[2]); print_byte(sfp_buf[3]); write_char('\n'); + print_string("TX Bias: "); print_byte(sfp_buf[4]); print_byte(sfp_buf[5]); write_char('\n'); + print_string("TX Power: "); print_byte(sfp_buf[6]); print_byte(sfp_buf[7]); write_char('\n'); + print_string("RX Power: "); print_byte(sfp_buf[8]); print_byte(sfp_buf[9]); write_char('\n'); + print_string("Laser: "); print_byte(sfp_buf[10]); print_byte(sfp_buf[11]); write_char('\n'); + print_string("State: "); print_byte(sfp_buf[14]); write_char('\n'); + + return true; } @@ -788,19 +905,26 @@ void parse_sfp(void) print_string(" - empty\n"); continue; } - print_string(" - Rate: "); print_byte(sfp_read_reg(slot, 12)); - print_string(" Encoding: "); print_byte(sfp_read_reg(slot, 11)); + if (!sfp_read_block(slot, 11, 2)) { + print_string(" - I2C read failed on this slot\n"); + continue; + } + print_string(" - Rate: "); print_byte(sfp_buf[1]); + print_string(" Encoding: "); print_byte(sfp_buf[0]); write_char('\n'); - sfp_print_info(slot); - sfp_print_measurements(slot); + if (!sfp_print_info(slot) || !sfp_print_measurements(slot)) + print_string("I2C read failed on this slot\n"); } return; } - if (cmd_buffer[cmd_words_b[1]] < '1' || cmd_buffer[cmd_words_b[1]] > '2' || cmd_buffer[cmd_words_b[1] + 1] != ' ' ) { + uint8_t idx = cmd_words_b[1]; + uint8_t ret = atoi_byte(idx); + idx += ret; + slot = atoi_results_u8 - 1; + if (ret == 0 || !cmd_is_space(idx) || slot > 1) { print_string("Illegal SFP slot number\n"); return; } - slot = cmd_buffer[cmd_words_b[1]] - '1'; if (slot >= machine.n_sfp) { print_string("SFP slot not present\n"); return; @@ -834,8 +958,6 @@ err: void parse_regget(void) { - uint16_t reg = 0; - if (cmd_words_len != 2) { goto err; } @@ -846,7 +968,7 @@ void parse_regget(void) goto err; } - reg = hexvalue[0]; + uint16_t reg = hexvalue[0]; if (hex_size == 2) { reg <<= 8; reg |= hexvalue[1]; @@ -869,8 +991,6 @@ err: void parse_regset(void) { - uint16_t reg = 0; - if (cmd_words_len != 3) { goto err; } @@ -880,7 +1000,7 @@ void parse_regset(void) goto err; } - reg = hexvalue[0]; + uint16_t reg = hexvalue[0]; if (hex_size == 2) { reg <<= 8; reg |= hexvalue[1]; @@ -923,9 +1043,10 @@ void parse_sdsget(void) goto err; } - if (atoi_byte(&sds_id, cmd_words_b[1])) { + if (!atoi_byte(cmd_words_b[1])) { goto err; } + sds_id = atoi_results_u8; hex_size = atoi_hex(cmd_words_b[2]); if (hex_size != 1) { @@ -967,9 +1088,10 @@ void parse_sdsset(void) goto err; } - if (atoi_byte(&sds_id, cmd_words_b[1])) { + if (!atoi_byte(cmd_words_b[1])) { goto err; } + sds_id = atoi_results_u8; hex_size = atoi_hex(cmd_words_b[2]); if (hex_size != 1) { @@ -1022,13 +1144,16 @@ void parse_phyget(void) goto err; } - if (atoi_byte(&phy_id, cmd_words_b[1])) { + if (!atoi_byte(cmd_words_b[1])) { goto err; } + phy_id = atoi_results_u8; - if (atoi_byte(&dev_id, cmd_words_b[2])) { + + if (!atoi_byte(cmd_words_b[2])) { goto err; } + dev_id = atoi_results_u8; hex_size = atoi_hex(cmd_words_b[3]); if (hex_size == 0 || hex_size > 2) { @@ -1068,13 +1193,16 @@ void parse_physet(void) goto err; } - if (atoi_byte(&phy_id, cmd_words_b[1])) { + if (!atoi_byte(cmd_words_b[1])) { goto err; } + phy_id = atoi_results_u8; - if (atoi_byte(&dev_id, cmd_words_b[2])) { + + if (!atoi_byte(cmd_words_b[2])) { goto err; } + dev_id = atoi_results_u8; hex_size = atoi_hex(cmd_words_b[3]); if (hex_size == 0 || hex_size > 2) { @@ -1134,7 +1262,7 @@ void parse_rnd(void) void parse_passwd(void) { - // cmd_words_len can be more then 2 if a space in the password. + // cmd_words_len can be more then 2 if a SPACE in the password. if (cmd_words_len >= 2) { uint8_t i = cmd_words_b[1]; uint8_t c = 0; @@ -1142,8 +1270,8 @@ void parse_passwd(void) do { c = cmd_buffer[i++]; passwd[j++] = c; - } while (c != '\0' && j < 20); - passwd[j] = '\0'; + } while (c != NUL && j < 20); + passwd[j] = NUL; return; } print_string("Missing password\n"); @@ -1167,10 +1295,13 @@ void parse_eee(void) if (cmd_buffer[idx] == 'g' || cmd_buffer[idx] == 'm') { // Word 2 is a speed (e.g., "2g5", "100m", "1g") speed_word = 2; - } else if (cmd_buffer[idx] == ' ' || cmd_buffer[idx] == '\0') { + } else if (cmd_is_space_or_nul(idx)) { // Word 2 is a port number - port = cmd_buffer[cmd_words_b[2]] - '1'; - port = machine.phys_to_log_port[port]; + if (cmd_parse_port_separator(idx) == 0) { + print_string("Speed word invalid, use: [100m|1g|2g5]\n"); + return; + } + port = atoi_results_u8; // Check if word 3 is a speed if (cmd_words_len >= 4) speed_word = 3; @@ -1213,17 +1344,12 @@ void parse_eee(void) void parse_bw(void) { - __xdata uint8_t port; - __xdata uint32_t bw = 0; - - if (cmd_words_len < 2) // Check for at least 2 arguments + if (cmd_words_len < 3) // Check for at least 3 arguments goto err; - port = cmd_buffer[cmd_words_b[2]] - '1'; - if (port > 9) + if (cmd_parse_port_separator(cmd_words_b[2]) == 0) goto err; - - port = machine.phys_to_log_port[port]; + uint8_t port = atoi_results_u8; if (cmd_compare(1, "status")) { bandwidth_status(port); @@ -1233,8 +1359,13 @@ void parse_bw(void) if (cmd_words_len < 4) // Check for at least 4 arguments goto err; + // Ensure first argument is only `in` or `out`. + __bit is_in = cmd_compare(1, "in") != 0; + if (!(is_in || cmd_compare(1, "out"))) + goto err; + if (cmd_compare(3, "drop")) { - if (cmd_compare(1, "in")) { + if (is_in) { bandwidth_ingress_drop(port); return; } @@ -1242,7 +1373,7 @@ void parse_bw(void) } if (cmd_compare(3, "fc")) { - if (cmd_compare(1, "in")) { + if (is_in) { bandwidth_ingress_fc(port); return; } @@ -1250,34 +1381,30 @@ void parse_bw(void) } if (cmd_compare(3, "off")) { - if (cmd_compare(1, "in")) { + if (is_in) { bandwidth_ingress_disable(port); - return; - } else if (cmd_compare(1, "out")) { + } else { bandwidth_egress_disable(port); - return; } - goto err; + return; } + __xdata uint32_t bw = 0; uint8_t hex_size = atoi_hex(cmd_words_b[3]); - if (hex_size == 0 || hex_size > 4) { + if (hex_size == 0 || hex_size > 4) goto err; - } + uint8_t i = 0; - while (hex_size) { + do { hex_size--; *(((uint8_t *) &bw) + hex_size) = hexvalue[i++]; - } + } while (hex_size); - if (cmd_compare(1, "in")) { + if (is_in) { bandwidth_ingress_set(port, bw); - } else if (cmd_compare(1, "out")) { - bandwidth_egress_set(port, bw); } else { - goto err; + bandwidth_egress_set(port, bw); } - return; err: @@ -1291,8 +1418,8 @@ void parse_syslog(void) print_string("Current syslog status: "); if (syslog_state.enabled) { print_string("enabled, sending to "); - itoa(syslog_state.server_ip[0]); write_char('.'); itoa(syslog_state.server_ip[1]); write_char('.'); - itoa(syslog_state.server_ip[2]); write_char('.'); itoa(syslog_state.server_ip[3]); + print_ip(syslog_state.server_ip); + write_char(':'); itoa_short(syslog_state.server_port); write_char('\n'); } else { print_string("disabled\n"); @@ -1307,10 +1434,9 @@ void parse_syslog(void) } else if (cmd_compare(1, "ip")) { if (cmd_words_len < 3) { // no additional arguemnt -> print current ip print_string("Current syslog IP: "); - itoa(syslog_state.server_ip[0]); write_char('.'); itoa(syslog_state.server_ip[1]); write_char('.'); - itoa(syslog_state.server_ip[2]); write_char('.'); itoa(syslog_state.server_ip[3]); + print_ip(syslog_state.server_ip); return; - } else if (!parse_ip(cmd_words_b[2])) { + } else if (parse_ip(cmd_words_b[2]) != 0) { uint8_t was_enabled = syslog_state.enabled; if (was_enabled) syslog_stop(); @@ -1322,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"); } } @@ -1350,7 +1499,7 @@ void cmd_tokenize(void) __banked while(1) { c = cmd_buffer[line_ptr]; - if (c == '\0') { + if (c == NUL) { // Store the word count cmd_words_len = word; break; @@ -1477,8 +1626,7 @@ void cmd_parser(void) __banked dhcp_start(); } else if (cmd_words_len == 1) { print_string("Current IP: "); - itoa(uip_hostaddr[0]); write_char('.'); itoa(uip_hostaddr[0] >> 8); write_char('.'); - itoa(uip_hostaddr[1]); write_char('.'); itoa(uip_hostaddr[1] >> 8); + print_ip(uip_hostaddr); if (dhcp_state.state == DHCP_LEASING) { print_string(" (dhcp, renewal in sec: "); print_short(dhcp_state.dhcp_timer); @@ -1490,46 +1638,43 @@ void cmd_parser(void) __banked } else { if (dhcp_state.state) dhcp_stop(); - if (!parse_ip(cmd_words_b[1])) { + if (parse_ip(cmd_words_b[1]) != 0) { uip_ipaddr(&uip_hostaddr, ip[0], ip[1], ip[2], ip[3]); print_string("Setting ip: "); - itoa(ip[0]); write_char('.'); itoa(ip[1]); write_char('.'); - itoa(ip[2]); write_char('.'); itoa(ip[3]); write_char('\n'); + print_ip(ip); write_char('\n'); } else { - print_string("Invalid IP address\n"); - print_string("Error: ip [|dhcp]\n"); - print_string(" The dhcp option enables the dhcp client, calling ip without options prints the current IP\n"); - print_string(" Calling with a valid IP address will stop any ongoing dhcp client and set the IP address\n"); + print_string("Invalid IP address\n" \ + "Error: ip [|dhcp]\n" \ + " The dhcp option enables the dhcp client, calling ip without options prints the current IP\n" \ + " Calling with a valid IP address will stop any ongoing dhcp client and set the IP address\n"); } } } else if (cmd_compare(0, "gw")) { if (cmd_words_len == 1) { print_string("Current gw: "); - itoa(uip_draddr[0]); write_char('.'); itoa(uip_draddr[0] >> 8); write_char('.'); - itoa(uip_draddr[1]); write_char('.'); itoa(uip_draddr[1] >> 8); + print_ip(uip_draddr); write_char('\n'); } else { - if (!parse_ip(cmd_words_b[1])) + if (parse_ip(cmd_words_b[1]) != 0) { uip_ipaddr(&uip_draddr, ip[0], ip[1], ip[2], ip[3]); - else - print_string("Invalid IP address\n"); - print_string("Setting gw: "); - itoa(ip[0]); write_char('.'); itoa(ip[1]); write_char('.'); - itoa(ip[2]); write_char('.'); itoa(ip[3]); + print_string("Setting gw: "); + print_ip(ip); write_char('\n'); + } else { + print_string("Invalid IP address\n" \ + "Error: gw \n"); + } } - write_char('\n'); } else if (cmd_compare(0, "netmask")) { if (cmd_words_len == 1) { print_string("Current netmask: "); - itoa(uip_netmask[0]); write_char('.'); itoa(uip_netmask[0] >> 8); write_char('.'); - itoa(uip_netmask[1]); write_char('.'); itoa(uip_netmask[1] >> 8); + print_ip(uip_netmask); write_char('\n'); } else { - if (!parse_ip(cmd_words_b[1])) + if (parse_ip(cmd_words_b[1]) != 0) { uip_ipaddr(&uip_netmask, ip[0], ip[1], ip[2], ip[3]); - else + print_string("Setting netmask: "); + print_ip(ip); write_char('\n'); + } else { print_string("Invalid IP address\n"); - print_string("Setting netmask: "); - itoa(ip[0]); write_char('.'); itoa(ip[1]); write_char('.'); - itoa(ip[2]); write_char('.'); itoa(ip[3]); + } } write_char('\n'); } else if (cmd_compare(0, "l2")) { @@ -1559,33 +1704,22 @@ void cmd_parser(void) __banked __xdata char *dst = hostname; for (uint8_t hn = 0; hn < sizeof(hostname) - 1; hn++) { uint8_t c = *hp++; - if (c == '\0' || c == '\r' || c == '\n') + if (c == NUL || c == '\r' || c == '\n') break; if (c < 0x20 || c > 0x7e || c == '"' || c == '\\') c = '.'; *dst++ = c; } - *dst = '\0'; + *dst = NUL; } else { print_string("Error: hostname [name] - the name must not contain spaces\n"); } } else if (cmd_compare(0, "stp")) { - if (cmd_compare(1, "on")) { - print_string("STP enabled\n"); - stpEnabled = 1; - stp_setup(); - } else { - print_string("STP disabled\n"); - stp_off(); - stpEnabled = 0; - } - } else if (cmd_compare(0, "pvid") && cmd_words_len == 3) { - __xdata uint16_t pvid; - if (cmd_buffer[cmd_words_b[1]] >= '1' - && cmd_buffer[cmd_words_b[1]] <= '9' - && cmd_buffer[cmd_words_b[1] + 1] <= ' ' - && !atoi_short(&pvid, cmd_words_b[2]) && pvid && pvid <= 4094) - port_pvid_set(machine.phys_to_log_port[cmd_buffer[cmd_words_b[1]] - '1'], pvid); + stp_parse(); + } else if (cmd_compare(0, "pvid")) { + if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 + && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) + port_pvid_set(atoi_results_u8, atoi_results_short); else print_string("Error: pvid <1-4094>\n"); } else if (cmd_compare(0, "vlan")) { @@ -1652,16 +1786,17 @@ void cmd_parser(void) __banked if (save_cmd && cmd_words_len) { - // Find end of the cmd-buffer, looking for the NULL-byte. + // Find end of the cmd-buffer, looking for the NUL-byte. uint8_t i = cmd_words_b[cmd_words_len - 1]; do { i++; - } while(cmd_buffer[i] != '\0'); + } while(cmd_buffer[i] != NUL); // Copy last cmd-buffer to history. cmd_history_ptr = (cmd_history_ptr + i) & CMD_HISTORY_MASK; __xdata uint16_t p = cmd_history_ptr; - cmd_history[cmd_history_ptr++] = '\n'; + cmd_history[cmd_history_ptr] = '\n'; + cmd_history_ptr = (cmd_history_ptr + 1) & CMD_HISTORY_MASK; do { i--; cmd_history[--p & CMD_HISTORY_MASK] = cmd_buffer[i]; @@ -1704,7 +1839,7 @@ void execute_config(void) __banked uint8_t c = 0; do { if (cmd_idx >= (CMD_BUF_SIZE - 1)) { - cmd_buffer[cmd_idx] = '\0'; + cmd_buffer[cmd_idx] = NUL; print_string("ERROR: Command too long: "); print_string_x(cmd_buffer); write_char('\n'); @@ -1713,7 +1848,7 @@ void execute_config(void) __banked } c = flash_buf[cfg_idx++]; if (c == 0 || c == '\n') { - cmd_buffer[cmd_idx] = '\0'; + cmd_buffer[cmd_idx] = NUL; if (cmd_idx) { cmd_tokenize(); if (err_status != ERR_OK) @@ -1749,7 +1884,7 @@ void execute_commands(__xdata uint8_t *p) __banked { while (1) { if (*p == 0 || *p == '\n' || *p == '\r') { if (cmd_idx) { - cmd_buffer[cmd_idx] = '\0'; + cmd_buffer[cmd_idx] = NUL; cmd_tokenize(); if (err_status != ERR_OK) return; @@ -1762,7 +1897,7 @@ void execute_commands(__xdata uint8_t *p) __banked { if (cmd_idx < (CMD_BUF_SIZE - 1)) { cmd_buffer[cmd_idx++] = *p; } else { - cmd_buffer[CMD_BUF_SIZE - 1] = '\0'; + cmd_buffer[CMD_BUF_SIZE - 1] = NUL; print_string("ERROR: Command too long: "); print_string_x(cmd_buffer); write_char('\n'); diff --git a/cmd_parser.h b/cmd_parser.h index a8bf7975..0afd0327 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -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; diff --git a/dhcp.c b/dhcp.c index ad5c5364..d821f805 100644 --- a/dhcp.c +++ b/dhcp.c @@ -351,7 +351,12 @@ void dhcp_start(void) __banked return; } get_random_32(); - dhcp_state.transaction_id = SFR_DATA_U32; + // Workaround SDCC bug 4070: dhcp_state.transaction_id = SFR_DATA_U32; + __xdata uint8_t * tid = &dhcp_state.transaction_id; + *tid++ = SFR_DATA_24; + *tid++ = SFR_DATA_16; + *tid++ = SFR_DATA_8; + *tid = SFR_DATA_0; dhcp_state.state = DHCP_START; print_string("dhcp_start done\n"); } diff --git a/doc/CpuPort.md b/doc/CpuPort.md index 9e0b0286..c10d0a15 100644 --- a/doc/CpuPort.md +++ b/doc/CpuPort.md @@ -64,3 +64,33 @@ Writing 0x1 to register 0x7850 will transmit the frame. The Ethernet frame checksum and the TCP checksum are automatically calculated (offloaded) by the ASIC before transmitting on the wire. + +## The RTL tag words + +The frame header uses the Realtek Remote Control Protocol (RRCP) format or +the like. + +The `flags` word: + +``` +bit15 EFID_EN | 14:12 EFID | 11 PRI_EN | 10:8 PRI | +bit7 KEEP | 6 VSEL | 5 LEARN_DIS | 4:0 VIDX +``` + +All fields are in network byte order. + +* `EFID_EN`, `EFID`: look the destination up under this filtering ID + instead of the port's own +* `PRI_EN`, `PRI`: force the given priority on the frame +* `KEEP`: keep the 802.1Q tagging of the frame exactly as injected, + bypassing the egress tagging rules of the port +* `VSEL`, `VIDX`: classify the frame into the VLAN at this index of the + VLAN table +* `LEARN_DIS`: do not learn the source address from this frame + +The `pmask` word: bit 15 is `ALLOW`, bits 14 to 0 are a port mask. + +* `ALLOW` clear: the mask is the egress set, the frame goes to exactly + the ports given +* `ALLOW` set: the ASIC looks the destination up as usual and the mask + only limits which ports the result may use diff --git a/doc/devices/2M-PCB43-V1.1.md b/doc/devices/2M-PCB43-V1.1.md index 28673e8a..97343f02 100644 --- a/doc/devices/2M-PCB43-V1.1.md +++ b/doc/devices/2M-PCB43-V1.1.md @@ -15,7 +15,7 @@ and sold by Mokerlink. The device is fully supported: - All 4 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps - The SFP+ port supports 1G, 2.5G and 10G modules -- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2 in machine.h if building yourself or the corresponding pre-compiled binary) +- LEDs work with the same indiciations as the OEM firmware (use KP_9000_6XHML_X2_V1_1 in machine.h if building yourself or the corresponding pre-compiled binary) - untested due to missing Hardware: SFP+ ports equipped with 1G or 2.5G SFPs. ### Hardware overview diff --git a/doc/devices/FG-8GT-1SX.md b/doc/devices/FG-8GT-1SX.md new file mode 100644 index 00000000..11b2369c --- /dev/null +++ b/doc/devices/FG-8GT-1SX.md @@ -0,0 +1,79 @@ +# FG-8GT-1SX + +Following is documentation for unmanaged switch marked as `FG-8GT-1SX`. + +Original software is running UART on 9600 baud rate. + +Using SPI clamp in-board is the only method for initial installation. + +### Brands + +| Brand | Type | Managed | PCB | Flash | Chip RTL | +|---------|------------|---------|------------|-------------|---------------| +| Ruiying | RY-8GT-1SX | No | FG-8GT-1SX | GD25Q80ESIG | 8373N + 8224N | + +### What works + +- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps +- SFP port with 1G/2.5G/10G modules +- LEDs + +### PCB overview + +**Board markings** + +- Top silkscreen: FG-8GT-1SX + +Top side: + + + +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 +DATA_IN_B = MAC[1..0] | vid<<16 | IVL<<29 | pmask[1:0]<<30 +DATA_IN_C = pmask[9:2] +``` + +Lookups are IVL, so an entry made for VID 0 is never matched and a caller adds +one entry per PVID in use. The write goes through the table access register +with the table selector set to the L2 lookup table, `TBL_L2_UNICAST` in the +code, a name that despite appearances covers the multicast entries as well. +The hardware hashes MAC and VID to pick the bucket slot by itself. +Writing the same MAC and VID again replaces the entry rather than adding a +second one, so a caller can retarget the mask at will, for instance back to all +ports to restore flooding. diff --git a/doc/stp.md b/doc/stp.md new file mode 100644 index 00000000..49bedd36 --- /dev/null +++ b/doc/stp.md @@ -0,0 +1,155 @@ +# Spanning Tree (STP / RSTP) + +The switch can take part in a spanning tree (IEEE 802.1D / 802.1w) so that +redundant links between bridges are blocked instead of forming a loop. The +implementation elects a root bridge from the BPDUs it receives, promotes ports +to forwarding once their listen period expires, ages the root out when it goes +silent, and blocks a port on which it sees its own BPDU. + +STP can be enabled and controlled via the web interface or the command line, +as follows: + +## Quick start + +``` +stp on # start participating +stp off # stop, all ports back to forwarding +``` + +Live status is on the Spanning Tree page of the web UI (or `/stp.json`), +and on the serial console via `stp status`. + +With no other bridge around, the switch elects itself root and every port ends +up forwarding — you can leave it on safely. Put the settings in the startup +config to make them survive a reboot: + +``` +stp prio 15 +stp port 1 edge on +stp on +``` + +## Hardware background + +BPDUs are addressed to `01:80:C2:00:00:00`, a reserved link-local group. The +ASIC's Reserved-Multicast action for that address decides what happens to the +frame. + +Forwarding to the CPU port works normally: the 8051 sits behind an ordinary +port of the internal switch and is an ordinary member of a forwarding mask. +The *trap* action does not deliver to it. Its destination is an external CPU +attached to a physical port (`cpuTag_externalCpuPort_set`, `EXT_CPU_CTRL` in +the vendor SDK), which these boards do not populate. The ACL trap and +redirect actions do not deliver to the 8051 either. + +Delivery therefore uses the *forward* action, constrained to the CPU port +by a static L2 multicast entry (`port_l2mc_set()`), one per VLAN in use: + +* while STP runs, the entry's member mask is the CPU port only — BPDUs reach + the CPU and are not flooded to other ports, as a participating bridge + requires; +* with STP off, the same entries are retargeted to all ports, restoring the + transparency an unmanaged switch is expected to have, so a surrounding + spanning tree can span *through* this device. + +A BPDU delivered this way is an ordinary frame to the port's ingress logic +and passes through its acceptable-frame-type filter. BPDUs are untagged, so a +port set to admit tagged frames only (`ingress t`) never delivers one +to the CPU. `stp_setup()` prints a warning for every STP-enabled port in that +state. + +Port states live in `RTL837X_MSTP_STATES (0x5310)`, two bits per port: +`00` disabled, `01` blocking, `10` learning, `11` forwarding. A port in +blocking forwards nothing between ports, but it still sends what the CPU +hands it and still passes a received BPDU up to the CPU, which is what lets +loop detection go on working on a port it has already blocked. + +## Timers + +`stp_timers()` runs at 50 Hz (the main loop idles on the 200 Hz system tick and +STP is called every fourth pass), which is what `STP_HZ` in `rtl837x_stp.h` +encodes. All configured values are in seconds: + +| setting | default | range | +|---|---|---| +| `stp hello ` | 2 | 1–10 | +| `stp maxage ` | 20 | 6–40 | +| `stp fwd ` | 15 | 4–30 | +| `stp txhold ` | 6 | 1–10 | + +A port entering the tree spends `fwd` seconds in blocking before it forwards +(an edge port skips the wait). Root information is discarded after `maxage` +seconds without a BPDU, and the switch then reclaims the root role. + +## Topology changes + +A change on a local non-edge port (the link coming or going, a port promoted +to forwarding) flushes the addresses learned on it and sets the TC flag in +our BPDUs for `maxage + fwd` seconds. A TC flag received in a BPDU is passed +on: the switch flushes the other non-edge ports once and keeps the flag in +its own BPDUs until one hello after the last flagged frame, so the +notification crosses the switch instead of dying at it. A legacy TCN is +acknowledged with TCA and then treated like a local change. + +## Bridge settings + +``` +stp prio <0-15> # bridge priority = n * 4096, default 8 (32768) +stp version rstp|stp # RST BPDUs (default) or legacy Config BPDUs +stp hello|maxage|fwd|txhold +``` + +The bridge with the lowest priority wins the root election; ties are broken by +the MAC address. If you do not want this switch to become the root of an +existing network, give it a worse priority than the current root — `stp prio 15` +(61440) is the usual "never me" value. + +## Per-port settings + +``` +stp port <1-9> on|off # take part in STP, or stay plain forwarding +stp port <1-9> edge on|off|auto # host-facing port handling (default: auto) +stp port <1-9> cost <0-200000000> # path cost, 0 = automatic (20000) +stp port <1-9> prio <0-240> # port priority, steps of 16 +stp port <1-9> guard none|bpdu|root +stp port <1-9> filter on|off # neither send nor accept BPDUs +stp port <1-9> p2p auto|on|off +``` + +**edge** — an edge port forwards immediately and does not trigger a +topology change when its link comes and goes; `auto` promotes a port to edge +after three seconds without a BPDU, and demotes it as soon as one arrives. Use +`edge on` for ports where only hosts are attached. + +**guard** — `bpdu` disables a port as soon as a BPDU arrives on it (a host port +should never see one); `root` keeps a port from ever becoming the path to the +root, which protects an existing topology from a newly attached bridge that +claims a better priority. + +**filter** — the port neither sends nor accepts BPDUs. Useful when the device +on the far side reacts badly to them (some unmanaged switches with loop +prevention cut the link) but you still want STP on the rest of the ports. + +## Status + +The Spanning Tree page shows the elected root (priority and MAC), the path cost +to it, the root port, the topology-change counter and, per port, the live state +read from the ASIC together with the configured options. The same data is +available as JSON: + +``` +GET /stp.json +``` + +The `stp status` command prints the same view on the serial console. + +## Limitations + +* One spanning-tree instance; no MSTP, no per-VLAN trees. +* No proposal/agreement handshake — an RST-capable neighbour will still + converge, but through the timers rather than the fast transition. +* Port roles are approximated: the root port and designated ports are + distinguished, alternate/backup are not. +* Topology changes propagate away from the root only: nothing is announced + on the root port (no TCN and no BPDUs at all), so bridges upstream rely on + their own detection. diff --git a/doc/supported_devices.md b/doc/supported_devices.md index bb187bfd..3cdd1ecc 100644 --- a/doc/supported_devices.md +++ b/doc/supported_devices.md @@ -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 | @@ -15,13 +16,16 @@ The following devices have been tested and are fully working: | Horaco | ZX310S-4T2XT | Yes | [PCB-SL310S-4T2XT-V1.0.0-22273](devices/ZX310S-4T2XT.md) | 2M | 6 | | Horaco | ZX-SG4T2 | No | [SWTG024AS-A-V2.0.1_19650_4C_2SFP](devices/SWTG024AS-A-V2.0.1_4C_2SFP.md) | 0.5M | 4 + 2 | | Horaco | ZX-SWTG124AS | Yes | [SWTG024AS-v2.0](devices/SWTG024AS.md) | | 4 + 2 | -| Keeplink | KP-9000-6XH-X2 | No | [2M-PCB43-V2.1](devices/KP-9000-6XH-X2.md) | | 4 + 2 | +| Keeplink | KP-9000-6XH-X2 / KP-9000-6XHML-X2 | No/Yes | [2M-PCB43-V1.2 / V2.1](devices/KP-9000-6XH-X2.md) | | 4 + 2 | | 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 | @@ -29,6 +33,10 @@ The following devices have been tested and are fully working: | XikeStor | SKS3200-8E1X | Yes | [SWTG118AS-V2.1-17462](devices/SWTGW218AS.md) | 2M | 8 + 1 | | Ztyuav | Z-QWYT0402 | No | [PCB-K0402WS-V3.0](devices/PCB-K0402WS-V3.0.md) | | 4 + 2 | +For KP-9000-6XH-X2 / KP-9000-6XHML-X2 / Mokerlink 2G040210GSM devices, select +the machine target by PCB revision. The ML/non-ML or managed/unmanaged label +alone does not identify the wiring. + Other device based on RTL8272/3 that may work are described here: [Up-N-Atoms 2.5 GBit RTL Switch hacking guide](https://github.com/up-n-atom/SWTG118AS) Many of the RTL8272/3 devices come in versions with PoE support. The RTLPlayground usually also diff --git a/doc/vlan.md b/doc/vlan.md index a3d50c74..907f94be 100644 --- a/doc/vlan.md +++ b/doc/vlan.md @@ -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 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 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 d deletes the VLAN diff --git a/html/index.html b/html/index.html index 332fc527..de315ca9 100644 --- a/html/index.html +++ b/html/index.html @@ -49,7 +49,7 @@

Port Configuration

- +
Port Name Current Link SpeedSet SpeedDisabledApply
Port Name Current Link SpeedConnected devicesSet SpeedDisabledApply

Configure Maximum Frame Size (MTU) forwarded at Port

@@ -132,6 +132,40 @@
+
+

Spanning Tree (RSTP)

+

Mode: +

+
+

Bridge settings

+ + + + + + + + + + + + +
PriorityVersionHello [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 configuration

+ + + + +
PortStatePath Cost
(0 = Auto)
PriorityEdge PortBPDU FilterGuardPoint-to-Point
+

Port status

+ + + + +
PortPort StateRoleDesignated BridgeDesignated Port IDDesignated CostOper. EdgeOper. P2P
+
+

Mirror Configuration


@@ -258,6 +292,8 @@


Be careful when entering console commands, you can lock yourself out!
+
+

       
diff --git a/html/main.js b/html/main.js index 78ba9c24..37bf7b26 100644 --- a/html/main.js +++ b/html/main.js @@ -43,6 +43,8 @@ var LANG = { port_col_speed: 'Current Link Speed', port_col_set_speed: 'Set Speed', port_col_disabled: 'Disabled', + port_col_devices: 'Connected devices', + port_devices: 'devices', port_col_apply: 'Apply', port_mtu_heading: 'Configure Maximum Frame Size (MTU) forwarded at Port', port_auto: 'Auto', @@ -230,6 +232,8 @@ var LANG = { port_col_speed: '現在のリンク速度', port_col_set_speed: '速度設定', port_col_disabled: '無効', + port_col_devices: '接続デバイス', + port_devices: 'デバイス', port_col_apply: '適用', port_mtu_heading: 'ポートの最大フレームサイズ (MTU) 設定', port_auto: '自動', @@ -417,6 +421,8 @@ var LANG = { port_col_speed: '当前链路速率', port_col_set_speed: '设置速率', port_col_disabled: '禁用', + port_col_devices: '已连接设备', + port_devices: '台设备', port_col_apply: '应用', port_mtu_heading: '配置端口转发的最大帧大小 (MTU)', port_auto: '自动', @@ -903,7 +909,8 @@ document.addEventListener('DOMContentLoaded', function() { + "
  • Port Configuration
  • " + "
  • Port Statistics
  • " + "
  • VLAN
  • " - + "
  • L2 Configuration
  • " + + "
  • L2 Configuration
  • " + + "
  • Spanning Tree
  • " + "
  • Mirroring
  • " + "
  • Link Aggregation
  • " + "
  • EEE
  • " @@ -981,10 +988,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]$/, @@ -996,17 +1004,35 @@ const conf_cmds = [ /^laghash\s+\d(\s+\w+)+$/, /^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/, /^stp\s+(on|off)$/, + /^stp\s+(prio|hello|maxage|fwd|txhold)\s+\d{1,2}$/, + /^stp\s+version\s+(rstp|stp)$/, + /^stp\s+port\s+\d{1,2}\s+(on|off)$/, + /^stp\s+port\s+\d{1,2}\s+edge\s+(on|off|auto)$/, + /^stp\s+port\s+\d{1,2}\s+cost\s+\d{1,9}$/, + /^stp\s+port\s+\d{1,2}\s+prio\s+\d{1,3}$/, + /^stp\s+port\s+\d{1,2}\s+guard\s+(none|bpdu|root)$/, + /^stp\s+port\s+\d{1,2}\s+filter\s+(on|off)$/, + /^stp\s+port\s+\d{1,2}\s+p2p\s+(auto|on|off)$/, /^igmp\s+(on|off)$/, /^mtu\s+\d{1,2}\s+\d+$/, /^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)/, @@ -1020,7 +1046,8 @@ const conf_overwrite = [ /^lag\s+\d+\b/, /^laghash\b/, /^isolate\s+\d{1,2}\b/, - /^stp\b/, + /^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/, + /^stp\s+port\s+\d{1,2}\s+(edge|cost|prio|guard|filter|p2p)\b/, /^igmp\b/, /^mtu\s+\d{1,2}\b/, /^bw\s+(in|out)\s+\d{1,2}\b/, @@ -1043,6 +1070,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); @@ -1121,14 +1155,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}`); } } @@ -1317,6 +1365,7 @@ function createPortTable() { let portName = portNames[physToLogPort[i-1]] || ''; td = tr.insertCell(); td.appendChild(document.createTextNode(portName)); td = tr.insertCell(); td.innerHTML = linkText(pState[i] + 1); + tr.insertCell(); // filled by devRender() td = tr.insertCell(); td.innerHTML = sSelect.replaceAll("speed_sel", "speed_sel_" + i); td = tr.insertCell(); td.innerHTML = dSwitch.replaceAll("disable_port", "disable_port_" + i) .replace("portOnOff()", "portOnOff(" + i + ")"); @@ -1412,6 +1461,46 @@ async function applyMTU(port) { } } +function devRender(entries) { + var tbl = document.getElementById('speedtable'); + if (tbl.rows.length <= 2 || !numPorts) + return; + var perPort = {}; + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + if (e.port == 'CPU') + continue; + if (!perPort[e.port]) + perPort[e.port] = []; + if (perPort[e.port].indexOf(e.mac) < 0) + perPort[e.port].push(e.mac); + } + for (let i = 1; i <= numPorts; i++) { + if (pIsSFP[i-1]) + continue; + var cell = tbl.rows[i].cells[3]; + var macs = perPort[i] || []; + if (macs.length == 1) { + cell.textContent = macs[0]; + cell.title = ''; + } else if (macs.length > 1) { + cell.textContent = macs.length + ' ' + t('port_devices'); + cell.title = macs.join('\n'); + } else { + cell.textContent = ''; + cell.title = ''; + } + } +} + +function devWalk() { + walkL2(function(entries, ok) { + if (ok) + devRender(entries); + setTimeout(devWalk, 15000); + }); +} + function getMTUs() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { @@ -1438,6 +1527,7 @@ sectionInits.ports = function() { createPortTable(); updatePortTable(); getMTUs() + setTimeout(devWalk, 3000); setSectionInterval(update, 2000); setSectionInterval(updatePortTable, 1000); }); @@ -1747,6 +1837,13 @@ function fillL2(s) renderL2(); } +function delL2Button(e) +{ + if (e.port == 'CPU') + return ''; + return ''; +} + function paintL2(tbl, s) { console.log("L2: ", JSON.stringify(s)); @@ -1758,14 +1855,14 @@ function paintL2(tbl, s) tbl.rows[i+1].cells[1].innerHTML = `${e.mac}`; tbl.rows[i+1].cells[2].innerHTML = `${e.vlan}`; tbl.rows[i+1].cells[3].innerHTML = `${e.type}`; - tbl.rows[i+1].cells[4].innerHTML = ''; + tbl.rows[i+1].cells[4].innerHTML = delL2Button(e); } else { const tr = tbl.insertRow(); let td = tr.insertCell(); td.innerHTML = `${e.port}`; td = tr.insertCell(); td.innerHTML = `${e.mac}`; td = tr.insertCell(); td.innerHTML = `${e.vlan}`; td = tr.insertCell(); td.innerHTML = `${e.type}`; - td = tr.insertCell(); td.innerHTML = ''; + td = tr.insertCell(); td.innerHTML = delL2Button(e); } } for (let i = tbl.rows.length - 1; i > s.length; i--) @@ -2446,6 +2543,196 @@ sectionInits.bandwidth = function() { }); }; +/* STP page (stp.html / stp.js merged) */ +const STP_STATES = ["Disabled", "Blocking", "Learning", "Forwarding"]; +const STP_ROLES = ["-", "Root", "Designated", "Alternate"]; + +const PF_ENABLED = 1, PF_ADMEDGE = 2, PF_AUTOEDGE = 4, PF_BPDUGUARD = 8, + PF_ROOTGUARD = 16, PF_FILTER = 32, PF_OPEREDGE = 64, PF_TRIPPED = 128; + +var stpDirty = false; +var stpRows = 0; + +async function stpCmd(cmd) { + stpDirty = true; + try { + await fetch('/cmd', { method: 'POST', body: cmd }); + } catch(err) { + console.error(`Error: ${err}`); + } + stpDirty = false; + fetchStp(); +} + +function sel(id, opts, onch) { + const s = document.createElement("select"); + s.id = id; + for (const [v, label] of opts) { + const o = document.createElement("option"); + o.value = v; o.textContent = label; + s.appendChild(o); + } + s.addEventListener("change", onch); + return s; +} + +function num(id, min, max, onch) { + const n = document.createElement("input"); + n.type = "number"; n.id = id; n.min = min; n.max = max; n.style.width = "4em"; + n.addEventListener("change", onch); + return n; +} + +function buildPortsTable(ports) { + const tbl = document.getElementById("stpPortsTbl"); + const stat = document.getElementById("stpStatTbl"); + for (const p of [...ports].sort((a, b) => a.p - b.p)) { + const tr = tbl.insertRow(); + tr.insertCell().textContent = p.p; + tr.insertCell().appendChild(sel("en_" + p.p, + [["on","Enable"],["off","Disable"]], + e => stpCmd("stp port " + p.p + " " + e.target.value))); + const pc = num("cost_" + p.p, 0, 200000000, + e => stpCmd("stp port " + p.p + " cost " + e.target.value)); + pc.style.width = "7em"; + pc.title = "0 - 200000000 (0 = Auto)"; + tr.insertCell().appendChild(pc); + const pr = sel("prio_" + p.p, [], + e => stpCmd("stp port " + p.p + " prio " + e.target.value)); + for (let v = 0; v <= 240; v += 16) { + const o = document.createElement("option"); + o.value = v; o.textContent = v + (v === 128 ? " (default)" : ""); + pr.appendChild(o); + } + tr.insertCell().appendChild(pr); + tr.insertCell().appendChild(sel("edge_" + p.p, + [["auto","Auto"],["on","Enable"],["off","Disable"]], + e => stpCmd("stp port " + p.p + " edge " + e.target.value))); + tr.insertCell().appendChild(sel("filt_" + p.p, + [["off","Disable"],["on","Enable"]], + e => stpCmd("stp port " + p.p + " filter " + e.target.value))); + tr.insertCell().appendChild(sel("guard_" + p.p, + [["none","None"],["bpdu","BPDU"],["root","Root"]], + e => stpCmd("stp port " + p.p + " guard " + e.target.value))); + tr.insertCell().appendChild(sel("p2p_" + p.p, + [["auto","Auto"],["on","Enable"],["off","Disable"]], + e => stpCmd("stp port " + p.p + " p2p " + e.target.value))); + + const sr = stat.insertRow(); + sr.insertCell().textContent = p.p; + for (const id of ["st","role","db","dp","dc","oe","op"]) + sr.insertCell().id = id + "_" + p.p; + } + stpRows = ports.length; +} + +function bridgeSelf(s) { + return fmtBridgeId((s.prio * 4096).toString(16).padStart(4, "0") + s.myMac); +} + +function fmtBridgeId(h) { + if (!h || h.length < 16) return ""; + const prio = parseInt(h.slice(0, 4), 16); + const mac = h.slice(4).replace(/(..)(?=.)/g, "$1:"); + return prio + "-" + mac.toUpperCase(); +} + +function fetchStp() { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4 && this.status == 200) { + const s = JSON.parse(xhttp.responseText); + if (!stpRows) + buildPortsTable(s.ports); + document.getElementById("stpStat").textContent = s.on + ? (s.weRoot + ? "This switch (" + bridgeSelf(s) + ") is the root bridge \u2014 topology changes: " + + parseInt(s.tc, 16) + : "This switch: " + bridgeSelf(s) + + " \u2014 root bridge: " + fmtBridgeId(s.rootPrio + s.rootMac) + + " via port " + s.rootPort + " \u2014 path cost: " + parseInt(s.cost, 16) + + " \u2014 topology changes: " + parseInt(s.tc, 16)) + : ""; + for (const p of s.ports) { + const trip = (p.f & PF_TRIPPED) ? " (guard!)" : ""; + document.getElementById("st_" + p.p).textContent = + s.on ? STP_STATES[p.st] + trip : "-"; + document.getElementById("role_" + p.p).textContent = + s.on ? STP_ROLES[p.role] : "-"; + document.getElementById("db_" + p.p).textContent = s.on ? fmtBridgeId(p.db) : "-"; + document.getElementById("dp_" + p.p).textContent = + s.on ? (parseInt(p.dp.slice(0, 2), 16) + "-" + parseInt(p.dp.slice(2), 16)) : "-"; + document.getElementById("dc_" + p.p).textContent = s.on ? parseInt(p.dc, 16) : "-"; + document.getElementById("oe_" + p.p).textContent = + s.on ? ((p.f & PF_OPEREDGE) ? "True" : "False") : "-"; + document.getElementById("op_" + p.p).textContent = s.on ? (p.p2 == 2 ? "False" : "True") : "-"; + } + if (stpDirty) + return; + document.getElementById("stpMode").value = s.on ? "on" : "off"; + document.getElementById("bPrio").value = s.prio; + document.getElementById("bVer").value = s.rstp ? "rstp" : "stp"; + document.getElementById("bHello").value = s.hello; + document.getElementById("bMaxage").value = s.maxage; + document.getElementById("bFwd").value = s.fwd; + document.getElementById("bTxhold").value = s.txhold; + for (const p of s.ports) { + document.getElementById("en_" + p.p).value = (p.f & PF_ENABLED) ? "on" : "off"; + document.getElementById("edge_" + p.p).value = + (p.f & PF_ADMEDGE) ? "on" : ((p.f & PF_AUTOEDGE) ? "auto" : "off"); + document.getElementById("cost_" + p.p).value = parseInt(p.pc, 16); + document.getElementById("prio_" + p.p).value = p.prio; + document.getElementById("p2p_" + p.p).value = ["auto","on","off"][p.p2]; + document.getElementById("guard_" + p.p).value = + (p.f & PF_BPDUGUARD) ? "bpdu" : ((p.f & PF_ROOTGUARD) ? "root" : "none"); + document.getElementById("filt_" + p.p).value = (p.f & PF_FILTER) ? "on" : "off"; + } + } + }; + xhttp.open("GET", `/stp.json`, true); + sendXHTTP(xhttp); +} + +async function stpSub() { + const on = document.getElementById("stpMode").value === "on"; + document.getElementById("stpStat").textContent = on + ? "Enabling STP. The ports start blocked and take up to " + + (2 * document.getElementById("bFwd").value) + + " s to reach forwarding, and this page can stay silent until they do." + : "Disabling STP."; + await stpCmd(on ? "stp on" : "stp off"); +} + +function initStpSection() { + const bp = document.getElementById("bPrio"); + if (!bp || bp.options.length) return; + for (let i = 0; i < 16; i++) { + const o = document.createElement("option"); + o.value = i; o.textContent = (i * 4096) + (i === 8 ? " (default)" : ""); + bp.appendChild(o); + } + bp.addEventListener("change", e => stpCmd("stp prio " + e.target.value)); + document.getElementById("bVer") + .addEventListener("change", e => stpCmd("stp version " + e.target.value)); + document.getElementById("bHello") + .addEventListener("change", e => stpCmd("stp hello " + e.target.value)); + document.getElementById("bMaxage") + .addEventListener("change", e => stpCmd("stp maxage " + e.target.value)); + document.getElementById("bFwd") + .addEventListener("change", e => stpCmd("stp fwd " + e.target.value)); + document.getElementById("bTxhold") + .addEventListener("change", e => stpCmd("stp txhold " + e.target.value)); + document.getElementById("stpMode") + .addEventListener("change", () => { stpDirty = true; }); +} + +sectionInits.stp = function() { + initStpSection(); + fetchStp(); + setSectionInterval(fetchStp, 2000); + setSectionInterval(update, 2000); +}; + document.addEventListener("DOMContentLoaded", function () { if (!document.getElementById('infoTable')) return; fetch('/information.json') diff --git a/httpd/httpd.c b/httpd/httpd.c index 5655426b..3fb48308 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -20,6 +20,8 @@ #pragma constseg BANK1 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[]; @@ -41,8 +43,19 @@ __xdata uint32_t cont_addr; // HTTP header properties __xdata uint8_t boundary[72]; -__xdata uint8_t *content_type = 0; -__xdata uint8_t *session = 0; + +// a client may split the request anywhere, including inside a boundary or a +// part header, so a configuration upload is parsed only once it is complete; +// sized for a full config sector plus the multipart framing around it +#define CONFIG_UPLOAD_BUF (CONFIG_LEN + 384) +__xdata uint8_t config_upload; +__xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; +// bytes buffered in config_buf so far (config body, or a firmware part +// header); accumulates across TCP segments +__xdata uint16_t pre_acc; +__xdata uint8_t * __xdata content_type = 0; +__xdata uint8_t * __xdata session = 0; +__xdata uint16_t content_length; // Global variables holding POST state __xdata uint16_t bindex; // Current index into the boundary @@ -50,11 +63,20 @@ __xdata uint8_t verify_crc; __xdata uint32_t max_upload; __xdata uint16_t short_parsed; +#define POSTBODY_CMD 1 +#define POSTBODY_LOGIN 2 +#define POSTBODY_TIMEOUT (5 * SYS_TICK_HZ) +__xdata uint8_t postbody_endpoint; +__xdata uint16_t postbody_start; + __xdata char passwd[21]; +// Set when a verified firmware upload awaits its response ACK, after +// which the chip resets to apply the staged image +__xdata uint8_t fw_reset_pending; __xdata char session_id[SESSION_ID_LENGTH + 1]; __xdata uint8_t authenticated; __xdata uint32_t now; -__xdata uint8_t *timeptr; +__xdata uint8_t * __xdata timeptr; __xdata uint32_t last_session_use; #define TSTATE_NONE 0 @@ -63,6 +85,7 @@ __xdata uint32_t last_session_use; #define TSTATE_CLOSED 3 #define TSTATE_POST 4 #define TSTATE_MULTIPART 5 +#define TSTATE_POSTBODY 6 extern __xdata uint16_t crc_value; __xdata uint16_t crc_final; @@ -77,10 +100,12 @@ inline uint8_t is_separator(uint8_t c) void httpd_init(void) __banked { + config_upload = 0; // xdata is not zeroed by the startup code __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); // Start listening to port 80 uip_listen(HTONS(80)); s->tstate = TSTATE_CLOSED; + fw_reset_pending = 0; // xdata is not zeroed by the startup code } @@ -109,8 +134,8 @@ bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p) u = *xdata_str_p++; c = *code_str_p++; - if (c == '\0') { - if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r') + if (c == NUL) { + if (u != NUL && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r') return false; return true; } @@ -122,6 +147,24 @@ bool is_word(__xdata uint8_t *xdata_str_p, __code uint8_t * __xdata code_str_p) } +/* name must be lower-case, starting with the '\n' of the previous line's end */ +__xdata uint8_t *header_value(__xdata uint8_t *p, __code uint8_t *name) +{ + uint8_t u, c; + + while ((c = *name++)) { + u = *p++; + if (u >= 'A' && u <= 'Z') + u += 'a' - 'A'; + if (u != c) + return 0; + } + while (*p == ' ' || *p == '\t') + p++; + return p; +} + + bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p) { uint8_t u, s; @@ -130,8 +173,8 @@ bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p) u = *uri_str_p++; s = *src_str_p++; - if (s == '\0') { - if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r') + if (s == NUL) { + if (u != NUL && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r') return false; return true; } @@ -165,7 +208,7 @@ bool is_url_word_x(__xdata uint8_t *uri_str_p, __xdata uint8_t *src_str_p) } -bool is_word_x(__xdata uint8_t *lhs_str_p, __xdata uint8_t *rhs_str_p) +bool is_word_x(__xdata uint8_t * lhs_str_p, __xdata uint8_t * rhs_str_p) { uint8_t u, c; @@ -173,9 +216,9 @@ bool is_word_x(__xdata uint8_t *lhs_str_p, __xdata uint8_t *rhs_str_p) u = *lhs_str_p++; c = *rhs_str_p++; - if (c == '\0') { + if (c == NUL) { /* ';' separates cookies in a Cookie header, so it ends a value too. */ - if (u != '\0' && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r' && u != ';') + if (u != NUL && u != ' ' && u != '\t' && u != ':' && u != '?' && u != '=' && u != '\n' && u != '\r' && u != ';') return false; return true; } @@ -197,7 +240,10 @@ uint8_t parse_short(__xdata uint8_t *p) c = *p++ - '0'; if (c > 9) { break; } err = 0; - short_parsed = (short_parsed * 10) + c; + if (short_parsed > 6552) + short_parsed = 0xffff; + else + short_parsed = (short_parsed * 10) + c; } return err; } @@ -230,41 +276,48 @@ void send_unauthorized(void) } -__xdata uint8_t *skip_boundary(__xdata uint8_t *p) +void send_length_required(void) { - while (*p) { - if (is_word_x(p, boundary)) - return p + strlen_x(boundary); - p++; - } - return p; + slen = strtox(outbuf, "HTTP/1.1 411 Length Required\r\nConnection: close\r\n\r\n"); } -__xdata uint8_t *scan_header(__xdata uint8_t *p) +void send_ok(void) { + slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); +} + + +__xdata uint8_t *scan_header(__xdata uint8_t * __xdata p) +{ + __xdata uint8_t *v; + content_type = 0; + content_length = 0; session = 0; authenticated = 0; - while (*p != '\r' || *(p + 1) != '\n' || *(p + 2) != '\r' || *(p + 3) != '\n') { + while (!strstart(p, "\r\n\r\n")) { dbg_char(*p); - if (!*p++) + if (!*p) break; - if (is_word(p, "\nContent-Type:")) - content_type = p + 15; - else if (is_word(p, "\nCookie:")) { + p++; + if ((v = header_value(p, "\ncontent-type:"))) + content_type = v; + else if ((v = header_value(p, "\ncontent-length:"))) { + parse_short(v); + content_length = short_parsed; + } else if ((v = header_value(p, "\ncookie:"))) { /* Scan for the "session" key: the header may hold several * cookies in any order. Match "session" not "session=" - * is_word() requires a separator after the match and '=' is * one, so this also rejects a longer key like "sessionx". */ - __xdata uint8_t *c = p + 8; /* past "\nCookie:" */ - while (*c && *c != '\r' && *c != '\n') { - if (is_word(c, "session")) { - session = c + 8; /* past "session=" */ + while (*v && *v != '\r' && *v != '\n') { + if (is_word(v, "session")) { + session = v + 8; /* past "session=" */ break; } - c++; + v++; } } } @@ -300,10 +353,12 @@ __xdata uint8_t *scan_header(__xdata uint8_t *p) return p; } - -void gen_random_bytes(__xdata uint8_t *b, uint8_t bytes) +/* + * Generate random HEX-chars at the buffer location. + */ +void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes) { - __xdata uint8_t i = 0; + uint8_t i = 0; while (bytes) { if (!i) get_random_32(); @@ -315,21 +370,110 @@ void gen_random_bytes(__xdata uint8_t *b, uint8_t bytes) } +/* 0: body incomplete, 1: configuration stored, 2: malformed */ +static uint8_t config_take(void) +{ + // #386: needs static, otherwise it still lands in SRAM/DSEG + static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; + __xdata uint8_t cfg_bl; + + cfg_bl = strlen_x(boundary); + + // the body is complete once the closing boundary has arrived + cfg_last = 0; + while (1) { + if (cfg_last + cfg_bl + 1 >= pre_acc) + return 0; + if (strstart_x(&config_buf[cfg_last], boundary) + && strstart(&config_buf[cfg_last + cfg_bl], "--")) + break; + cfg_last++; + } + + // every part lies ahead of the closing boundary, so it bounds the walk + cfg_pos = 0; + while (cfg_pos < cfg_last) { + if (!strstart_x(&config_buf[cfg_pos], boundary)) { + cfg_pos++; + continue; + } + cfg_hdr = cfg_pos + cfg_bl; + cfg_body = cfg_hdr; + while (1) { + if (cfg_body + 3 >= cfg_last) + return 2; + if (strstart(&config_buf[cfg_body], "\r\n\r\n")) + break; + cfg_body++; + } + cfg_end = cfg_body; + cfg_body += 4; + // reaching cfg_last is a match: the last part ends at the closing boundary + while (cfg_end < cfg_last && !strstart_x(&config_buf[cfg_end], boundary)) + cfg_end++; + while (cfg_hdr + 8 < cfg_body) { + // the part carrying a filename holds the configuration + if (strstart(&config_buf[cfg_hdr], "filename")) { + // the payload plus its terminator must fit the sector + if (cfg_end - cfg_body + 1 > CONFIG_LEN) + return 2; + config_buf[cfg_end] = 0; + flash_region.addr = CONFIG_START; + flash_sector_erase(); + flash_region.addr = CONFIG_START; + flash_region.len = cfg_end - cfg_body + 1; + flash_write_bytes(config_buf + cfg_body); + return 1; + } + cfg_hdr++; + } + cfg_pos = cfg_end; + } + return 2; +} + + +// unlike scan_header(), keeps no auth state, so it may run on every buffered segment +static uint16_t preamble_payload_start(uint16_t n) +{ + uint16_t pos; + + for (pos = 0; pos + 24 <= n; pos++) { + if (strstart(&config_buf[pos], "application/octet-stream")) + break; + } + if (pos + 24 > n) + return 0; + pos += 24; + while (pos + 3 < n && !strstart(&config_buf[pos], "\r\n\r\n")) + pos++; + if (pos + 3 >= n) + return 0; + return pos + 4; +} + + +// Source window for stream_upload(); filled by the caller before the call +__xdata struct { + __xdata uint8_t *p; + uint16_t bptr; + uint16_t plen; +} upload_settings; + /* * Reads post data from the http stream and writes it into flash memory - * Input: the current position in the TCP buffer (uip_appdata) + * Input: upload_settings, set by the caller * Returns 1: More data to read, 0: Upload complete, all parts reads */ -uint8_t stream_upload(uint16_t bptr) +uint8_t stream_upload(void) { - __xdata uint8_t *p = uip_appdata; __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); dbg_string("Stream_upload called: "); - dbg_short(bptr); dbg_char('\n'); + dbg_short(upload_settings.bptr); dbg_char('\n'); do { - if (bptr >= uip_len) { + if (upload_settings.bptr >= upload_settings.plen) { s->tstate = TSTATE_POST; return 1; } @@ -342,17 +486,23 @@ uint8_t stream_upload(uint16_t bptr) flash_write_bytes(flash_buf); uptr += write_len; write_len = 0; - // TODO: This is a bit premature, what about a nice web-page saying the device will reset??? if (verify_crc) { dbg_string("CRC16: "); dbg_short(crc_final); dbg_char('\n'); + // Both bodies are 33 bytes; Content-Length lets the + // browser complete the response without waiting for + // the connection close (which a reset would swallow) if (crc_final == 0xb001) { print_string("Checksum OK.\nUpload to flash done, will reset!\n"); - // close connection to avoid retries by browser - uip_close(); - reset_chip(); + slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nContent-Length: 33\r\n" + "Content-Type: text/plain\r\n\r\n" + "OK: checksum verified, rebooting\n"); + // Reset once the response is fully ACKed + fw_reset_pending = 1; } else { print_string("Checksum incorrect! Aborting.\n"); - uip_close(); + slen = strtox(outbuf, "HTTP/1.1 400 Bad Request\r\nContent-Length: 33\r\n" + "Content-Type: text/plain\r\n\r\n" + "NO: checksum failed, not applied\n"); } } // Make sure there is a 0 at the end of the uploaded data @@ -360,18 +510,15 @@ uint8_t stream_upload(uint16_t bptr) flash_region.addr = uptr; flash_region.len = 1; flash_write_bytes(flash_buf); - if (bptr >= uip_len) + if (upload_settings.bptr >= upload_settings.plen) return 0; - if(!verify_crc) - //ugly hack to signal connection finished after config upload. - uip_close(); return 1; } - if (p[bptr] == boundary[bindex]) { + if (upload_settings.p[upload_settings.bptr] == boundary[bindex]) { if (!bindex) crc_final = crc_value; - crc16(p + bptr); - bptr++; + crc16(upload_settings.p + upload_settings.bptr); + upload_settings.bptr++; bindex++; } else { if (bindex) { @@ -379,8 +526,8 @@ uint8_t stream_upload(uint16_t bptr) write_len += bindex; bindex = 0; } - crc16(p + bptr); - flash_buf[write_len++] = p[bptr++]; + crc16(upload_settings.p + upload_settings.bptr); + flash_buf[write_len++] = upload_settings.p[upload_settings.bptr++]; if (write_len >= FLASH_PAGE_SIZE) { dbg_string("len: "); dbg_short(write_len); dbg_char(' '); dbg_string("CRC16: "); dbg_short(crc_value); dbg_char('\n'); @@ -405,12 +552,184 @@ uint8_t stream_upload(uint16_t bptr) } +static void handle_config_fragment(__xdata uint8_t *p) +{ + __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); + __xdata uint16_t frag_len; + uint8_t taken; + + frag_len = uip_len - (p - uip_appdata); + if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { + print_string("Configuration too large, aborting.\n"); + config_upload = 0; + s->tstate = TSTATE_NONE; + send_bad_request(); + return; + } + memcpy(config_buf + pre_acc, p, frag_len); + pre_acc += frag_len; + taken = config_take(); + if (!taken) { + s->tstate = TSTATE_MULTIPART; + return; + } + config_upload = 0; + s->tstate = TSTATE_NONE; + if (taken == 2) { + send_bad_request(); + return; + } + send_ok(); +} + + +static void handle_firmware_fragment(__xdata uint8_t *p) +{ + __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); + __xdata uint16_t frag_len, payload_start; + + frag_len = uip_len - (p - uip_appdata); + if (pre_acc + frag_len >= CONFIG_UPLOAD_BUF) { + print_string("Firmware upload header too large, aborting.\n"); + config_upload = 0; + s->tstate = TSTATE_NONE; + send_bad_request(); + return; + } + memcpy(config_buf + pre_acc, p, frag_len); + pre_acc += frag_len; + payload_start = preamble_payload_start(pre_acc); + if (!payload_start) { + s->tstate = TSTATE_MULTIPART; + return; + } + dbg_string("Have content octets\n"); + + flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails + set_sys_led_state(SYS_LED_FAST); + + crc_value = 0; + bindex = 0; + write_len = 0; + // A verdict is only built once the upload part completes; + // clear any stale response so the completion check in the + // appcall POST branch cannot send leftovers + slen = 0; + upload_settings.p = config_buf; + upload_settings.bptr = payload_start; + upload_settings.plen = pre_acc; + stream_upload(); + + dbg_string("Done reading first fragment\n"); +} + + +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) { + outbuf[9] = '4'; + outbuf[10] = '0'; + outbuf[11] = '0'; + outbuf[13] = 'N'; + outbuf[14] = 'O'; + } +} + + +static void run_login_body(__xdata uint8_t *body) +{ + if (strstart(body, "pwd=") && is_url_word_x(body + 4, passwd)) { + dbg_string("Password accepted!\n"); + read_reg_timer(&last_session_use); + gen_random_hex_chars(session_id, SESSION_ID_LENGTH); + session_id[SESSION_ID_LENGTH] = NUL; + slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \ + "Set-Cookie: session="); + for (uint8_t i = 0; i < SESSION_ID_LENGTH; i++) + outbuf[slen++] = session_id[i]; + slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n"); + } else { + dbg_string("Password invalid!\n"); + slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: login.html\r\n\r\n"); + } +} + + +static uint8_t post_body_take(__xdata uint8_t *p) +{ + uint16_t have; + + if (!content_length) { + send_length_required(); + return 0; + } + if (content_length >= CONFIG_UPLOAD_BUF) { + send_bad_request(); + return 0; + } + have = uip_len - (p - uip_appdata); + if (have >= content_length) { + p[content_length] = NUL; + return 1; + } + memcpy(config_buf, p, have); + pre_acc = have; + postbody_start = ticks; + uip_conn->appstate.tstate = TSTATE_POSTBODY; + return 0; +} + + +static void post_body_continue(void) +{ + uint16_t take; + + // no header scan runs while the body is pending: content_length is this request's + take = content_length - pre_acc; + if (take > uip_len) + take = uip_len; + memcpy(config_buf + pre_acc, uip_appdata, take); + pre_acc += take; + if (pre_acc < content_length) { + postbody_start = ticks; + return; + } + config_buf[pre_acc] = NUL; + uip_conn->appstate.tstate = TSTATE_NONE; + if (postbody_endpoint == POSTBODY_CMD) + run_cmd_body(config_buf); + else + run_login_body(config_buf); +} + + void handle_post(void) { __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); __xdata uint8_t *p = uip_appdata; __xdata uint8_t *request_path = p + 6; + if (s->tstate == TSTATE_POSTBODY) { + post_body_continue(); + return; + } + // Was the multipart header sent in multiple packets? if (s->tstate != TSTATE_MULTIPART) { dbg_string("Is POST\n"); @@ -418,10 +737,10 @@ void handle_post(void) // Find end of request path while (*p && !is_separator(*p)) p++; - *p++ = '\0'; + *p++ = NUL; // Find end of request header - boundary[0] ='\0'; + boundary[0] =NUL; p = scan_header(p); dbg_string("Boundary: >"); dbg_string_x(boundary); dbg_string("<\n"); if (!*p || !content_type) { @@ -437,20 +756,20 @@ void handle_post(void) return; } print_string("Firmware upload started."); + config_upload = 0; uptr = FIRMWARE_UPLOAD_START; verify_crc = 1; max_upload = 1024576; + pre_acc = 0; } else if (is_word(request_path, "config")) { if (!authenticated) { send_unauthorized(); return; } - dbg_string("Configuration upload, erasing config mem!\n"); - uptr = CONFIG_START; + dbg_string("Configuration upload\n"); verify_crc = 0; - max_upload = 2048; - flash_region.addr = CONFIG_START; - flash_sector_erase(); + config_upload = 1; + pre_acc = 0; } // Check for other POST requests, which are not multipart, below } else { @@ -463,11 +782,11 @@ void handle_post(void) send_unauthorized(); return; } - execute_commands(p); - if (err_status != ERR_OK) { - send_bad_request(); + postbody_endpoint = POSTBODY_CMD; + if (!post_body_take(p)) return; - } + run_cmd_body(p); + return; } else if (is_word(request_path, "login")) { dbg_string("POST login\n"); @@ -477,21 +796,11 @@ void handle_post(void) return; } - p += 8; // Read also over "pwd=" - if (is_url_word_x(p, passwd)) { - dbg_string("Password accepted!\n"); - read_reg_timer(&last_session_use); - gen_random_bytes(session_id, SESSION_ID_LENGTH); - session_id[SESSION_ID_LENGTH] = '\0'; - slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: index.html\r\n" \ - "Set-Cookie: session="); - for (register uint8_t i = 0; i < SESSION_ID_LENGTH; i++) - outbuf[slen++] = session_id[i]; - slen += strtox(outbuf + slen, "; SameSite=Strict\r\n\r\n"); - } else { - dbg_string("Password invalid!\n"); - slen = strtox(outbuf, "HTTP/1.1 302 Found\r\nConnection: close\r\nLocation: login.html\r\n\r\n"); - } + p += 4; + postbody_endpoint = POSTBODY_LOGIN; + if (!post_body_take(p)) + return; + run_login_body(p); return; } else if (s->tstate == TSTATE_MULTIPART || is_word(request_path, "upload") || is_word(request_path, "config")) { dbg_string("POST upload/config request\n"); @@ -504,42 +813,15 @@ void handle_post(void) send_bad_request(); return; } - // We skip the intial parts as part of the header - do { - p = skip_boundary(p); - if (!*p) { - s->tstate = TSTATE_MULTIPART; - return; - } - p = scan_header(p); - if (!*p) - goto bad_request; - if (!content_type) // We are waiting for the part with the octet stream - continue; - } while (!is_word(content_type, "application/octet-stream")); - dbg_string("Have content octets\n"); - p += 4; // Skip \r\n\r\n sequence at end of preamble of part - - flash_init(0); // Re-initialize flash for non-DIO operation, otherwise flashing fails - set_sys_led_state(SYS_LED_FAST); - - crc_value = 0; - bindex = 0; - write_len = 0; - stream_upload(p - uip_appdata); - - dbg_string("Done reading first fragment\n"); + if (config_upload) + handle_config_fragment(p); + else + handle_firmware_fragment(p); return; - } else { send_not_found(); return; } - slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); - return; -bad_request: - send_bad_request(); - return; } @@ -560,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; @@ -570,6 +852,11 @@ void httpd_appcall(void) dbg_string("Closing because everything has been transmitted\n"); uip_close(); s->tstate = TSTATE_CLOSED; + } else if (s->tstate == TSTATE_POSTBODY + && (uint16_t)ticks - postbody_start > POSTBODY_TIMEOUT) { + dbg_string("Body never arrived\n"); + uip_abort(); + s->tstate = TSTATE_CLOSED; } } else if (uip_acked() && s->tstate == TSTATE_TX) { dbg_string("ACK\n"); @@ -603,11 +890,23 @@ void httpd_appcall(void) cont_len -= slen; cont_addr += slen; s->tstate = TSTATE_TX; + } else if (fw_reset_pending) { + // The upload verdict has been fully ACKed by the client; + // now it is safe to reset and apply the staged image + print_string("Resetting to apply update\n"); + reset_chip(); } } else if (uip_newdata() && s->tstate == TSTATE_POST) { // Check here maxupload by subtracting uip_len and close socekt if fails! if (max_upload - uip_len > 0) { - stream_upload(0); + upload_settings.p = uip_appdata; + upload_settings.bptr = 0; + upload_settings.plen = uip_len; + stream_upload(); + // A completed part with a built verdict must go out + // through the normal TX path + if (s->tstate == TSTATE_NONE && slen) + goto do_send; write_char('.'); } else { send_bad_request(); @@ -625,24 +924,31 @@ void httpd_appcall(void) dbg_char('\n'); #endif p = uip_appdata; - if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART) { + if (is_word(p, "POST") || s->tstate == TSTATE_MULTIPART + || s->tstate == TSTATE_POSTBODY) { handle_post(); // If this is an ongoing post stream, then wait for the next packet - if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART) { + if (s->tstate == TSTATE_POST || s->tstate == TSTATE_MULTIPART + || s->tstate == TSTATE_POSTBODY) { uip_len = 0; return; } goto do_send; } - if (is_word(p, "GET")) - dbg_string("GET request "); + // We only expect a GET request here. + if (!is_word(p, "GET")) { + send_bad_request(); + goto do_send; + } + + dbg_string("GET request "); p += 4; scan_header(p); __xdata uint8_t *q = p; while (*p && !is_separator(*p)) p++; - *p = '\0'; + *p = NUL; dbg_string_x(q); dbg_char('\n'); @@ -664,16 +970,9 @@ void httpd_appcall(void) parse_short(q + 15); send_vlan(short_parsed); } else if (is_word(q, "/counters.json")) { - /* The port is one raw character of the request line and - * indexes a nine entry table, so bound it here instead - * of trusting the client to have sent a digit. Anything - * below '0' wraps well past eight, so the one test - * covers both ends. */ uint8_t cport = q[20] - '0'; - if (cport > 8) + if (send_counters(cport)) send_bad_request(); - else - send_counters(cport); } else if (is_word(q, "/eee.json")) { send_eee(); } else if (is_word(q, "/bandwidth.json")) { @@ -690,6 +989,8 @@ void httpd_appcall(void) send_mtu(); } else if (is_word(q, "/lag.json")) { send_lag(); + } else if (is_word(q, "/stp.json")) { + send_stp(); } else if (is_word(q, "/vlanlist")) { send_vlanlist(); } else if (is_word(q, "/config")) { @@ -711,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; diff --git a/httpd/page_impl.c b/httpd/page_impl.c index bf3e2682..313a652e 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -12,6 +12,7 @@ #include "phy.h" #include "version.h" #include "machine.h" +#include "rtl837x_stp.h" #include "page_impl.h" #include "syslog.h" @@ -47,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) @@ -157,14 +158,14 @@ void sfr_data_to_html(void) } -void reg_to_html(register uint16_t reg) +void reg_to_html(uint16_t reg) { reg_read_m(reg); sfr_data_to_html(); } -void reg_to_html_long(register uint16_t reg) +void reg_to_html_long(uint16_t reg) { reg_read_m(reg); byte_to_html(sfr_data[0]); @@ -177,10 +178,12 @@ void reg_to_html_long(register uint16_t reg) void send_sfp_info(uint8_t sfp) { // This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields - for (uint8_t i = 20; i < 60; i++) { - if (i >= 36 && i < 40) // Skip Non-ASCII codes + for (uint8_t i = 16; i < 64; i++) { + if (!(i & 0xf) && !sfp_read_block(sfp, i, 16)) + return; + if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes continue; - uint8_t c = sfp_read_reg(sfp, i); + uint8_t c = sfp_buf[i & 0xf]; if (c && c != 0xa0) // a0 is the byte read from a non-existant I2C EEPROM char_to_html(c); } @@ -193,32 +196,11 @@ void sfp_send_data(uint8_t slot, uint8_t reg, uint8_t len) if (len > 16) return; - if (reg & 0x80) { // Configure SFP readings address (0x51) as I2C device address - reg &= 0x7f; - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | (len - 1) & 0xf, 0x51 >> 5, (0x51 << 3) & 0xff); - } else { - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | (len - 1) & 0xf, 0x50 >> 5, (0x50 << 3) & 0xff); - } + if (!sfp_read_block(slot, reg, len)) + return; - reg_read_m(RTL837X_REG_I2C_CTRL); - sfr_mask_data(1, 0xfc, i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2); - reg_write_m(RTL837X_REG_I2C_CTRL); - - REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); - - // Execute I2C Read - reg_bit_set(RTL837X_REG_I2C_CTRL, 0); - - // Wait for execution to finish - do { - reg_read_m(RTL837X_REG_I2C_CTRL); - } while (sfr_data[3] & 0x1); - - for (uint8_t i = 0; i < len; i++) { - if (!(i & 0x3)) - reg_read_m(RTL837X_REG_I2C_OUT + i); - byte_to_html(sfr_data[3 - (i & 0x3)]); - } + for (uint8_t i = 0; i < len; i++) + byte_to_html(sfp_buf[i]); } @@ -241,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(':'); @@ -309,17 +292,26 @@ void send_vlan(uint16_t vlan) slen += strtox(outbuf + slen, "\"}"); } - -void send_counters(char port) +/* Send counters + * Only accepts physical port 1..9. + * Returns an error if the port physical don't exists. + */ +bool send_counters(uint8_t phys_port) { - dbg_string("send_counters called: "); dbg_byte(port); dbg_char('\n'); + uint8_t phys_port_idx = phys_port - 1; + if (phys_port_idx > 8) + goto err; + uint8_t log_port = machine.phys_to_log_port[phys_port_idx]; + if (log_port == 0) + goto err; + + dbg_string("send_counters called: "); dbg_byte(phys_port_idx); dbg_char('\n'); slen = strtox(outbuf, HTTP_RESPONCE_JSON); - dbg_string("sending counters\n"); - dbg_byte(port); - uint8_t i = machine.phys_to_log_port[port]; - slen += strtox(outbuf + slen, "["); + dbg_string("sending counters\n"); dbg_byte(phys_port_idx); + + char_to_html('['); for (uint8_t counter = 0; counter < 0x37; counter++) { - STAT_GET(counter, i); + STAT_GET(counter, log_port); slen += strtox(outbuf + slen, "\"0x"); reg_to_html(RTL837X_STAT_V_HIGH); reg_to_html_long(RTL837X_STAT_V_LOW); @@ -328,6 +320,12 @@ void send_counters(char port) char_to_html(','); } char_to_html(']'); + + return false; + +err: + dbg_string("Error: counters: phy_port_idx don't exists\n"); + return true; } @@ -397,7 +395,7 @@ void send_l2(uint16_t idx) // type reg_read_m(RTL837x_L2_DATA_OUT_C); - if (sfr_data[2] & 0x1) + if (sfr_data[1] & 0x1) slen += strtox(outbuf + slen, "\",\"type\":\"s\",\"port\":"); else slen += strtox(outbuf + slen, "\",\"type\":\"l\",\"port\":"); @@ -435,42 +433,52 @@ 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); - reg_write_m(RTL837x_TBL_DATA_IN_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); - sfr_data[3] &= 0xc0; // Clear age, auth and second part of ports - sfr_data[1] &= 0xfe; // Clear nosalearn - reg_write_m(RTL837x_TBL_DATA_IN_C); + reg_read_m(RTL837x_L2_DATA_OUT_C); + sfr_data[3] &= 0xc0; // Clear age, auth and second part of ports + sfr_data[1] &= 0xfe; // Clear nosalearn + reg_write_m(RTL837x_TBL_DATA_IN_C); - reg_read_m(RTL837x_TBL_DATA_0); - REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1], TBL_L2_UNICAST, sfr_data[3]); + reg_read_m(RTL837x_TBL_DATA_0); + 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_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); + do { + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & TBL_EXECUTE); - char_to_html('1'); + char_to_html('1'); + } } char_to_html('}'); } @@ -535,6 +543,119 @@ void send_lag(void) } +static __xdata uint32_t pi_u32; +static __xdata uint8_t pi_prio, pi_ext; +static __xdata uint8_t * __xdata pi_mac; + + +static void u32hex_html(void) +{ + __xdata uint8_t *b = (__xdata uint8_t *)&pi_u32; + byte_to_html(b[3]); + byte_to_html(b[2]); + byte_to_html(b[1]); + byte_to_html(b[0]); +} + + +static void bridge_to_html(void) +{ + byte_to_html(pi_prio); + byte_to_html(pi_ext); + for (uint8_t i = 0; i < 6; i++) + byte_to_html(pi_mac[i]); +} + + +void send_stp(void) +{ + uint8_t i, j, st, dsg; + + dbg_string("send_stp called\n"); + slen = strtox(outbuf, HTTP_RESPONCE_JSON); + + slen += strtox(outbuf + slen, "{\"on\":"); + bool_to_html(stp_enabled); + slen += strtox(outbuf + slen, ",\"rstp\":"); + bool_to_html(stp_rstp); + slen += strtox(outbuf + slen, ",\"prio\":"); + itoa_html(stp_prio >> 4); + slen += strtox(outbuf + slen, ",\"hello\":"); + itoa_html(stp_hello_s); + slen += strtox(outbuf + slen, ",\"maxage\":"); + itoa_html(stp_maxage_s); + slen += strtox(outbuf + slen, ",\"fwd\":"); + itoa_html(stp_fwddelay_s); + slen += strtox(outbuf + slen, ",\"txhold\":"); + itoa_html(stp_txhold); + slen += strtox(outbuf + slen, ",\"rootPrio\":\""); + byte_to_html(root_bridge.prio); + byte_to_html(root_bridge.ext); + slen += strtox(outbuf + slen, "\",\"rootMac\":\""); + for (j = 0; j < 6; j++) + byte_to_html(root_bridge.mac[j]); + slen += strtox(outbuf + slen, "\",\"myMac\":\""); + for (j = 0; j < 6; j++) + byte_to_html(uip_ethaddr.addr[j]); + slen += strtox(outbuf + slen, "\",\"cost\":\""); + byte_to_html(root_bridge_cost >> 24); + byte_to_html(root_bridge_cost >> 16); + byte_to_html(root_bridge_cost >> 8); + byte_to_html(root_bridge_cost); + slen += strtox(outbuf + slen, "\",\"weRoot\":"); + bool_to_html(stp_root_port == 0xff ? 1 : 0); + slen += strtox(outbuf + slen, ",\"rootPort\":"); + itoa_html(stp_root_port == 0xff ? 0 : machine.log_to_phys_port[stp_root_port]); + slen += strtox(outbuf + slen, ",\"tc\":\""); + byte_to_html(stp_tc_count >> 8); + byte_to_html(stp_tc_count); + slen += strtox(outbuf + slen, "\",\"ports\":["); + reg_read_m(RTL837X_MSTP_STATES); + for (i = machine.min_port; i <= machine.max_port; i++) { + slen += strtox(outbuf + slen, "{\"p\":"); + itoa_html(machine.log_to_phys_port[i]); + slen += strtox(outbuf + slen, ",\"st\":"); + st = (sfr_data[3 - (i >> 2)] >> ((i << 1) & 0x7)) & 0x3; + itoa_html(st); + slen += strtox(outbuf + slen, ",\"role\":"); + if (!(stp_pflags[i] & STP_PF_ENABLED) || (stp_pflags[i] & STP_PF_TRIPPED)) + itoa_html(0); + else if (i == stp_root_port) + itoa_html(1); + else if (st == 3) + itoa_html(2); + else + itoa_html(3); + slen += strtox(outbuf + slen, ",\"f\":"); + itoa_html(stp_pflags[i]); + slen += strtox(outbuf + slen, ",\"pc\":\""); + pi_u32 = stp_pcost[i]; u32hex_html(); + slen += strtox(outbuf + slen, "\",\"prio\":"); + itoa_html(stp_pprio[i]); + slen += strtox(outbuf + slen, ",\"p2\":"); + itoa_html(stp_pp2p[i]); + dsg = stp_dpid[i] && stp_bpdu_age[i] < (uint16_t)stp_maxage_s * STP_HZ; + slen += strtox(outbuf + slen, ",\"db\":\""); + if (dsg) { + pi_prio = stp_dbridge[i].prio; pi_ext = stp_dbridge[i].ext; + pi_mac = stp_dbridge[i].mac; + } else { + pi_prio = stp_prio; pi_ext = 0; + pi_mac = uip_ethaddr.addr; + } + bridge_to_html(); + slen += strtox(outbuf + slen, "\",\"dp\":\""); + byte_to_html(dsg ? (stp_dpid[i] >> 8) : stp_pprio[i]); + byte_to_html(dsg ? stp_dpid[i] : (i + 1)); + slen += strtox(outbuf + slen, "\",\"dc\":\""); + pi_u32 = dsg ? stp_dcost[i] : root_bridge_cost; u32hex_html(); + slen += strtox(outbuf + slen, "\"},"); + } + slen -= 1; // remove comma + slen += strtox(outbuf + slen, "]}"); +} + + void send_eee(void) { dbg_string("send_eee called\nsending EEE status\n"); @@ -702,13 +823,13 @@ void send_status(void) sfp_send_data(sfp, 238, 1); } slen += strtox(outbuf + slen,"\",\"sfp_vendor\":\""); - for (register uint8_t s = 0; s < 16 && sfp_module_vendor[sfp][s]; s++) + for (uint8_t s = 0; s < 16 && sfp_module_vendor[sfp][s]; s++) outbuf[slen++] = sfp_module_vendor[sfp][s]; slen += strtox(outbuf + slen,"\",\"sfp_model\":\""); - for (register uint8_t s = 0; s < 16 && sfp_module_model[sfp][s]; s++) + for (uint8_t s = 0; s < 16 && sfp_module_model[sfp][s]; s++) outbuf[slen++] = sfp_module_model[sfp][s]; slen += strtox(outbuf + slen,"\",\"sfp_serial\":\""); - for (register uint8_t s = 0; s < 16 && sfp_module_serial[sfp][s]; s++) + for (uint8_t s = 0; s < 16 && sfp_module_serial[sfp][s]; s++) outbuf[slen++] = sfp_module_serial[sfp][s]; slen += strtox(outbuf + slen,"\",\"sfp_los\":"); if (machine.sfp_port[sfp].pin_los == GPIO_NA) { @@ -822,7 +943,7 @@ found_end: if (valid_len > (TCP_OUTBUF_SIZE - slen)) { cont_len = valid_len - (TCP_OUTBUF_SIZE - slen); valid_len = TCP_OUTBUF_SIZE - slen; - cont_addr = valid_len; + cont_addr = CONFIG_START + valid_len; } flash_region.addr = CONFIG_START; diff --git a/httpd/page_impl.h b/httpd/page_impl.h index 7907285e..cad4cd6f 100644 --- a/httpd/page_impl.h +++ b/httpd/page_impl.h @@ -1,7 +1,11 @@ #ifndef __PAGE_IMPL_H__ #define __PAGE_IMPL_H__ -void send_counters(char port); +#include + +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); void send_basic_info(void); @@ -14,6 +18,7 @@ void send_mtu(void); void send_config(void); void send_cmd_log(void); void send_lag(void); +void send_stp(void); void send_vlanlist(void); /* Convert only the lower nibble to ascii HEX char. diff --git a/installer/installer.c b/installer/installer.c index 675a1420..f86b7821 100644 --- a/installer/installer.c +++ b/installer/installer.c @@ -213,7 +213,7 @@ uint8_t flash_read_status(void) * Reads bulk data of length len from the flash memory starging at address src * and writes the data into a buffer pointed to by dst in XMEM */ -void flash_read_bulk(register __xdata uint8_t *dst, __xdata uint32_t src, register uint16_t len) +void flash_read_bulk(__xdata uint8_t *dst, __xdata uint32_t src, uint16_t len) { short status; do { diff --git a/machine.c b/machine.c index c27b92a2..3ce13d33 100644 --- a/machine.c +++ b/machine.c @@ -5,9 +5,23 @@ #include "rtl837x_regs.h" #include "rtl837x_common.h" -#ifdef MACHINE_KP_9000_6XHML_X2 +#if defined(MACHINE_KP_9000_6XHML_X2) || \ + defined(MACHINE_KP_9000_6XH_X2_V1_1) || \ + defined(MACHINE_KP_9000_6XHML_X2_V1_1) || \ + defined(MACHINE_KP_9000_6XH_X2_V1_2) || \ + defined(MACHINE_KP_9000_6XHML_X2_V1_2) __code const struct machine machine = { +#if defined(MACHINE_KP_9000_6XH_X2_V1_1) + .machine_name = "keepLink KP-9000-6XH V1.1", +#elif defined(MACHINE_KP_9000_6XHML_X2_V1_1) + .machine_name = "keepLink KP-9000-6XHML V1.1", +#elif defined(MACHINE_KP_9000_6XH_X2_V1_2) + .machine_name = "keepLink KP-9000-6XH V1.2", +#elif defined(MACHINE_KP_9000_6XHML_X2_V1_2) + .machine_name = "keepLink KP-9000-6XHML V1.2", +#else .machine_name = "keepLink KP-9000-6XHML-X2", +#endif .isRTL8373 = 0, .min_port = 3, .max_port = 8, @@ -42,8 +56,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_KP_9000_6XH_X __code const struct machine machine = { .machine_name = "keepLink KP-9000-6XH-X", @@ -72,11 +84,15 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - -#elif defined MACHINE_KP_9000_6XH_X2 +#elif defined(MACHINE_KP_9000_6XH_X2) || defined(MACHINE_KP_9000_6XH_X2_V2_1) || defined(MACHINE_KP_9000_6XHML_X2_V2_1) __code const struct machine machine = { +#if defined(MACHINE_KP_9000_6XHML_X2_V2_1) + .machine_name = "keepLink KP-9000-6XHML V2.1", +#elif defined(MACHINE_KP_9000_6XH_X2_V2_1) + .machine_name = "keepLink KP-9000-6XH-X2 V2.1", +#else .machine_name = "keepLink KP-9000-6XH-X2", +#endif .isRTL8373 = 0, .min_port = 3, .max_port = 8, @@ -122,10 +138,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { - reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); -} - #elif defined MACHINE_KP_9000_9XH_X_EU __code const struct machine machine = { .machine_name = "keepLink KP-9000-9XH-X-EU", @@ -151,8 +163,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_KP_9000_9XHML_X_V2_2 __code const struct machine machine = { .machine_name = "keepLink KP-9000-9XHML-X V2.2", @@ -204,8 +214,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_KP_9000_9XHML_X_V3_1 __code const struct machine machine = { .machine_name = "keepLink KP-9000-9XHML-X V3.1", @@ -241,8 +249,6 @@ __code const struct machine machine = { 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21}, }; -void machine_custom_init(void) { } - #elif defined MACHINE_SWGT024_V2_0_MANAGED __code const struct machine machine = { .machine_name = "SWGT024 V2.0 Managed", @@ -284,8 +290,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_SWGT024_V2_0_UNMANAGED __code const struct machine machine = { .machine_name = "SWGT024 V2.0 Unmanaged", @@ -327,8 +331,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_SWTG018AS_A_V_2_0 __code const struct machine machine = { .machine_name = "SWTG018AS-A V2.0", @@ -368,8 +370,6 @@ __code const struct machine machine = { 0x1d, 0x20, 0x21 }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_HG0402XG_V1_1 __code const struct machine machine = { .machine_name = "HG0402XG V1.1", @@ -408,8 +408,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_SWTGW218AS __code const struct machine machine = { @@ -443,11 +441,53 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } +#elif defined MACHINE_PCB_SWTG018AS_V2_1_0 // Sold as Sodola SL902 / Horaco "SWTGW218AS"; the SWTGW218AS label also covers other PCBs with different SFP and LED wiring (see MACHINE_SWTGW218AS) + +__code const struct machine machine = { + .machine_name = "SWTGW218AS (SWTG018AS-V2.1.0)", + .isRTL8373 = 1, + .mac_flash_offset = 0x1FC000, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, // pulled low on module insert + .sfp_port[0].pin_los = GPIO_NA, // no LOS pin wired + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 }, + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + // LED wiring matches the SWTG018AS-A V2.0 (same PCB family) + .led_sets = { + { /* RJ45: First LED, yellow, second LED: green */ + LEDS_2G5 | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, { /* SFP set (superseded by the raw register override in machine_custom_init) */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + 0, + 0, + }}, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 + 0x09, 0x0c, 0x09, 0x0d, 0x10, // 65e4 + 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 + 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 + 0x1d, 0x20, 0x21 }, +}; + #elif defined MACHINE_LIANGUO_ZX_SWTGW215AS // Has PCB branded PCB-SWTG115AS-V2.0 but is labeled and reports as a ZX-SWTGW215AS, seems to be identical to the "real" ZX-SWTGW215AS except for the LEDs __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, @@ -476,8 +516,6 @@ __code const struct machine machine = { .led_mux_custom = 0, }; -void machine_custom_init(void) { } - #elif defined MACHINE_DEFAULT_8C_1SFP __code const struct machine machine = { .machine_name = "8+1 SFP Port Switch", @@ -503,8 +541,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_TRENDNET_TEG_S562 __code const struct machine machine = { .machine_name = "Trendnet TEG-S562", @@ -544,8 +580,6 @@ __code const struct machine machine = { }; -void machine_custom_init(void) { } - #elif defined(MACHINE_PCB_K0402WS_V3) || defined(MACHINE_HI_K0402WS) // Sold as a variety of devices, see doc/ __code const struct machine machine = { .machine_name = "PCB-K0402WS-V3.0", @@ -592,10 +626,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { - reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); -} - #elif defined MACHINE_K0501W_V2_0 __code const struct machine machine = { .machine_name = "K0501W V2.0", @@ -630,8 +660,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_ZX310S_4T2XH __code const struct machine machine = { .machine_name = "ZX310S-4T2XH", @@ -675,8 +703,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_STEAMEMO_IG204_V1 __code const struct machine machine = { .machine_name = "Steamemo IG204 V1", @@ -727,8 +753,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - #elif defined MACHINE_HI_K0801WS __code const struct machine machine = { .machine_name = "Hi-Source HI-k0801WS", @@ -778,11 +802,13 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { } - -#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, @@ -835,11 +861,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) -{ - reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); -} - #elif defined MACHINE_PCB_SWTG024AS_A_2_0_1 __code const struct machine machine = { @@ -889,14 +910,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) -{ - reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); - reg_bit_set(RTL837X_REG_LED_MODE, 17); - reg_bit_clear(RTL837X_REG_LED_MODE, 9); - reg_bit_clear(RTL837X_REG_LED_MODE, 7); -} - #elif defined MACHINE_SWTG024AS_A_2_0_1_5C_1SFP __code const struct machine machine = { .machine_name = "SWTG024AS-A-V2.0.1-5C-1SFP", @@ -940,25 +953,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) -{ - uint16_t pval; - - reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); - reg_bit_set(RTL837X_REG_LED_MODE, 17); - reg_bit_clear(RTL837X_REG_LED_MODE, 9); - reg_bit_clear(RTL837X_REG_LED_MODE, 7); - - // OEM firmware sets these companion SDS0 polarity bits for the RTL8221B. - sds_read(0, 0, 0); - pval = SFR_DATA_U16; - sds_write_v(0, 0, 0, pval | 0x100); - - sds_read(0, 6, 2); - pval = SFR_DATA_U16; - sds_write_v(0, 6, 2, pval | 0x4000); -} - #elif defined MACHINE_SWTG024AS_V2_0 __code const struct machine machine = { .machine_name = "SWTG024AS-V2.0", @@ -1002,25 +996,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) -{ - uint16_t pval; - - reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); - reg_bit_set(RTL837X_REG_LED_MODE, 17); - reg_bit_clear(RTL837X_REG_LED_MODE, 9); - reg_bit_clear(RTL837X_REG_LED_MODE, 7); - - // OEM firmware sets these companion SDS0 polarity bits for the RTL8221B. - sds_read(0, 0, 0); - pval = SFR_DATA_U16; - sds_write_v(0, 0, 0, pval | 0x100); - - sds_read(0, 6, 2); - pval = SFR_DATA_U16; - sds_write_v(0, 6, 2, pval | 0x4000); -} - #elif defined MACHINE_ZX310S_4T2XT __code const struct machine machine = { .machine_name = "ZX310S_4T2XT", @@ -1058,12 +1033,6 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { - // For this device, the reset value of RTL837X_PIN_MUX_0 is 0x30000000, - // which would disables all LEDS, enable them manually: - REG_SET(RTL837X_PIN_MUX_0, 0x30db68bf); -} - #elif defined MACHINE_FG_4GT_2SX_V2_0 __code const struct machine machine = { .machine_name = "FG-4GT-2SX_V2.0", @@ -1130,9 +1099,50 @@ __code const struct machine machine = { }, }; -void machine_custom_init(void) { - REG_SET(RTL837X_REG_LED_GLB_IO_EN, 0x7624155b); -} +#elif defined MACHINE_FG_8GT_1SX +__code const struct machine machine = { + .machine_name = "FG-8GT-1SX", + .isRTL8373 = 1, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, + .sfp_port[0].pin_los = GPIO_NA, + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 }, + .reset_pin = GPIO_NA, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .led_sets = { + /* + Ports 1-8: Left Amber 10/100/1000MBit (Vendor no 10M, but we add it) + Right Green 2.5GBit + SFP-port: Any speed + */ + { + LEDS_2G5 | LEDS_LINK | LEDS_ACT, + LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, + { + LEDS_10G | LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + 0, + }, + }, + .led_mux_custom = 1, + .led_mux = { + 0x00, 0x01, 0x04, 0x05, 0x08, 0x09, 0x0c, 0x09, 0x0d, 0x10, + 0x11, 0x0e, 0x14, 0x11, 0x12, 0x15, 0x15, 0x16, 0x18, 0x19, + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, 0x1d, 0x20, 0x21, + }, +}; #else #error "Please select a machine type in machine.h" diff --git a/machine.h b/machine.h index 523beea3..91c4b1ff 100644 --- a/machine.h +++ b/machine.h @@ -6,9 +6,19 @@ /* * Select your machine type below */ +// Legacy KP-9000 4+2 targets. Prefer the PCB-revision-specific targets below. // #define MACHINE_KP_9000_6XHML_X2 -// #define MACHINE_KP_9000_6XH_X // #define MACHINE_KP_9000_6XH_X2 + +// KP-9000 4+2 targets by PCB silkscreen revision. +// #define MACHINE_KP_9000_6XH_X2_V1_1 +// #define MACHINE_KP_9000_6XHML_X2_V1_1 +// #define MACHINE_KP_9000_6XH_X2_V1_2 +// #define MACHINE_KP_9000_6XHML_X2_V1_2 +// #define MACHINE_KP_9000_6XH_X2_V2_1 +// #define MACHINE_KP_9000_6XHML_X2_V2_1 + +// #define MACHINE_KP_9000_6XH_X // #define MACHINE_KP_9000_9XH_X_EU // #define MACHINE_KP_9000_9XHML_X_V2_2 // #define MACHINE_KP_9000_9XHML_X_V3_1 @@ -18,7 +28,9 @@ // #define MACHINE_HG0402XG_V1_1 // #define MACHINE_SWTG018AS_A_V_2_0 // #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 @@ -31,6 +43,7 @@ // #define MACHINE_SWTG024AS_A_2_0_1_5C_1SFP // #define MACHINE_SWTG024AS_V2_0 // #define MACHINE_FG_4GT_2SX_V2_0 +// #define MACHINE_FG_8GT_1SX typedef struct { // GPIO pins for SDA/SCL @@ -94,6 +107,6 @@ typedef struct machine_runtime uint8_t isN : 1; }; -void machine_custom_init(void); +void machine_custom_init(void) __banked; #endif diff --git a/machine_init.c b/machine_init.c new file mode 100644 index 00000000..2f674a33 --- /dev/null +++ b/machine_init.c @@ -0,0 +1,122 @@ +/* + * Per-machine one-shot boot hooks, hosted in BANK2 so board-specific + * tables and code do not consume the common bank. + */ +#include +#include "machine.h" +#include "rtl837x_pins.h" +#include "rtl837x_leds.h" +#include "rtl837x_sfr.h" +#include "rtl837x_regs.h" +#include "rtl837x_common.h" + +#pragma codeseg BANK2 +#pragma constseg BANK2 + +#if defined(MACHINE_KP_9000_6XH_X2) || \ + defined(MACHINE_KP_9000_6XH_X2_V2_1) || \ + defined(MACHINE_KP_9000_6XHML_X2_V2_1) +void machine_custom_init(void) __banked +{ + reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); +} + +#elif defined MACHINE_PCB_SWTG018AS_V2_1_0 +// Stock-firmware values for what the LED-set encoding cannot express: the +// bi-color SFP LED (blue pin at 10G) and the PIN_MUX_0 routing of that pin +// to the LED controller. Runs after leds_setup(), which covers the rest. +static __code const struct { uint16_t reg; uint32_t val; } custom_init_regs[] = { + { RTL837X_REG_LED3_0_SET1, 0x00100000UL }, + { RTL837X_REG_LED1_0_SET1, 0x01400155UL }, + { RTL837X_REG_LED1_0_SET0, 0x01740141UL }, + { RTL837X_REG_LED_GLB_IO_EN, 0x7f24977fUL }, + { RTL837X_PIN_MUX_0, 0x20db6880UL }, +}; + +void machine_custom_init(void) __banked +{ + uint8_t i; + // REG_SET is a multi-statement macro without a do-while wrapper: braces required + for (i = 0; i < sizeof(custom_init_regs) / sizeof(custom_init_regs[0]); i++) { + REG_SET(custom_init_regs[i].reg, custom_init_regs[i].val); + } +} + +#elif defined(MACHINE_PCB_K0402WS_V3) || defined(MACHINE_HI_K0402WS) +void machine_custom_init(void) __banked +{ + reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); +} + +#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); +} + +#elif defined MACHINE_PCB_SWTG024AS_A_2_0_1 +void machine_custom_init(void) __banked +{ + reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); + reg_bit_set(RTL837X_REG_LED_MODE, 17); + reg_bit_clear(RTL837X_REG_LED_MODE, 9); + reg_bit_clear(RTL837X_REG_LED_MODE, 7); +} + +#elif defined MACHINE_SWTG024AS_A_2_0_1_5C_1SFP +void machine_custom_init(void) __banked +{ + uint16_t pval; + + reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); + reg_bit_set(RTL837X_REG_LED_MODE, 17); + reg_bit_clear(RTL837X_REG_LED_MODE, 9); + reg_bit_clear(RTL837X_REG_LED_MODE, 7); + + // OEM firmware sets these companion SDS0 polarity bits for the RTL8221B. + sds_read(0, 0, 0); + pval = SFR_DATA_U16; + sds_write_v(0, 0, 0, pval | 0x100); + + sds_read(0, 6, 2); + pval = SFR_DATA_U16; + sds_write_v(0, 6, 2, pval | 0x4000); +} + +#elif defined MACHINE_SWTG024AS_V2_0 +void machine_custom_init(void) __banked +{ + uint16_t pval; + + reg_bit_set(RTL837X_REG_LED_GLB_IO_EN, 6); + reg_bit_set(RTL837X_REG_LED_MODE, 17); + reg_bit_clear(RTL837X_REG_LED_MODE, 9); + reg_bit_clear(RTL837X_REG_LED_MODE, 7); + + // OEM firmware sets these companion SDS0 polarity bits for the RTL8221B. + sds_read(0, 0, 0); + pval = SFR_DATA_U16; + sds_write_v(0, 0, 0, pval | 0x100); + + sds_read(0, 6, 2); + pval = SFR_DATA_U16; + sds_write_v(0, 6, 2, pval | 0x4000); +} + +#elif defined MACHINE_ZX310S_4T2XT +void machine_custom_init(void) __banked +{ + // For this device, the reset value of RTL837X_PIN_MUX_0 is 0x30000000, + // which would disables all LEDS, enable them manually: + REG_SET(RTL837X_PIN_MUX_0, 0x30db68bf); +} + +#elif defined MACHINE_FG_4GT_2SX_V2_0 +void machine_custom_init(void) __banked +{ + REG_SET(RTL837X_REG_LED_GLB_IO_EN, 0x7624155b); +} + +#else +void machine_custom_init(void) __banked { } +#endif diff --git a/rtl837x_common.h b/rtl837x_common.h index 66df88a4..0d4ccc01 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -8,6 +8,7 @@ #define SYS_TICK_HZ 200 #define CPU_PORT 9 +#define NUL '\0' // Define Port-masks for 9-port devices and 6-port devices #define PMASK_9 0x1ff @@ -36,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 @@ -71,6 +76,9 @@ struct vlan_tag { #define VLAN_TAG_SIZE (sizeof (struct vlan_tag)) #define RTL_FRAME_TAG_ID 0x8899 #define RTL_FRAME_TAG_VERSION 0x04 +/* Bits of the tag's `flags` word, see doc/CpuPort.md. */ +#define RTL_TAG_LEARN_DIS 0x0020 /* do not learn the source address from this frame */ +#define RTL_TAG_KEEP 0x0080 /* keep the frame's 802.1Q tag format as injected */ // For TX, an 8 byte (plus 4 byte padding when when VLAN is enabled) // header describing the frame to be moved to the Asic is used @@ -115,6 +123,8 @@ struct flash_region_t { extern __xdata char port_names[9][PORT_NAME_SIZE]; +extern __xdata bool stp_enabled; + /* System hostname (device identity). Set via `hostname ` and the System * Settings page, reported in /information.json. Other modules (e.g. LLDP, which * advertises it as the System Name TLV) read it from here. */ @@ -125,15 +135,18 @@ extern __xdata struct uip_eth_addr uip_ethaddr; // Headers for calls in the common code area (HOME/BANK0) void print_string_no_syslog(__code char *p); +void print_string_newline_no_syslog(__code char *p); void print_string(__code char *p); void print_string_x(__xdata char *p); 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); +void print_phys_port(uint8_t port); void phy_write_mask(uint16_t phy_mask, uint8_t dev_id, uint16_t reg, uint16_t v); void phy_write(uint8_t phy_id, uint8_t dev_id, uint16_t reg, uint16_t v); void phy_read(uint8_t phy_id, uint8_t dev_id, uint16_t reg); @@ -149,7 +162,8 @@ void sleep(uint16_t t); void write_char_no_syslog(char c); void write_char(char c); void print_reg(uint16_t reg); -uint8_t sfp_read_reg(uint8_t slot, uint8_t reg); +bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) __banked __reentrant; +extern __xdata uint8_t sfp_buf[16]; void reg_bit_set(uint16_t reg_addr, char bit); void reg_bit_clear(uint16_t reg_addr, char bit); uint8_t reg_bit_test(uint16_t reg_addr, char bit); @@ -157,18 +171,20 @@ void sfr_mask_data(uint8_t n, uint8_t mask, uint8_t set); void sfr_set_zero(void); void reset_chip(void); void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16_t len); -void memcpyc(register __xdata uint8_t *dst, register __code uint8_t *src, register uint16_t len); -void memset(register __xdata uint8_t *dst, register __xdata uint8_t v, register uint8_t len); -uint16_t strlen(register __code const char *s); -uint16_t strlen_x(register __xdata const char *s); -uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s); -uint16_t strcpy(register __xdata uint8_t *dst, register const char *s); -char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b); +void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len); +void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len); +uint16_t strlen(__code const char *s); +uint16_t strlen_x(__xdata const char *s); +uint16_t strtox(__xdata uint8_t *dst, __code const char *s); +uint16_t strcpy(__xdata uint8_t *dst, const char *s); +char strcmp(__xdata const uint8_t *a, __code const uint8_t *b); +bool strstart(__xdata const uint8_t *a, __code const uint8_t *b); +bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b); void tcpip_output(void); uint8_t read_flash(uint8_t bank, __code uint8_t *addr); void get_random_32(void); void read_reg_timer(__xdata uint32_t * tmr); -void sfp_print_info(uint8_t sfp); +bool sfp_print_info(uint8_t sfp); bool gpio_pin_test(uint8_t pin); void set_sys_led_state(uint8_t state); void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg); diff --git a/rtl837x_igmp.c b/rtl837x_igmp.c index feddf9ad..da029036 100644 --- a/rtl837x_igmp.c +++ b/rtl837x_igmp.c @@ -92,8 +92,11 @@ void igmp_setup(void) __banked REG_SET(RTL837X_IPV6_PORT_MC_LM_ACT, LOOKUP_MISS_FLOOD); // Define ports where unknown MC addresses are flooded to: - REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, machine_detected.isRTL8373? PMASK_9: PMASK_6); - REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, machine_detected.isRTL8373? PMASK_9: PMASK_6); + uint16_t mask = PMASK_6; + if (machine_detected.isRTL8373) + mask = PMASK_9; + REG_SET(RTL837X_IPV4_UNKN_MC_FLD_PMSK, mask); + REG_SET(RTL837X_IPV6_UNKN_MC_FLD_PMSK, mask); // Enable lookup of IPv4 MC addresses in table reg_bit_set(RTL837X_L2_CTRL, L2_CTRL_LUT_IPMC_HASH); diff --git a/rtl837x_phy.c b/rtl837x_phy.c index 16fd214b..a3383544 100644 --- a/rtl837x_phy.c +++ b/rtl837x_phy.c @@ -262,7 +262,7 @@ void phy_set_speed(void) __banked { uint16_t v; - print_string("Setting port "); write_char(machine.log_to_phys_port[phy_settings.port] + '0'); + print_string("Setting port "); print_phys_port(phy_settings.port); if (machine.n_10g && phy_settings.port == 3) phy_settings.is10g_port = 1; if (machine.n_10g == 2 && phy_settings.port == 8) @@ -381,7 +381,7 @@ void phy_set_duplex(void) __banked { uint16_t v; - print_string("Setting port "); write_char(machine.log_to_phys_port[phy_settings.port] + '0'); + print_string("Setting port "); print_phys_port(phy_settings.port); if (phy_settings.duplex) print_string(" to full duplex"); else @@ -468,9 +468,13 @@ void phy_show(uint8_t port) __banked phy_read(port, PHY_MMD_PMAPMD, 0); v = SFR_DATA_U16; print_string("\nForced speed: "); print_short(v); write_char('\n'); - uint8_t s1 = ((v & 0x40) ? 0x2 : 0x0) | ((v & 0x2000) ? 0x1 : 0x0); - uint8_t s2 = (v >> 2) & 0xf; - switch(s1) { + uint8_t s1 = 0x00; + if ((uint8_t)v & 0x40) + s1 = 0x2; + if (v & 0x2000) + s1 |= 0x1; + uint8_t s2 = ((uint8_t)v >> 2) & 0xf; + switch(s1 & 0x3) { case 0: print_string("10M\n"); break; @@ -495,8 +499,6 @@ void phy_show(uint8_t port) __banked print_string("Unknown\n"); } break; - default: - print_string("Unknown\n"); } phy_read(port, PHY_MMD31, PHY_MMD31_FEDCR); v = SFR_DATA_U16; @@ -582,7 +584,7 @@ void phy_reset(uint8_t port) __banked // Reading only reads the lower 16-bit part of the 32-bit register. // When also needing read the upper 16-bits, use register address + 1. // Readed values it return via sfr-data. -void inline rtl8224_read_reg_u16(uint16_t reg) __banked +void rtl8224_read_reg_u16(uint16_t reg) __banked { // void phy_read(uint8_t phy_id, uint8_t dev_id, uint16_t reg) // phy_read(RTL8224_PHY_ID, PHY_MMD30, reg); @@ -590,7 +592,7 @@ void inline rtl8224_read_reg_u16(uint16_t reg) __banked SFR_SMI_REG_U16 = reg; // c2, c2 SFR_SMI_PHY = RTL8224_PHY_ID; // a5 - SFR_SMI_DEV = PHY_MMD30 << 3 | 2; // c4 + SFR_SMI_DEV = (uint8_t)PHY_MMD30 << 3 | 2; // c4 SFR_EXEC_GO = SFR_EXEC_READ_SMI; do { @@ -601,7 +603,7 @@ void inline rtl8224_read_reg_u16(uint16_t reg) __banked // Registers names are the same as on the RTL837x. // Writing only the lower 16-bit part of the 32-bit register. // When also needing to write the upper 16-bits, use register address + 1. -void inline rtl8224_write_reg_u16(uint16_t reg, uint16_t val) __banked +void rtl8224_write_reg_u16(uint16_t reg, uint16_t val) __banked { SFR_DATA_U16 = val; // SFR_A6, SFR_A7 SFR_SMI_REG_U16 = reg; // SFR_C2, SFR_C3 @@ -633,7 +635,7 @@ void inline rtl8224_write_reg_u16(uint16_t reg, uint16_t val) __banked // Write to the RTL8224 SDS registers. -void rtl8224_sds_write(uint16_t sds_cmd, uint16_t value) __banked +void rtl8224_sds_write(uint16_t sds_cmd, __xdata uint16_t value) __banked { // Wait for command bit is cleared do { diff --git a/rtl837x_phy.h b/rtl837x_phy.h index 932516a0..91e675d9 100644 --- a/rtl837x_phy.h +++ b/rtl837x_phy.h @@ -28,14 +28,16 @@ void phy_show(uint8_t port) __banked; void phy_reset(uint8_t port) __banked; void rtl8224_read_reg_u16(uint16_t reg) __banked; void rtl8224_write_reg_u16(uint16_t reg, uint16_t val) __banked; -void rtl8224_sds_write(uint16_t sds_cmd, uint16_t val) __banked; +void rtl8224_sds_write(uint16_t sds_cmd, __xdata uint16_t val) __banked; void phy_config_8261(uint8_t phy, uint8_t sds) __banked; -#define RTL8224_SDS_WRITE(sds_id, page, reg, v) uint16_t _sdscmd = (uint16_t)(sds_id & 0x01) | (1 << 14) | (1 << 15); \ +#define RTL8224_SDS_WRITE(sds_id, page, reg, v) do { \ + uint16_t _sdscmd = (uint16_t)(sds_id & 0x01) | (1 << 14) | (1 << 15); \ _sdscmd |= (page & 0x3F) << 1; \ _sdscmd |= ((uint16_t)(reg & 0x1f)) << 7; \ print_string("CMD: "); print_short(_sdscmd); \ write_char('-'); print_short(v); \ - rtl8224_sds_write(_sdscmd, v); + rtl8224_sds_write(_sdscmd, v); \ + } while (0) #endif diff --git a/rtl837x_pins.c b/rtl837x_pins.c index 90015b76..bee98187 100644 --- a/rtl837x_pins.c +++ b/rtl837x_pins.c @@ -1,6 +1,11 @@ #include "rtl837x_pins.h" #include "rtl837x_common.h" +#include "rtl837x_sfr.h" #include "rtl837x_regs.h" +#include "machine.h" + +extern __code const struct machine machine; +extern __xdata uint8_t sfr_data[4]; #pragma codeseg BANK2 #pragma constseg BANK2 @@ -121,3 +126,56 @@ void gpio_output_setup(uint8_t pin, __xdata uint8_t initial_val) __banked{ reg_bit_set(gpio_direction_reg(pin), (pin % 32)); } + + +/* + * Read up to 16 consecutive registers of the EEPROM via I2C into sfp_buf + */ +bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) __banked __reentrant +{ + uint8_t dev; + uint8_t val; + + len--; + if (len > 15) + return false; + + dev = (reg & 0x80) ? 0x51 : 0x50; // 0x51 holds the diagnostics, 0x50 the module data + reg &= 0x7f; + + REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); + + REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, + 0x1 << (I2C_MEM_ADDR_WIDTH - 16) | len, + (dev >> 5) | i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 + | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2, + ((dev << 3) & 0xff) | 0x1); + + do { + reg_read(RTL837X_REG_I2C_CTRL); + } while (SFR_DATA_0 & 0x1); + + if (SFR_DATA_0 & 0x2) + return false; + + for (uint8_t i = 0; i <= len; i++) { + switch (i & 0x3) { + case 0: + reg_read(RTL837X_REG_I2C_OUT + i); + val = SFR_DATA_0; + break; + case 1: + val = SFR_DATA_8; + break; + case 2: + val = SFR_DATA_16; + break; + default: + val = SFR_DATA_24; + break; + } + sfp_buf[i] = val; + } + + return true; +} diff --git a/rtl837x_port.c b/rtl837x_port.c index a85e90df..1c966728 100644 --- a/rtl837x_port.c +++ b/rtl837x_port.c @@ -30,7 +30,7 @@ __xdata uint32_t l2_head; __xdata struct vlan_settings vlan_settings; -void port_mirror_set(register uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked +void port_mirror_set(uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked { print_string("\nport_mirror_set called \n"); print_string("Mirroring port: "); print_byte(port); print_string(" with rx-mask: "); @@ -87,10 +87,9 @@ vlan_ingress_mode_t port_ingress_filter_get(__xdata uint8_t port) __banked /* * Define a Primary VLAN ID for a port */ -void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked +static void port_pvid_write(uint8_t port, __xdata uint16_t pvid) { // r4e1c:00001001 R4e1c-000017d0 r6738:00000000 R6738-00000000 (no filtering) - print_string("\nport_pvid_set called \n"); uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2); reg_read_m(reg); @@ -101,6 +100,22 @@ void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked } } +void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked +{ + uint8_t lag = port_lag_of(port); + + print_string("\nport_pvid_set called \n"); + if (lag == PORT_LAG_NONE) { + port_pvid_write(port, pvid); + return; + } + + uint16_t members = port_lag_members_get(lag); + for (uint8_t i = 0; i < 10; i++) + if ((members >> i) & 1) + port_pvid_write(i, pvid); +} + uint16_t port_pvid_get(uint8_t port) __banked { uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2); @@ -162,7 +177,7 @@ void vlan_name_remove(uint16_t vlan) __banked * Reads VLAN information from VLAN table * Returns data in sfr_data */ -int8_t vlan_get(register uint16_t vlan) __banked +int8_t vlan_get(uint16_t vlan) __banked { if (vlan >= 0xfff) // VLAN 4095 is special return -1; @@ -177,7 +192,7 @@ int8_t vlan_get(register uint16_t vlan) __banked } -__xdata uint16_t vlan_name(register uint16_t vlan) __banked +__xdata uint16_t vlan_name(uint16_t vlan) __banked { __xdata int16_t i = 0; __xdata uint8_t begin = 1; @@ -314,6 +329,20 @@ void vlan_setup(void) __banked } +/* + * Forget the dynamic L2 entries learned on one port. + */ +void port_l2_forget_port(uint8_t port) __banked +{ + REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0); /* port-based, dynamic entries */ + REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | (((uint16_t)1) << port)); + + do { + reg_read(RTL837x_L2_TBL_FLUSH_CTRL); + } while (SFR_DATA_16); +} + + /* * Forget all dynamic L2 learned entries */ @@ -325,7 +354,10 @@ uint8_t port_l2_forget(void) __banked REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0); // Flush L2 table for all ports by setting the ports and the flush-exec bit (bit 16) - REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6)); + uint16_t mask = PMASK_6; + if (machine_detected.isRTL8373) + mask = PMASK_9; + REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | mask); // Wait for flush completed do { @@ -390,10 +422,7 @@ void port_l2_learned(void) __banked print_string("\tlearned\t"); port |= (sfr_data[3] & 0x3) << 2; - if (port < 9) - write_char(machine.log_to_phys_port[port] + '0'); - else - print_string("CPU"); + print_phys_port(port); } entry++; @@ -402,6 +431,27 @@ void port_l2_learned(void) __banked } +/* + * Static L2 multicast entry for the link-local group 01:80:C2:00:00: + * in VLAN `vid`, with member portmask `pmask` (bit 9 = CPU port). + */ + +void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked +{ + do { + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & TBL_EXECUTE); + + REG_WRITE(RTL837x_TBL_DATA_IN_A, 0xc2, 0x00, 0x00, mac_last); + REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | (vid >> 8) | ((pmask & 0x3) << 6), vid, 0x01, 0x80); + REG_WRITE(RTL837x_TBL_DATA_IN_C, 0, 0, 0, pmask >> 2); + REG_WRITE(RTL837X_TBL_CTRL, 0, 0, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE); + do { + reg_read(RTL837X_TBL_CTRL); + } while (SFR_DATA_0 & TBL_EXECUTE); +} + + /* * Basic L2 configuration such as time to forget an entry */ @@ -413,12 +463,14 @@ void port_l2_setup(void) __banked for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { // Limit the number of automatically learned MAC-Entries per port to 0x1040 - uint16_t reg = RTL837X_L2_LRN_PORT_CONSTRAINT + (i << 2); - REG_SET(reg, 0x00001040); + uint8_t idx = (i << 2); + REG_SET(RTL837X_L2_LRN_PORT_CONSTRAINT + idx, 0x00001040); // All ports may communicate with each other and CPU-Port - reg = RTL837X_PORT_ISOLATION_BASE + (i << 2); - REG_SET(reg, PMASK_CPU | (machine_detected.isRTL8373? PMASK_9 : PMASK_6)); + uint16_t mask = PMASK_CPU | PMASK_6; + if (machine_detected.isRTL8373) + mask = PMASK_CPU | PMASK_9; + REG_SET(RTL837X_PORT_ISOLATION_BASE + idx, mask); } // When maximim entries learned, then simply flood the packet reg_bit_set(RTL837X_L2_LRN_PORT_CONSTRT_ACT, 0); @@ -431,7 +483,7 @@ void port_stats_print(void) __banked { print_string("\nPort\tState\tLink\tTxGood\t\tTxBad\t\tRxGood\t\tRxBad\n"); for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - write_char('0' + machine.log_to_phys_port[i]); write_char('\t'); + print_phys_port(i); write_char('\t'); if (!machine.is_sfp[i]) { phy_read(i, PHY_MMD31, 0xa610); @@ -508,14 +560,14 @@ void port_stats_print(void) __banked } -void port_isolate(register uint8_t port, __xdata uint16_t pmask) __banked +void port_isolate(uint8_t port, __xdata uint16_t pmask) __banked { if (port <= machine.max_port) REG_SET(RTL837X_PORT_ISOLATION_BASE + (port << 2), pmask); } -uint16_t port_isolation_get(register uint8_t port) __banked +uint16_t port_isolation_get(uint8_t port) __banked { if (port > machine.max_port) return 0; @@ -606,7 +658,7 @@ void port_eee_disable(uint8_t port) __banked void port_eee_status(uint8_t port) __banked { - print_string("Port: "); write_char('0' + machine.log_to_phys_port[port]); + print_string("Port: "); print_phys_port(port); print_string(": "); if (machine.is_sfp[port]) { print_string("SFP\n"); @@ -745,6 +797,14 @@ uint16_t port_lag_members_get(uint8_t lag) __banked return ((uint16_t)SFR_DATA_8 << 8) | SFR_DATA_0; } +uint8_t port_lag_of(uint8_t port) __banked +{ + for (uint8_t lag = 0; lag < 4; lag++) + if ((port_lag_members_get(lag) >> port) & 1) + return lag; + return PORT_LAG_NONE; +} + /* * Configure LAGs @@ -801,16 +861,6 @@ void print_port_ingress_filter_mode(vlan_ingress_mode_t mode) __banked } } -static void print_phys_port(uint8_t port) __banked -{ - if (port >= machine.min_port && port <= machine.max_port) - write_char(machine.log_to_phys_port[port] + '0'); - else if (port == 9) - write_char('9'); - else - write_char('?'); -} - void print_vlan_ingress_port(uint8_t log_port) __banked { print_phys_port(log_port);write_char('\t'); @@ -839,21 +889,79 @@ void vlan_dump(void) __banked /** Set the ingress VLAN filtering */ -bool port_ingress_vlan_filter_set(__xdata uint8_t port, __xdata bool enabled) __banked +bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked { - if (port < machine.min_port || port > machine.max_port && port != 9) { + if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) { return false; } - reg_bit_set(RTL837X_VLAN_PORT_IGR_FLTR, port); + if (enabled) + reg_bit_set(RTL837X_VLAN_PORT_IGR_FLTR, port); + else + reg_bit_clear(RTL837X_VLAN_PORT_IGR_FLTR, port); + return true; } /** Get the ingress VLAN filtering status */ -bool port_ingress_vlan_filter_get(__xdata uint8_t port) __banked +bool port_ingress_vlan_filter_get(uint8_t port) __banked { - if (port < machine.min_port || port > machine.max_port && port != 9) { + if (port < machine.min_port || port > machine.max_port && port != CPU_PORT) { return false; } 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(); +} diff --git a/rtl837x_port.h b/rtl837x_port.h index b4598321..0325249c 100644 --- a/rtl837x_port.h +++ b/rtl837x_port.h @@ -8,11 +8,12 @@ #define STAT_COUNTER_RX_PKTS 47 #define STAT_COUNTER_ERR_PKTS 48 -#define STAT_GET(cnt, port) \ +#define STAT_GET(cnt, port) do { \ REG_WRITE(RTL837X_STAT_GET, 0x00, 0x00, cnt >> 3, (cnt << 5) | (port << 1) | 1); \ do { \ reg_read_m(RTL837X_STAT_GET); \ - } while (sfr_data[3] & 0x1); + } while (sfr_data[3] & 0x1); \ + } while (0) // Possible values for ingress filter type typedef enum { @@ -48,20 +49,24 @@ extern __xdata struct vlan_settings vlan_settings; uint8_t port_l2_forget(void) __banked; void port_l2_learned(void) __banked; void port_stats_print(void) __banked; -int8_t vlan_get(register uint16_t vlan) __banked; -__xdata uint16_t vlan_name(register uint16_t vlan) __banked; +int8_t vlan_get(uint16_t vlan) __banked; +__xdata uint16_t vlan_name(uint16_t vlan) __banked; void vlan_name_remove(uint16_t vlan) __banked; void vlan_setup(void) __banked; void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked; uint16_t port_pvid_get(uint8_t port) __banked; +void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked; +void port_l2_forget_port(uint8_t port) __banked; void vlan_create(void) __banked; void vlan_delete(uint16_t vlan) __banked; void vlan_dump(void) __banked; -void port_mirror_set(register uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked; +void port_mirror_set(uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked; void port_mirror_del(void) __banked; bool port_ingress_filter(__xdata uint8_t port, __xdata vlan_ingress_mode_t type) __banked; void port_l2_setup(void) __banked; uint16_t port_lag_members_get(uint8_t lag) __banked; +#define PORT_LAG_NONE 0xff +uint8_t port_lag_of(uint8_t port) __banked; void port_lag_members_set(__xdata uint8_t lag, __xdata uint16_t members) __banked; void port_lag_hash_set(__xdata uint8_t lag, __xdata uint8_t hash) __banked; void port_eee_enable_all(__xdata uint8_t speed) __banked; @@ -71,9 +76,11 @@ void port_eee_enable(__xdata uint8_t port, __xdata uint8_t speed) __banked; void port_eee_disable(uint8_t port) __banked; void port_eee_status(uint8_t port) __banked; void print_port_ingress_filter_mode(vlan_ingress_mode_t mode) __banked; -bool port_ingress_vlan_filter_set(__xdata uint8_t port, __xdata bool enabled) __banked; -bool port_ingress_vlan_filter_get(__xdata uint8_t port) __banked; -void port_isolate(register uint8_t port, __xdata uint16_t pmask) __banked; -uint16_t port_isolation_get(register uint8_t port) __banked; +bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked; +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 diff --git a/rtl837x_sfr.h b/rtl837x_sfr.h index f3e4a723..45c117a6 100644 --- a/rtl837x_sfr.h +++ b/rtl837x_sfr.h @@ -5,8 +5,13 @@ __sfr16 __at(0xa2a3) SFR_REG_ADDR_U16; __sfr __at(0xa2) SFR_REG_ADDRH; __sfr __at(0xa3) SFR_REG_ADDRL; __sfr16 __at(0xa6a7) SFR_DATA_U16; -__sfr32 __at(0xa4a5a6a7) SFR_DATA_U32; -__sfr32 __at(0xa7a6a5a4) SFR_DATA_U32LE; +// Disabling until the SDCC bug #4070 is fixed. +// Generate wrong address for `a4` location. +// Both read from and write to `SFR_DATA_U32`. +// __sfr32 __at(0xa4a5a6a7) SFR_DATA_U32; +// __sfr32 __at(0xa7a6a5a4) SFR_DATA_U32LE; +// This is the upper part of the U32 as a workaround for SDCC bug #4070. +__sfr16 __at(0xa4a5) SFR_DATA_U16_UPPER; __sfr __at(0xa4) SFR_DATA_24; __sfr __at(0xa5) SFR_DATA_16; __sfr __at(0xa6) SFR_DATA_8; diff --git a/rtl837x_stp.c b/rtl837x_stp.c index 2eb4f928..e33dbb4a 100644 --- a/rtl837x_stp.c +++ b/rtl837x_stp.c @@ -11,29 +11,84 @@ #include "rtl837x_sfr.h" #include "rtl837x_regs.h" #include "rtl837x_stp.h" +#include "rtl837x_port.h" #include "uip.h" #include "machine.h" +// All entry points are __banked and nothing here runs from an interrupt, +// so the module does not need to stay in the resident bank +#pragma codeseg BANK2 +#pragma constseg BANK2 + extern __code struct machine machine; extern __xdata uint8_t sfr_data[4]; +extern __xdata struct machine_runtime machine_detected; + extern __xdata struct uip_eth_addr uip_ethaddr; extern __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE + 2]; -struct bridge { - uint8_t prio; - uint8_t ext; - uint8_t mac[6]; -}; +extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; +extern __xdata uint8_t cmd_words_len; +extern __xdata uint8_t cmd_words_b[15]; +extern __xdata char save_cmd; /* 0 while execute_config() replays the saved config */ +uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd); +uint8_t atoi_byte(uint8_t idx); +uint8_t cmd_parse_port_separator(uint8_t idx); +extern __xdata uint8_t atoi_results_u8; +/* ---- Configuration ---- */ +__xdata uint8_t stp_prio; /* bridge priority high byte (0x80 = 32768) */ +__xdata uint8_t stp_hello_s; /* 1-10 s */ +__xdata uint8_t stp_maxage_s; /* 6-40 s */ +__xdata uint8_t stp_fwddelay_s; /* 4-30 s, also our listen period */ +__xdata uint8_t stp_rstp; /* 1 = RST BPDUs, 0 = legacy Config BPDUs */ +__xdata uint8_t stp_txhold; /* BPDUs per port per second */ + + +__xdata uint8_t stp_pflags[10]; +__xdata uint32_t stp_pcost[10]; /* 0 = auto */ +__xdata uint8_t stp_pprio[10]; +__xdata uint8_t stp_pp2p[10]; /* admin point-to-point: 0 auto, 1 on, 2 off */ + +/* Designated bridge, port and cost last heard on the port; stp_bpdu_age tells + * whether they are still current. + */ +__xdata struct bridge stp_dbridge[10]; +__xdata uint16_t stp_dpid[10]; +__xdata uint32_t stp_dcost[10]; + +/* ---- Status / runtime ---- */ __xdata struct bridge root_bridge; -__xdata uint32_t root_bridge_cost; +__xdata uint32_t root_bridge_cost; /* our cost to the root (rx cost + root port cost) */ +__xdata uint8_t stp_root_port; /* 0xff = we are the root */ +__xdata uint16_t stp_tc_count; +__xdata uint16_t stp_scratch16; /* scratch for status printing only */ -__xdata uint8_t port_types[10]; -__xdata uint16_t port_timers[10]; -__xdata uint16_t port_hello[10]; +__xdata uint16_t port_timers[10]; /* listen-period countdown (0 = not listening) */ +__xdata uint16_t port_hello[10]; /* hello TX countdown */ +__xdata uint16_t stp_bpdu_age[10]; /* ticks since last BPDU seen on port (saturating) */ +__xdata uint8_t stp_loop_held[10]; /* port is out of forwarding because a loop was seen on it */ +__xdata uint8_t stp_tx_budget[10]; /* tx hold: BPDUs left in the current second */ +__xdata uint8_t stp_tx_count[10]; /* BPDUs actually put on the wire, wraps at 256 */ +__xdata uint16_t stp_sec_tick; /* 1 s window for the tx budget */ +__xdata uint16_t stp_link_prev; /* carrier bitmap as of the last check */ +__xdata uint16_t stp_link_now; +__xdata uint8_t stp_scratch; +__xdata uint8_t stp_tx_flags_extra; /* one-shot flags OR-ed into the next BPDU (TCA) */ +__xdata uint16_t stp_rxlen; /* received frame length, saved before uip_len is consumed */ +__xdata uint8_t stp_msg_age; /* message age of the root info we hold, seconds */ +__xdata uint16_t stp_tc_while; /* ticks left to set the TC flag in our BPDUs */ +__xdata uint8_t stp_i; +__xdata uint32_t stp_cost_scratch; +__xdata uint8_t stp_loop_peer; /* the other own port seen on a looped segment */ + +#define STP_EDGE_DELAY (3 * STP_HZ) /* auto-edge: forward after 3 s without BPDU */ + +#define AUTO_COST 20000UL /* path cost used when stp_pcost == 0 (1G default) */ +#define PCOST(i) (stp_pcost[i] ? stp_pcost[i] : AUTO_COST) struct stp_pkt { uint8_t stp_addr[6]; @@ -56,6 +111,7 @@ struct stp_pkt { uint16_t age_max; uint16_t hello; uint16_t fwd_delay; + uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */ }; struct stp_pkt_in { @@ -80,18 +136,126 @@ struct stp_pkt_in { uint16_t age_max; uint16_t hello; uint16_t fwd_delay; + uint8_t version1_length; /* RST BPDU only: length of the (empty) v1 part */ }; #define STP_O ((__xdata struct stp_pkt *)&uip_buf[RTL_FRAME_DESC_SIZE]) #define STP_I ((__xdata struct stp_pkt_in *)&uip_buf[0]) -#define FLAG_PROPOSAL 0x02 -#define P_DESIGNATED ((STP_I->flags & 0x0c) == 0x0c) -#define P_PROPOSAL (STP_I->flags & FLAG_PROPOSAL) +#define BPDU_VER_STP 0x00 +#define BPDU_VER_RSTP 0x02 -signed char cmpMAC(__xdata uint8_t *m1, __xdata uint8_t *m2) +#define BPDU_TYPE_CONFIG 0x00 +#define BPDU_TYPE_RST 0x02 +#define BPDU_TYPE_TCN 0x80 + +#define BPDU_LEN_CONFIG 0x26 // LLC and a 35 byte body +#define BPDU_LEN_RST 0x27 // LLC and a 36 byte body +#define BPDU_LEN_MIN_HEADER 33 // addresses through bpdu_type + +#define BPDU_FLAG_TC 0x01 +#define BPDU_FLAG_LEARNING 0x10 +#define BPDU_FLAG_FORWARDING 0x20 +#define BPDU_FLAG_TCACK 0x80 + +#define BPDU_ROLE_ROOT (0b10 << 2) +#define BPDU_ROLE_DESIGNATED (0b11 << 2) + +/* Console messages name the port on the front panel, not the internal index. */ +static void print_port_nl(uint8_t port) __reentrant { - for (uint8_t i = 0; i < 6; i++) { + print_byte(machine.log_to_phys_port[port]); + write_char('\n'); +} + + +static void print_bridge_id(uint8_t prio, uint8_t ext, __xdata uint8_t *mac) __reentrant +{ + print_byte(prio); print_byte(ext); write_char('/'); + for (stp_i = 0; stp_i < 6; stp_i++) + print_byte(mac[stp_i]); +} + + +/* Fixed width columns so the rows line up under the header without a + * formatter. The state indices are the ASIC's own two bits, in the order + * stp_state_set() writes them. */ +static __code const char stp_state_txt[] = "off blocklearnfwd "; +static __code const char stp_role_txt[] = "desgroot"; +static __code const char stp_edge_txt[] = "no yes "; + +static void print_field(__code const char *txt, uint8_t idx, uint8_t width) __reentrant +{ + txt += idx * width; + while (width--) + write_char(*txt++); +} + + +static void stp_status(void) +{ + if (!stp_enabled) { + print_string("STP off\n"); + return; + } + print_string(stp_rstp ? "STP on, RSTP\n" : "STP on, STP\n"); + print_string("bridge "); + print_bridge_id(stp_prio, 0, uip_ethaddr.addr); + print_string("\nroot "); + print_bridge_id(root_bridge.prio, root_bridge.ext, root_bridge.mac); + if (stp_root_port == 0xff) { + print_string(" (this switch)\n"); + } else { + print_string(" port "); + print_byte(machine.log_to_phys_port[stp_root_port]); + print_string(" cost "); + print_long(root_bridge_cost); + write_char('\n'); + } + print_string("changes "); + print_short(stp_tc_count); + write_char('\n'); + print_string("port state role edge tx bpdu\n"); + reg_read_m(RTL837X_MSTP_STATES); + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { + write_char(' '); + print_byte(machine.log_to_phys_port[stp_i]); + print_string(" "); + print_field(stp_state_txt, (sfr_data[3 - (stp_i >> 2)] >> ((stp_i << 1) & 0x7)) & 0x3, 5); + write_char(' '); + print_field(stp_role_txt, stp_i == stp_root_port ? 1 : 0, 4); + write_char(' '); + print_field(stp_edge_txt, stp_pflags[stp_i] & STP_PF_OPEREDGE ? 1 : 0, 4); + write_char(' '); + print_byte(stp_tx_count[stp_i]); + write_char(' '); + stp_scratch16 = stp_bpdu_age[stp_i] / STP_HZ; + itoa(stp_scratch16 > 255 ? 255 : (uint8_t)stp_scratch16); + write_char('\n'); + } +} + + +static void stp_record_designated(uint8_t port) __reentrant +{ + stp_dbridge[port].prio = STP_I->bridge.prio; + stp_dbridge[port].ext = STP_I->bridge.ext; + memcpy(stp_dbridge[port].mac, STP_I->bridge.mac, 6); + stp_dpid[port] = ((uint16_t)STP_I->port_prio << 8) | STP_I->port_id; + stp_cost_scratch = STP_I->root_path_cost; + stp_dcost[port] = ((stp_cost_scratch & 0xff) << 24) + | ((stp_cost_scratch & 0xff00) << 8) + | ((stp_cost_scratch >> 8) & 0xff00) + | (stp_cost_scratch >> 24); +} + + +/* Lexicographic compare of n bytes. A MAC is 6 of them; a Bridge Identifier + * is 8, the two priority octets ahead of the MAC, compared as one unsigned + * number per 802.1D. */ +int8_t cmpBytes(__xdata uint8_t *m1, __xdata uint8_t *m2, uint8_t n) __reentrant +{ + for (uint8_t i = 0; i < n; i++) { if (m1[i] == m2[i]) continue; if (m1[i] < m2[i]) @@ -102,103 +266,448 @@ signed char cmpMAC(__xdata uint8_t *m1, __xdata uint8_t *m2) } -void stp_in(void) __banked +/* Write one port's 2-bit state into the ASIC's MSTP register. + * 00 disable, 01 blocking, 10 learning, 11 forwarding. */ +static void stp_state_set(uint8_t port, uint8_t state) __reentrant { - // By default we do not send anything out - uip_len = 0; - // MSTPSTP_I_STATES 0x5310 - // reg_read_m(RTL837X_MSTP_STATES); - - print_string("Check BPDU... \n"); - for (uint8_t i = 0; i < 80; i++) { - print_byte(uip_buf[i]); - write_char(' '); - } - write_char('\n'); - print_byte(STP_I->dsap); - print_byte(STP_I->ssap); - print_byte(STP_I->ctrl); - - write_char('\n'); - // Make sure this is the type of RSTP packet we are interested in: - if (!(STP_I->dsap == 0x42 && STP_I->ssap == 0x42 && STP_I->ctrl == 0x03)) - return; - print_string("Checking RSTP\n"); - if (STP_I->proto) - return; -// write_char('A'); print_byte(STP_I->version); write_char('\n'); - if (STP_I->version != 2) - return; -// write_char('B'); print_byte(STP_I->bpdu_type); write_char('\n'); - if (STP_I->bpdu_type != 2) - return; -// write_char('\n'); -// print_string("Flags: "); print_byte(STP_I->flags); write_char('\n'); - print_string("Check new Root\n"); - if (STP_I->root.prio < root_bridge.prio - || ((STP_I->root.prio == root_bridge.prio) && cmpMAC(STP_I->root.mac, root_bridge.mac) < 0)) { - print_string("Updating Root bridge\n"); - root_bridge.prio = STP_I->root.prio; - memcpy(root_bridge.mac, STP_I->root.mac, 6); - } + reg_read_m(RTL837X_MSTP_STATES); + stp_scratch = 3 - (port >> 2); + sfr_data[stp_scratch] &= ~(uint8_t)(0b11 << ((port << 1) & 0x7)); + sfr_data[stp_scratch] |= (uint8_t)(state << ((port << 1) & 0x7)); + reg_write_m(RTL837X_MSTP_STATES); } -void stp_cnf_send(uint8_t port) +/* Signal a topology change. Edge ports are exempt. */ +static void stp_topology_change(uint8_t port) __reentrant { + if (stp_pflags[port] & STP_PF_OPEREDGE) + return; + stp_tc_count++; + stp_tc_while = ((uint16_t)stp_maxage_s + stp_fwddelay_s) * STP_HZ; + port_l2_forget_port(port); +} + + +/* Hold one port out of forwarding because a loop was seen on it, and keep + * holding it for as long as the caller keeps saying so. The caller is the + * port that won the Port ID compare (see stp_in) - a different port than + * the one held, except when the frame came back on the port it left. + */ +static void stp_loop_hold_peer(uint8_t port) __reentrant +{ + if (port < machine.min_port || port > machine.max_port) + return; + if (!(stp_pflags[port] & STP_PF_ENABLED)) + return; + if (stp_pflags[port] & STP_PF_TRIPPED) + return; + if (!port_timers[port]) { + print_string("STP: loop detected, blocking port "); + print_port_nl(port); + stp_state_set(port, 0b01); + stp_pflags[port] &= ~STP_PF_OPEREDGE; + stp_topology_change(port); + } + stp_loop_held[port] = 1; + port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ; +} + + +/* Take the bridge back as root of its own tree (initial state / root aged out) */ +static void stp_claim_root(void) +{ + root_bridge.prio = stp_prio; + root_bridge.ext = 0x00; + memcpy(root_bridge.mac, uip_ethaddr.addr, 6); + root_bridge_cost = 0; + stp_root_port = 0xff; + stp_msg_age = 0; +} + + +void stp_cnf_send(uint8_t port) __reentrant +{ + /* A one-shot flag (TCA) belongs to the BPDU we were asked to send: drop + * it with the frame, or it would surface on an unrelated port later. */ + if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & (STP_PF_FILTER | STP_PF_TRIPPED))) { + stp_tx_flags_extra = 0; + return; + } + if (!stp_tx_budget[port]) { /* tx hold count exhausted for this second */ + stp_tx_flags_extra = 0; + return; + } + stp_tx_budget[port]--; + stp_tx_count[port]++; + STP_O->stp_addr[0] = 0x01; STP_O->stp_addr[1] = 0x80; STP_O->stp_addr[2] = 0xc2; STP_O->stp_addr[3] = STP_O->stp_addr[4] = STP_O->stp_addr[5] = 0x00; STP_O->rtl_tag.tag = HTONS(RTL_FRAME_TAG_ID); STP_O->rtl_tag.version = RTL_FRAME_TAG_VERSION; STP_O->rtl_tag.reason = 0x00; - STP_O->rtl_tag.flags = 0x0020; // Disable L2 learning + STP_O->rtl_tag.flags = HTONS(RTL_TAG_LEARN_DIS); STP_O->rtl_tag.pmask = HTONS(((uint16_t)1) << port); - STP_O->msg_len = HTONS(0x27); STP_O->dsap = 0x42; STP_O->ssap = 0x42; STP_O->ctrl = 0x03; STP_O->proto = 0x0000; - STP_O->version = 0x02; // RSTP - STP_O->bpdu_type = 0x00; // Config - STP_O->flags = 0x81; + if (stp_rstp) { + STP_O->msg_len = HTONS(BPDU_LEN_RST); + STP_O->version = BPDU_VER_RSTP; + STP_O->bpdu_type = BPDU_TYPE_RST; + reg_read_m(RTL837X_MSTP_STATES); + STP_O->flags = port == stp_root_port ? BPDU_ROLE_ROOT : BPDU_ROLE_DESIGNATED; + if (((sfr_data[3 - (port >> 2)] >> ((port << 1) & 0x7)) & 0b11) == 0b11) + STP_O->flags |= BPDU_FLAG_LEARNING | BPDU_FLAG_FORWARDING; + } else { + STP_O->msg_len = HTONS(BPDU_LEN_CONFIG); + STP_O->version = BPDU_VER_STP; + STP_O->bpdu_type = BPDU_TYPE_CONFIG; + STP_O->flags = 0x00; + } + if (stp_tc_while) + STP_O->flags |= BPDU_FLAG_TC; + STP_O->flags |= stp_tx_flags_extra; + stp_tx_flags_extra = 0; memcpy(STP_O->src_addr, uip_ethaddr.addr, 6); + STP_O->src_addr[0] |= 0x02; + STP_O->src_addr[5] = (uip_ethaddr.addr[5] & 0xf0) | port; memcpy(STP_O->root.mac, root_bridge.mac, 6); memcpy(STP_O->bridge.mac, uip_ethaddr.addr, 6); STP_O->root.prio = root_bridge.prio; - STP_O->root.ext = 0x00; - STP_O->root_path_cost = 0x00000000; + STP_O->root.ext = root_bridge.ext; + /* Our root path cost, big-endian (0 while we are the root ourselves) */ + STP_O->root_path_cost = ((root_bridge_cost & 0xff) << 24) + | ((root_bridge_cost & 0xff00) << 8) + | ((root_bridge_cost >> 8) & 0xff00) + | (root_bridge_cost >> 24); - STP_O->bridge.prio = 0x80; + STP_O->bridge.prio = stp_prio; STP_O->bridge.ext = 0x00; - STP_O->port_prio = 0x80; - STP_O->port_id = port; - STP_O->age = 0x00; // FIXME: This only works because we do not use HTONS and the values are in 1/256 seconds - STP_O->age_max = 20; - STP_O->hello = 2; - STP_O->fwd_delay = 0x0f; + STP_O->port_prio = stp_pprio[port]; + STP_O->port_id = port + 1; + /* Message age, incremented by one second per bridge we relay through. + * The timer fields are in 1/256 s on the wire, and sdcc stores uint16 + * little-endian, so assigning the plain second count lands the value in + * the high (seconds) octet - see age_max/hello/fwd_delay below. */ + STP_O->age = (stp_root_port == 0xff) ? 0 : (uint16_t)(stp_msg_age + 1); + STP_O->age_max = stp_maxage_s; + STP_O->hello = stp_hello_s; + STP_O->fwd_delay = stp_fwddelay_s; + STP_O->version1_length = 0; /* RST BPDU: no version-1 information */ -// uip_len = 0x27 + sizeof(struct rtl_tag); - uip_len = sizeof(struct stp_pkt); + uip_len = stp_rstp ? sizeof(struct stp_pkt) : sizeof(struct stp_pkt) - 1; tcpip_output(); } +void stp_in(void) __banked +{ + uint8_t port; + + if (uip_len < BPDU_LEN_MIN_HEADER) { + uip_len = 0; + return; + } + stp_rxlen = uip_len; + + // By default we do not send anything out + uip_len = 0; + + /* Ingress port: low nibble of the CPU tag's pmask on RX */ + stp_scratch = ((uint8_t)HTONS(STP_I->rtl_tag.pmask)) & 0x0f; + if (stp_scratch < machine.min_port || stp_scratch > machine.max_port) + return; + port = stp_scratch; + + // Make sure this is the type of (R)STP packet we are interested in: + if (!(STP_I->dsap == 0x42 && STP_I->ssap == 0x42 && STP_I->ctrl == 0x03)) + return; + if (STP_I->proto) + return; + if (!((STP_I->version >= BPDU_VER_RSTP && STP_I->bpdu_type == BPDU_TYPE_RST) + || (STP_I->version == BPDU_VER_STP + && (STP_I->bpdu_type == BPDU_TYPE_CONFIG + || STP_I->bpdu_type == BPDU_TYPE_TCN)))) + return; + + if (!(stp_pflags[port] & STP_PF_ENABLED) || (stp_pflags[port] & STP_PF_FILTER)) + return; + + /* BPDU guard: an edge-facing port must never see a BPDU - shut it down. */ + if (stp_pflags[port] & STP_PF_BPDUGUARD) { + print_string("STP: BPDU guard tripped, disabling port "); + print_port_nl(port); + stp_pflags[port] |= STP_PF_TRIPPED; + stp_state_set(port, 0b00); + stp_tc_count++; + return; + } + + stp_bpdu_age[port] = 0; + + /* A port that hears a BPDU is not an edge port, whatever it decided + * during the silence after the link came up. Only the flag is dropped: + * the port keeps whatever forwarding state the rules below give it, + * rather than being pushed back through the listen period, which would + * black-hole a working link for a forward delay on the first BPDU. The + * flag matters beyond the status page, since stp_topology_change() + * exempts edge ports and so would go on skipping the counter and the + * L2 flush for a port that has a bridge behind it. */ + stp_pflags[port] &= ~STP_PF_OPEREDGE; + + if (STP_I->bpdu_type == BPDU_TYPE_TCN) { + stp_tx_flags_extra = BPDU_FLAG_TCACK; + stp_cnf_send(port); + uip_len = 0; + stp_topology_change(port); + return; + } + + /* Everything below reads the full Config/RST body. */ + if (stp_rxlen < 64) + return; + + /* Our own BPDU coming back: two of our ports sit on one segment. Only + * the one with the worse Port ID stops forwarding, and only the other + * one writes that state, so the two never race each other. + */ + if (cmpBytes(STP_I->bridge.mac, uip_ethaddr.addr, 6) == 0) { + /* Equal means the frame came back on the port it left: a loop + * further out, behind an unmanaged switch. There is no pair to + * pick from, so that port holds itself down - and since it can + * only re-arm while it is receiving, that case degrades to the + * forward-delay pulse we had before rather than a real latch. + * The peer's number is validated by the callee, not here. */ + stp_loop_peer = STP_I->port_id; /* 1-based, as we send it */ + if (!stp_loop_peer) + return; + stp_loop_peer--; + /* A Port ID is (priority, number) and priority is compared + * first - stp_cnf_send() puts stp_pprio[] on the wire next to + * the number, so "stp port N prio" has to be able to decide + * which end of a looped pair keeps forwarding. Comparing the + * number alone would quietly ignore it. */ + if (STP_I->port_prio != stp_pprio[port]) { + if (STP_I->port_prio < stp_pprio[port]) + return; /* peer is better: it decides */ + } else if (stp_loop_peer < port) { + return; + } + stp_loop_hold_peer(stp_loop_peer); + return; + } + + /* Topology Change in transit. The flag arms a short window that our + * own BPDUs copy downstream (the TX side already sends TC while + * stp_tc_while runs) and that each further flagged BPDU refreshes, so + * it expires one hello after the neighbour stops - without shortening + * the long window a local change may have armed. The flush runs once, + * on the arming edge: everything learned on the other non-edge ports + * may sit behind the moved link and must be relearned. */ + if (STP_I->flags & BPDU_FLAG_TC) { + if (!stp_tc_while) { + uint8_t i; + stp_tc_count++; + for (i = machine.min_port; i <= machine.max_port; i++) + if (i != port && (stp_pflags[i] & STP_PF_ENABLED) + && !(stp_pflags[i] & STP_PF_OPEREDGE)) + port_l2_forget_port(i); + } + if (stp_tc_while < ((uint16_t)stp_hello_s + 1) * STP_HZ) + stp_tc_while = ((uint16_t)stp_hello_s + 1) * STP_HZ; + } + + stp_record_designated(port); + + /* Better root than the one we know? The identifier is priority, system + * ID extension and MAC in that order: comparing the priority byte and + * then jumping to the MAC skipped the twelve bits in between, so two + * bridges differing only in the extension were ranked by MAC. */ + if (cmpBytes((__xdata uint8_t *)&STP_I->root, (__xdata uint8_t *)&root_bridge, 8) < 0) { + /* Root guard: this port must never become our path to the root. */ + if (stp_pflags[port] & STP_PF_ROOTGUARD) { + print_string("STP: root guard blocking port "); + print_port_nl(port); + stp_state_set(port, 0b01); + port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ; + stp_pflags[port] &= ~STP_PF_OPEREDGE; + return; + } + print_string("Updating Root bridge\n"); + root_bridge.prio = STP_I->root.prio; + root_bridge.ext = STP_I->root.ext; + memcpy(root_bridge.mac, STP_I->root.mac, 6); + stp_root_port = port; + stp_tc_count++; + } + + /* Refresh our cost to the root when the update comes in on the root port */ + if (port == stp_root_port) { + /* Age of the information we now hold (see the TX note on the wire + * format); saturate rather than wrap on absurd input. */ + stp_msg_age = (STP_I->age > 254) ? 254 : (uint8_t)STP_I->age; + root_bridge_cost = stp_dcost[port] + PCOST(port); + } +} + + void stp_timers(void) __banked { - for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - port_hello[i]--; - if (!port_hello[i]) { - port_hello[i] = TIME_HELLO; - print_string("STP_HELLO port "); - print_byte(i); write_char('\n'); - stp_cnf_send(i); + /* Refill the per-port tx budgets once per second (tx hold count) */ + if (++stp_sec_tick >= STP_HZ) { + stp_sec_tick = 0; + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) + stp_tx_budget[stp_i] = stp_txhold; + + /* Link supervision. Without this the state machine never learns + * that a port lost carrier: it keeps the port in forwarding, keeps + * announcing on it, and never flushes what was learned behind it - + * yet losing a link is the most ordinary topology change there is. + * Once per second is soon enough, and it keeps register reads out + * of the 50 Hz tick. */ + reg_read_m(RTL837X_REG_LINKS_STS); + stp_link_now = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8); + if (stp_link_now != stp_link_prev) { + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { + if (!(stp_pflags[stp_i] & STP_PF_ENABLED)) + continue; + if (!((stp_link_now ^ stp_link_prev) >> stp_i & 1)) + continue; + /* Either way the port must stop forwarding first. */ + stp_state_set(stp_i, 0b01); + if ((stp_link_now >> stp_i) & 1) { + /* Carrier back: re-run the listen period rather than + * forwarding straight away - the segment may have been + * rewired while we were down. Auto edge still applies. */ + port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ; + stp_pflags[stp_i] &= ~STP_PF_OPEREDGE; + stp_bpdu_age[stp_i] = 0; + } else { + port_timers[stp_i] = 0; + print_string("STP: link down, port blocking "); + print_port_nl(stp_i); + stp_topology_change(stp_i); + } + } + stp_link_prev = stp_link_now; } } + + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { + if (!(stp_pflags[stp_i] & STP_PF_ENABLED)) + continue; + + if (stp_bpdu_age[stp_i] < 0xffff) + stp_bpdu_age[stp_i]++; + + /* Periodic hello */ + if (port_hello[stp_i]) + port_hello[stp_i]--; + if (!port_hello[stp_i]) { + port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ; + /* Only designated ports announce periodically: the root port is + * where our own root information comes FROM, and echoing it back + * there just feeds the upstream bridge its own data (and looks + * like a competing designated bridge on that segment). */ + if (stp_i != stp_root_port) + stp_cnf_send(stp_i); + } + + /* Promote a port out of blocking once its listen period expires + * with no reason to stay blocked (no better root heard: we are + * the designated bridge on that port). */ + if (port_timers[stp_i]) { + if (!--port_timers[stp_i]) { + stp_loop_held[stp_i] = 0; + stp_state_set(stp_i, 0b11); + print_string("STP: port forwarding "); + print_port_nl(stp_i); + stp_topology_change(stp_i); + } else if ((stp_pflags[stp_i] & STP_PF_AUTOEDGE) + && !stp_loop_held[stp_i] + && stp_bpdu_age[stp_i] > STP_EDGE_DELAY) { + /* Auto edge: nothing talks (R)STP on this port - it is + * host-facing, go to forwarding without the full wait. */ + port_timers[stp_i] = 0; + stp_pflags[stp_i] |= STP_PF_OPEREDGE; + stp_state_set(stp_i, 0b11); + print_string("STP: edge port forwarding "); + print_port_nl(stp_i); + } + } + } + + if (stp_tc_while) + stp_tc_while--; + + /* Age out a root that went silent: reclaim the tree. */ + if (stp_root_port != 0xff + && stp_bpdu_age[stp_root_port] > (uint16_t)stp_maxage_s * STP_HZ) { + print_string("STP: root aged out, claiming root\n"); + stp_claim_root(); + stp_tc_count++; + } +} + + +/* Reset all configuration to the 802.1D/802.1w defaults. Called once at boot + * (before the startup config replays "stp ..." commands over it). */ +void stp_defaults(void) __banked +{ + stp_prio = 0x80; /* high byte of the priority: 0x8000 is 32768 */ + stp_hello_s = 2; + stp_maxage_s = 20; + stp_fwddelay_s = 15; + stp_rstp = 1; + stp_txhold = 6; + for (stp_i = 0; stp_i < 10; stp_i++) { + /* enabled, auto-edge on: host-facing ports go forwarding after + * 3 s of BPDU silence instead of the full forward delay */ + stp_pflags[stp_i] = STP_PF_ENABLED | STP_PF_AUTOEDGE; + stp_pcost[stp_i] = 0; /* auto */ + stp_pprio[stp_i] = 0x80; + stp_bpdu_age[stp_i] = 0; + port_timers[stp_i] = 0; + port_hello[stp_i] = 0; + stp_tx_budget[stp_i] = 6; + } + stp_tc_count = 0; + stp_tc_while = 0; + stp_claim_root(); +} + + +/* + * Steer BPDUs while STP runs, and restore flooding when it stops. + * Changing a port's PVID while STP runs needs "stp off" then "stp on". + */ +static void stp_fdb_update(__xdata uint16_t pmask) +{ + uint16_t stp_fdb_vid; + uint8_t stp_fdb_i; + + /* Unlike LACPDUs (always untagged, so per-PVID entries suffice), BPDUs + * can arrive VLAN-tagged and then classify into the tag's VID - cover + * every VLAN that exists in the VLAN table, plus every port's PVID for + * the untagged case. A duplicate VID just overwrites the same slot. */ + for (stp_fdb_vid = 1; stp_fdb_vid < 4095; stp_fdb_vid++) { + if (vlan_get(stp_fdb_vid) < 0) + continue; + if (!(sfr_data[0] & 0x02)) /* bit 1: VLAN table entry valid */ + continue; + port_l2mc_set(0x00, stp_fdb_vid, pmask); + } + for (stp_fdb_i = machine.min_port; stp_fdb_i <= machine.max_port; stp_fdb_i++) { + stp_fdb_vid = port_pvid_get(stp_fdb_i); + port_l2mc_set(0x00, stp_fdb_vid, pmask); + } } @@ -206,34 +715,216 @@ void stp_setup(void) __banked { print_string("Enabling STP: "); sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0; - for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { - // Set STP port state to blocking - // States are: 00 disable, 01 blocking, 10 learning, 11 forwarding - uint8_t bit_mask = 0b01 << ( (i << 1) & 0x7); - sfr_data[3 - (i >> 2)] |= bit_mask; - port_hello[i] = TIME_HELLO; - port_timers[i] = 0xa00; // 10 sec in blocking state + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { + stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED); + stp_loop_held[stp_i] = 0; + stp_bpdu_age[stp_i] = 0; + stp_tx_budget[stp_i] = stp_txhold; + stp_tx_count[stp_i] = 0; + if (!(stp_pflags[stp_i] & STP_PF_ENABLED) || (stp_pflags[stp_i] & STP_PF_ADMEDGE)) { + /* not participating, or admin edge: forwarding immediately */ + if (stp_pflags[stp_i] & STP_PF_ADMEDGE) + stp_pflags[stp_i] |= STP_PF_OPEREDGE; + sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7)); + port_timers[stp_i] = 0; + } else { + /* listen first: blocking until the forward-delay expires */ + sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b01 << ((stp_i << 1) & 0x7)); + port_timers[stp_i] = (uint16_t)stp_fwddelay_s * STP_HZ; + } + port_hello[stp_i] = (uint16_t)stp_hello_s * STP_HZ; } - sfr_data[1] |= 0x0f; // Do not block CPU-Port - reg_write_m(RTL837X_MSTP_STATES); // R5310-000d555f + sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9) + reg_write_m(RTL837X_MSTP_STATES); print_reg(RTL837X_MSTP_STATES); write_char('\n'); - root_bridge.prio = 0x80; // This corresponds to 32768 - root_bridge.ext = 0x00; - memcpy(root_bridge.mac, uip_ethaddr.addr, 6); + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { + if (!(stp_pflags[stp_i] & STP_PF_ENABLED)) + continue; + if (port_ingress_filter_get(stp_i) != VLAN_TAGGED) + continue; + print_string("STP: port "); + write_char('0' + machine.log_to_phys_port[stp_i]); + print_string(" admits tagged frames only - BPDUs are untagged and will not arrive\n"); + } + + /* Seed the carrier bitmap, so turning STP on does not report every + * port that was already down as a fresh topology change. */ + reg_read_m(RTL837X_REG_LINKS_STS); + stp_link_prev = (uint16_t)sfr_data[1] | ((uint16_t)sfr_data[2] << 8); + + stp_claim_root(); + + /* Take BPDUs to the CPU only - we are a participating bridge now. */ + stp_fdb_update(PMASK_CPU); } void stp_off(void) __banked { sfr_data[0] = sfr_data[1] = sfr_data[2] = sfr_data[3] = 0; - for (uint8_t i = machine.min_port; i <= machine.max_port; i++) { + for (stp_i = machine.min_port; stp_i <= machine.max_port; stp_i++) { // Set STP port state to forwarding // States are: 00 disable, 01 blocking, 10 learning, 11 forwarding - uint8_t bit_mask = 0b11 << ( (i << 1) & 0x7); - sfr_data[3 - (i >> 2)] |= bit_mask; + sfr_data[3 - (stp_i >> 2)] |= (uint8_t)(0b11 << ((stp_i << 1) & 0x7)); + stp_pflags[stp_i] &= ~(STP_PF_OPEREDGE | STP_PF_TRIPPED); + stp_loop_held[stp_i] = 0; + port_timers[stp_i] = 0; } - sfr_data[1] |= 0x0f; // Do not block CPU-Port + sfr_data[1] |= 0x0c; // Do not block the CPU port (bits 3:2 of byte 1 = port 9) reg_write_m(RTL837X_MSTP_STATES); + + /* Restore BPDU transparency: flood them again like an unmanaged switch. */ + stp_fdb_update(PMASK_CPU | (machine_detected.isRTL8373 ? PMASK_9 : PMASK_6)); +} + + +void stp_parse(void) __banked __reentrant +{ + uint8_t port; + + if (cmd_compare(1, "on")) { + print_string("STP enabled\n"); + stp_enabled = 1; + stp_setup(); + return; + } + if (cmd_compare(1, "off")) { + print_string("STP disabled\n"); + stp_off(); + stp_enabled = 0; + return; + } + if (cmd_compare(1, "status")) { + stp_status(); + return; + } + if (cmd_words_len < 3) + goto err; + + if (cmd_compare(1, "port")) { + if (cmd_words_len < 4) + goto err; + if (!cmd_parse_port_separator(cmd_words_b[2])) + goto err; + port = atoi_results_u8; + if (cmd_words_len < 5 && !cmd_compare(3, "on") && !cmd_compare(3, "off")) + goto err; + if (cmd_compare(3, "on")) { + stp_pflags[port] |= STP_PF_ENABLED; + stp_pflags[port] &= ~STP_PF_TRIPPED; + if (stp_enabled) { /* (re)join: listen first */ + stp_state_set(port, 0b01); + port_timers[port] = (uint16_t)stp_fwddelay_s * STP_HZ; + } + } else if (cmd_compare(3, "off")) { + stp_pflags[port] &= ~STP_PF_ENABLED; + if (stp_enabled) + stp_state_set(port, 0b11); /* plain forwarding */ + } else if (cmd_compare(3, "edge")) { + /* Also drop the *operational* edge flag: it is what exempts the + * port from topology changes and lets it skip the listen period, + * so leaving it set would keep the old behaviour until the next + * "stp off"/"stp on". An admin edge is operational immediately. */ + stp_pflags[port] &= ~(STP_PF_ADMEDGE | STP_PF_AUTOEDGE | STP_PF_OPEREDGE); + if (cmd_compare(4, "on")) + stp_pflags[port] |= STP_PF_ADMEDGE | STP_PF_OPEREDGE; + else if (cmd_compare(4, "auto")) + stp_pflags[port] |= STP_PF_AUTOEDGE; + else if (!cmd_compare(4, "off")) + goto err; + } else if (cmd_compare(3, "cost")) { + /* raw 802.1D value, 0..200000000; 0 = auto (speed-based) */ + stp_cost_scratch = 0; + { + __xdata uint8_t *cp = &cmd_buffer[cmd_words_b[4]]; + if (*cp < '0' || *cp > '9') + goto err; + while (*cp >= '0' && *cp <= '9') { + stp_cost_scratch = stp_cost_scratch * 10 + (*cp - '0'); + cp++; + } + } + if (stp_cost_scratch > 200000000UL) + goto err; + stp_pcost[port] = stp_cost_scratch; + } else if (cmd_compare(3, "p2p")) { + if (cmd_compare(4, "auto")) + stp_pp2p[port] = 0; + else if (cmd_compare(4, "on")) + stp_pp2p[port] = 1; + else if (cmd_compare(4, "off")) + stp_pp2p[port] = 2; + else + goto err; + } else if (cmd_compare(3, "prio")) { + if (!atoi_byte(cmd_words_b[4])) + goto err; + if (atoi_results_u8 > 240 || (atoi_results_u8 & 0x0f)) + goto err; + stp_pprio[port] = atoi_results_u8; + } else if (cmd_compare(3, "guard")) { + stp_pflags[port] &= ~(STP_PF_BPDUGUARD | STP_PF_ROOTGUARD); + if (cmd_compare(4, "bpdu")) + stp_pflags[port] |= STP_PF_BPDUGUARD; + else if (cmd_compare(4, "root")) + stp_pflags[port] |= STP_PF_ROOTGUARD; + else if (!cmd_compare(4, "none")) + goto err; + } else if (cmd_compare(3, "filter")) { + if (cmd_compare(4, "on")) + stp_pflags[port] |= STP_PF_FILTER; + else if (cmd_compare(4, "off")) + stp_pflags[port] &= ~STP_PF_FILTER; + else + goto err; + } else { + goto err; + } + return; + } + + if (!atoi_byte(cmd_words_b[2])) { + if (cmd_compare(1, "version")) { + if (cmd_compare(2, "rstp")) + stp_rstp = 1; + else if (cmd_compare(2, "stp")) + stp_rstp = 0; + else + goto err; + return; + } + goto err; + } + stp_scratch = atoi_results_u8; + + if (cmd_compare(1, "prio")) { + if (stp_scratch > 15) + goto err; + stp_prio = stp_scratch << 4; /* n * 4096, as the BPDU's high byte */ + if (stp_root_port == 0xff) + stp_claim_root(); /* re-announce with the new priority */ + } else if (cmd_compare(1, "hello")) { + if (stp_scratch < 1 || stp_scratch > 10) + goto err; + stp_hello_s = stp_scratch; + } else if (cmd_compare(1, "maxage")) { + if (stp_scratch < 6 || stp_scratch > 40) + goto err; + stp_maxage_s = stp_scratch; + } else if (cmd_compare(1, "fwd")) { + if (stp_scratch < 4 || stp_scratch > 30) + goto err; + stp_fwddelay_s = stp_scratch; + } else if (cmd_compare(1, "txhold")) { + if (stp_scratch < 1 || stp_scratch > 10) + goto err; + stp_txhold = stp_scratch; + } else { + goto err; + } + return; +err: + print_string("Error: stp on|off|status | prio <0-15> | hello <1-10> | maxage <6-40> | fwd <4-30> | txhold <1-10> | version rstp|stp | port <1-9> on|off|edge|cost|prio|guard|filter ...\n"); } diff --git a/rtl837x_stp.h b/rtl837x_stp.h index b1c2a716..97080ce0 100644 --- a/rtl837x_stp.h +++ b/rtl837x_stp.h @@ -6,7 +6,49 @@ void stp_in(void) __banked; void stp_setup(void) __banked; void stp_timers(void) __banked; void stp_off(void) __banked; +void stp_parse(void) __banked __reentrant; +void stp_defaults(void) __banked; -#define TIME_HELLO 0x200 // 2 sec +/* Tick rate of stp_timers(), also used by the web UI. */ +#define STP_HZ 50 + +/* Bridge identifier as carried in a BPDU (priority, extension, MAC). */ +struct bridge { + uint8_t prio; + uint8_t ext; + uint8_t mac[6]; +}; + +extern __xdata uint8_t stp_prio; +extern __xdata uint8_t stp_hello_s; +extern __xdata uint8_t stp_maxage_s; +extern __xdata uint8_t stp_fwddelay_s; +extern __xdata uint8_t stp_rstp; +extern __xdata uint8_t stp_txhold; + +/* Per-port config/status flags (stp_pflags[]) */ +#define STP_PF_ENABLED 0x01 /* port participates in STP (default on) */ +#define STP_PF_ADMEDGE 0x02 /* admin edge: forwarding immediately */ +#define STP_PF_AUTOEDGE 0x04 /* auto edge: forward after 3 s without BPDU */ +#define STP_PF_BPDUGUARD 0x08 /* disable port if a BPDU arrives */ +#define STP_PF_ROOTGUARD 0x10 /* never accept a better root on this port */ +#define STP_PF_FILTER 0x20 /* neither send nor accept BPDUs */ +#define STP_PF_OPEREDGE 0x40 /* runtime: port went forwarding as an edge */ +#define STP_PF_TRIPPED 0x80 /* runtime: disabled by BPDU guard */ + +extern __xdata uint8_t stp_pflags[10]; +extern __xdata uint32_t stp_pcost[10]; +extern __xdata uint8_t stp_pprio[10]; +extern __xdata uint8_t stp_pp2p[10]; + +extern __xdata struct bridge stp_dbridge[10]; +extern __xdata uint16_t stp_dpid[10]; +extern __xdata uint32_t stp_dcost[10]; +extern __xdata uint16_t stp_bpdu_age[10]; + +extern __xdata struct bridge root_bridge; +extern __xdata uint32_t root_bridge_cost; +extern __xdata uint8_t stp_root_port; +extern __xdata uint16_t stp_tc_count; #endif diff --git a/rtlplayground.c b/rtlplayground.c index b9cc05d9..b3710c76 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -118,9 +118,10 @@ __xdata uint8_t uip_buf[UIP_CONF_BUFFER_SIZE+2]; __xdata uint16_t rx_packet_vlan; __xdata uint16_t management_vlan; +__xdata bool frame_tagged; __xdata uint8_t tx_seq; -__xdata uint8_t stpEnabled; +__xdata bool stp_enabled; __xdata uint8_t igmpEnabled; __xdata char hostname[24]; /* device hostname, default set at boot, see rtl837x_common.h */ @@ -140,6 +141,7 @@ __xdata char sfp_module_vendor[2][17]; __xdata char sfp_module_model[2][17]; __xdata char sfp_module_serial[2][17]; __xdata uint8_t sfp_options[2]; +__xdata uint8_t sfp_buf[16]; /* scratch for one I2C transaction, the controller reads at most 16 bytes */ __xdata uint8_t sfp_speed[2]; __xdata uint8_t sfp_quirks[2]; __xdata bool button_last; @@ -203,6 +205,9 @@ struct nonq_frame { // The output frame structure with 802.1Q field and the padding moved before the buffer-start #define FRAME_Q ((__xdata struct q_frame *)&uip_buf[0]) +// Ether-type of the output frame, which is the RTL tag on a CPU-tagged frame +#define FRAME_ETHERTYPE (*(__xdata uint16_t *)&uip_buf[RTL_FRAME_DESC_SIZE + 2 * sizeof(struct uip_eth_addr)]) + void isr_timer0(void) __interrupt(1) { } @@ -235,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') { @@ -277,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) @@ -289,6 +337,12 @@ void print_string_no_syslog(__code char *p) write_char_no_syslog(*p++); } +void print_string_newline_no_syslog(__code char *p) +{ + write_char_no_syslog('\n'); + print_string_no_syslog(p); +} + void print_string_x(__xdata char *p) { while (*p) @@ -304,20 +358,20 @@ void memcpy(__xdata void * __xdata dst, __xdata const void * __xdata src, uint16 *d++ = *s++; } -void memcpyc(register __xdata uint8_t *dst, register __code uint8_t *src, register uint16_t len) +void memcpyc(__xdata uint8_t *dst, __code uint8_t *src, uint16_t len) { while (len--) *dst++ = *src++; } -void memset(register __xdata uint8_t *dst, register __xdata uint8_t v, register uint8_t len) +void memset(__xdata uint8_t *dst, __xdata uint8_t v, uint8_t len) { while (len--) *dst++ = v; } -uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s) +uint16_t strtox(__xdata uint8_t *dst, __code const char *s) { __xdata uint8_t *b = dst; while (*s) @@ -327,7 +381,7 @@ uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s) } -uint16_t strlen(register __code const char *s) +uint16_t strlen(__code const char *s) { uint16_t l = 0; while (s[l]) @@ -336,7 +390,7 @@ uint16_t strlen(register __code const char *s) } -uint16_t strlen_x(register __xdata const char *s) +uint16_t strlen_x(__xdata const char *s) { uint16_t l = 0; while (s[l]) @@ -345,7 +399,7 @@ uint16_t strlen_x(register __xdata const char *s) } -char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b) +char strcmp(__xdata const uint8_t *a, __code const uint8_t *b) { uint8_t i = 0; @@ -360,6 +414,32 @@ char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b) } +/* + * True when b is a prefix of a. Unlike strcmp() the byte after the match is not + * compared, and unlike is_word_x() it need not be a separator. + */ +bool strstart(__xdata const uint8_t *a, __code const uint8_t *b) +{ + uint8_t i = 0; + + while (b[i] && (b[i] == a[i])) + i++; + + return !b[i]; +} + + +bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b) +{ + uint8_t i = 0; + + while (b[i] && (b[i] == a[i])) + i++; + + return !b[i]; +} + + void print_short(uint16_t a) { // allocating the registers first improves the sdcc code here @@ -618,13 +698,21 @@ void get_random_32(void) * data will be stored in the rx_header structure * len is the length of data to be transferred */ -void nic_rx_header(uint16_t ring_ptr) +bool nic_rx_header(uint16_t ring_ptr) { uint16_t buffer = (uint16_t) &rx_headers[0]; + uint16_t guard = 0; + SFR_NIC_DATA_U16LE = buffer; SFR_NIC_RING_U16LE = ring_ptr; SFR_NIC_CTRL = 1; - do { } while (SFR_NIC_CTRL != 0); + while (SFR_NIC_CTRL != 0) { + if (++guard == 0) { + print_string("NIC: RX header transfer did not complete\n"); + return false; + } + } + return true; } @@ -634,8 +722,10 @@ void nic_rx_header(uint16_t ring_ptr) * data will be returned in the xmem buffer points to * ring_ptr is the current position of the RX Ring on the ASIC side */ -void nic_rx_packet(register uint16_t buffer, register uint16_t ring_ptr) +bool nic_rx_packet(uint16_t buffer, uint16_t ring_ptr) { + uint16_t guard = 0; + SFR_NIC_DATA_U16LE = buffer; SFR_NIC_RING_U16LE = ring_ptr; @@ -647,7 +737,13 @@ void nic_rx_packet(register uint16_t buffer, register uint16_t ring_ptr) print_short(len); #endif SFR_NIC_CTRL = len; - do { } while (SFR_NIC_CTRL != 0); + while (SFR_NIC_CTRL != 0) { + if (++guard == 0) { + print_string("NIC: RX transfer did not complete\n"); + return false; + } + } + return true; } @@ -657,14 +753,13 @@ void nic_rx_packet(register uint16_t buffer, register uint16_t ring_ptr) void nic_tx_packet(uint16_t ring_ptr) { uint16_t len; + uint16_t guard = 0; - /* If we have a management VLAN, we have inserted a dot1Q-tag into the frame and - * the frame starts at the beginning of uip_buf with the RTL TX descriptor, - * otherwise the frame is a normal Ethernet frame which starts with - * an RTL TX descriptor being padded at the beginning, in the second case - * we need to skip the padding for the sending of the frame. + /* A frame that got a dot1Q tag was shifted forward over its padding, so it + * starts at uip_buf and carries the q_frame layout. One that did not keeps + * the padding in front and the nonq_frame layout, so the padding is skipped. */ - if (management_vlan) { + if (frame_tagged) { SFR_NIC_DATA_U16LE = (uint16_t) uip_buf; len = FRAME_Q->len; /* @@ -691,7 +786,12 @@ void nic_tx_packet(uint16_t ring_ptr) len += 0xf; len >>= 3; SFR_NIC_CTRL = len; - do { } while (SFR_NIC_CTRL != 0); + while (SFR_NIC_CTRL != 0) { + if (++guard == 0) { + print_string("NIC: TX transfer did not complete\n"); + return; + } + } } @@ -780,6 +880,19 @@ void print_reg(uint16_t reg) print_sfr_data(); } +// Print the physical port of a logical port number. +void print_phys_port(uint8_t port) +{ + if (port < CPU_PORT) + write_char(machine.log_to_phys_port[port] + '0'); + else if (port == CPU_PORT) + print_string("CPU"); + else { + print_string("UNKNOWN "); + write_char(port + '0'); + } +} + /* // TODO: This uses 2 DSEG bytes and is not used! @@ -1036,37 +1149,6 @@ void sds_config(uint8_t sds, uint8_t mode) } -/* - * Read a register of the EEPROM via I2C - */ -uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) -{ - if (reg & 0x80) { // Configure SFP readings address (0x51) as I2C device address - reg &= 0x7f; - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x51 >> 5, (0x51 << 3) & 0xff); - } else { - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x50 >> 5, (0x50 << 3) & 0xff); - } - - reg_read_m(RTL837X_REG_I2C_CTRL); - sfr_mask_data(1, 0xfc, i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2); - reg_write_m(RTL837X_REG_I2C_CTRL); - - REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); - - // Execute I2C Read - reg_bit_set(RTL837X_REG_I2C_CTRL, 0); - - // Wait for execution to finish - do { - reg_read_m(RTL837X_REG_I2C_CTRL); - } while (sfr_data[3] & 0x1); - - reg_read_m(RTL837X_REG_I2C_OUT); - return sfr_data[3]; -} - - /* * Adds TX Header to uip_buf and calls nic_tx_packet to send the packet * over the wire @@ -1080,8 +1162,11 @@ void tcpip_output(void) FRAME->len = uip_len; FRAME->reserved_2[0] = 0x00; FRAME->reserved_2[1] = 0x00; - // For the management VLAN we insert an 802.1Q VLAN tag - if (management_vlan) { + // For the management VLAN we insert an 802.1Q VLAN tag, but never into a + // CPU-tagged frame, where the ASIC expects its tag right behind the addresses + frame_tagged = false; + if (management_vlan && FRAME_ETHERTYPE != HTONS(RTL_FRAME_TAG_ID)) { + frame_tagged = true; // Shift the ethernet header before the HW type including the rtl_frame_desc to the beginning of uip_buf // to allow space to insert the dot 1Q tag for (uint8_t i = 0; i < sizeof(struct q_frame) - DOT_1Q_TAG_SIZE; i++) @@ -1115,7 +1200,10 @@ void handle_rx(void) uint16_t ring_ptr = ((uint16_t)sfr_data[2]) << 8; ring_ptr |= sfr_data[3]; ring_ptr <<= 3; - nic_rx_header(ring_ptr); + if (!nic_rx_header(ring_ptr)) { + REG_SET(RTL837X_REG_NIC_RXCMD, 1); + return; + } #ifdef RXTXDBG __xdata uint8_t *ptr = rx_headers; print_string("RX on port "); print_byte(rx_headers[3] & 0xf); @@ -1125,7 +1213,10 @@ void handle_rx(void) write_char(' '); } #endif - nic_rx_packet((uint16_t) &uip_buf[0], ring_ptr + 8); + if (!nic_rx_packet((uint16_t) &uip_buf[0], ring_ptr + 8)) { + REG_SET(RTL837X_REG_NIC_RXCMD, 1); + return; + } #ifdef RXTXDBG print_string("\n<< "); @@ -1146,7 +1237,7 @@ void handle_rx(void) print_byte(uip_buf[3]); print_byte(uip_buf[4]); print_byte(uip_buf[5]); write_char('\n'); print_string(" MGMT-VLAN: "); print_short(management_vlan); write_char('\n'); #endif - if (stpEnabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet? + if (stp_enabled && uip_buf[0] == 0x01 && uip_buf[1] == 0x80 && uip_buf[2] == 0xc2 // STP packet? && uip_buf[3] == 0x00 && uip_buf[4] == 0x00 && uip_buf[5] == 0x00) { stp_in(); if (uip_len) { @@ -1205,7 +1296,7 @@ void handle_tx(void) } -static inline uint8_t sfp_rate_to_sds_config(register uint8_t rate) +static inline uint8_t sfp_rate_to_sds_config(uint8_t rate) { if (rate == 0x1 || rate == 0x2) return SDS_100FX; @@ -1219,36 +1310,46 @@ static inline uint8_t sfp_rate_to_sds_config(register uint8_t rate) } -void sfp_print_info(uint8_t sfp) +bool sfp_print_info(uint8_t sfp) { // This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields - for (uint8_t i = 20; i < 60; i++) { - if (i >= 36 && i < 40) // Skip Non-ASCII codes + for (uint8_t i = 16; i < 64; i++) { + if (!(i & 0xf) && !sfp_read_block(sfp, i, 16)) + return false; + if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes continue; - uint8_t c = sfp_read_reg(sfp, i); + uint8_t c = sfp_buf[i & 0xf]; if (c) write_char(c); } print_string("\n"); + + return true; } // Normalize strings from EEPROM by removing any trailing spaces; this allows simpler comparisons -void sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t length) __reentrant +bool sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t length) __reentrant { - dst[length] = '\0'; + if (!sfp_read_block(sfp, start, length)) + return false; - for (uint8_t i = 0; i < length; i++) - dst[i] = sfp_read_reg(sfp, start + i); + dst[length] = NUL; + memcpy(dst, sfp_buf, length); while (length > 0 && dst[--length] == ' ') - dst[length] = '\0'; + dst[length] = NUL; + + return true; } -void sfp_get_info(uint8_t sfp) +bool sfp_get_info(uint8_t sfp) { - sfp_read_field(sfp_module_vendor[sfp], sfp, 20, 16); - sfp_read_field(sfp_module_model[sfp], sfp, 40, 16); - sfp_read_field(sfp_module_serial[sfp], sfp, 68, 16); + if (!sfp_read_field(sfp_module_vendor[sfp], sfp, 20, 16)) + return false; + if (!sfp_read_field(sfp_module_model[sfp], sfp, 40, 16)) + return false; + + return sfp_read_field(sfp_module_serial[sfp], sfp, 68, 16); } void sfp_apply_quirks(uint8_t sfp) __reentrant @@ -1267,7 +1368,7 @@ void sfp_apply_quirks(uint8_t sfp) __reentrant if (!(sfp_options[sfp] & 0x40)) { // The module reports that DDM is not implemented, but try a dummy read to confirm // 0xff would mean a failed I2C read or an impossible (per spec) voltage greater than 6.5V - if (sfp_read_reg(sfp, 226) != 0xff) { + if (sfp_read_block(sfp, 226, 1) && sfp_buf[0] != 0xff) { sfp_options[sfp] |= 0x40; } } @@ -1291,6 +1392,45 @@ void setup_sfp_gpio(void) } } +static bool sfp_module_read(uint8_t sfp) +{ + uint8_t rate; + + // Read Reg 11: Encoding, see SFF-8472 and SFF-8024 + // Read Reg 12: Signalling rate (including overhead) in 100Mbit: 0xd: 1Gbit, 0x67:10Gbit + delay(100); // Delay, because some modules need time to wake up + if (!sfp_read_block(sfp, 11, 2)) + return false; + + rate = sfp_buf[1]; + if (sfp_speed[sfp] == SFP_SPEED_100M) + rate = 0x1; + else if (sfp_speed[sfp] == SFP_SPEED_1G) + rate = 0xc; + else if (sfp_speed[sfp] == SFP_SPEED_2G5) + rate = 0x19; + else if (sfp_speed[sfp] == SFP_SPEED_10G) + rate = 0x69; + print_string(" Rate: "); print_byte(rate); // Normally 1, but 0 for DAC, can be ignored? + print_string(" Encoding: "); print_byte(sfp_buf[0]); + print_string(" Module: "); + if (!sfp_print_info(sfp)) + return false; + print_string("\n"); + + if (!sfp_read_block(sfp, 92, 1)) + return false; + sfp_options[sfp] = sfp_buf[0]; + if (!sfp_get_info(sfp)) + return false; + + sfp_apply_quirks(sfp); + sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate)); + + return true; +} + + void handle_sfp(void) { for (uint8_t sfp = 0; sfp < machine.n_sfp; sfp++) { @@ -1298,26 +1438,10 @@ void handle_sfp(void) if (sfp_pins_last & (0x1 << (sfp << 2))) { sfp_pins_last &= ~(0x01 << (sfp << 2)); print_string("\n Slot: "); write_char('1' + sfp); - // Read Reg 11: Encoding, see SFF-8472 and SFF-8024 - // Read Reg 12: Signalling rate (including overhead) in 100Mbit: 0xd: 1Gbit, 0x67:10Gbit - delay(100); // Delay, because some modules need time to wake up - uint8_t rate = sfp_read_reg(sfp, 12); - if (sfp_speed[sfp] == SFP_SPEED_100M) - rate = 0x1; - else if (sfp_speed[sfp] == SFP_SPEED_1G) - rate = 0xc; - else if (sfp_speed[sfp] == SFP_SPEED_2G5) - rate = 0x19; - else if (sfp_speed[sfp] == SFP_SPEED_10G) - rate = 0x69; - print_string(" Rate: "); print_byte(rate); // Normally 1, but 0 for DAC, can be ignored? - print_string(" Encoding: "); print_byte(sfp_read_reg(sfp, 11)); - print_string(" Module: "); sfp_print_info(sfp); - print_string("\n"); - sfp_options[sfp] = sfp_read_reg(sfp, 92); - sfp_get_info(sfp); - sfp_apply_quirks(sfp); - sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate)); + if (!sfp_module_read(sfp)) { + print_string("SFP: an I2C read failed, retrying on the next poll\n"); + sfp_pins_last |= 0x01 << (sfp << 2); + } } } else { if (!(sfp_pins_last & (0x1 << (sfp << 2)))) { @@ -1490,7 +1614,7 @@ void idle(void) // Check UIP for packets to transmit handle_tx(); // If STP protocol enabled, decrease STP timers to trigger actions - if (stpEnabled) { + if (stp_enabled) { if (!stp_clock) { stp_clock = STP_TICK_DIVIDER; stp_timers(); @@ -1765,18 +1889,35 @@ void init_smi(void) /* Set the SMI(i.e.I2C) type for PHY polling, 0b01 is 2.5/10G PHY. Disable (0b00) for the SFP-ports * which are at port 8 and additionally at port 3 for a dual SFP device */ + + // Default: 0x00005555 + // Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x00005555; + SFR_DATA_U16_UPPER = 0x0000; + SFR_DATA_U16 = 0x5555; if (machine.n_10g == 2) { - REG_SET(RTL837X_REG_SMI_MAC_TYPE, 0x00015555); - } else { - REG_SET(RTL837X_REG_SMI_MAC_TYPE, machine.n_sfp == 2 ? 0x00005515 : 0x00005555); - } + // 0x00015555, only change the bytes that differs from the default. + SFR_DATA_16 = 0x01; + } else if (machine.n_sfp == 2) + // 0x00005515 + SFR_DATA_0 = 0x15; + reg_write(RTL837X_REG_SMI_MAC_TYPE); // Configure polling of all PHYs by the MAC to detect link-state changes - if (machine_detected.isRTL8373) { - REG_SET(RTL837X_REG_SMI_PORT_POLLING, 0xff); - } else { - REG_SET(RTL837X_REG_SMI_PORT_POLLING, machine.n_sfp == 2 ? 0xf0 : 0x1f8); + // Default: 0x000000ff + // Workaround for SDCC BUG 4070: SFR_DATA_U32 = 0x000000ff; + SFR_DATA_U16_UPPER = 0x0000; + SFR_DATA_U16 = 0x00ff; + if (!machine_detected.isRTL8373) { + if (machine.n_sfp == 2) { + // 0x000000f0, only change the bytes that differs from the default. + SFR_DATA_0 = 0xf0; + } else { + // 0x000001f8, only change the bytes that differs from the default. + SFR_DATA_8 = 0x01; + SFR_DATA_0 = 0xf8; + } } + reg_write(RTL837X_REG_SMI_PORT_POLLING); // Enable MDC reg_read_m(RTL837X_REG_SMI_CTRL); sfr_mask_data(1, 0, 0x70); // Set bits 12-14 to enable MDC for SMI0-SMI2 @@ -2021,7 +2162,7 @@ void check_and_flash_update_image(void) * because itohex() is inline and brings its own frame. */ void set_hostname_default(void) { - if (hostname[0] != '\0') + if (hostname[0] != NUL) return; strcpy((__xdata uint8_t *)hostname, "RTLPlayground-"); @@ -2031,7 +2172,7 @@ void set_hostname_default(void) hostname[17] = hex[uip_ethaddr.addr[4] & 0xf]; hostname[18] = hex[uip_ethaddr.addr[5] >> 4]; hostname[19] = hex[uip_ethaddr.addr[5] & 0xf]; - hostname[20] = '\0'; + hostname[20] = NUL; } @@ -2179,7 +2320,8 @@ void main(void) REG_SET(RTL837X_REG_SEC_COUNTER, 0x3); write_char(' '); print_reg(RTL837X_REG_SEC_COUNTER); #endif - stpEnabled = 0; + stp_enabled = 0; + stp_defaults(); /* 802.1D/w default config before any "stp ..." replay */ nic_setup(); vlan_setup(); port_l2_setup(); @@ -2207,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(); diff --git a/syslog.c b/syslog.c index ad97fd2f..de11af53 100644 --- a/syslog.c +++ b/syslog.c @@ -1,5 +1,6 @@ #include "machine.h" #include "syslog.h" +#include "cmd_parser.h" #include "uip/uip.h" #include "rtl837x_common.h" @@ -22,24 +23,25 @@ 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_no_syslog("Failed to create a new UDP client\n"); + print_string_newline_no_syslog("Failed to create a new UDP client"); return; } - print_string_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_string_newline_no_syslog("Started syslog to IP "); + print_ip(state.server_ip); + write_char(':'); itoa_short(state.server_port); write_char('\n'); state.enabled = 1; } else { - print_string_no_syslog("Syslog is already running\n"); + print_string_newline_no_syslog("Syslog is already running"); } } @@ -49,32 +51,33 @@ void syslog_stop(void) __banked if (state.syslog_conn != 0) { uip_udp_remove(state.syslog_conn); state.syslog_conn = 0; - print_string_no_syslog("Stopped syslog\n"); + print_string_newline_no_syslog("Stopped syslog"); } else { - print_string_no_syslog("Syslog is not running\n"); + print_string_newline_no_syslog("Syslog is not running"); } } void syslog_callback(uint16_t lport) __banked { + uint16_t syslog_hdr; if (lport != state.syslog_conn->lport) return; if ((state.readptr != state.writeptr) && state.line_available) { - int16_t log_size = state.writeptr - state.readptr; + __xdata int16_t log_size = state.writeptr - state.readptr; if (log_size < 0) log_size += LOGBUF_SIZE; // Skipping linefeeds at the start of the log line - uint16_t log_start = state.readptr; + __xdata uint16_t log_start = state.readptr; while (log_size > 0 && logbuf[log_start] == '\n') { log_start = (log_start + 1) & (LOGBUF_SIZE - 1); log_size--; } // Skipping linefeeds and whitespaces at the end of the log line - uint16_t log_end = state.writeptr; + __xdata uint16_t log_end = state.writeptr; while ( (log_size > 0) && ((logbuf[(log_end-1) & (LOGBUF_SIZE - 1)] == '\n') || (logbuf[(log_end-1) & (LOGBUF_SIZE - 1)] == ' '))) @@ -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; } diff --git a/syslog.h b/syslog.h index f3666da8..ad9c3fe1 100644 --- a/syslog.h +++ b/syslog.h @@ -1,16 +1,20 @@ #ifndef _SYSLOG_H_ #define _SYSLOG_H_ +#include #include #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; }; diff --git a/uip/psock.c b/uip/psock.c index eb4d1af1..f1c11158 100644 --- a/uip/psock.c +++ b/uip/psock.c @@ -75,8 +75,7 @@ /*---------------------------------------------------------------------------*/ inline void -buf_setup(register __xdata struct psock_buf *buf, - register __xdata u8_t *bufptr, register u16_t bufsize) +buf_setup(__xdata struct psock_buf *buf, __xdata u8_t *bufptr, u16_t bufsize) { buf->ptr = bufptr; buf->left = bufsize; @@ -84,7 +83,7 @@ buf_setup(register __xdata struct psock_buf *buf, /*---------------------------------------------------------------------------*/ inline u8_t -buf_bufdata(register __xdata struct psock_buf *buf, register __xdata u8_t **dataptr, register __xdata u16_t *datalen) +buf_bufdata(__xdata struct psock_buf *buf, __xdata u8_t **dataptr, __xdata u16_t *datalen) { if(*datalen < buf->left) { memcpy(buf->ptr, *dataptr, *datalen); @@ -145,7 +144,7 @@ buf_bufto(__xdata struct psock_buf *buf, u8_t endmarker, } /*---------------------------------------------------------------------------*/ static char -send_data(register __xdata struct psock *s) +send_data(__xdata struct psock *s) { if(s->state != STATE_DATA_SENT || uip_rexmit()) { if(s->sendlen > uip_mss()) { @@ -160,7 +159,7 @@ send_data(register __xdata struct psock *s) } /*---------------------------------------------------------------------------*/ static char -data_acked(register __xdata struct psock *s) +data_acked(__xdata struct psock *s) { if(s->state == STATE_DATA_SENT && uip_acked()) { if(s->sendlen > uip_mss()) { @@ -176,8 +175,7 @@ data_acked(register __xdata struct psock *s) return 0; } /*---------------------------------------------------------------------------*/ -PT_THREAD(psock_send(register __xdata struct psock *s, register __xdata const char *buf, - register uint16_t len)) +PT_THREAD(psock_send(__xdata struct psock *s, __xdata const char *buf, uint16_t len)) { PT_BEGIN(&s->psockpt); @@ -218,7 +216,7 @@ PT_THREAD(psock_send(register __xdata struct psock *s, register __xdata const ch /*---------------------------------------------------------------------------*/ -// PT_THREAD(psock_generator_send(register __xdata struct psock *s, +// PT_THREAD(psock_generator_send(__xdata struct psock *s, // unsigned short (*generate)(void *), void *arg)) // { // PT_BEGIN(&s->psockpt); @@ -275,7 +273,7 @@ psock_newdata(__xdata struct psock *s) } } /*---------------------------------------------------------------------------*/ -PT_THREAD(psock_readto(register __xdata struct psock *psock, unsigned char c)) +PT_THREAD(psock_readto(__xdata struct psock *psock, unsigned char c)) { PT_BEGIN(&psock->psockpt); @@ -304,7 +302,7 @@ PT_THREAD(psock_readto(register __xdata struct psock *psock, unsigned char c)) PT_END(&psock->psockpt); } /*---------------------------------------------------------------------------*/ -PT_THREAD(psock_readbuf(register __xdata struct psock *psock)) +PT_THREAD(psock_readbuf(__xdata struct psock *psock)) { PT_BEGIN(&psock->psockpt); @@ -334,7 +332,7 @@ PT_THREAD(psock_readbuf(register __xdata struct psock *psock)) } /*---------------------------------------------------------------------------*/ void -psock_init(register __xdata struct psock *psock, register __xdata char *buffer, register uint16_t buffersize) +psock_init(__xdata struct psock *psock, __xdata char *buffer, uint16_t buffersize) { psock->state = STATE_NONE; psock->readlen = 0; diff --git a/uip/psock.h b/uip/psock.h index a4967d22..3a6e7e41 100644 --- a/uip/psock.h +++ b/uip/psock.h @@ -124,7 +124,7 @@ struct psock { u8_t state; /* The state of the protosocket. */ }; -void psock_init(__xdata struct psock *psock, register __xdata char *buffer, register uint16_t buffersize); +void psock_init(__xdata struct psock *psock, __xdata char *buffer, uint16_t buffersize); /** * Initialize a protosocket. * @@ -158,7 +158,7 @@ void psock_init(__xdata struct psock *psock, register __xdata char *buffer, regi */ #define PSOCK_BEGIN(psock) PT_BEGIN(&((psock)->pt)) -PT_THREAD(psock_send(register __xdata struct psock *psock, register __xdata const char *buf, register uint16_t len)); +PT_THREAD(psock_send(__xdata struct psock *psock, __xdata const char *buf, uint16_t len)); /** * Send data. * diff --git a/uip/uip-conf.h b/uip/uip-conf.h index 2da25a3e..9e87fda2 100644 --- a/uip/uip-conf.h +++ b/uip/uip-conf.h @@ -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! * diff --git a/uip/uip.c b/uip/uip.c index ee07a043..de822aa8 100644 --- a/uip/uip.c +++ b/uip/uip.c @@ -157,7 +157,7 @@ __xdata u16_t uip_len, uip_slen; __xdata u8_t uip_flags; /* The uip_flags variable is used for communication between the TCP/IP stack and the application program. */ -__xdata struct uip_conn *uip_conn; /* uip_conn always points to the current +__xdata struct uip_conn * __xdata uip_conn; /* uip_conn always points to the current connection. */ __xdata struct uip_conn uip_conns[UIP_CONNS]; @@ -167,10 +167,15 @@ __xdata u16_t uip_listenports[UIP_LISTENPORTS]; /* The uip_listenports list all currently listning ports. */ #if UIP_UDP -__xdata struct uip_udp_conn *uip_udp_conn; +__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. */ @@ -234,7 +239,7 @@ __xdata struct uip_stats uip_stat; #endif /* UIP_STATISTICS == 1 */ #if UIP_LOGGING == 1 -#define UIP_LOG(m) print_string_no_syslog(m); +#define UIP_LOG(m) print_string_newline_no_syslog(m); #else #define UIP_LOG(m) #endif /* UIP_LOGGING == 1 */ @@ -373,11 +378,10 @@ uip_udpchksum(void) void uip_init(void) __banked { - uint8_t c; - for(c = 0; c < UIP_LISTENPORTS; ++c) { + for(uint8_t c = 0; c < UIP_LISTENPORTS; c++) { uip_listenports[c] = 0; } - for(c = 0; c < UIP_CONNS; ++c) { + for(uint8_t c = 0; c < UIP_CONNS; c++) { uip_conns[c].tcpstateflags = UIP_CLOSED; } #if UIP_ACTIVE_OPEN @@ -385,7 +389,7 @@ uip_init(void) __banked #endif /* UIP_ACTIVE_OPEN */ #if UIP_UDP - for(c = 0; c < UIP_UDP_CONNS; ++c) { + for(uint8_t c = 0; c < UIP_UDP_CONNS; c++) { uip_udp_conns[c].lport = 0; } #endif /* UIP_UDP */ @@ -400,7 +404,7 @@ uip_init(void) __banked /*---------------------------------------------------------------------------*/ #if UIP_ACTIVE_OPEN __xdata struct uip_conn * -uip_connect(register __xdata uip_ipaddr_t *ripaddr, __xdata u16_t rport) __banked +uip_connect(__xdata uip_ipaddr_t *ripaddr, __xdata u16_t rport) __banked { __xdata struct uip_conn *conn, *cconn; @@ -466,7 +470,7 @@ uip_connect(register __xdata uip_ipaddr_t *ripaddr, __xdata u16_t rport) __banke /*---------------------------------------------------------------------------*/ #if UIP_UDP __xdata struct uip_udp_conn * -uip_udp_new(__xdata uip_ipaddr_t *ripaddr, __xdata u16_t rport) __banked +uip_udp_new(__xdata uip_ipaddr_t * __xdata ripaddr, __xdata u16_t rport) __banked { __xdata struct uip_udp_conn *conn; @@ -677,7 +681,7 @@ uip_add_rcv_nxt(u16_t n) void uip_process(u8_t flag) __banked { - register __xdata struct uip_conn *uip_connr = uip_conn; + __xdata struct uip_conn *uip_connr = uip_conn; #if UIP_UDP if(flag == UIP_UDP_SEND_CONN) { @@ -703,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; @@ -734,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 || @@ -1461,7 +1489,7 @@ uip_process(u8_t flag) __banked } /* Do different things depending on in what state the connection is. */ - switch(uip_connr->tcpstateflags & UIP_TS_MASK) { + switch(uip_connr->tcpstateflags & (uint8_t)UIP_TS_MASK) { /* CLOSED and LISTEN are not handled here. CLOSE_WAIT is not implemented, since we force the application to close when the peer sends a FIN (hence the application goes directly from @@ -1551,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 @@ -1577,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; @@ -1666,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; } @@ -1694,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 diff --git a/uip/uip.h b/uip/uip.h index 7cd12e25..5f4d8e29 100644 --- a/uip/uip.h +++ b/uip/uip.h @@ -763,7 +763,7 @@ void uip_send(__xdata const void *data, __xdata uint16_t len) __banked; * \return The uip_udp_conn structure for the new connection or NULL * if no connection could be allocated. */ -__xdata struct uip_udp_conn *uip_udp_new(__xdata uip_ipaddr_t *ripaddr, __xdata u16_t rport) __banked; +__xdata struct uip_udp_conn *uip_udp_new(__xdata uip_ipaddr_t * __xdata ripaddr, __xdata u16_t rport) __banked; /** * Removed a UDP connection. @@ -1193,7 +1193,7 @@ struct uip_conn { * The uip_conn pointer can be used to access the current TCP * connection. */ -extern __xdata struct uip_conn *uip_conn; +extern __xdata struct uip_conn * __xdata uip_conn; /* The array containing all uIP connections. */ extern __xdata struct uip_conn uip_conns[UIP_CONNS]; /** @@ -1226,7 +1226,7 @@ struct uip_udp_conn { /** * The current UDP connection. */ -extern __xdata struct uip_udp_conn *uip_udp_conn; +extern __xdata struct uip_udp_conn * __xdata uip_udp_conn; extern __xdata struct uip_udp_conn uip_udp_conns[UIP_UDP_CONNS]; #endif /* UIP_UDP */ diff --git a/uip/uip_arp.c b/uip/uip_arp.c index 8b8bd004..44bee6c4 100644 --- a/uip/uip_arp.c +++ b/uip/uip_arp.c @@ -175,7 +175,7 @@ uip_arp_timer(void) __banked } /*-----------------------------------------------------------------------------------*/ static void -uip_arp_update(__xdata u16_t *ipaddr, __xdata struct uip_eth_addr *ethaddr) +uip_arp_update(__xdata u16_t * __xdata ipaddr, __xdata struct uip_eth_addr * __xdata ethaddr) { uint8_t i; /* Walk through the ARP mapping table and try to find an entry to diff --git a/uip/uipopt.h b/uip/uipopt.h index 731484c0..20aec637 100644 --- a/uip/uipopt.h +++ b/uip/uipopt.h @@ -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 */ + /** @} */ /*------------------------------------------------------------------------------*/