diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8fc36371..77b37312 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,5 +22,9 @@ jobs: run: make -C test - name: Check if machine.c can be compiled for all machines run: make machine_check - - name: Make project - run: make MACHINE="KP_9000_6XHML_X2" + - name: Make project (4x2/RTL8372) + run: make CI=1 MACHINE="KP_9000_6XHML_X2" + - name: Make project (8x1/RTL8373) + run: make CI=1 MACHINE="KP_9000_9XHML_X_V3_1" + - name: Make project with the health instrumentation + run: make clean && make CI=1 MACHINE="KP_9000_6XHML_X2" HEALTH=1 diff --git a/.gitignore b/.gitignore index 3ccb2819..f5c8e8c7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ version.h tools/httpd_sim tools/injector tools/fileadder + +# host-test stand-ins for the two generated headers +!test/stub/html_data.h +!test/stub/version.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 050eacf0..2c490505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ ## Fixed +- Config + - A startup configuration line longer than the command buffer no longer stops the replay: + the offending line is skipped and the following lines are still applied. + - The web UI and the configuration upload now refuse a line the replay cannot take, + instead of writing it to flash. + ## Breaking changes - Config diff --git a/Dockerfile b/Dockerfile index e4b393a4..8942883f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y \ python3 \ libjson-c-dev \ git \ + zlib1g-dev \ && rm -rf /var/lib/apt/lists/* # git safe.directory for mounted repos (Makefile uses git describe) diff --git a/Makefile b/Makefile index 4b0190d2..67fd42d7 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,14 @@ ifeq ($(MACHINE),) else CC_FLAGS += -DMACHINE_$(MACHINE) endif +# Health instrumentation and the "health" console command: HEALTH=1 gmake ... +ifneq ($(HEALTH),) + CC_FLAGS += -DHEALTH +endif + +ifeq ($(CI),1) + CC_FLAGS += --Werror +endif BUILDDIR = output/$(MACHINE) VERSION_HEADER := version.h @@ -126,15 +134,25 @@ distclean: $(SUBDIRSCLEAN) $(SUBDIRSCLEAN): $(MAKE) -C $(@:clean=) clean -$(BUILDDIR)/%.rel: %.c | create_build_dir html_data.h +# Objects depend on the flags they were built with, through a stamp file that +# is rewritten only when CC_FLAGS actually changes. +CCFLAGS_STAMP := $(BUILDDIR)/.ccflags + +.PHONY: FORCE +FORCE: + +$(CCFLAGS_STAMP): FORCE | create_build_dir + @echo '$(CC_FLAGS)' | cmp -s - $@ 2>/dev/null || echo '$(CC_FLAGS)' > $@ + +$(BUILDDIR)/%.rel: %.c $(CCFLAGS_STAMP) | create_build_dir html_data.h $(CC) -MMD $(CC_FLAGS) -o $@ -c $< -$(BUILDDIR)/%.rel: %.asm | create_build_dir +$(BUILDDIR)/%.rel: %.asm $(CCFLAGS_STAMP) | create_build_dir ${ASM} ${AFLAGS} -o $@ $< # mv -f $(addprefix $(basename $^), .lst .rel .sym) . $(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel - $(CC) $(CC_FLAGS) -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^ + $(CC) $(CC_FLAGS) --xram-size 49151 -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-bBANK3=0x34000 -Wl-r -o $@ $^ $(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx objcopy --input-target=ihex -O binary $< $@ diff --git a/README.md b/README.md index 1968fb26..81fd8dda 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,15 @@ A Playground for Firmware development for advanced user of RTL8372/RTL8373 based 2.5GBit Switches. For each hardware configuration of these devices, there is usually a managed and an -umanaged version sold, with mostly identical hardware. The aim is to provide management +unmanaged version sold, with mostly identical hardware. The aim is to provide management features also for unmanaged devices with additional features such as Management VLAN, -dhcp servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however +DHCP servers, multi-language support, IPv6 and TLS-encrypted web-pages. At present, however only the following features are provided: - A modern web-interface with mouse-over to display further information - A serial console interface to configure all features - IGMP to configure Multicast streaming -- Port configuration showing detailed informtion about own and Link-partner advertised - Speed settins and configuration of these settings on the local side +- Port configuration showing detailed information about own and Link-partner advertised + Speed settings and configuration of these settings on the local side - Per-port configuration of frame sizes (MTUs) for Jumbo-Frame support or limiting MTUs for particular devices - EEE (Energy Efficient Ethernet) can be configured per-port. Detailed information is @@ -25,7 +25,7 @@ only the following features are provided: - Firmware updates via the web - Installation as a firmware upgrade from the original web-interface -GUI +GUI While the firmware provides already considerable improvements over the original managed firmware, the firmware still lacks support for the proprietary loop prevention @@ -37,7 +37,7 @@ a backup of the original flash content via a SOIC clamp such as also used for BI backups and can re-install that firmware in case something is wrong. For this no soldering skills are necessary. -The firmware supports all hardware featues of devices with +The firmware supports all hardware features of devices with - 4 2.5GBit ports + 2 SFP+ ports - 5 2.5GBIT + 1 SFP+ port - 8 2.5GBit + 1 SFP+ port @@ -131,7 +131,7 @@ rtlplayground-v0.1.0-12c98ba-dirty-LIANGUO_ZX_SWTGW215AS.bin > [!CAUTION] > This image can be flashed directly to the chip OR through the firmware update/upgrade -> interface of RTLPlaygound interface +> interface of RTLPlayground interface ## (2) Compiling for OEM running device with management options (web upgrade) @@ -179,7 +179,7 @@ ghidra see this information about [Ghidra images](doc/ghidra.md). ## (4) Installation through the Web interface (software way) -Managed switches (OEM firmware of RTLplaygroud firmware) can be upgraded via the web interface. +Managed switches (OEM firmware of RTLplayground firmware) can be upgraded via the web interface. Unmanaged switch cannot be flashed this way (see 5). Go to "Firmware update" tab, select the correct file. @@ -190,7 +190,7 @@ Go to "Firmware update" tab, select the correct file. > [!CAUTION] > Check one more time that your device matches the machine type before flashing. -> Be shure you have a backup of the original firmware before diving in RTLPlaygroung. +> Be sure you have a backup of the original firmware before diving in RTLPlayground. Finally, push the Upload File Button and you're done ! @@ -198,7 +198,7 @@ Finally, push the Upload File Button and you're done ! ## (5) Flashing the ROM directly (hardware way, but also only way to rescue) This procedure is the only way to flash unmanaged switches, if the ROM chip is large enough. -This is also the only way to unbrick your device if something went wroong. +This is also the only way to unbrick your device if something went wrong. > [!IMPORTANT] > You need a SOIC-8 clip to flash the ROM chip directly onboard. @@ -218,7 +218,7 @@ This is also the only way to unbrick your device if something went wroong. - ERASE THE ROM (BLANK) ! - Load the firmware into IMSProg. - Flash is to the ROM chip. -- Disconect the clip from the ROM chip. +- Disconnect the clip from the ROM chip. - You're done, ready for the first boot. ## (6) Connecting a serial interface (optional) @@ -233,7 +233,7 @@ When you power up the switch, the device will perform some examples and provide ## (8) The web-interface The web-interface can be reached under the [default 192.168.10.247](http://192.168.10.247) unless you -specified an IP adress in the config.txt before compilation. +specified an IP address in the config.txt before compilation. > [!TIP] > The default password is `1234`. @@ -333,13 +333,13 @@ While in compilation part, you might write directly to config.txt file before ma nano config.txt ``` -If you want to modify settings after the flash is done, go to the Advanced Settings tab in System Settings +If you want to modify settings after the flash is done, go to the System tab and find the Startup Configuration. -ADVANCED SETTINGS +ADVANCED SETTINGS ``` -ip xxx.xxx.xxx.xxx = IP adress of the switch -gw yyy.yyy.yyy.yyy = IP adress of the gateway +ip xxx.xxx.xxx.xxx = IP address of the switch +gw yyy.yyy.yyy.yyy = IP address of the gateway netmask zzz.zzz.zzz.zzz = Network mask of the switch port x name xxx = Name xxx the port number x port z 1g = Set 1g speed for port z diff --git a/boot.c b/boot.c index c72e24ed..9e271660 100644 --- a/boot.c +++ b/boot.c @@ -21,8 +21,9 @@ extern __code const struct machine machine; extern __xdata struct machine_runtime machine_detected; extern __xdata uint8_t sfr_data[4]; extern volatile __xdata uint32_t ticks; +__xdata uint32_t rtl8224_release_tick; extern __xdata uint8_t tx_seq; -extern __code uint8_t * __code hex; +extern __code const uint8_t * __code const hex; @@ -240,10 +241,10 @@ void rtl8224_enable(void) __banked reg_bit_clear(RTL837X_REG_GPIO_32_63_OUTPUT, 4); // Configure Pin as output reg_bit_set(RTL837X_REG_GPIO_32_63_DIRECTION, 4); - delay(100); + delay(4); // Set pin 4 high reg_bit_set(RTL837X_REG_GPIO_32_63_OUTPUT, 4); - delay(500); + rtl8224_release_tick = ticks; } @@ -397,10 +398,12 @@ void setup_i2c(void) __banked // HW Control register, enable I2C depending on PIN configuration reg_read_m(RTL837X_PIN_MUX_1); for (uint8_t sfp = 0; sfp < machine.n_sfp; sfp++) { - const uint8_t scl_bus = i2c_bus_from_scl_pin(machine.sfp_port[sfp].i2c.scl); - const uint8_t sda_bus = i2c_bus_from_sda_pin(machine.sfp_port[sfp].i2c.sda); + uint8_t i2c = machine.sfp_port[sfp].i2c; + uint8_t scl_bus = (i2c >> RTL837X_REG_I2C_SCL_SHIFT) & RTL837X_REG_I2C_SCL_MASK; + uint8_t sda_bus = (i2c >> RTL837X_REG_I2C_SDA_SHIFT) & RTL837X_REG_I2C_SDA_MASK; print_string("Configuring I2C for SFP idx="); print_byte(sfp); print_string(" SCL="); print_byte(scl_bus); print_string(", SDA="); print_byte(sda_bus); write_char('\n'); - switch (scl_bus) { + // `& RTL837X_REG_I2C_SCL_MASK` is needed to silens a compiler warning 110. + switch (scl_bus & RTL837X_REG_I2C_SCL_MASK) { case 3: // Bit 5-6 0b10 -> SCL (implies enabled SDA on bus 3) sfr_mask_data(0, 0x60, 0x40); @@ -419,8 +422,6 @@ void setup_i2c(void) __banked sfr_mask_data(0, 0x80, 0x80); sfr_mask_data(1, 0x01, 0x00); break; - default: - print_string("Invalid SCL bus number: "); print_byte(scl_bus); write_char('\n'); } switch (sda_bus) { @@ -444,8 +445,6 @@ void setup_i2c(void) __banked // Bit 9-10 0b01 -> SDA sfr_mask_data(1, 0x06, 0x02); break; - default: - print_string("Invalid SDA bus number: "); print_byte(sda_bus); write_char('\n'); } } reg_write_m(RTL837X_PIN_MUX_1); diff --git a/cmd_parser.c b/cmd_parser.c index e6decbeb..c0c549dc 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -5,6 +5,8 @@ // #define DEBUG // #define REGDBG 1 +#include <8051.h> +#include "cmd_parser.h" #include "rtl837x_common.h" #include "rtl837x_port.h" #include "rtl837x_flash.h" @@ -26,15 +28,15 @@ #pragma codeseg BANK2 #pragma constseg BANK2 -extern __code struct machine machine; +extern __code const struct machine machine; extern __xdata bool stp_enabled; -extern __code uint8_t log_to_phys_port[9]; +extern __code const uint8_t log_to_phys_port[9]; extern volatile __xdata uint32_t ticks; extern volatile __xdata uint8_t sfr_data[4]; -extern __code uint8_t * __code greeting; -extern __code uint8_t * __code hex; +extern __code const uint8_t * __code const greeting; +extern __code const uint8_t * __code const hex; extern __xdata uint8_t flash_buf[FLASH_BUF_SIZE]; extern __xdata struct flash_region_t flash_region; @@ -61,7 +63,6 @@ __xdata uint8_t hexvalue[4] = { 0 }; // Buffer for writing to flash 0x1fd000, copy to 0x1fe000 __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; -__xdata uint8_t cmd_available; __xdata char save_cmd; @@ -107,7 +108,7 @@ inline uint8_t isnumber(uint8_t l) return (l <= ('9'-'0')); } -uint8_t cmd_compare(uint8_t start, __code uint8_t * cmd) +uint8_t cmd_compare(uint8_t start, __code const uint8_t * cmd) { if (cmd_words_len == 0 || start > (cmd_words_len - 1)) { return 0; @@ -428,6 +429,22 @@ err: return 0; } +static void cmd_error(__code const char *msg) +{ + err_status = ERR_INVALID_ARGUMENT; + print_string("Error: "); + print_string(msg); +} + + +static uint8_t name_char(uint8_t c) +{ + if (c < 0x20 || c > 0x7e || c == '"' || c == '\\') + return '.'; + return c; +} + + // Sets the management MAC from a "mac " command (or prints // the current one with a bare "mac"). Refuses blank/multicast/locally // administered/all-zero-OUI addresses so the running MAC stays reachable, and @@ -443,11 +460,11 @@ void parse_mac_cmd(void) return; } if (cmd_words_len != 2 || !parse_mac(cmd_words_b[1])) { - print_string("Error: mac []\n"); + cmd_error("mac []\n"); return; } if ((mac_parse_result[0] & 0x03) || ((mac_parse_result[0] | mac_parse_result[1] | mac_parse_result[2]) == 0)) { - print_string("refusing: must be unicast, globally administered\n"); + cmd_error("the MAC must be unicast and globally administered\n"); return; } if (memcmp(mac_parse_result, uip_ethaddr.addr, 6) == 0) { @@ -525,7 +542,7 @@ void parse_lag(void) port_lag_members_set(group, members); return; err: - print_string("Error: lag (show | <1-4> (d | ...))\n"); + cmd_error("lag (show | <1-4> (d | ...))\n"); } @@ -567,7 +584,7 @@ void parse_lag_hash(void) port_lag_hash_set(group, hash); return; err: - print_string("Error: laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n"); + cmd_error("laghash <1-4> [smac|dmac|sip|dip|sport|dport]\n"); } @@ -621,7 +638,7 @@ void parse_vlan(void) *vlan_str++ = hex[(vlan_settings.vlan >> 4) & 0xf] ; *vlan_str++ = hex[vlan_settings.vlan & 0xf]; while(!cmd_is_space_or_nul(pos)) { - uint8_t c = cmd_buffer[pos++]; + uint8_t c = name_char(cmd_buffer[pos++]); write_char(c); *vlan_str++ = c; } @@ -665,7 +682,7 @@ void parse_vlan(void) } return; err: - print_string("Error: vlan (|show) [port][t]...\n"); + cmd_error("vlan (|show) [port][t]...\n"); } @@ -716,7 +733,7 @@ void parse_isolate(void) return; err: - print_string("Error: isolate [show|off] [port]...\n"); + cmd_error("isolate [show|off] [port]...\n"); } @@ -753,7 +770,7 @@ void parse_ingress(void) // 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_phys_port(log_port); write_char('\n'); + cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n'); return; } print_string("All ports ingress filter set to: "); @@ -764,18 +781,18 @@ void parse_ingress(void) idx = cmd_words_b[w]; uint8_t ret = cmd_parse_port(idx); if (ret != 1) { - print_string("Invalid physical port number\n"); + cmd_error("Invalid physical port number\n"); continue; } 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"); + cmd_error("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 "); print_phys_port(log_port); write_char('\n'); + cmd_error("cannot set the ingress filter for port "); print_phys_port(log_port); write_char('\n'); return; } print_string("Port "); print_phys_port(log_port); @@ -785,7 +802,7 @@ void parse_ingress(void) } return; err: - print_string("Error: ingress [p]...\n"); + cmd_error("ingress [p]...\n"); } void parse_mirror(void) @@ -850,7 +867,7 @@ void parse_mirror(void) return; err: - print_string("Port/command missing: mirror [status/off/ [port][t/r]]...\n"); + cmd_error("Port/command missing: mirror [status/off/ [port][t/r]]...\n"); return; } @@ -858,7 +875,7 @@ err: void parse_port(void) { if (cmd_words_len < 3) { - print_string("\nUsage:" \ + cmd_error("\nUsage:" \ "\nport [show|on|off]" \ "\nport [10m|100m|1g|2g5|duplex] [half|full]" \ "\nport name [custom port name]\n"); @@ -866,7 +883,7 @@ void parse_port(void) } if (cmd_parse_port_separator(cmd_words_b[1]) == 0) { - print_string("Invalid port number\n"); + cmd_error("Invalid port number\n"); return; } phy_settings.port = atoi_results_u8; @@ -879,11 +896,14 @@ void parse_port(void) print_string_x(port_names[phy_settings.port]); if (!machine.is_sfp[phy_settings.port]) { phy_show(phy_settings.port); + } else { + write_char('\n'); } + port_media_show(phy_settings.port); } else if (cmd_compare(2, "name")) { uint8_t 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]; + while ( cmd_words_len > 3 && (i < PORT_NAME_SIZE-1) && (cmd_buffer[cmd_words_b[3] + i] != NUL) ) { + port_names[phy_settings.port][i] = name_char(cmd_buffer[cmd_words_b[3] + i]); i++; } port_names[phy_settings.port][i] = NUL; @@ -944,7 +964,7 @@ void parse_port(void) phy_settings.duplex = PHY_DUPLEX_HALF; phy_set_duplex(); } else { - print_string("Unknown port command\n"); + cmd_error("Unknown port command\n"); } } @@ -972,7 +992,7 @@ void parse_mtu(void) print_byte(p); if (atoi_short(cmd_words_b[2]) == 0 || atoi_results_short < 64 || atoi_results_short > 0x3fff) { - print_string("MTU must be 64..16383\n"); + cmd_error("MTU must be 64..16383\n"); return; } REG_WRITE(RTL8373_REG_MAC_L2_PORT_MAX_LEN + ((uint16_t) p << 8), (atoi_results_short >> 10) & 0xf, (atoi_results_short >> 2) & 0xff, @@ -981,7 +1001,7 @@ void parse_mtu(void) return; err: - print_string("mtu [port] [size]\n"); + cmd_error("mtu [port] [size]\n"); return; } @@ -1038,11 +1058,11 @@ void parse_sfp(void) idx += ret; slot = atoi_results_u8 - 1; if (ret == 0 || !cmd_is_space(idx) || slot > 1) { - print_string("Illegal SFP slot number\n"); + cmd_error("Illegal SFP slot number\n"); return; } if (slot >= machine.n_sfp) { - print_string("SFP slot not present\n"); + cmd_error("SFP slot not present\n"); return; } @@ -1068,7 +1088,7 @@ void parse_sfp(void) handle_sfp(); return; err: - print_string("\nUsage:\n\tsfp\n\tsfp [1|2] [1g|2g5|10g]\n"); + cmd_error("\nUsage:\n\tsfp\n\tsfp [1|2] [1g|2g5|10g]\n"); } @@ -1100,7 +1120,7 @@ void parse_regget(void) return; err: - print_string("usage: regget \n\tlike: regget 0BB0 or regget 0c"); + cmd_error("regget \n\tlike: regget 0BB0 or regget 0c"); return; } @@ -1147,7 +1167,7 @@ void parse_regset(void) return; err: - print_string("usage: regset \n\tlike regset 0b abcd1234."); + cmd_error("regset \n\tlike regset 0b abcd1234."); } @@ -1190,7 +1210,7 @@ void parse_sdsget(void) return; err: - print_string("usage: sdsget \n"); + cmd_error("sdsget \n"); return; } @@ -1246,7 +1266,7 @@ void parse_sdsset(void) return; err: - print_string("usage: sdsset \n"); + cmd_error("sdsset \n"); return; } @@ -1295,7 +1315,7 @@ void parse_phyget(void) return; err: - print_string("usage: phyget \n"); + cmd_error("phyget \n"); return; } @@ -1355,7 +1375,7 @@ void parse_physet(void) return; err: - print_string("usage: physet \n"); + cmd_error("physet \n"); return; } @@ -1390,7 +1410,7 @@ void parse_passwd(void) passwd[j] = NUL; return; } - print_string("Missing password\n"); + cmd_error("Missing password\n"); } @@ -1413,8 +1433,8 @@ void parse_eee(void) speed_word = 2; } else if (cmd_is_space_or_nul(idx)) { // Word 2 is a port number - if (cmd_parse_port_separator(idx) == 0) { - print_string("Speed word invalid, use: [100m|1g|2g5]\n"); + if (cmd_parse_port_separator(cmd_words_b[2]) == 0) { + cmd_error("Speed word invalid, use: [100m|1g|2g5]\n"); return; } port = atoi_results_u8; @@ -1433,7 +1453,7 @@ void parse_eee(void) speed = EEE_2G5; else { - print_string("Speed word invalid, use: [100m|1g|2g5]\n"); + cmd_error("Speed word invalid, use: [100m|1g|2g5]\n"); return; } } @@ -1453,7 +1473,7 @@ void parse_eee(void) else port_eee_status_all(); } else { - print_string("eee [on|off|status] [port] [100m|1g|2g5]\n"); + cmd_error("eee [on|off|status] [port] [100m|1g|2g5]\n"); } } @@ -1524,7 +1544,7 @@ void parse_bw(void) return; err: - print_string("usage: bw [in|out|status] [|off|drop|fc]\n"); + cmd_error("bw [in|out|status] [|off|drop|fc]\n"); } void parse_syslog(void) @@ -1562,7 +1582,7 @@ void parse_syslog(void) if (was_enabled) syslog_start(); } else { - print_string("Invalid IP address\n"); + cmd_error("Invalid IP address\n"); } } else if (cmd_compare(1, "port")) { if (cmd_words_len < 3) { // no additional argument -> print current port @@ -1573,7 +1593,7 @@ void parse_syslog(void) /* 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"); + cmd_error("Invalid port\n"); return; } /* The port is frozen into the connection by uip_udp_new(), so @@ -1589,9 +1609,9 @@ void parse_syslog(void) } else { - 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"); + cmd_error("syslog [on|off|ip [ip-address]|port [number]]\n" + " on/off enables or disables syslog, ip sets the syslog server IP address,\n" + " port sets the destination UDP port (default 514)\n"); } } @@ -1778,7 +1798,7 @@ void cmd_parser(void) __banked print_string("Setting ip: "); print_ip(ip); write_char('\n'); } else { - print_string("Invalid IP address\n" \ + cmd_error("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"); @@ -1794,7 +1814,7 @@ void cmd_parser(void) __banked print_string("Setting gw: "); print_ip(ip); write_char('\n'); } else { - print_string("Invalid IP address\n" \ + cmd_error("Invalid IP address\n" \ "Error: gw \n"); } } @@ -1808,7 +1828,7 @@ void cmd_parser(void) __banked print_string("Setting netmask: "); print_ip(ip); write_char('\n'); } else { - print_string("Invalid IP address\n"); + cmd_error("Invalid IP address\n"); } } write_char('\n'); @@ -1824,8 +1844,9 @@ void cmd_parser(void) __banked igmp_setup(); else if (cmd_compare(1, "show")) igmp_show(); - else - print_string("Error: igmp on|off|show\n"); + else { + cmd_error("igmp on|off|show\n"); + } } else if (cmd_compare(0, "mac")) { parse_mac_cmd(); } else if (cmd_compare(0, "hostname")) { @@ -1843,22 +1864,25 @@ void cmd_parser(void) __banked uint8_t c = *hp++; if (c == NUL || c == '\r' || c == '\n') break; - if (c < 0x20 || c > 0x7e || c == '"' || c == '\\') - c = '.'; - *dst++ = c; + *dst++ = name_char(c); } *dst = NUL; } else { - print_string("Error: hostname [name] - the name must not contain spaces\n"); + cmd_error("hostname [name] - the name must not contain spaces\n"); } } else if (cmd_compare(0, "stp")) { stp_parse(); +#ifdef HEALTH + } else if (cmd_compare(0, "health")) { + health_show(); +#endif } 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 { + cmd_error("pvid <1-4094>\n"); + } } else if (cmd_compare(0, "vlan")) { parse_vlan(); } else if (cmd_compare(0, "isolate")) { @@ -1920,11 +1944,11 @@ void cmd_parser(void) __banked parse_session(); } else { - print_string("Unknown command\n"); + cmd_error("Unknown command\n"); } - if (save_cmd && cmd_words_len) { + if (save_cmd && cmd_words_len && err_status == ERR_OK) { // Find end of the cmd-buffer, looking for the NUL-byte. uint8_t i = cmd_words_b[cmd_words_len - 1]; do { @@ -1969,6 +1993,7 @@ void execute_config(void) __banked save_cmd = 0; uint8_t cmd_idx = 0; + uint8_t skipping = 0; do { flash_region.addr = pos; flash_region.len = FLASH_READ_BURST_SIZE; @@ -1979,20 +2004,23 @@ void execute_config(void) __banked do { if (cmd_idx >= (CMD_BUF_SIZE - 1)) { cmd_buffer[cmd_idx] = NUL; - print_string("ERROR: Command too long: "); + print_string("ERROR: Command too long, skipped: "); print_string_x(cmd_buffer); write_char('\n'); - err_status = ERR_CMD_TOO_LONG; - goto config_done; + cmd_idx = 0; + skipping = 1; } c = flash_buf[cfg_idx++]; if (c == 0 || c == '\n') { - cmd_buffer[cmd_idx] = NUL; - if (cmd_idx) { - cmd_tokenize(); - if (err_status != ERR_OK) - goto config_done; - cmd_parser(); + if (skipping) { + skipping = 0; + } else { + cmd_buffer[cmd_idx] = NUL; + if (cmd_idx) { + cmd_tokenize(); + if (err_status == ERR_OK) + cmd_parser(); + } } if (c == 0) goto config_done; @@ -2000,8 +2028,10 @@ void execute_config(void) __banked continue; } - cmd_buffer[cmd_idx] = c; - cmd_idx++; + if (!skipping) { + cmd_buffer[cmd_idx] = c; + cmd_idx++; + } } while (cfg_idx); pages_left--; @@ -2018,6 +2048,7 @@ config_done: // If a command is too long or can't be tokenized, remaining commands are not executed // Returns the status via `err_status`-variable. void execute_commands(__xdata uint8_t *p) __banked { + __xdata uint8_t failed = ERR_OK; err_status = ERR_OK; uint8_t cmd_idx = 0; while (1) { @@ -2028,9 +2059,13 @@ void execute_commands(__xdata uint8_t *p) __banked { if (err_status != ERR_OK) return; cmd_parser(); + if (failed == ERR_OK) + failed = err_status; } - if (*p == 0) + if (*p == 0) { + err_status = failed; return; + } cmd_idx = 0; } else { if (cmd_idx < (CMD_BUF_SIZE - 1)) { @@ -2047,3 +2082,161 @@ void execute_commands(__xdata uint8_t *p) __banked { p++; }; } + +#ifdef HEALTH +/* + * Health instrumentation. The counters live in xdata and are fed from the + * main loop through the banked entry points below; the "health" command + * prints everything in one go. All figures are raw hex, diffing two dumps + * gives the rates. Phase times are in system ticks (SYS_TICK_HZ = 200, + * 5 ms each); a phase counts as slow when it spans 2 or more ticks. + */ +extern __xdata uint16_t len_left; /* httpd: bytes still to send of the current file */ +extern __xdata uint8_t entry; /* httpd: index of the file being sent */ + +static __xdata uint32_t health_loops; +__xdata uint16_t health_rx_frames; + +static __xdata uint16_t ph_last_t; +static __xdata uint8_t ph_max[HEALTH_PHASES]; +static __xdata uint16_t ph_slow[HEALTH_PHASES]; + +/* The ISR increments ticks between the two byte reads of a 16-bit load, so + * read until two loads agree. */ +static uint16_t ticks_lo(void) +{ + uint16_t a, b; + a = (uint16_t)ticks; + do { + b = a; + a = (uint16_t)ticks; + } while (a != b); + return a; +} + + +void health_loop_start(void) __banked +{ + health_loops++; + ph_last_t = ticks_lo(); +} + + +void health_phase(uint8_t id) __banked +{ + __xdata uint16_t now = ticks_lo(); + __xdata uint16_t d = now - ph_last_t; + ph_last_t = now; + if (d > 0xff) + d = 0xff; + if ((uint8_t)d > ph_max[id]) + ph_max[id] = d; + if (d >= 2) + ph_slow[id]++; +} + + +/* Fill the stack area above the current frame with a pattern; the dump scans + * for how much of it survived. The 8051 stack grows upward, so everything + * above SP is free at this point. Interrupts are held off so an ISR does not + * push into the area mid-paint. */ +void health_stack_paint(void) __banked +{ + __xdata uint8_t from = SP + 4; + __xdata uint8_t ea = EA; + EA = 0; + for (uint8_t i = 0xff; i >= from; i--) + *(__idata uint8_t *)i = 0xa5; + EA = ea; +} + + +static void ph_line(__code const char *name, uint8_t id) +{ + print_string(name); + print_string(" max "); + print_byte(ph_max[id]); + print_string(" slow "); + print_short(ph_slow[id]); + write_char('\n'); +} + + +void health_show(void) __banked +{ + __xdata uint8_t i; + + print_string("up "); + reg_read_m(RTL837X_REG_SEC_COUNTER); + print_byte(sfr_data[0]); print_byte(sfr_data[1]); + print_byte(sfr_data[2]); print_byte(sfr_data[3]); + print_string(" ticks "); + print_long(ticks); + print_string(" loops "); + print_long(health_loops); + print_string(" rx "); + print_short(health_rx_frames); + write_char('\n'); + + /* Phase timing: max duration seen (ticks of 5 ms) and slow-pass count */ + ph_line("link", HEALTH_PH_LINK); + ph_line("sfp ", HEALTH_PH_SFP); + ph_line("rx ", HEALTH_PH_RX); + ph_line("tx ", HEALTH_PH_TX); + ph_line("stp ", HEALTH_PH_STP); + ph_line("cmd ", HEALTH_PH_CMD); + + /* Stack: current depth plus how much of the painted area is untouched */ + print_string("sp "); + print_byte(SP); + print_string(" untouched "); + i = 0; + while (*(__idata uint8_t *)(0xff - i) == 0xa5 && i < 0x86) + i++; + print_byte(i); + write_char('\n'); + + /* The TCP connection slot. One on this configuration; a build with more + * has to say which slot each line is, so make that a visible decision. */ +#if UIP_CONNS != 1 +#error "health: the tcp dump prints a single slot, extend it for UIP_CONNS > 1" +#endif + i = 0; + { + print_string("tcp "); + print_string("st "); + print_byte(uip_conns[i].tcpstateflags); + print_string(" tmr "); + print_byte(uip_conns[i].timer); + print_string(" rtx "); + print_byte(uip_conns[i].nrtx); + print_string(" rto "); + print_byte(uip_conns[i].rto); + print_string(" len "); + print_short(uip_conns[i].len); + print_string(" mss "); + print_short(uip_conns[i].mss); + write_char('\n'); + print_string(" lport "); + print_short(HTONS(uip_conns[i].lport)); + print_string(" rport "); + print_short(HTONS(uip_conns[i].rport)); + print_string(" rip "); + print_short(HTONS(uip_conns[i].ripaddr[0])); + write_char(' '); + print_short(HTONS(uip_conns[i].ripaddr[1])); + write_char('\n'); + } + + /* httpd transfer state and the protocol flags */ + print_string("httpd left "); + print_short(len_left); + print_string(" entry "); + print_byte(entry); + print_string(" stp "); + print_byte(stp_enabled); + print_string(" mvlan "); + print_short(management_vlan); + write_char('\n'); +} +#endif diff --git a/cmd_parser.h b/cmd_parser.h index 7564b458..f96c72ae 100644 --- a/cmd_parser.h +++ b/cmd_parser.h @@ -6,9 +6,35 @@ #include "rtl837x_common.h" extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE]; -extern __xdata uint8_t cmd_available; +extern __bit __at(0x03) cmd_available; extern __xdata uint8_t err_status; +/* Health instrumentation, built with HEALTH=1. Without it the hooks below + * compile to nothing and the "health" command does not exist. */ +#ifdef HEALTH +/* Phase ids, in main-loop order */ +#define HEALTH_PH_LINK 0 +#define HEALTH_PH_SFP 1 +#define HEALTH_PH_RX 2 +#define HEALTH_PH_TX 3 +#define HEALTH_PH_STP 4 +#define HEALTH_PH_CMD 5 +#define HEALTH_PHASES 6 + +extern __xdata uint16_t health_rx_frames; +#define health_rx_frame() health_rx_frames++ + +void health_loop_start(void) __banked; /* once per idle() pass, resets the phase baseline */ +void health_phase(uint8_t id) __banked; /* after each phase; accounts ticks since last checkpoint */ +void health_stack_paint(void) __banked; /* paint the unused stack area once at boot */ +void health_show(void) __banked; /* "health" CLI dump */ +#else +#define health_rx_frame() +#define health_loop_start() +#define health_phase(id) +#define health_stack_paint() +#endif + void cmd_tokenize(void) __banked; void cmd_parser(void) __banked; void execute_config(void) __banked; diff --git a/dhcp.c b/dhcp.c index 2fbce36c..079cd521 100644 --- a/dhcp.c +++ b/dhcp.c @@ -139,7 +139,6 @@ void dhcp_addopt_request_ip(void) DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[1]; DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[2]; DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.current_ip[3]; - memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4); } @@ -151,13 +150,11 @@ void dhcp_addopt_server_id(void) DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[1]; DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[2]; DHCP_OPT[dhcp_state.opt_ptr++] = dhcp_state.server[3]; - memcpy(&DHCP_OPT[dhcp_state.opt_ptr], uip_ethaddr.addr, 4); } void dhcp_send_discover(void) { - print_string("dhcp_send_discover called\n"); dhcp_prepare_request(); dhcp_state.opt_ptr = 0; @@ -193,7 +190,6 @@ void dhcp_send_discover(void) void dhcp_send_request(void) { - print_string("dhcp_send_request called\n"); dhcp_prepare_request(); dhcp_state.opt_ptr = 0; @@ -232,19 +228,21 @@ void ip_opt(__xdata uint8_t * ip) { dhcp_state.opt_ptr++; uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++]; - *ip++ = DHCP_OPT[dhcp_state.opt_ptr++]; - *ip++ = DHCP_OPT[dhcp_state.opt_ptr++]; - *ip++ = DHCP_OPT[dhcp_state.opt_ptr++]; - *ip++ = DHCP_OPT[dhcp_state.opt_ptr++]; + if (len >= 4) + memcpy(ip, &DHCP_OPT[dhcp_state.opt_ptr], 4); // There may be more than one IP option, such as 2 DNS servers advertised - dhcp_state.opt_ptr += len - 4; + dhcp_state.opt_ptr += len; } -void long_opt(void) +uint8_t long_opt(void) { dhcp_state.opt_ptr++; - dhcp_state.opt_ptr++; + uint8_t len = DHCP_OPT[dhcp_state.opt_ptr++]; + if (len < 4) { + dhcp_state.opt_ptr += len; + return 0; + } long_value = DHCP_OPT[dhcp_state.opt_ptr++]; long_value <<= 8; long_value |= DHCP_OPT[dhcp_state.opt_ptr++]; @@ -252,6 +250,8 @@ void long_opt(void) long_value |= DHCP_OPT[dhcp_state.opt_ptr++]; long_value <<= 8; long_value |= DHCP_OPT[dhcp_state.opt_ptr++]; + dhcp_state.opt_ptr += len - 4; + return 1; } @@ -275,16 +275,16 @@ void parse_opts(void) ip_opt(&dhcp_state.broadcast[0]); break; case DHCP_LEASE: - long_opt(); - dhcp_state.lease = long_value; + if (long_opt()) + dhcp_state.lease = long_value; break; case DHCP_REBIND: - long_opt(); - dhcp_state.rebind = long_value; + if (long_opt()) + dhcp_state.rebind = long_value; break; case DHCP_RENEWAL: - long_opt(); - dhcp_state.renewal = long_value; + if (long_opt()) + dhcp_state.renewal = long_value; break; case DHCP_END: break; @@ -376,13 +376,11 @@ void dhcp_start(void) __banked *tid++ = SFR_DATA_8; *tid = SFR_DATA_0; dhcp_state.state = DHCP_START; - print_string("dhcp_start done\n"); } void dhcp_stop(void) __banked { - print_string("dhcp_stop called\n"); uip_udp_remove(dhcp_state.conn); dhcp_state.state = DHCP_OFF; } diff --git a/doc/devices/F7008-2.5.md b/doc/devices/F7008-2.5.md new file mode 100644 index 00000000..f069d250 --- /dev/null +++ b/doc/devices/F7008-2.5.md @@ -0,0 +1,70 @@ +# F7008-2.5 + +Documentation for the unmanaged 8 + 1 switch sold as `7018-2.5G` by Guangnianwei +(光年微), board marking `F7008-2.5`, PCB `F7008-2.5GPOE-V1.2`. + +### Brands + +| Brand | Type | Managed | PCB | Flash | Chip RTL | +|-------|------|---------|-----|-------|----------| +| Guangnianwei (光年微) | 7018-2.5G | No | F7008-2.5GPOE-V1.2 | W25Q64JVSSIQ | 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 of the eight RJ45 ports + +### Not verified + +- The LED pads for the SFP+ cage are not mapped yet. The SFP+ lamps are left + undriven, so an inserted module lights nothing. +- The UART header footprint is present but unpopulated, and no boot log could be + captured from it, so the baud rate is unknown. There are most likely + unpopulated 0-Ohm resistors next to the header; bridging those should bring + the console to life. + +### Build target + +``` +make MACHINE=F7008_2_5 +``` + +### LED wiring + +Measured pad to lamp map (`led_mux` index -> lamp): + +| Port | green lamp | yellow lamp | +|------|-----------|-------------| +| 1 | pad 0 | pad 1 | +| 2 | pad 2 | pad 3 | +| 3 | pad 4 | pad 5 | +| 4 | pad 6 | pad 8 | +| 5 | pad 9 | pad 10 | +| 6 | pad 12 | pad 15 | +| 7 | pad 18 | pad 21 | +| 8 | pad 24 | pad 25 | + +Pads 7, 11, 13, 14, 16, 17, 19, 20, 22, 23, 26 and 27 do not drive an RJ45 lamp. + +The front panel is silkscreened `2.5G: Green / 10/100M: Yellow`, and the target +follows it: the green lamp is on while the port is linked at 2.5G, the yellow +lamp blinks while data is flowing. + +### PCB overview + +The board is marked `F7008-2.5GPOE-V1.2`. + +Top side: + + + +Bottom side: + + + +### Hardware notes + +- 4 x `RT4802SNE` magnetics +- Unpopulated UART header footprint, next to the magnetics +- Power input 12V 1A, 180 x 84 x 29 mm metal case diff --git a/doc/devices/HASIVO_S1100WP_8GT_1SX_SE.md b/doc/devices/HASIVO_S1100WP_8GT_1SX_SE.md new file mode 100644 index 00000000..64c342e0 --- /dev/null +++ b/doc/devices/HASIVO_S1100WP_8GT_1SX_SE.md @@ -0,0 +1,75 @@ +# Hasivo S1100WP-8GT-1SX-SE + +RTL8373-based 8×2.5G PoE + 1×SFP switch. + +### Label specifications + +- **Manufacturer**: Hasivo +- **Model**: S1100WP-8GT-1SX-SE +- **Ports**: + - 8 × RJ45: 10/100/1000/2500 Mbps with PoE + - 1 × SFP: 1G / 2.5G / 10G +- **Power input**: DC 52V 2.5A +- **Stock firmware defaults**: IP 192.168.0.1, user/password `admin`/`admin` + +### What works + +- All eight 2.5GBASE-T RJ45 ports at 10/100/1000/2500 Mbps (PoE is not configurable via RTLPlayground) +- SFP port supporting 1G, 2.5G and 10G modules +- LEDs: green (2.5G) and amber (1G/100M/10M) per copper port; combined link/act on the SFP port + +### Photos + +Front panel: + + + +Label: + + + +### PCB overview + +Top side: + + + +Bottom side: + + + +### Port layout + +| Logical port | Physical port | Type | +|--------------|---------------|--------| +| 1-8 | 1-8 | Copper | +| 9 | 9 | SFP | + +### LED configuration + +Copper ports use LED SET0, the SFP port uses LED SET1. + +| SET | LED0 | LED2 | +|------|-----------------------------------------------|------------------------------------------------------| +| SET0 | Green — lights on 2.5G link with activity | Amber — lights on 1G / 100M / 10M link with activity | +| SET1 | All speeds — lights on any link with activity | — | + +### SFP GPIO assignments + +| SFP (logical 9) | pin_detect (ModAbs) | pin_los | pin_tx_disable | SerDes | I2C SDA | I2C SCL | +|-----------------|---------------------|---------|----------------|--------|-----------------|---------------------| +| SFP | GPIO30_ACL_BIT3_EN | GPIO37 | GPIO_NA | SDS1 | GPIO39_I2C_SDA4 | GPIO40_I2C_SCL3_MDC1 | + +### Machine configuration + +- `reset_pin`: GPIO54_ACL_BIT2_EN +- High LEDs (pads 27-29): mux = `LED_27 | LED_28_SYS | LED_29`, enable = `LED_28_SYS | LED_29` + +### PoE + +The 8 copper ports support PoE powered by a DC 52V 2.5A input. Power management is handled by two HS104PTI chipsets over I2C (GPIO47_I2C_SDA0, GPIO46_I2C_SCL0): + +* Chip 1 (I2C address 0x1A): 2.5G Ports 1–4 +* Chip 2 (I2C address 0x2A): 2.5G Ports 5–8 + +Note: The current implementation does not yet support PoE management. Consequently, PoE currently operates in standalone mode without software control. \ No newline at end of file diff --git a/doc/devices/PB-2132.md b/doc/devices/PB-2132.md new file mode 100644 index 00000000..d3e91d4e --- /dev/null +++ b/doc/devices/PB-2132.md @@ -0,0 +1,53 @@ +# IG108 + +Following is documentation for unmanaged switch marked as `IG108`. + +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 | +|---------|------------|---------|------------|-------------|---------------| +| Goalake | IG108 | No | PB-2132 | cFeon Q32A | 8373N + 8224N | +| Netcore | GS9 | No | PB-2132 | cFeon Q32A | 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: PB-2132 + +Front Goalake: + + + +Label Goalake: + + + +Top side: + + + +Bottom: + + + +### Serial console + +The PCB has unpopulated through-holes near the SFP Port, with description of each pin, works out of the box. + +- **Settings**: 115200 baud / 8N1 / 3.3V TTL + +### Power supply + +Input power is delivered via barell plug, `12V 1A` adapter was provided. + diff --git a/doc/devices/photos/F7008-2.5/PCB-bottom.jpg b/doc/devices/photos/F7008-2.5/PCB-bottom.jpg new file mode 100644 index 00000000..c4b34c13 Binary files /dev/null and b/doc/devices/photos/F7008-2.5/PCB-bottom.jpg differ diff --git a/doc/devices/photos/F7008-2.5/PCB-top.jpg b/doc/devices/photos/F7008-2.5/PCB-top.jpg new file mode 100644 index 00000000..c8f73825 Binary files /dev/null and b/doc/devices/photos/F7008-2.5/PCB-top.jpg differ diff --git a/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/front.jpg b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/front.jpg new file mode 100644 index 00000000..028b6382 Binary files /dev/null and b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/front.jpg differ diff --git a/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/label.jpg b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/label.jpg new file mode 100644 index 00000000..c5ab79ac Binary files /dev/null and b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/label.jpg differ diff --git a/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_bottom.jpg b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_bottom.jpg new file mode 100644 index 00000000..0b72c3c0 Binary files /dev/null and b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_bottom.jpg differ diff --git a/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_top.jpg b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_top.jpg new file mode 100644 index 00000000..6f72b543 Binary files /dev/null and b/doc/devices/photos/HASIVO_S1100WP-8GT-1SX-SE/pcb_top.jpg differ diff --git a/doc/devices/photos/PB-2132/PCB-bottom.jpg b/doc/devices/photos/PB-2132/PCB-bottom.jpg new file mode 100644 index 00000000..63cc5352 Binary files /dev/null and b/doc/devices/photos/PB-2132/PCB-bottom.jpg differ diff --git a/doc/devices/photos/PB-2132/PCB-top.jpg b/doc/devices/photos/PB-2132/PCB-top.jpg new file mode 100644 index 00000000..a0d47ab9 Binary files /dev/null and b/doc/devices/photos/PB-2132/PCB-top.jpg differ diff --git a/doc/devices/photos/PB-2132/case.jpg b/doc/devices/photos/PB-2132/case.jpg new file mode 100644 index 00000000..4dbfed06 Binary files /dev/null and b/doc/devices/photos/PB-2132/case.jpg differ diff --git a/doc/devices/photos/PB-2132/label.jpg b/doc/devices/photos/PB-2132/label.jpg new file mode 100644 index 00000000..a70dc2df Binary files /dev/null and b/doc/devices/photos/PB-2132/label.jpg differ diff --git a/doc/health.md b/doc/health.md new file mode 100644 index 00000000..736d52b7 --- /dev/null +++ b/doc/health.md @@ -0,0 +1,79 @@ +# The health command + +`health` prints a one-page snapshot of the firmware's vital signs on the +serial console. It exists for the situations where the switch misbehaves and +every probe from the network measures the network instead of the device: the +dump is taken from inside, costs one command, and works while HTTP does not. + +``` +> health +up 0000001f ticks 0x00001f4c loops 0x000016da rx 0x024e +link max 01 slow 0x0000 +sfp max 74 slow 0x0001 +rx max 00 slow 0x0000 +tx max 00 slow 0x0000 +stp max 04 slow 0x0001 +cmd max 02 slow 0x0001 +sp 80 untouched 62 +tcp st 00 tmr 00 rtx 00 rto 00 len 0x0000 mss 0x05b4 + lport 0x0000 rport 0x0000 rip 0x0000 0x0000 +httpd left 0x0000 entry 00 stp 01 mvlan 0x0002 +``` + +The instrumentation is not part of a default build. It costs about 1.1 kB +of bank 2, a few bytes of xdata and seven banked calls per main-loop pass, +which is nothing a user needs unless they are chasing a problem, so it is +switched on at build time: + +``` +make MACHINE=SWTGW218AS HEALTH=1 +``` + +Without it the hooks compile to nothing and `health` is not a command. + +All figures are raw hex. The counters are cumulative: take two dumps a known +time apart and the differences give the rates. + +## The first line + +`up` is the seconds counter, `ticks` the system tick (200 per second), +`loops` the number of main-loop passes and `rx` the frames handle_rx has +taken off the NIC. A healthy loop completes one pass per tick, so the +difference in `loops` tracks the difference in `ticks`; a loop that falls +behind is being held up by one of the phases below. The `rx` rate separates +a flooded CPU port from a stalled loop, which look identical from outside. + +## Phases + +Each line is one section of the main loop: the link poll, SFP handling, +frame reception, TCP transmission, the STP timers and the command dispatch. +`max` is the worst time that section has taken, in ticks of 5 ms; `slow` +counts the passes that took two ticks or more. A misbehaving subsystem +names itself here, which is the difference between knowing that the loop is +slow and knowing why. + +A dump of its own output takes the console some milliseconds, so running +`health` repeatedly increments the `cmd` counters by itself. That is the +instrument observing itself, not a fault. + +## Stack + +At boot the free stack area is painted with a pattern. `sp` is the live +stack pointer; `untouched` counts the painted bytes still intact at the top, +which is the closest thing to a high-water mark this hardware offers. A +shrinking `untouched` across dumps means something is reaching deeper than +anything before it. + +## The TCP slot + +The connection table is printed whole, one slot per pair of lines: TCP +state (3 is ESTABLISHED, 0 closed), retransmission timer and counter, bytes +in flight, negotiated MSS, then the ports and the peer address. With a +single slot serving the entire web interface, one glance answers the +question every management outage starts with: who is holding it. A slot +showing state 3 with nothing in flight and a peer that no longer answers is +a connection whose owner went away. + +`httpd left` and `entry` describe the file transfer the web server believes +it is in the middle of, and the trailing flags show whether STP is running +and which VLAN carries management. diff --git a/doc/images/LAG_config.png b/doc/images/LAG_config.png index c02dac7e..d0ef9214 100644 Binary files a/doc/images/LAG_config.png and b/doc/images/LAG_config.png differ diff --git a/doc/images/advanced_settings.png b/doc/images/advanced_settings.png index fec750d0..90be29df 100644 Binary files a/doc/images/advanced_settings.png and b/doc/images/advanced_settings.png differ diff --git a/doc/images/gui.png b/doc/images/gui.png index bb79bd10..d092c5ac 100644 Binary files a/doc/images/gui.png and b/doc/images/gui.png differ diff --git a/doc/supported_devices.md b/doc/supported_devices.md index 6ecc5054..247d489f 100644 --- a/doc/supported_devices.md +++ b/doc/supported_devices.md @@ -6,6 +6,8 @@ The following devices have been tested and are fully working: | Ampcom | WAM902-SWTG018AS| No | [SWTG018AS-A V2.0](devices/SWTG018AS_A_V2_0.md) | | 8 + 1 | | 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 | +| Goalake | IG108 | No | [PB-2132](devices/PB-2132.md) | 4M | 8 + 1 | +| Hasivo | S1100WP_8GT_1SX_SE | Yes | [S1100WP_8GT_1SX_SE-V1.05](devices/HASIVO_S1100WP_8GT_1SX_SE.md) | 2M | 8 + 1 | | 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 | @@ -21,9 +23,11 @@ The following devices have been tested and are fully working: | 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 / PCB-SWTG115AS-V2.1](devices/SWTGW215AS.md) | 2M | 5 + 1 | +| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 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 | | MokerLink| POE-2G080110GS | No | [2M-PCB51-ML-V1.1](devices/2M-PCB51-ML-V1_1.md) | 4M | 8 + 1 | +| Netcore | GS9 | No | [PB-2132](devices/PB-2132.md) | 4M | 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 | @@ -33,7 +37,7 @@ The following devices have been tested and are fully working: | Xikestore| SKS3200M-4GPY2XF| Yes | [SWTG024AS-v1.0](devices/SWTG024AS.md) | | 4 + 2 | | 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 | -| Lianguo | HYWS-SGT0108S | No | [2G5F_20G_V1.01](devices/HYWS-SGT0108S.md) | 0.5M | 8 + 1 | +| Guangnianwei | 7018-2.5G | No | [F7008-2.5GPOE-V1.2](devices/F7008-2.5.md) | 8M | 8 + 1 | 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 diff --git a/html/app.js b/html/app.js index e4ddb7ae..22a884cb 100644 --- a/html/app.js +++ b/html/app.js @@ -91,6 +91,7 @@ sy_rebooting:"Rebooting, reconnect in about 20 s",sy_bytes:"bytes", cw_title:"Write this startup configuration?",cw_save_title:"Save running configuration to flash?", cw_info:"{n} / 2048 bytes, replayed line by line on every boot", cw_toolarge:"Too large: the config sector accepts at most 2048 bytes. Remove lines first.", +cw_longline:"Line {n} is {m} bytes long: the switch can only replay lines up to 126 bytes and would skip it on boot, so the write is refused. Shorten it first.", cw_unknown:"Not in the known config grammar (will still be written): ",cw_empty:"(empty)", cw_writing:"Writing configuration...",cw_failed:"config write failed: HTTP {n}", cw_verify_fail:"Verification failed: flash content differs from what was sent. Command log NOT cleared.", @@ -203,6 +204,7 @@ sy_rebooting:"再起動中です。約 20 秒後に再接続してください", cw_title:"この起動設定を書き込みますか?",cw_save_title:"実行中の設定をフラッシュに保存しますか?", cw_info:"{n} / 2048 バイト、起動のたびに 1 行ずつ実行されます", cw_toolarge:"サイズ超過: 設定セクタは最大 2048 バイトです。先に行を削除してください。", +cw_longline:"{n} 行目は {m} バイトです: スイッチが起動時に実行できる行は 126 バイトまでで、この行は読み飛ばされるため、書き込みを拒否します。先に短くしてください。", cw_unknown:"既知の設定文法に含まれない行 (そのまま書き込まれます): ",cw_empty:"(空)", cw_writing:"設定を書き込み中...",cw_failed:"設定の書き込みに失敗しました: HTTP {n}", cw_verify_fail:"検証失敗: フラッシュの内容が送信内容と異なります。コマンドログは消去されていません。", @@ -315,6 +317,7 @@ sy_rebooting:"正在重启,约 20 秒后重新连接",sy_bytes:"字节", cw_title:"写入此启动配置?",cw_save_title:"将运行配置保存到 Flash?", cw_info:"{n} / 2048 字节,每次启动时逐行执行", cw_toolarge:"过大: 配置扇区最多容纳 2048 字节。请先删除部分行。", +cw_longline:"第 {n} 行有 {m} 字节: 交换机启动时只能执行不超过 126 字节的行,该行会被跳过,因此拒绝写入。请先缩短该行。", cw_unknown:"不属于已知配置语法的行 (仍会写入): ",cw_empty:"(空)", cw_writing:"正在写入配置...",cw_failed:"配置写入失败: HTTP {n}", cw_verify_fail:"校验失败: Flash 内容与发送内容不一致。命令日志未清除。", @@ -335,6 +338,117 @@ fw_applied:"升级已应用",fw_done_t:"固件已升级", fw_done:"交换机已校验镜像并以新镜像重启。会话已重置,需要重新登录。", fw_login:"前往登录",fw_rebooting:"交换机正在重启...", fw_timeout:"150 秒后交换机仍未恢复: 请检查电源 / 串口控制台" +}, +es:{ +nav_dash:"Panel",nav_ports:"Puertos",nav_stp:"Árbol de expansión",nav_stats:"Estadísticas", +nav_vlan:"VLAN",nav_l2:"Tabla MAC",nav_mirror:"Duplicación",nav_lag:"LAG",nav_eee:"EEE", +nav_bw:"Ancho de banda",nav_system:"Sistema",nav_fw:"Firmware", +hdr_dirty:"cambios sin guardar",hdr_dirty_t:"La configuración en ejecución difiere de la guardada", +hdr_save:"Guardar en flash",hdr_save_t:"Persistir la configuración en ejecución a flash", +th_auto:"Sistema",th_auto_sel:"Sistema (Selenized)",sy_display:"Pantalla",sy_theme:"Tema",sy_display_note:"Se almacena solo en este navegador.",th_light:"Claro",th_dark:"Oscuro", +th_sel_light:"Selenized claro",th_sel_dark:"Selenized oscuro", +c_port:"Puerto",c_name:"Nombre",c_link:"Enlace",c_apply:"Aplicar",c_cancel:"Cancelar",c_confirm:"Confirmar", +c_close:"Cerrar",c_refresh:"Actualizar",c_off:"apagado",c_offc:"Apagado",c_down:"caído",c_on:"Encendido",c_auto:"Auto",c_delete:"Eliminar", +c_type:"Tipo",c_state:"Estado",c_enabled:"Habilitado",c_load:"Cargar",c_disable:"Deshabilitar",c_sfp:"SFP", +c_full:"dúplex completo",c_half:"semidúplex",c_devices:"dispositivos",c_yes:"sí",c_no:"no", +d_ports:"Puertos",d_ports_h:"pulsa un puerto para ver detalles",d_system:"Sistema",d_traffic:"Tráfico", +d_traffic_h:"paquetes/s, en vivo",d_txpps:"TX pps",d_rxpps:"RX pps",d_txbad:"TX erróneos",d_rxbad:"RX erróneos", +i_host:"Nombre de host",i_ip:"Dirección IP",i_mask:"Máscara de red",i_gw:"Puerta de enlace",i_mac:"MAC",i_fw:"Firmware", +i_built:"Compilado",i_hw:"Hardware",i_flash:"Flash",i_syslog:"Syslog", +p_state:"Estado",p_disabled:"deshabilitado",p_up:"activo",p_txgb:"TX correctos / erróneos",p_rxgb:"RX correctos / erróneos", +p_pkts:"paquetes",p_module:"Módulo",p_temp:"Temperatura",p_vcc:"Vcc",p_txbias:"Polarización TX",p_txpower:"Potencia TX", +p_rxpower:"Potencia RX",p_txfault:"Fallo TX",p_txdis:"TX deshabilitado",p_rxlos:"Pérdida RX (LOS)",p_adv:"Anunciado", +pt_title:"Configuración de puerto",pt_conn:"Conectado",pt_speed:"Velocidad",pt_mtu:"MTU", +pt_sfp_note:"La velocidad del SFP se ajusta por ranura y no se guarda en la configuración de inicio.", +pt_name_err:"Nombre de puerto: 1-15 caracteres, sin espacios",pt_mtu_err:"El MTU debe estar entre 64 y 16383", +stp_title:"Árbol de expansión",stp_h:"RSTP, 802.1w",stp_version:"Versión",stp_v_stp:"STP compatible", +stp_prio:"Prioridad del puente",stp_hello:"Hello [s]",stp_maxage:"Edad máxima [s]",stp_fwd:"Retardo de reenvío [s]", +stp_txhold:"Retención TX",stp_bridge_apply:"Aplicar ajustes del puente", +stp_off_msg:"El árbol de expansión está deshabilitado: todos los puertos reenvían.",stp_bridge:"Puente",stp_root:"Raíz", +stp_root_self:"este puente es la raíz",stp_via:"vía puerto",stp_cost:"coste de ruta",stp_tc:"cambios de topología", +stp_prio_note:"La prioridad más baja gana la elección de raíz (8 por defecto = 32768). Los ajustes se aplican inmediatamente; usa Guardar en flash para conservarlos.", +stp_portcfg:"Configuración de puertos",stp_edge:"Borde",stp_pcost:"Coste de ruta",stp_pprio:"Prioridad", +stp_guard:"Protección",stp_filter:"Filtro BPDU",stp_p2p:"Punto a punto", +stp_port_note:"Los puertos de borde reenvían inmediatamente y no causan cambios de topología; Auto trata un puerto como de borde tras 3 s sin BPDUs, y cualquier BPDU recibido lo revoca. Coste de ruta 0 = automático.", +stp_status:"Estado de puertos",stp_role:"Rol",stp_db:"Puente designado",stp_dp:"Puerto designado", +stp_dc:"Coste designado",stp_oedge:"Borde operativo",stp_op2p:"P2P operativo", +stp_s0:"deshabilitado",stp_s1:"bloqueo",stp_s2:"aprendizaje",stp_s3:"reenvío", +stp_r1:"Raíz",stp_r2:"Designado",stp_r3:"Alternativo",stp_trip:"protección disparada", +stp_g_none:"Ninguno",stp_g_bpdu:"BPDU",stp_g_root:"Raíz", +stp_en_q:"¿Habilitar el árbol de expansión?", +stp_en_d:"Los puertos empiezan bloqueados y tardan hasta el doble del retardo de reenvío en alcanzar el reenvío; los puertos de borde se recuperan inmediatamente.", +stp_dis_q:"¿Deshabilitar el árbol de expansión?",stp_dis_d:"Todos los puertos pasan directamente a reenviar; se pierde la protección contra bucles.", +stp_cost_err:"El coste de ruta debe estar entre 0 y 200000000", +st_title:"Estadísticas de puertos",st_h:"totales desde el arranque",st_txg:"TX correctos",st_txb:"TX erróneos",st_rxg:"RX correctos", +st_rxb:"RX erróneos",st_details:"Detalles",st_counters:"Contadores MIB",st_nonzero:"solo no nulos", +st_autoref:"actualización automática",st_counter:"Contador",st_value:"Valor",st_fail:"error al cargar contadores", +v_title:"VLANs",v_vid:"VID",v_members:"Miembros",v_tagged:"Etiquetados",v_untagged:"Sin etiquetar",v_pvid_on:"PVID activo", +v_empty:"No hay VLANs configuradas.",v_mgmt:"VLAN de gestión",v_mgmt_none:"sin etiquetar",v_editor:"Editor de VLANs", +v_optional:"opcional",v_setmgmt:"Establecer como VLAN de gestión",v_setmgmt_t:"Hacer esta la VLAN de gestión", +v_member:"Miembro",v_pvid:"PVID", +v_legend:"U = miembro sin etiquetar, T = miembro etiquetado, - = no miembro. PVID asigna esta VLAN al tráfico entrante sin etiquetar.", +v_ingress:"Filtrado de entrada",v_ingress_h:"solo escritura: el estado no se puede leer desde el switch", +v_accept:"Aceptar",v_ing_all:"Todos",v_ing_apply:"Aplicar modos de entrada",v_del_t:"Eliminar VLAN", +v_del_q:"¿Eliminar VLAN {n}?",v_del_d:"Los puertos conservan su PVID hasta que se reasignen.", +v_vid_err:"Introduce un ID de VLAN (1-4094)",v_name_err:"El nombre debe empezar por una letra (letras, dígitos, _)", +v_loaded:"VLAN {n} cargada",v_notfound:"VLAN {n} no encontrada (aún se puede crear una nueva)", +v_nomember:"Selecciona al menos un puerto miembro (o elimina la VLAN)", +v_ing_none:"Elige un modo de entrada para al menos un puerto",v_vid_first:"Introduce primero un ID de VLAN", +v_mgmt_q:"¿Establecer VLAN {n} como VLAN de gestión?", +v_mgmt_d:"El switch empezará a etiquetar su propio tráfico con esa VLAN. Si el puerto por el que te conectas no la transporta, esta página dejará de ser accesible y el ajuste solo podrá deshacerse por la consola.", +l2_title:"Tabla de direcciones MAC",l2_filter:"filtrar...",l2_flush:"Vaciar entradas aprendidas",l2_static:"estática", +l2_learned:"aprendida",l2_loading:"cargando...",l2_failed:"error de carga",l2_entries:"entradas", +l2_del_t:"Eliminar entrada",l2_flush_q:"¿Vaciar todas las entradas MAC aprendidas?", +m_title:"Duplicación de puertos",m_active:"activa",m_monitor:"Puerto monitor",m_mirror:"Duplicar",m_both:"Ambos", +m_note:"Ambos = duplica RX y TX del puerto al puerto monitor.",m_none:"Selecciona al menos un puerto duplicado", +lag_hash:"Hash:",lag_note:"Un LAG necesita al menos un miembro para guardarse en la configuración de inicio; aplicar un grupo vacío lo borra.", +lag_clear_q:"¿Borrar LAG {n}?",lag_clear_d:"Todos los puertos miembros vuelven a la operación normal.", +e_title:"Ethernet de bajo consumo",e_adv:"Anunciado",e_lp:"Par de enlace",e_active:"Activo",e_enable:"Habilitar", +e_note:"Banderas anunciadas y del socio de enlace por velocidad: 100M, 1G, 2.5G. Los puertos SFP no soportan EEE.", +e_idle:"inactivo",e_na:"n/d", +bw_title:"Límites de ancho de banda",bw_h:"Mbit/s, 0.016-10000",bw_in:"Límite de entrada",bw_out:"Límite de salida", +bw_exceed:"Al excederse",bw_fc:"Control de flujo",bw_drop:"Descartar", +bw_in_err:"El límite de entrada debe estar entre 0.016 y 10000 Mbit/s",bw_out_err:"El límite de salida debe estar entre 0.016 y 10000 Mbit/s", +sy_network:"Red",sy_dhcp:"Usar DHCP",sy_dhcp_t:"Solicitar dirección mediante DHCP",sy_services:"Servicios", +sy_igmp:"IGMP snooping",sy_sysip:"IP del servidor",sy_server:"Servidor",sy_port:"Puerto", +sy_services_note:"El estado del servicio refleja la configuración de inicio; el estado en ejecución no es legible.", +sy_password:"Contraseña de administrador",sy_newpw:"Nueva contraseña",sy_repeat:"Repetir",sy_pwapply:"Cambiar contraseña", +sy_pw_note:"Toma efecto inmediatamente; guarda en flash para conservarla tras el reinicio.",sy_console:"Consola", +sy_cmd:"Comando de CLI...",sy_send:"Enviar",sy_startup:"Configuración de inicio",sy_replayed:"reaplicada en cada arranque", +sy_reload:"Recargar desde flash",sy_write:"Escribir en flash",sy_unknown_note:"Las líneas desconocidas se resaltan antes de escribir.", +sy_maint:"Mantenimiento",sy_reboot:"Reiniciar switch",sy_lang:"Idioma", +sy_ip_err:"IP / máscara / puerta de enlace no válidos",sy_host_err:"Nombre de host: 1-23 caracteres imprimibles, sin espacios ni comillas", +sy_net_q:"¿Aplicar ajustes de red?",sy_net_d:"La IP de gestión cambia a {ip}: esta página deberá reabrirse allí.", +sy_ip_changed:"IP cambiada, reconecta en http://{ip}/",sy_dhcp_q:"¿Cambiar a DHCP?", +sy_dhcp_d:"El switch solicita una dirección mediante DHCP. Deberás encontrar su nueva IP para reconectar.", +sy_sysip_err:"IP del servidor syslog no válida",sy_sysport_err:"El puerto de syslog debe estar entre 1 y 65535", +sy_pw_len:"Contraseña: 1-20 caracteres",sy_pw_space:"La contraseña no puede contener espacios",sy_pw_match:"Las contraseñas no coinciden", +sy_pw_q:"¿Cambiar la contraseña de administrador?",sy_pw_d:"Toma efecto inmediatamente para los nuevos inicios de sesión. Guarda en flash para persistir.", +sy_reboot_q:"¿Reiniciar el switch?",sy_reboot_d:"Hay cambios SIN GUARDAR y se perderán. Guarda en flash primero si quieres conservarlos.", +sy_rebooting:"Reiniciando, reconecta en unos 20 s",sy_bytes:"bytes", +cw_title:"¿Escribir esta configuración de inicio?",cw_save_title:"¿Guardar la configuración en ejecución en flash?", +cw_info:"{n} / 2048 bytes, reaplicada línea a línea en cada arranque", +cw_toolarge:"Demasiado grande: el sector de configuración admite como máximo 2048 bytes. Elimina líneas primero.", +cw_unknown:"No está en la gramática de configuración conocida (aun así se escribirá): ",cw_empty:"(vacío)", +cw_writing:"Escribiendo configuración...",cw_failed:"error al escribir la configuración: HTTP {n}", +cw_verify_fail:"Verificación fallida: el contenido de la flash difiere de lo enviado. El registro de comandos NO se ha borrado.", +cw_saved:"Configuración de inicio guardada y verificada",cw_collect:"Recopilando cambios en ejecución...", +t_applied:"Aplicado: {c}",t_cmds:"{n} comando(s) aplicado(s)",t_rejected:"comando rechazado: {c}",t_failed:"fallo: {c}", +fw_title:"Actualización de firmware", +fw_intro:"Sube una imagen de RTLPlayground (512 KiB .bin). La imagen se almacena provisionalmente en flash y se comprueba con CRC; si es correcta, el switch se reinicia, vuelve a verificar la imagen y la aplica. La configuración de inicio se conserva.", +fw_upload:"Subir",fw_checking:"{f}: {n} bytes. Comprobando...",fw_size_err:"el tamaño es {n}, se esperaban 524288 (512 KiB)", +fw_magic_err:"falta la cabecera de banco / magia LJMP: no es una imagen de firmware",fw_crc_err:"comprobación CRC16 fallida: imagen corrupta", +fw_valid:"imagen de RTLPlayground válida (tamaño, magia y CRC16 correctos)",fw_q:"¿Subir firmware?", +fw_d:"Si la suma de comprobación es correcta, el switch se reinicia y aplica la imagen durante el arranque (la configuración de inicio se conserva). No lo apagues hasta que vuelva.", +fw_finishing:"terminando escritura en flash... {s} s",fw_uploading:"subiendo... {p}% / {s} s", +fw_verified:"suma de comprobación verificada, el switch se está reiniciando...", +fw_rejected:"el switch rechazó la imagen (suma de comprobación incorrecta), no se aplicó nada", +fw_lost:"error de subida: conexión perdida durante la transferencia", +fw_noreboot:"no se detectó reinicio: lo más probable es que la imagen fuera rechazada. Si actualizas desde un firmware antiguo, verifica la versión en la barra lateral tras volver a iniciar sesión.", +fw_applied:"actualización aplicada",fw_done_t:"Firmware actualizado", +fw_done:"El switch verificó la imagen y se reinició con ella. La sesión se restableció, así que se te pedirá que vuelvas a iniciar sesión.", +fw_login:"Ir al inicio de sesión",fw_rebooting:"el switch se está reiniciando...", +fw_timeout:"el switch no ha vuelto tras 150 s: comprueba la alimentación / consola serie" } }; var rtlLang=(function(){ @@ -438,7 +552,7 @@ var CONF_CMDS=[ /^ingress(\s+\d{1,2}[tua])+$/,/^ingress\s+[tua]$/, /^port\s+\d{1,2}\s+(10m|100m|1g|2g5|5g|10g|auto|on|off)(\s+(half|full))?$/, /^port\s+\d{1,2}\s+name\s+\S+$/, - /^eee(\s+\d{1,2})?\s+(on|off)$/, + /^eee\s+(on|off)(\s+\d{1,2})?(\s+(100m|1g|2g5))?$/, /^mirror(\s+\d{1,2})(\s+\d{1,2}[tr]?)+$/,/^mirror\s+off$/, /^lag\s+[1-4](\s+\d{1,2})+$/,/^lag\s+[1-4]\s+d$/,/^laghash\s+[1-4](\s+\w+)+$/, /^isolate\s+\d{1,2}(\s+(off|\d{1,2}))+$/, @@ -537,13 +651,18 @@ TABS.forEach(function(tb){ }); $("burger").addEventListener("click",function(){$("nav").classList.toggle("open")}); +var IDLE_STOP_MS=600000,lastInput=Date.now(); +["mousemove","mousedown","keydown","touchstart","wheel"].forEach(function(ev){ + addEventListener(ev,function(){lastInput=Date.now()},{passive:true}); +}); function Poller(fn,ms){this.fn=fn;this.ms=ms;this.on=false;this.t=null} Poller.prototype.start=function(){if(this.on)return;this.on=true;this.tick()}; Poller.prototype.stop=function(){this.on=false;clearTimeout(this.t)}; Poller.prototype.tick=function(){ var self=this; if(!self.on)return; - var run=document.hidden?Promise.resolve():Promise.resolve().then(self.fn).catch(function(){}); + var quiet=document.hidden||Date.now()-lastInput>IDLE_STOP_MS; + var run=quiet?Promise.resolve():Promise.resolve().then(self.fn).catch(function(){}); run.then(function(){ if(self.on)self.t=setTimeout(function(){self.tick()},self.ms); }); }; @@ -727,6 +846,7 @@ tabHooks.dash={ var SPEEDS=[["auto","c_auto"],["2g5","2.5G"],["1g","1G"],["100m full","100M full"], ["100m half","100M half"],["10m full","10M full"],["10m half","10M half"]]; var SFPRATES=[["auto","c_auto"],["10g","10G"],["2g5","2.5G"],["1g","1G"],["100m","100M"]]; +var ADVSEL={63:"auto",31:"auto",32:"2g5",16:"1g",8:"100m full",4:"100m half",2:"10m full",1:"10m half"}; function speedLabel(s){ if(s==="c_auto")return t("c_auto"); return s.replace(" full"," "+t("c_full")).replace(" half"," "+t("c_half")); @@ -789,6 +909,8 @@ function portsStatus(){ S.ports.forEach(function(p){ var i=p.portNum-1,el=$("plink"+i); if(el)el.innerHTML=linkBadge(p); + var sel=$("pspd"+i),v=p.isSFP?0:ADVSEL[parseInt(p.adv,2)]; + if(sel&&v&&document.activeElement!==sel)sel.value=v; }); } function applyPort(i){ @@ -1428,7 +1550,7 @@ function eeeLoad(){ var on=parseInt(p.eee,2)!==0; var sw=h("label",{class:"switch"},[ h("input",{type:"checkbox",onchange:function(){ - postCmd("eee "+p.portNum+" "+(this.checked?"on":"off")) + postCmd("eee "+(this.checked?"on":"off")+" "+p.portNum) .then(function(){setTimeout(eeeLoad,300)}).catch(function(){}); }}),h("i")]); sw.firstChild.checked=on; @@ -1606,11 +1728,16 @@ function cfgReload(){ cfgParseKnown(x); }).catch(function(){}); } +function cfgLongLine(v){ + var ls=v.split("\n"); + for(var i=0;i126)return{n:i+1,m:n};} + return null; +} function cfgBytes(){ - var n=new Blob([$("cfgedit").value]).size; + var v=$("cfgedit").value,n=new Blob([v]).size; var el=$("cfgbytes"); el.textContent=n+" / 2048 "+t("sy_bytes"); - el.style.color=n>2048?"var(--bad)":""; + el.style.color=(n>2048||cfgLongLine(v))?"var(--bad)":""; return n; } $("cfgedit").addEventListener("input",cfgBytes); @@ -1624,9 +1751,9 @@ var CONF_OVERWRITE=[ /^ip\b/,/^gw\b/,/^netmask\b/,/^hostname\b/, /^syslog\s+ip\b/,/^syslog\s+port\b/,/^passwd\b/,/^session\b/, /^vlan\s+\d{1,4}\s+mgmt$/,/^vlan\s+\d{1,4}(?!\s+mgmt\b)/, - /^pvid\s+\d{1,2}\b/,/^ingress\b/, + /^pvid\s+\d{1,2}\b/, /^port\s+\d{1,2}(?!\s+name\b)/,/^port\s+\d{1,2}\s+name\b/, - /^eee\s+\d{1,2}\b/,/^eee\b/,/^mirror\b/, + /^mirror\b/, /^lag\s+\d\b/,/^laghash\s+\d\b/,/^isolate\s+\d{1,2}\b/, /^stp\s+(prio|hello|maxage|fwd|txhold|version)\b/, /^stp\s+(port\s+\d{1,2}|lag\s+[1-4])\s+(edge|cost|prio|guard|filter|p2p)\b/, @@ -1643,8 +1770,28 @@ function mergeConf(base,texts){ var m; if((m=line.match(/^vlan (\d{1,4}) d$/))){drop(new RegExp("^vlan "+m[1]+"( |$)"));return;} if((m=line.match(/^lag (\d) d$/))){drop(new RegExp("^lag(hash)? "+m[1]+"( |$)"));return;} + if((m=line.match(/^eee (on|off)(?: (\d{1,2}))?(?: (?:100m|1g|2g5))?$/))){ + if(m[2])drop(new RegExp("^eee (on|off) "+m[2]+"\\b")); + else drop(/^eee /); + conf.push(line);return; + } if(line==="mirror off"){drop(/^mirror /);return;} if(!isConfCmd(line))return; + if((m=line.match(/^ingress (.+)$/))){ + if(/^[tua]$/.test(m[1])){drop(/^ingress /);conf.push(line);return;} + var ports={},order=[],last=-1; + conf.forEach(function(c,i){if(/^ingress [tua]$/.test(c))last=i;}); + conf.forEach(function(c,i){ + var cm=c.match(/^ingress (.+)$/); + if(!cm||/^[tua]$/.test(cm[1])||i2048; + ok.disabled=bytes>2048||!!long; warn.style.color=ok.disabled?"var(--bad)":"var(--warn)"; - warn.textContent=ok.disabled?t("cw_toolarge"):(unknown.length?t("cw_unknown")+unknown.join(" | "):""); + warn.textContent=bytes>2048?t("cw_toolarge"):long?t("cw_longline",long):(unknown.length?t("cw_unknown")+unknown.join(" | "):""); } ed.addEventListener("input",refresh);refresh(); modal(title,h("div",null,[info,warn,ed]),[h("button",{class:"ctl",text:t("c_cancel"),onclick:closeModal}),ok]); @@ -1692,7 +1839,7 @@ function cfgText(txt){ txt=txt.replace(/\r\n/g,"\n"); return txt&&txt.slice(-1)!=="\n"?txt+"\n":txt; } -function doWriteConfig(txt){ +function doWriteConfig(txt,fromLog){ var form=new FormData(); form.append("configuration",new Blob([txt],{type:"application/octet-stream"}),"config.txt"); toast(t("cw_writing")); @@ -1702,9 +1849,10 @@ function doWriteConfig(txt){ }).then(function(back){ back=back.replace(/\0[\s\S]*$/,"").replace(/\r\n/g,"\n").trim(); if(back!==txt.trim())throw new Error(t("cw_verify_fail")); - return api("/cmd_log_clear").catch(function(){}); - }).then(function(){ - setDirty(false); + if(!fromLog)return false; + return api("/cmd_log_clear").then(function(){return true},function(){return true}); + }).then(function(cleared){ + if(cleared)setDirty(false); $("cfgedit").value=txt;cfgBytes();cfgParseKnown(txt); toast(t("cw_saved"),"ok"); }).catch(function(e){toast(e.message||String(e),"err")}); @@ -1715,9 +1863,9 @@ $("saveBtn").addEventListener("click",function(){ getText("/config").catch(function(){return""}), getText("/cmd_log").catch(function(){return""}), ]).then(function(r){ - var cur=r[0].replace(/\0[\s\S]*$/,""); - var merged=mergeConf([],[cur,r[1].replace(/\0[\s\S]*$/,"")]); - writeConfig(merged.join("\n"),t("cw_save_title")); + var cur=r[0].replace(/\0[\s\S]*$/,"").split(/\r?\n/).map(function(l){return l.trim().replace(/\s+/g," ")}).filter(Boolean); + var merged=mergeConf(cur,[r[1].replace(/\0[\s\S]*$/,"")]); + writeConfig(merged.join("\n"),t("cw_save_title"),true); }); }); diff --git a/html/index.html b/html/index.html index 3bd9105c..1bb3e7cc 100644 --- a/html/index.html +++ b/html/index.html @@ -398,6 +398,7 @@ button.danger{background:var(--bad);color:#fff;border-color:transparent} +