diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f3bb7ae --- /dev/null +++ b/.dockerignore @@ -0,0 +1,19 @@ +.git/ +.gitignore +.gitattributes +.github/ +output/ +installer/output/ +*.bin +html_data.c +html_data.h +*.o +*.rel +*.lst +*.sym +*.asm +*.ihx +*.img +*.map +*.mem +*.lk diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6ed31e0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:13-slim + +RUN apt-get update && apt-get install -y \ + make \ + gcc \ + sdcc \ + xxd \ + python3 \ + libjson-c-dev \ + golang-go \ + git \ + && rm -rf /var/lib/apt/lists/* + +# git safe.directory for mounted repos (Makefile uses git describe) +RUN git config --global --add safe.directory /workspace + +WORKDIR /workspace + +CMD ["bash"] diff --git a/Makefile b/Makefile index ada70cf..11d89b4 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,11 @@ DEFAULT_CONFIG_LOCATION = 454656 CONFIG_LOCATION = 458752 HTML_LOCATION = 262144 +ifeq ($(origin CC),default) CC = sdcc +endif CC_FLAGS = -mmcs51 -I. -Ihttpd -Iuip -ASM = sdas8051 +ASM ?= sdas8051 AFLAGS= -plosgff SUBDIRS := tools @@ -30,30 +32,70 @@ endif VERSION_EXTENSION = v$(VERSION)-$(GIT_VERSION) FILENAME_EXTENSION = $(VERSION_EXTENSION)-$(MACHINE) +# Deterministic build date: honor SOURCE_DATE_EPOCH, else the HEAD commit date, +# else wall-clock (no-git fallback). Keeps same-commit builds byte-identical +# (BUILD_DATE is baked into the image and covered by the trailing CRC). +SOURCE_DATE_EPOCH ?= $(shell git show -s --format=%ct HEAD 2>/dev/null) +ifeq ($(SOURCE_DATE_EPOCH),) +BUILD_DATE := $(shell date +"%Y-%m-%d %H:%M:%S") +else +BUILD_DATE := $(shell date -u -d @$(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S" 2>/dev/null \ + || date -u -r $(SOURCE_DATE_EPOCH) +"%Y-%m-%d %H:%M:%S") +endif + all: create_build_dir $(VERSION_HEADER) $(SUBDIRS) $(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin create_build_dir: - mkdir -p $(BUILDDIR) - mkdir -p $(BUILDDIR)/uip - mkdir -p $(BUILDDIR)/httpd + mkdir -p "$(BUILDDIR)" + mkdir -p "$(BUILDDIR)/uip" + mkdir -p "$(BUILDDIR)/httpd" + +# Keep machine.c in first position to fail immediately on invalid $MACHINE value +SRCS = \ + machine.c \ + cmd_editor.c \ + cmd_parser.c \ + dhcp.c \ + html_data.c \ + rtlplayground.c \ + syslog.c \ + udp_apps.c + +# RTL837x +SRCS += \ + rtl837x_bandwidth.c \ + rtl837x_flash.c \ + rtl837x_igmp.c \ + rtl837x_init.c \ + rtl837x_leds.c \ + rtl837x_phy.c \ + rtl837x_pins.c\ + rtl837x_port.c \ + rtl837x_stp.c +SRCS += \ + httpd/httpd.c \ + httpd/page_impl.c +SRCS += \ + uip/timer.c \ + uip/uip.c \ + uip/uiplib.c \ + uip/uip_arp.c \ + uip/uip-fw.c \ + uip/uip-neighbor.c \ + uip/uip-split.c -SRCS = rtlplayground.c rtl837x_flash.c rtl837x_leds.c rtl837x_phy.c rtl837x_port.c cmd_parser.c html_data.c rtl837x_igmp.c -SRCS += rtl837x_stp.c rtl837x_pins.c dhcp.c machine.c cmd_editor.c rtl837x_bandwidth.c rtl837x_init.c syslog.c -SRCS += uip/timer.c uip/uip.c uip/uip_arp.c uip/uiplib.c uip/uip-fw.c uip/uip-neighbor.c uip/uip-split.c udp_apps.c -SRCS += httpd/httpd.c httpd/page_impl.c OBJS = ${SRCS:%.c=$(BUILDDIR)/%.rel} DEPS := ${SRCS:%.c=$(BUILDDIR)/%.d} -HTML := $(shell find $(html) -name '*.js' -or -name '*.html' -or -name '*.svg') +HTML := $(shell find html -name '*.js' -or -name '*.html' -or -name '*.svg') -html_data.c html_data.h: $(HTML) tools/output/fileadder +html_data.c html_data.h &: $(HTML) | tools tools/output/fileadder -a $(HTML_LOCATION) -s $(IMAGESIZE) -b BANK1 -d html -p html_data $(VERSION_HEADER): - @echo "#ifndef VERSION_H" > $(VERSION_HEADER) - @echo "#define VERSION_H" >> $(VERSION_HEADER) - @echo "#define VERSION_SW \"$(VERSION_EXTENSION)\"" >> $(VERSION_HEADER) - @echo "#define BUILD_DATE \"$(shell date +"%Y-%m-%d %H:%M:%S")\"" >> $(VERSION_HEADER) - @echo "#endif" >> $(VERSION_HEADER) + @printf '%s\n' "#ifndef VERSION_H" "#define VERSION_H" \ + "#define VERSION_SW \"$(VERSION_EXTENSION)\"" \ + "#define BUILD_DATE \"$(BUILD_DATE)\"" \ + "#endif" > $(VERSION_HEADER) httpd: html_data.h @@ -68,20 +110,20 @@ distclean: -rm -f html_data.c html_data.h $(VERSION_HEADER) -rm -rf $(BUILDDIR) -$(BUILDDIR)/%.rel: %.c +$(BUILDDIR)/%.rel: %.c | create_build_dir html_data.h $(CC) -MMD $(CC_FLAGS) -o $@ -c $< -$(BUILDDIR)/%.rel: %.asm +$(BUILDDIR)/%.rel: %.asm | create_build_dir ${ASM} ${AFLAGS} -o $@ $< # mv -f $(addprefix $(basename $^), .lst .rel .sym) . -$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtstart.rel $(BUILDDIR)/crc16.rel +$(BUILDDIR)/rtlplayground.ihx: $(OBJS) $(BUILDDIR)/crtbank.rel $(BUILDDIR)/crc16.rel $(CC) $(CC_FLAGS) -Wl-bHOME=0x00000 -Wl-bBANK1=0x14000 -Wl-bBANK2=0x24000 -Wl-r -o $@ $^ $(BUILDDIR)/rtlplayground.img: $(BUILDDIR)/rtlplayground.ihx objcopy --input-target=ihex -O binary $< $@ -$(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.img +$(BUILDDIR)/rtlplayground-$(FILENAME_EXTENSION).bin: $(BUILDDIR)/rtlplayground.img | tools if [ -e $@ ]; then rm $@; fi tools/output/imagebuilder -i $^ $@ tools/output/fileadder -a $(DEFAULT_CONFIG_LOCATION) -s $(IMAGESIZE) -d config.txt $@ @@ -90,7 +132,7 @@ $(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) +.PHONY: clean all $(SUBDIRS) $(VERSION_HEADER) create_build_dir .PHONY: machine_check: diff --git a/README.md b/README.md index a1d11bf..e149058 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,53 @@ still has an older version of sdcc, but you will need sdcc version 4.5 for the c sudo apt install make gcc sdcc xxd python-is-python3 libjson-c-dev ``` +
+If using Docker (click to expand) + +### Prerequisites + +Install Docker for your platform: + +- **Linux (Debian/Ubuntu)**: `sudo apt install docker.io` then `sudo usermod -aG docker $USER` (log out and back in) +- **Linux (other distros)**: Follow the [Docker Engine install guide](https://docs.docker.com/engine/install/) +- **Windows**: Install [Docker Desktop for Windows](https://docs.docker.com/desktop/setup/install/windows-install/) +- **macOS**: Install [Docker Desktop for Mac](https://docs.docker.com/desktop/setup/install/mac-install/) + +### Usage + +A Dockerfile is provided for a reproducible build environment: + +``` +docker build -t rtlplayground-dev . +``` + +Build the firmware (replace MACHINE with your target, e.g. `DEFAULT_8C_1SFP`): + +``` +docker run --rm -v $(pwd):/workspace rtlplayground-dev make MACHINE=DEFAULT_8C_1SFP +``` + +The resulting `.bin` file appears in `output/` on your host. + +Build host tools only: + +``` +docker run --rm -v $(pwd):/workspace rtlplayground-dev make -C tools +``` + +Run the web-interface simulator locally: + +``` +docker run --rm -p 8080:8080 -v $(pwd):/workspace rtlplayground-dev \ + tools/output/httpd_sim /workspace/html +``` + +Edit `machine.h` or `config.txt` on your host, then re-run `make` — the +source directory is mounted into the container, so changes take effect +immediately. To build for a different machine, pass `MACHINE=...`. + +
+ ## (1) Compiling for direct chip flashing AND upgrading an existing RTLPlayground running device Edit machine.h with an editor like vi or nano. Select the correct machine the firmware should build for. diff --git a/cmd_editor.c b/cmd_editor.c index 9a837e9..866cf40 100644 --- a/cmd_editor.c +++ b/cmd_editor.c @@ -40,21 +40,24 @@ void cmd_edit(void) __banked { while (l != sbuf_ptr) { if (sbuf[l] >= ' ' && sbuf[l] < 127) { // A printable character, copy to command line - if (cmd_line_len >= CMD_BUF_SIZE) - continue; - write_char(sbuf[l]); - // Shift buffer to right - for (uint8_t i = cmd_line_len; i > cursor; i--) - cmd_buffer[i] = cmd_buffer[i-1]; - // Insert char in comand buffer - cmd_buffer[cursor++] = sbuf[l]; - cmd_line_len++; - // Print rest of line - for (uint8_t i = cursor; i < cmd_line_len; i++) - write_char(cmd_buffer[i]); - // Move backwards - for (uint8_t i = cursor; i < cmd_line_len; i++) - write_char('\010'); // BS works like cursor-left + // Reserve one byte for the terminating NUL written on Enter. When the + // line is full, drop the character but still fall through to advance the + // serial-ring read pointer below; a 'continue' here would spin forever. + if (cmd_line_len < CMD_BUF_SIZE - 1) { + write_char(sbuf[l]); + // Shift buffer to right + for (uint8_t i = cmd_line_len; i > cursor; i--) + cmd_buffer[i] = cmd_buffer[i-1]; + // Insert char in comand buffer + cmd_buffer[cursor++] = sbuf[l]; + cmd_line_len++; + // Print rest of line + for (uint8_t i = cursor; i < cmd_line_len; i++) + write_char(cmd_buffer[i]); + // Move backwards + for (uint8_t i = cursor; i < cmd_line_len; i++) + write_char('\010'); // BS works like cursor-left + } } else if (sbuf[l] == '\033') { // ESC-Sequence // Wait until we have at least 3 characters including the ESC character in the serial buffer if (((sbuf_ptr + SBUF_SIZE - l) & SBUF_MASK) < 3) diff --git a/cmd_parser.c b/cmd_parser.c index 6f631cc..93c51e6 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -50,6 +50,7 @@ __xdata char port_names[9][PORT_NAME_SIZE]; extern __xdata uint16_t management_vlan; extern __xdata uint8_t sfp_speed[2]; extern __xdata uint8_t sfp_pins_last; +extern __xdata uint8_t sfp_options[2]; __xdata uint8_t gpio_last_value[8] = { 0 }; // Temporatly for str to hex convertion value. @@ -190,8 +191,11 @@ uint8_t atoi_byte(__xdata uint8_t *out, uint8_t idx) uint8_t num = 0; while (isnumber(cmd_buffer[idx])) { + uint8_t val = cmd_buffer[idx] - '0'; err = 0; - num = (num * 10) + cmd_buffer[idx] - '0'; + if (num > 25 || (num == 25 && val > 5)) + return 1; + num = (num * 10) + val; idx++; } @@ -208,6 +212,8 @@ uint8_t atoi_short(__xdata uint16_t *vlan, uint8_t idx) 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++; } @@ -244,8 +250,7 @@ void parse_lag(void) print_string("LAG status:\n"); for (uint8_t i = 0; i < 4; i++) { write_char(' '); write_char('1' + i); - reg_read_m(RTL837X_TRK_MBR_CTRL_BASE + (i << 2)); - members = ((uint16_t)sfr_data[2]) << 8 | sfr_data[3]; + members = port_lag_members_get(i); if (!members) { print_string(" disabled\n"); continue; @@ -268,7 +273,9 @@ void parse_lag(void) if (cmd_words_len < 2 || !isnumber(cmd_buffer[cmd_words_b[1]])) goto err; - group = cmd_buffer[cmd_words_b[1]] - '0'; + group = cmd_buffer[cmd_words_b[1]] - '1'; + if (group > 3) /* '0' wraps well past three, so one test does both ends */ + goto err; uint8_t w = 2; while (w < cmd_words_len) { @@ -278,7 +285,9 @@ void parse_lag(void) 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 (port > 8) /* phys_to_log_port holds nine entries */ + goto err; + port = machine.phys_to_log_port[port]; } else { goto err; } @@ -290,7 +299,7 @@ void parse_lag(void) port_lag_members_set(group, members); return; err: - print_string("Error: lag [port]...\n"); + print_string("Error: lag <1-4> [port]...\n"); } @@ -299,7 +308,11 @@ void parse_lag_hash(void) __xdata uint8_t group; __xdata uint8_t hash = 0; - group = cmd_buffer[cmd_words_b[1]] - '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 */ + goto err; uint8_t w = 2; while (w < cmd_words_len) { @@ -325,6 +338,9 @@ void parse_lag_hash(void) w++; } port_lag_hash_set(group, hash); + return; +err: + print_string("Error: lag hash <1-4> [type]...\n"); } @@ -341,6 +357,8 @@ void parse_vlan(void) return; } if (cmd_compare(2, "mgmt")) { + if (vlan_settings.vlan > 4094) + goto err; management_vlan = vlan_settings.vlan; if (!vlan_settings.vlan) print_string("Management VLAN disabled\n"); @@ -348,6 +366,8 @@ void parse_vlan(void) print_string("Management VLAN set to "); print_short(management_vlan); write_char('\n'); return; } + if (!vlan_settings.vlan || vlan_settings.vlan > 4094) + goto err; uint8_t w = 2; if (cmd_words_len > w && isletter(cmd_buffer[cmd_words_b[w]])) { register uint8_t i = 0; @@ -408,11 +428,11 @@ void parse_isolate(void) 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 (!isnumber(cmd_buffer[cmd_words_b[1]]) || cmd_buffer[cmd_words_b[1]] == '0' + || isnumber(cmd_buffer[cmd_words_b[1] + 1])) + goto err; + __xdata uint8_t port_configured = machine.phys_to_log_port[cmd_buffer[cmd_words_b[1]] - '1']; + if (port_configured < machine.min_port || port_configured > machine.max_port) goto err; print_byte(port_configured); write_char('\n'); @@ -509,7 +529,7 @@ void parse_ingress(void) if (!isnumber(p)) { continue; } - if (p - '1' > 9) { + if (p < '1') { print_string("Invalid physical port number: "); write_char(p); write_char('\n'); continue; } @@ -720,17 +740,18 @@ void parse_mtu(void) print_string("Port "); print_byte(machine.log_to_phys_port[p]); write_char(' '); print_short(mtu); write_char('\n'); } + return; } - p = cmd_buffer[cmd_words_b[1]] - '1'; - p = machine.phys_to_log_port[p]; - print_byte(p); - if (cmd_words_len != 3) { + 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; } - atoi_short(&mtu, cmd_words_b[2]); - if (mtu > 0x3fff) { - print_string("Maximum MTU is 16383\n"); + p = machine.phys_to_log_port[cmd_buffer[cmd_words_b[1]] - '1']; + print_byte(p); + if (atoi_short(&mtu, cmd_words_b[2]) || mtu < 64 || mtu > 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, @@ -741,7 +762,7 @@ void parse_mtu(void) void sfp_print_measurements(uint8_t sfp) { print_string("Options: "); print_byte(sfp_read_reg(sfp, 92)); write_char('\n'); - if (!(sfp_read_reg(sfp, 92) & 0x40)) + 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'); @@ -1519,10 +1540,35 @@ void cmd_parser(void) __banked } else if (cmd_compare(0, "igmp")) { if (cmd_compare(1, "on")) igmp_enable(); + else if (cmd_compare(1, "off")) + igmp_setup(); else if (cmd_compare(1, "show")) igmp_show(); else - igmp_setup(); // Reverts to default with IP-MC being flooded + print_string("Error: igmp on|off|show\n"); + } else if (cmd_compare(0, "hostname")) { + /* "hostname" alone reports the current name; "hostname " + * sets it, sanitized to JSON-safe printable ASCII. A name with + * spaces would tokenize into several words - reject it instead + * of silently keeping the first one. */ + if (cmd_words_len == 1) { + print_string_x(hostname); + write_char('\n'); + } else if (cmd_words_len == 2) { + __xdata uint8_t *hp = &cmd_buffer[cmd_words_b[1]]; + __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') + break; + if (c < 0x20 || c > 0x7e || c == '"' || c == '\\') + c = '.'; + *dst++ = c; + } + *dst = '\0'; + } 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"); @@ -1535,11 +1581,13 @@ void cmd_parser(void) __banked } } else if (cmd_compare(0, "pvid") && cmd_words_len == 3) { __xdata uint16_t pvid; - uint8_t port; - port = cmd_buffer[cmd_words_b[1]] - '1'; - port = machine.phys_to_log_port[port]; - if (!atoi_short(&pvid, cmd_words_b[2])) - port_pvid_set(port, 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); + else + print_string("Error: pvid <1-4094>\n"); } else if (cmd_compare(0, "vlan")) { parse_vlan(); } else if (cmd_compare(0, "isolate")) { diff --git a/crtbank.asm b/crtbank.asm new file mode 100644 index 0000000..7a00868 --- /dev/null +++ b/crtbank.asm @@ -0,0 +1,29 @@ + .area HOME (CODE) + .area GSINIT0 (CODE) + .area GSINIT1 (CODE) + .area GSINIT2 (CODE) + .area GSINIT3 (CODE) + .area GSINIT4 (CODE) + .area GSINIT5 (CODE) + .area GSINIT (CODE) + .area GSFINAL (CODE) + .area CSEG (CODE) + + + .area HOME (CODE) + +__sdcc_banked_call:: + push _PSBANK + xch a,r0 + push a + mov a,r1 + push a + mov a,r2 + anl a,#0x1f + mov _PSBANK, a + xch a, r0 + ret + +__sdcc_banked_ret:: + pop _PSBANK + ret diff --git a/crtstart.asm b/crtstart.asm deleted file mode 100644 index 23146a7..0000000 --- a/crtstart.asm +++ /dev/null @@ -1,17 +0,0 @@ - .area GSFINAL (CODE) - -__sdcc_banked_call:: - push _PSBANK - xch a,r0 - push a - mov a,r1 - push a - mov a,r2 - anl a,#0x1f - mov _PSBANK, a - xch a, r0 - ret - -__sdcc_banked_ret:: - pop _PSBANK - ret diff --git a/dhcp.c b/dhcp.c index 1884501..ad5c536 100644 --- a/dhcp.c +++ b/dhcp.c @@ -41,6 +41,7 @@ __xdata uip_ipaddr_t server; #define DHCP_REBIND_LEN 4 #define DHCP_CLIENT_ID 61 #define DHCP_CLIENT_ID_LEN 7 +#define DHCP_HOSTNAME 12 #define DHCP_REQUEST_IP 50 #define DHCP_REQUEST_IP_LEN 4 #define DHCP_PARAMS 55 @@ -116,6 +117,20 @@ void dhcp_addopt_client_id(void) } +void dhcp_addopt_hostname(void) +{ + uint8_t len = 0; + while (hostname[len]) + len++; + if (!len) + return; + DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_HOSTNAME; + DHCP_OPT[dhcp_state.opt_ptr++] = len; + memcpy(&DHCP_OPT[dhcp_state.opt_ptr], hostname, len); + dhcp_state.opt_ptr += len; +} + + void dhcp_addopt_request_ip(void) { DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_REQUEST_IP; @@ -152,6 +167,7 @@ void dhcp_send_discover(void) dhcp_addopt_client_id(); dhcp_addopt_request_ip(); + dhcp_addopt_hostname(); DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS; DHCP_OPT[dhcp_state.opt_ptr++] = 3; @@ -188,6 +204,7 @@ void dhcp_send_request(void) dhcp_addopt_client_id(); dhcp_addopt_request_ip(); dhcp_addopt_server_id(); + dhcp_addopt_hostname(); DHCP_OPT[dhcp_state.opt_ptr++] = DHCP_PARAMS; DHCP_OPT[dhcp_state.opt_ptr++] = 3; diff --git a/doc/devices/2M-PCB43-V1.1.md b/doc/devices/2M-PCB43-V1.1.md new file mode 100644 index 0000000..97343f0 --- /dev/null +++ b/doc/devices/2M-PCB43-V1.1.md @@ -0,0 +1,55 @@ +# 2G040210GSM + +The following is a documentation for the managed switch marked as `2G040210GSM` +and sold by Mokerlink. + +### Label specifications + +- **Name**: 4-port 2.5G Web Managed Switch +- **Ports**: + - 4 × RJ45: 10/100/1000/2500 Mbps + - 2 × SFP+: 1000 / 2500 / 10000 Mbps +- **Power**: 12V DC, 1A barrel connector + +### What works +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_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 +Front + + + +Label + + + +### PCB overview + +**Board markings** +- Top silkscreen: 2M-PCB43-V1.1 + +Top side + + + +Bottom + + + +### J1, serial console + +| `J8` pin | Signal | +| -------- | ----------- | +| 1 | RX (Input) | +| 2 | TX (Output) | +| 3 | GND | +| 4 | 3V3 | + + +## Power supply + +Input power is delivered via barell plug, `12V 1A` adapter was provided. diff --git a/doc/devices/FG-4GT-2SX_V2.0.md b/doc/devices/FG-4GT-2SX_V2.0.md new file mode 100644 index 0000000..110bb67 --- /dev/null +++ b/doc/devices/FG-4GT-2SX_V2.0.md @@ -0,0 +1,39 @@ +# FG-4GT-2SX_V2.0 + +Following is documentation for unmanaged switch marked as `FG-4GT-2SX_V2.0`. + +Original software is running UART on 9600 baud rate. + +## Brands + +* Ruiying RY-4GT-2SX + + + +## 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 + +## PCB overview + +**Board markings** + +- Top silkscreen: FG-4GT-2SX_V2.0 + +Front panel + + + +Top side + + + +Bottom + + + +## Power supply + +Input power is delivered via barell plug, `12V 1A` adapter was provided. diff --git a/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md b/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md new file mode 100644 index 0000000..f1fcc53 --- /dev/null +++ b/doc/devices/SWTG024AS-A-V2.0.1_4C_2SFP.md @@ -0,0 +1,43 @@ +### SWTG024AS-A-V2.0.1_4C_2SFP +It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port. + +## Brands +|Brand|Type|Managed|PCB|Flash|Chip RTL| +|---|---|---|---|---|---| +| Horaco | ZX-SG4T2 | | PCB-SWTG024AS-A-V2.0.1_19650 | P25D40SH | ??? | + +### Label specifications + +- **Name**: +- **Ports**: + - 4 × RJ45: 10/100/1000/2500 Mbps + - 2 × SFP+: 1000 / 2500 / 10000 Mbps + + + +### What works +The device is fully supported: +- ALL 2.5GBASE-T RJ45 ports work at 10/100/1000/2500 Mbps +- The SFP+ port supports 1G, 2.5G and 10G modules + +### PCB overview + +**Board markings** +- Top silkscreen: PCB-SWTG024AS-A-V2.0.1_19650 + +Top side + + + +Bottom + + + +### J1, serial console + +| `J1` pin | Signal | +| -------- | ----------- | +| 1 | 3V3 | +| 2 | GND | +| 3 | RX (Input) | +| 4 | TX (Output) | diff --git a/doc/devices/SWTG024AS-A-V2.0.1_5C_1SFP.md b/doc/devices/SWTG024AS-A-V2.0.1_5C_1SFP.md new file mode 100644 index 0000000..400a769 --- /dev/null +++ b/doc/devices/SWTG024AS-A-V2.0.1_5C_1SFP.md @@ -0,0 +1,44 @@ +### SWTG024AS-A-V2.0.1_5C_1SFP +It is highly similar to SWTG024AS-V2.0, with the only difference being the GPIO configuration for the SFP port. + +## Brands +|Brand|Type|Managed|PCB|Flash|Chip RTL| +|---|---|---|---|---|---| +| Horaco | HC-SWTGW215AS | | PCB-SWTG024AS-A-V2.0.1_19650 | W25Q16JV | 8272N | + +### Label specifications + +- **Name**: +- **Ports**: + - 5 × RJ45: 10/100/1000/2500 Mbps + - 1 × SFP+: 1000 / 2500 / 10000 Mbps + + + +### What works +The device is fully supported: +- ALL 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 + +### PCB overview + +**Board markings** +- Top silkscreen: PCB-SWTG024AS-A-V2.0.1_19650 + +Top side + + + +Bottom + + + +### J1, serial console + +| `J1` pin | Signal | +| -------- | ----------- | +| 1 | 3V3 | +| 2 | GND | +| 3 | RX (Input) | +| 4 | TX (Output) | diff --git a/doc/devices/SWTG024AS.md b/doc/devices/SWTG024AS.md index c893429..6da9c5c 100644 --- a/doc/devices/SWTG024AS.md +++ b/doc/devices/SWTG024AS.md @@ -9,8 +9,8 @@ Also the RJ45 connectors can be all plastic/non-shielded or with metal shielding |Brand|Type|Managed|PCB|PCB Label|Flash|Chip RTL| |---|---|---|---|---|---|---| | LIANGUO |SWTG024AS |No| SWTG024AS-v2.0-17452 | CM-23-11-2336 023-17453| 512 KiB | 8272 | -| Haraco |ZX-SWTG124AS | Yes | SWTG024AS-v2.0 | ??? | ??? | 8272 | -| Xikestore |SKS3200M-4GPY2XF | Yes | SWTG024AS-v1.0 | CM-23-08-2043 023-16721 | ??? | 8272 | +| Horaco |ZX-SWTG124AS | Yes | SWTG024AS-v2.0 | ??? | ??? | 8272 | +| Xikestore |SKS3200M-4GPY2XF | Yes | SWTG024AS-v1.0 | CM-23-08-2043 023-16721 | 2048 KiB | 8272 | | Sodola | SL-SWTG124AS-D | Yes | SWTG024AS-v2.0-17452 | ??? | 2048 KiB | 8272 | ## PCB diff --git a/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-bottom.jpeg b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-bottom.jpeg new file mode 100644 index 0000000..63d90f8 Binary files /dev/null and b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-bottom.jpeg differ diff --git a/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-front.jpeg b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-front.jpeg new file mode 100644 index 0000000..5c3910a Binary files /dev/null and b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-front.jpeg differ diff --git a/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-label.jpeg b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-label.jpeg new file mode 100644 index 0000000..d9e621d Binary files /dev/null and b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-label.jpeg differ diff --git a/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-top.jpeg b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-top.jpeg new file mode 100644 index 0000000..78d8b8e Binary files /dev/null and b/doc/devices/photos/2M-PCB43-V1.1-managed/2M-PCB43-V1.1-top.jpeg differ diff --git a/doc/devices/photos/FG-4GT-2SX_V2.0/PCB-bottom.jpg b/doc/devices/photos/FG-4GT-2SX_V2.0/PCB-bottom.jpg new file mode 100644 index 0000000..7c4c4b5 Binary files /dev/null and b/doc/devices/photos/FG-4GT-2SX_V2.0/PCB-bottom.jpg differ diff --git a/doc/devices/photos/FG-4GT-2SX_V2.0/PCB-top.jpg b/doc/devices/photos/FG-4GT-2SX_V2.0/PCB-top.jpg new file mode 100644 index 0000000..f24a90b Binary files /dev/null and b/doc/devices/photos/FG-4GT-2SX_V2.0/PCB-top.jpg differ diff --git a/doc/devices/photos/FG-4GT-2SX_V2.0/RY-4GT-2SX_label.jpg b/doc/devices/photos/FG-4GT-2SX_V2.0/RY-4GT-2SX_label.jpg new file mode 100644 index 0000000..62f7b59 Binary files /dev/null and b/doc/devices/photos/FG-4GT-2SX_V2.0/RY-4GT-2SX_label.jpg differ diff --git a/doc/devices/photos/FG-4GT-2SX_V2.0/chassis-front.jpg b/doc/devices/photos/FG-4GT-2SX_V2.0/chassis-front.jpg new file mode 100644 index 0000000..73229e1 Binary files /dev/null and b/doc/devices/photos/FG-4GT-2SX_V2.0/chassis-front.jpg differ diff --git a/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-label.jpg b/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-label.jpg new file mode 100644 index 0000000..09c181e Binary files /dev/null and b/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-label.jpg differ diff --git a/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-bottom.jpg b/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-bottom.jpg new file mode 100644 index 0000000..cf34816 Binary files /dev/null and b/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-bottom.jpg differ diff --git a/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-top.jpg b/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-top.jpg new file mode 100644 index 0000000..d23479f Binary files /dev/null and b/doc/devices/photos/SWTG024AS-A-V2_0_1_19650/horaco-zx-sg4t2-pcb-top.jpg differ diff --git a/doc/link_aggregation.md b/doc/link_aggregation.md index b901d37..6bde787 100644 --- a/doc/link_aggregation.md +++ b/doc/link_aggregation.md @@ -92,7 +92,7 @@ The following shows the network configuration On _both_ switches create a LAG with ports 1 and 2 inside and the default hash algorithm which takes source and destination ports into account, e.g. just use the default: ``` -> lag 0 1 2 +> lag 1 1 2 ``` diff --git a/doc/mods.md b/doc/mods.md index 27dd370..27f74e0 100644 --- a/doc/mods.md +++ b/doc/mods.md @@ -37,12 +37,13 @@ This list is incomplete. | Brand | Partnumber | | ---------- |----------- | | GigaDevice | GD25Q32E | +| Fundan | FM25Q16A | +| Puya | P25D40SH | | Winbond | W25Q16JV | | Winbond | W25Q32FV | | Winbond | W25Q32JV | | Winbond | W25Q16JL | | Winbond | W25Q16DV | | Winbond | W25Q80DV | -| Fundan | FM25Q16A | *NOTE*: Part numbers are incomplete. Part numbers may contain additional information such as package, temperature specifications, and even the number of devices on a reel. So always check the datasheet so that you have the right orderable partnumber. diff --git a/doc/support-multi-language.md b/doc/support-multi-language.md new file mode 100644 index 0000000..e5c5382 --- /dev/null +++ b/doc/support-multi-language.md @@ -0,0 +1,135 @@ +# Supporting Multiple Languages in the Web UI + +The firmware uses a client-side i18n approach +all translations are stored in a single JavaScript dictionary embedded in the firmware. +No server-side changes are needed. + +## Architecture + +All translation logic lives in `html/i18n.js`. The file contains: + +- A `LANG` object with one sub-object per language (`en`, `ja`, ...) +- Language auto-detection (browser language → `localStorage` override) +- `t(key)` — look up a translated string +- `setLang(lang)` — switch language and update the page +- `applyTranslation(el)` — apply translation to one DOM element + +Translation keys are **flat strings** (no nesting). The English keys in `LANG.en` also serve as the fallback when a key is missing in another language. + +## How to Add a New Language + +### 1. Add a dictionary entry in `html/i18n.js` + +Append a new sub-object to the `LANG` object. Every key from `LANG.en` must be present: + +```js +var LANG = { + en: { + nav_overview: 'Overview', + nav_port_config: 'Port Configuration', + // ... all keys for English + }, + ja: { + nav_overview: '概要', + nav_port_config: 'ポート設定', + // ... all keys for Japanese + }, + LANGCODE: { // ← add your language here + nav_overview: '...', + nav_port_config: '...', + // ... translate every key + }, +}; +``` + +### 2. Add the language to the navigation sidebar + +In `html/navigation.js`, add an `" +``` + +Replace with: + +```js ++ "" +``` + +### 3. Verify auto-detection + +The language detection code in `i18n.js` reads `navigator.language` and normalises it to the first two characters: + +```js +var browser = (navigator.language || navigator.userLanguage || 'en').substring(0, 2); +return LANG[browser] ? browser : 'en'; +``` + +If the two-letter code matches a key in `LANG`, it will be auto-selected. No changes needed here. + +## Two Translation Mechanisms + +### (A) `data-i18n` attribute (declarative — for HTML) + +Add `data-i18n="key_name"` to any HTML element. The English text goes in the element content as a fallback: + +```html +

Port Configuration

+ + +Port Configuration +``` + +On page load, `applyTranslation()` sets: + +- `el.value` for `` +- `el.textContent` for `