From 7a7ffd3bf86873c4bcb3d7801890d3d8ad7449a1 Mon Sep 17 00:00:00 2001 From: bennydiamond Date: Sun, 7 Jun 2026 14:52:56 -0400 Subject: [PATCH 1/8] Automatically print newline on serial interface For some print_string --- rtl837x_common.h | 1 + rtlplayground.c | 6 ++++++ syslog.c | 10 +++++----- uip/uip.c | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/rtl837x_common.h b/rtl837x_common.h index 6d6abb5..c3c6d13 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -120,6 +120,7 @@ extern __xdata struct uip_eth_addr uip_ethaddr; // Headers for calls in the common code area (HOME/BANK0) void print_string_no_syslog(__code char *p); +void print_string_newline_no_syslog(__code char *p); void print_string(__code char *p); void print_string_x(__xdata char *p); void print_long(uint32_t a); diff --git a/rtlplayground.c b/rtlplayground.c index 0f79a69..f1d943c 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -271,6 +271,12 @@ void print_string_no_syslog(__code char *p) write_char_no_syslog(*p++); } +void print_string_newline_no_syslog(__code char *p) +{ + write_char_no_syslog('\n'); + print_string_no_syslog(p); +} + void print_string_x(__xdata char *p) { while (*p) diff --git a/syslog.c b/syslog.c index ad97fd2..989e8cc 100644 --- a/syslog.c +++ b/syslog.c @@ -30,16 +30,16 @@ void syslog_start(void) __banked uip_ipaddr(server_ip, state.server_ip[0], state.server_ip[1], state.server_ip[2], state.server_ip[3]); state.syslog_conn = uip_udp_new(&server_ip, HTONS(514)); if (state.syslog_conn == 0) { - print_string_no_syslog("Failed to create a new UDP client\n"); + print_string_newline_no_syslog("Failed to create a new UDP client"); return; } - print_string_no_syslog("Started syslog to IP "); + print_string_newline_no_syslog("Started syslog to IP "); itoa(state.server_ip[0]); write_char('.'); itoa(state.server_ip[1]); write_char('.'); itoa(state.server_ip[2]); write_char('.'); itoa(state.server_ip[3]); write_char('\n'); state.enabled = 1; } else { - print_string_no_syslog("Syslog is already running\n"); + print_string_newline_no_syslog("Syslog is already running"); } } @@ -49,9 +49,9 @@ void syslog_stop(void) __banked if (state.syslog_conn != 0) { uip_udp_remove(state.syslog_conn); state.syslog_conn = 0; - print_string_no_syslog("Stopped syslog\n"); + print_string_newline_no_syslog("Stopped syslog"); } else { - print_string_no_syslog("Syslog is not running\n"); + print_string_newline_no_syslog("Syslog is not running"); } } diff --git a/uip/uip.c b/uip/uip.c index ee07a04..6a194c1 100644 --- a/uip/uip.c +++ b/uip/uip.c @@ -234,7 +234,7 @@ __xdata struct uip_stats uip_stat; #endif /* UIP_STATISTICS == 1 */ #if UIP_LOGGING == 1 -#define UIP_LOG(m) print_string_no_syslog(m); +#define UIP_LOG(m) print_string_newline_no_syslog(m); #else #define UIP_LOG(m) #endif /* UIP_LOGGING == 1 */ From 0998b7381fd8eeb0e69ec969c06f2c3a33bc8fcc Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Thu, 13 Aug 2026 15:58:55 -0500 Subject: [PATCH 2/8] machine: add PCB SWTG018AS-V2.1.0 variant of the SWTGW218AS The SWTGW218AS label covers more than one PCB. On boards with the SWTG018AS-V2.1.0 silkscreen the SFP module-detect is GPIO38 (no LOS pin wired) and the LED block is wired differently, including a bi-color SFP LED (green up to 2.5G, blue at 10G) that the LED-set encoding cannot express. With the existing MACHINE_SWTGW218AS definition these boards never detect an SFP module, so an SFP-uplinked switch comes up with no working uplink. Add the variant as its own machine define named by the PCB marking, leaving MACHINE_SWTGW218AS unchanged for boards that match its wiring. The LED register values are taken from the stock firmware; the PIN_MUX_0 write routes the blue-LED pin to the LED controller, without which no LED register value can drive it. PIN_MUX_1/2 stay untouched so SFP detect and i2c remain GPIOs. --- machine.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ machine.h | 1 + 2 files changed, 82 insertions(+) diff --git a/machine.c b/machine.c index c27b92a..d7d4823 100644 --- a/machine.c +++ b/machine.c @@ -444,6 +444,87 @@ __code const struct machine machine = { }; void machine_custom_init(void) { } + +#elif defined MACHINE_PCB_SWTG018AS_V2_1_0 // Sold as Sodola SL902 / Horaco "SWTGW218AS"; the SWTGW218AS label also covers other PCBs with different SFP and LED wiring (see MACHINE_SWTGW218AS) + +__code const struct machine machine = { + .machine_name = "SWTGW218AS (SWTG018AS-V2.1.0)", + .isRTL8373 = 1, + .mac_flash_offset = 0x1FC000, + .min_port = 0, + .max_port = 8, + .n_sfp = 1, + .log_to_phys_port = {1, 2, 3, 4, 5, 6, 7, 8, 9}, + .phys_to_log_port = {0, 1, 2, 3, 4, 5, 6, 7, 8}, + .is_sfp = {0, 0, 0, 0, 0, 0, 0, 0, 1}, + .sfp_port[0].pin_detect = GPIO38, // pulled low on module insert + .sfp_port[0].pin_los = GPIO_NA, // no LOS pin wired + .sfp_port[0].pin_tx_disable = GPIO_NA, + .sfp_port[0].sds = 1, + .sfp_port[0].i2c = { .sda = GPIO39_I2C_SDA4, .scl = GPIO40_I2C_SCL3_MDC1 }, + .reset_pin = GPIO54_ACL_BIT2_EN, + .high_leds = { .mux = LED_27 | LED_28_SYS | LED_29, .enable = LED_28_SYS | LED_29 }, + .port_led_set = { 0, 0, 0, 0, 0, 0, 0, 0, 1}, + // LED wiring matches the SWTG018AS-A V2.0 (same PCB family) + .led_sets = { + { /* RJ45: First LED, yellow, second LED: green */ + LEDS_2G5 | LEDS_LINK, + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT, + 0, + 0, + }, { /* SFP set (superseded by the raw register override in machine_custom_init) */ + LEDS_2G5 | LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT | LEDS_10G, + 0, + 0, + 0, + }}, + .led_mux_custom = 1, + .led_mux = { 0x00, 0x01, 0x04, 0x05, 0x08, // 65e0 + 0x09, 0x0c, 0x09, 0x0d, 0x10, // 65e4 + 0x11, 0x0e, 0x14, 0x11, 0x12, // 65e8 + 0x15, 0x15, 0x16, 0x18, 0x19, // 65ec + 0x1a, 0x19, 0x1d, 0x1e, 0x1c, // 65f0 + 0x1d, 0x20, 0x21 }, +}; + +// The LED-set encoding cannot express this board's bi-color SFP LED (green <= 2.5G, +// blue at 10G), so program the LED register block with the values the stock firmware +// uses. Runs after leds_setup() and overrides the values computed there. +// The final entry routes the blue-LED pin to the LED controller via PIN_MUX_0; +// as a GPIO (the default) no LED register can light it. PIN_MUX_1/2 stay +// untouched so SFP detect (GPIO38) and i2c remain GPIOs. +static __code const struct { uint16_t reg; uint32_t val; } custom_init_regs[] = { + { 0x6520, 0x0023e430UL }, // LED_MODE + { 0x6524, 0xff001400UL }, // LED3_0_SET3 + { 0x6528, 0x00100000UL }, // LED3_0_SET1 + { 0x652c, 0x007f013fUL }, // LED3_2_SET3 + { 0x6530, 0x02000400UL }, // LED1_0_SET3 + { 0x6534, 0x01400141UL }, // LED3_2_SET2 + { 0x6538, 0x01440170UL }, // LED1_0_SET2 + { 0x653c, 0x18000041UL }, // LED3_2_SET1 + { 0x6540, 0x01400155UL }, // LED1_0_SET1 + { 0x6544, 0x01411000UL }, // LED3_2_SET0 + { 0x6548, 0x01740141UL }, // LED1_0_SET0 + { 0x654c, 0x00010000UL }, // LED_PORT_SET_SEL + { 0x65d8, 0x3ffb6dffUL }, // LED_GLB_ACTIVE + { 0x65dc, 0x7f24977fUL }, // LED_GLB_IO_EN + { 0x65e0, 0x08144040UL }, // LED_GLB_MUX_1 + { 0x65e4, 0x10349309UL }, // LED_GLB_MUX_2 + { 0x65e8, 0x12454391UL }, // LED_GLB_MUX_3 + { 0x65ec, 0x19616555UL }, // LED_GLB_MUX_4 + { 0x65f0, 0x1c79d65aUL }, // LED_GLB_MUX_5 + { 0x65f4, 0x0002181dUL }, // LED_GLB_MUX_6 + { 0x7f8c, 0x20db6880UL }, // PIN_MUX_0 +}; + +void machine_custom_init(void) { + uint8_t i; + // REG_SET is a multi-statement macro without a do-while wrapper: braces required + for (i = 0; i < sizeof(custom_init_regs) / sizeof(custom_init_regs[0]); i++) { + REG_SET(custom_init_regs[i].reg, custom_init_regs[i].val); + } +} + #elif defined MACHINE_LIANGUO_ZX_SWTGW215AS // Has PCB branded PCB-SWTG115AS-V2.0 but is labeled and reports as a ZX-SWTGW215AS, seems to be identical to the "real" ZX-SWTGW215AS except for the LEDs __code const struct machine machine = { .machine_name = "Lianguo ZX-SWTGW215AS", diff --git a/machine.h b/machine.h index 523beea..29fe39f 100644 --- a/machine.h +++ b/machine.h @@ -18,6 +18,7 @@ // #define MACHINE_HG0402XG_V1_1 // #define MACHINE_SWTG018AS_A_V_2_0 // #define MACHINE_SWTGW218AS +// #define MACHINE_PCB_SWTG018AS_V2_1_0 // #define MACHINE_PCB_K0402WS_V3 // #define MACHINE_K0501W_V2_0 // #define MACHINE_LIANGUO_ZX_SWTGW215AS From 1d1e33f4d5008d645df4b5906beff7a61f66a193 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:25:51 +0200 Subject: [PATCH 3/8] sfp: notice when an I2C read fails sfp_read_reg() waited for the transfer to finish and then read the output register whatever the outcome, so an address nothing acknowledged came back as an ordinary byte and no caller could tell it apart from data. The vendor SDK looks at bit 1 of the control register for exactly this, and we did not. A failure now sets sfp_i2c_fail and the read returns 0xff, which is already the value sfp_apply_quirks() reads as either a failed transfer or a voltage the spec does not allow, so that test starts being true when it should be. The insertion path and the sfp command clear the flag first and say so afterwards, rather than presenting the bytes as though they came from the module. What this deliberately does not do is act on the failure. Skipping sds_config() when the rate read failed is the obvious next step, but a module that raises the bit spuriously would then never be configured at all, which is worse than what happens today, and I have no way to judge how often the bit is right. That decision belongs with someone holding the board. It also leaves the other half of the rewrite alone, reading and writing up to sixteen bytes per transaction. doc/sfp.md describes only the single byte path and does not name a length field, and guessing at a register I cannot test is how the last attempt at this function went wrong. 40 bytes of the common segment, 51 of BANK2 and 1 of xdata, nothing in BANK1 or internal RAM. Built for SWTGW218AS and KP_9000_6XHML_X2 on sdcc 4.5.0. Not tested on hardware: shorting the clock line, as in #342, should now print the failure line instead of a plausible looking byte. --- cmd_parser.c | 3 +++ rtl837x_common.h | 1 + rtlplayground.c | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/cmd_parser.c b/cmd_parser.c index 9e2be41..2b13edd 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -777,11 +777,14 @@ void parse_sfp(void) print_string(" - empty\n"); continue; } + sfp_i2c_fail = 0; print_string(" - Rate: "); print_byte(sfp_read_reg(slot, 12)); print_string(" Encoding: "); print_byte(sfp_read_reg(slot, 11)); write_char('\n'); sfp_print_info(slot); sfp_print_measurements(slot); + if (sfp_i2c_fail) + print_string("I2C read failed on this slot\n"); } return; } diff --git a/rtl837x_common.h b/rtl837x_common.h index 66df88a..98202b8 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -150,6 +150,7 @@ void write_char_no_syslog(char c); void write_char(char c); void print_reg(uint16_t reg); uint8_t sfp_read_reg(uint8_t slot, uint8_t reg); +extern __xdata uint8_t sfp_i2c_fail; void reg_bit_set(uint16_t reg_addr, char bit); void reg_bit_clear(uint16_t reg_addr, char bit); uint8_t reg_bit_test(uint16_t reg_addr, char bit); diff --git a/rtlplayground.c b/rtlplayground.c index bdb8ab3..f8cd721 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -139,6 +139,7 @@ __xdata char sfp_module_vendor[2][17]; __xdata char sfp_module_model[2][17]; __xdata char sfp_module_serial[2][17]; __xdata uint8_t sfp_options[2]; +__xdata uint8_t sfp_i2c_fail; /* set by sfp_read_reg() when the controller flags a failed transfer */ __xdata uint8_t sfp_speed[2]; __xdata uint8_t sfp_quirks[2]; __xdata bool button_last; @@ -1061,6 +1062,14 @@ uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) reg_read_m(RTL837X_REG_I2C_CTRL); } while (sfr_data[3] & 0x1); + /* Bit 1 is the controller's own failure indication, which the vendor SDK + * looks at and this did not. Without it an unacknowledged address comes + * back as an ordinary byte and the caller cannot tell it from data. */ + if (sfr_data[3] & 0x2) { + sfp_i2c_fail = 1; + return 0xff; + } + reg_read_m(RTL837X_REG_I2C_OUT); return sfr_data[3]; } @@ -1300,6 +1309,7 @@ void handle_sfp(void) // Read Reg 11: Encoding, see SFF-8472 and SFF-8024 // Read Reg 12: Signalling rate (including overhead) in 100Mbit: 0xd: 1Gbit, 0x67:10Gbit delay(100); // Delay, because some modules need time to wake up + sfp_i2c_fail = 0; uint8_t rate = sfp_read_reg(sfp, 12); if (sfp_speed[sfp] == SFP_SPEED_100M) rate = 0x1; @@ -1316,6 +1326,8 @@ void handle_sfp(void) sfp_options[sfp] = sfp_read_reg(sfp, 92); sfp_get_info(sfp); sfp_apply_quirks(sfp); + if (sfp_i2c_fail) + print_string("SFP: an I2C read failed, the module data above may be wrong\n"); sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate)); } } else { From 772e9dc52608a22eb8cad94abf8205724356b404 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Tue, 18 Aug 2026 14:14:14 -0500 Subject: [PATCH 4/8] httpd: parse a configuration upload once the whole request has arrived Saving the configuration works in Chrome and fails in Firefox, and the difference is only how each browser splits the request. The handler erased the config sector as soon as the request started and then parsed the multipart body one TCP segment at a time, which requires every boundary and every part header to fall inside a single segment. Firefox splits inside a part header, so the parser lost its place and the erased sector was left holding a truncated body or nothing at all. A single-burst scripted post lost the whole body the same way. The configuration is limited to two kilobytes, so the whole request body now accumulates in xdata and is parsed only after the closing boundary has arrived. The parts are walked in one pass, the part carrying a filename is written to a freshly erased sector, and the client receives a 200 instead of the previous silent close. No segmentation can confuse this, since the parser only ever sees a complete body. Locating the closing boundary first also bounds the walk over the parts, since none can lie beyond it, so the length of the buffer is no longer the bound and the test for the two trailing dashes is unnecessary. The walk matches at offsets inside a buffer that is not terminated, so neither existing helper fits: strcmp() goes on to compare the byte after the match and is_word_x() demands a separator there. Add strstart() and strstart_x() for that case, and use strlen_x() for the boundary length. The firmware upload path still streams, since a megabyte cannot be buffered, and is untouched. --- httpd/httpd.c | 100 ++++++++++++++++++++++++++++++++++++++++++++--- rtl837x_common.h | 2 + rtlplayground.c | 26 ++++++++++++ 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index d856ccd..3a54aca 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -41,6 +41,14 @@ __xdata uint32_t cont_addr; // HTTP header properties __xdata uint8_t boundary[72]; + +// a client may split the request anywhere, including inside a boundary or a +// part header, so a configuration upload is parsed only once it is complete +#define CONFIG_UPLOAD_BUF 2560 +__xdata uint8_t config_upload; +__xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; +__xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; +__xdata uint8_t cfg_bl; __xdata uint8_t *content_type = 0; __xdata uint8_t *session = 0; @@ -77,6 +85,7 @@ inline uint8_t is_separator(uint8_t c) void httpd_init(void) __banked { + config_upload = 0; // xdata is not zeroed by the startup code __xdata struct httpd_state * __xdata s = &(uip_conn->appstate); // Start listening to port 80 uip_listen(HTONS(80)); @@ -315,6 +324,62 @@ void gen_random_bytes(__xdata uint8_t *b, uint8_t bytes) } +/* 0: body incomplete, 1: configuration stored, 2: malformed */ +static uint8_t config_take(void) +{ + cfg_bl = strlen_x(boundary); + + // the body is complete once the closing boundary has arrived + cfg_last = 0; + while (1) { + if (cfg_last + cfg_bl + 1 >= write_len) + return 0; + if (strstart_x(&config_buf[cfg_last], boundary) + && strstart(&config_buf[cfg_last + cfg_bl], "--")) + break; + cfg_last++; + } + + // every part lies ahead of the closing boundary, so it bounds the walk + cfg_pos = 0; + while (cfg_pos < cfg_last) { + if (!strstart_x(&config_buf[cfg_pos], boundary)) { + cfg_pos++; + continue; + } + cfg_hdr = cfg_pos + cfg_bl; + cfg_body = cfg_hdr; + while (1) { + if (cfg_body + 3 >= cfg_last) + return 2; + if (strstart(&config_buf[cfg_body], "\r\n\r\n")) + break; + cfg_body++; + } + cfg_end = cfg_body; + cfg_body += 4; + // reaching cfg_last is a match: the last part ends at the closing boundary + while (cfg_end < cfg_last && !strstart_x(&config_buf[cfg_end], boundary)) + cfg_end++; + while (cfg_hdr + 8 < cfg_body) { + // the part carrying a filename holds the configuration + if (strstart(&config_buf[cfg_hdr], "filename")) { + config_buf[cfg_end] = 0; + flash_region.addr = CONFIG_START; + flash_sector_erase(); + flash_region.addr = CONFIG_START; + flash_region.len = cfg_end - cfg_body + 1; + flash_write_bytes(config_buf + cfg_body); + return 1; + } + cfg_hdr++; + } + cfg_pos = cfg_end; + } + return 2; +} + + /* * Reads post data from the http stream and writes it into flash memory * Input: the current position in the TCP buffer (uip_appdata) @@ -437,6 +502,7 @@ void handle_post(void) return; } print_string("Firmware upload started."); + config_upload = 0; uptr = FIRMWARE_UPLOAD_START; verify_crc = 1; max_upload = 1024576; @@ -445,12 +511,10 @@ void handle_post(void) send_unauthorized(); return; } - dbg_string("Configuration upload, erasing config mem!\n"); - uptr = CONFIG_START; + dbg_string("Configuration upload\n"); verify_crc = 0; - max_upload = 2048; - flash_region.addr = CONFIG_START; - flash_sector_erase(); + config_upload = 1; + write_len = 0; } // Check for other POST requests, which are not multipart, below } else { @@ -504,6 +568,32 @@ void handle_post(void) send_bad_request(); return; } + if (config_upload) { + cfg_pos = uip_len - (p - uip_appdata); + if (write_len + cfg_pos >= CONFIG_UPLOAD_BUF) { + print_string("Configuration too large, aborting.\n"); + config_upload = 0; + s->tstate = TSTATE_NONE; + send_bad_request(); + return; + } + memcpy(config_buf + write_len, p, cfg_pos); + write_len += cfg_pos; + uint8_t taken = config_take(); + + if (!taken) { + s->tstate = TSTATE_MULTIPART; + return; + } + config_upload = 0; + s->tstate = TSTATE_NONE; + if (taken == 2) { + send_bad_request(); + return; + } + slen = strtox(outbuf, "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"); + return; + } // We skip the intial parts as part of the header do { p = skip_boundary(p); diff --git a/rtl837x_common.h b/rtl837x_common.h index 66df88a..44e06d0 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -164,6 +164,8 @@ uint16_t strlen_x(register __xdata const char *s); uint16_t strtox(register __xdata uint8_t *dst, register __code const char *s); uint16_t strcpy(register __xdata uint8_t *dst, register const char *s); char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b); +bool strstart(__xdata const uint8_t *a, __code const uint8_t *b); +bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b); void tcpip_output(void); uint8_t read_flash(uint8_t bank, __code uint8_t *addr); void get_random_32(void); diff --git a/rtlplayground.c b/rtlplayground.c index b9cc05d..e08c09a 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -360,6 +360,32 @@ char strcmp(register __xdata const uint8_t *a, register __code const uint8_t *b) } +/* + * True when b is a prefix of a. Unlike strcmp() the byte after the match is not + * compared, and unlike is_word_x() it need not be a separator. + */ +bool strstart(__xdata const uint8_t *a, __code const uint8_t *b) +{ + uint8_t i = 0; + + while (b[i] && (b[i] == a[i])) + i++; + + return !b[i]; +} + + +bool strstart_x(__xdata const uint8_t *a, __xdata const uint8_t *b) +{ + uint8_t i = 0; + + while (b[i] && (b[i] == a[i])) + i++; + + return !b[i]; +} + + void print_short(uint16_t a) { // allocating the registers first improves the sdcc code here From 9a96c80af230ac825942eefcb6a527e25f89139b Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:34:39 +0200 Subject: [PATCH 5/8] sfp: move the I2C transfer to the banked pins module sfp_read_reg() sat in rtlplayground.c, so it occupied the common 16 KB window that every bank shares, even though nothing outside the SFP paths calls it. That window is the tightest resource in the image: SWTG024AS_V2_0 and SWTG024AS_A_2_0_1_5C_1SFP had 54 bytes left in it. rtl837x_pins.c is already in BANK2 and already holds the I2C bus helpers this function calls, so the transfer belongs there. The function moves verbatim and becomes __banked; the prototype in rtl837x_common.h says so, which is what keeps the callers in BANK1 and BANK2 honest. No behaviour change. The common segment gains 200 bytes on every machine: 159 to 359 free on SWTGW218AS, 54 to 254 on the two variants above. BANK1 +6 bytes, BANK2 +336. Built on sdcc 4.5.0. --- rtl837x_common.h | 2 +- rtl837x_pins.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ rtlplayground.c | 39 --------------------------------------- 3 files changed, 45 insertions(+), 40 deletions(-) diff --git a/rtl837x_common.h b/rtl837x_common.h index 98202b8..5432343 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -149,7 +149,7 @@ void sleep(uint16_t t); void write_char_no_syslog(char c); void write_char(char c); void print_reg(uint16_t reg); -uint8_t sfp_read_reg(uint8_t slot, uint8_t reg); +uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) __banked; extern __xdata uint8_t sfp_i2c_fail; void reg_bit_set(uint16_t reg_addr, char bit); void reg_bit_clear(uint16_t reg_addr, char bit); diff --git a/rtl837x_pins.c b/rtl837x_pins.c index 90015b7..ac0cea3 100644 --- a/rtl837x_pins.c +++ b/rtl837x_pins.c @@ -1,6 +1,11 @@ #include "rtl837x_pins.h" #include "rtl837x_common.h" +#include "rtl837x_sfr.h" #include "rtl837x_regs.h" +#include "machine.h" + +extern __code const struct machine machine; +extern __xdata uint8_t sfr_data[4]; #pragma codeseg BANK2 #pragma constseg BANK2 @@ -121,3 +126,42 @@ void gpio_output_setup(uint8_t pin, __xdata uint8_t initial_val) __banked{ reg_bit_set(gpio_direction_reg(pin), (pin % 32)); } + + +/* + * Read a register of the EEPROM via I2C + */ +uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) __banked +{ + if (reg & 0x80) { // Configure SFP readings address (0x51) as I2C device address + reg &= 0x7f; + REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x51 >> 5, (0x51 << 3) & 0xff); + } else { + REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x50 >> 5, (0x50 << 3) & 0xff); + } + + reg_read_m(RTL837X_REG_I2C_CTRL); + sfr_mask_data(1, 0xfc, i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2); + reg_write_m(RTL837X_REG_I2C_CTRL); + + REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); + + // Execute I2C Read + reg_bit_set(RTL837X_REG_I2C_CTRL, 0); + + // Wait for execution to finish + do { + reg_read_m(RTL837X_REG_I2C_CTRL); + } while (sfr_data[3] & 0x1); + + /* Bit 1 is the controller's own failure indication, which the vendor SDK + * looks at and this did not. Without it an unacknowledged address comes + * back as an ordinary byte and the caller cannot tell it from data. */ + if (sfr_data[3] & 0x2) { + sfp_i2c_fail = 1; + return 0xff; + } + + reg_read_m(RTL837X_REG_I2C_OUT); + return sfr_data[3]; +} diff --git a/rtlplayground.c b/rtlplayground.c index f8cd721..6926ae5 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -1036,45 +1036,6 @@ void sds_config(uint8_t sds, uint8_t mode) } -/* - * Read a register of the EEPROM via I2C - */ -uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) -{ - if (reg & 0x80) { // Configure SFP readings address (0x51) as I2C device address - reg &= 0x7f; - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x51 >> 5, (0x51 << 3) & 0xff); - } else { - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x50 >> 5, (0x50 << 3) & 0xff); - } - - reg_read_m(RTL837X_REG_I2C_CTRL); - sfr_mask_data(1, 0xfc, i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2); - reg_write_m(RTL837X_REG_I2C_CTRL); - - REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); - - // Execute I2C Read - reg_bit_set(RTL837X_REG_I2C_CTRL, 0); - - // Wait for execution to finish - do { - reg_read_m(RTL837X_REG_I2C_CTRL); - } while (sfr_data[3] & 0x1); - - /* Bit 1 is the controller's own failure indication, which the vendor SDK - * looks at and this did not. Without it an unacknowledged address comes - * back as an ordinary byte and the caller cannot tell it from data. */ - if (sfr_data[3] & 0x2) { - sfp_i2c_fail = 1; - return 0xff; - } - - reg_read_m(RTL837X_REG_I2C_OUT); - return sfr_data[3]; -} - - /* * Adds TX Header to uip_buf and calls nic_tx_packet to send the packet * over the wire From e8e7fcbe7fb2a46a5d8dc445a657b8d035e40789 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Mon, 24 Aug 2026 15:45:25 -0500 Subject: [PATCH 6/8] httpd: send the config tail from the config sector, not a bare offset send_config() streams a configuration larger than the TCP output buffer through the cont_addr/cont_len continuation, but set cont_addr to the offset within the config instead of a flash address, so the tail was read from code space. The file server sets cont_addr absolute; do the same by adding CONFIG_START. The bug was unreachable while CONFIG_UPLOAD_BUF capped uploads below TCP_OUTBUF_SIZE, but a stored config near the full sector exposes it. --- httpd/page_impl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpd/page_impl.c b/httpd/page_impl.c index bf3e268..3e2e696 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -822,7 +822,7 @@ found_end: if (valid_len > (TCP_OUTBUF_SIZE - slen)) { cont_len = valid_len - (TCP_OUTBUF_SIZE - slen); valid_len = TCP_OUTBUF_SIZE - slen; - cont_addr = valid_len; + cont_addr = CONFIG_START + valid_len; } flash_region.addr = CONFIG_START; From cefe48fe5125fca1c1712af8c5dd541084fe3231 Mon Sep 17 00:00:00 2001 From: bloqaudio Date: Mon, 24 Aug 2026 15:45:25 -0500 Subject: [PATCH 7/8] httpd: size the config upload buffer for a full config sector CONFIG_UPLOAD_BUF at 2560 capped a configuration upload at about 2.2K while the config sector holds 4K. Size the buffer as CONFIG_LEN plus room for the multipart framing so the whole sector is usable. config_take() wrote the payload without checking it against the sector: safe before only because the buffer could not hold an oversized one. Reject a payload that does not fit CONFIG_LEN, terminator included, instead of writing past the erased sector. --- httpd/httpd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/httpd/httpd.c b/httpd/httpd.c index 3a54aca..ff4eff9 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -43,8 +43,9 @@ __xdata uint32_t cont_addr; __xdata uint8_t boundary[72]; // a client may split the request anywhere, including inside a boundary or a -// part header, so a configuration upload is parsed only once it is complete -#define CONFIG_UPLOAD_BUF 2560 +// part header, so a configuration upload is parsed only once it is complete; +// sized for a full config sector plus the multipart framing around it +#define CONFIG_UPLOAD_BUF (CONFIG_LEN + 384) __xdata uint8_t config_upload; __xdata uint8_t config_buf[CONFIG_UPLOAD_BUF]; __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; @@ -364,6 +365,9 @@ static uint8_t config_take(void) while (cfg_hdr + 8 < cfg_body) { // the part carrying a filename holds the configuration if (strstart(&config_buf[cfg_hdr], "filename")) { + // the payload plus its terminator must fit the sector + if (cfg_end - cfg_body + 1 > CONFIG_LEN) + return 2; config_buf[cfg_end] = 0; flash_region.addr = CONFIG_START; flash_sector_erase(); From a0628c7df5b1ef856402f6c2c83ea7ad48cf07b5 Mon Sep 17 00:00:00 2001 From: d00f <8052722+DrDoof@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:35:02 +0200 Subject: [PATCH 8/8] sfp: read the EEPROM in blocks instead of a byte at a time The I2C controller transfers up to sixteen bytes per transaction and page_impl.c already used that for sfp_send_data(), while sfp_read_reg() asked for one byte and every caller looped. Reading a module therefore cost one address phase per byte: 87 transactions when a module is inserted, 52 for the sfp command, 36 for the vendor block in status.json. sfp_read_block() replaces sfp_read_reg() and the callers that already wanted a run of registers ask for it once: the vendor fields as three 16 byte pages, the diagnostics as one transfer, rate and encoding together. That drops the three paths above to 8, 6 and 3 transactions, and sfp_send_data() loses its copy of the transfer. The vendor loops now run over 16..63 rather than 20..59 so the page base is a multiple of sixteen and the index into the buffer is a single AND. The four extra bytes at each end are read and discarded. The diagnostics read asks for 16 bytes rather than the 15 it uses, because 16 is a width the shipped firmware already exercises and 15 is not. The device address, the bus selection and the start bit go into the control register in one write now that the memory address is written first, so a transfer touches that register once instead of three times. The register reads take their result from the SFRs directly rather than through the sfr_data mirror. The result is a bool and the destination is sfp_buf, so a caller that cares about a failed transfer looks at the return value instead of a flag. Every caller gives up on the first failed read rather than carrying a flag to the end, which is why the module read moved out of handle_sfp into a function of its own. A module whose read fails is left marked as absent, so the next poll retries it instead of configuring the SerDes from bytes that never arrived. BANK1 -194 bytes, BANK2 +382, common segment +44, xdata +15 for the buffer, and one byte more of internal RAM free than before the series. Built for all 25 machine definitions on sdcc 4.5.0; the tightest common segment is 98 bytes free on SWTG024AS_V2_0, against 54 before this series. --- cmd_parser.c | 40 +++++++++++------- httpd/page_impl.c | 36 ++++------------ rtl837x_common.h | 6 +-- rtl837x_pins.c | 62 ++++++++++++++++----------- rtlplayground.c | 104 +++++++++++++++++++++++++++++----------------- 5 files changed, 140 insertions(+), 108 deletions(-) diff --git a/cmd_parser.c b/cmd_parser.c index 2b13edd..d1c23b6 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -748,18 +748,25 @@ void parse_mtu(void) write_char('\n'); } -void sfp_print_measurements(uint8_t sfp) +bool sfp_print_measurements(uint8_t sfp) { - print_string("Options: "); print_byte(sfp_read_reg(sfp, 92)); write_char('\n'); + if (!sfp_read_block(sfp, 92, 1)) + return false; + + print_string("Options: "); print_byte(sfp_buf[0]); write_char('\n'); if (!(sfp_options[sfp] & 0x40)) - return; - print_string("Temp: "); print_byte(sfp_read_reg(sfp, 224)); print_byte(sfp_read_reg(sfp, 225)); write_char('\n'); - print_string("Vcc: "); print_byte(sfp_read_reg(sfp, 226)); print_byte(sfp_read_reg(sfp, 227)); write_char('\n'); - print_string("TX Bias: "); print_byte(sfp_read_reg(sfp, 228)); print_byte(sfp_read_reg(sfp, 229)); write_char('\n'); - print_string("TX Power: "); print_byte(sfp_read_reg(sfp, 230)); print_byte(sfp_read_reg(sfp, 231)); write_char('\n'); - print_string("RX Power: "); print_byte(sfp_read_reg(sfp, 232)); print_byte(sfp_read_reg(sfp, 233)); write_char('\n'); - print_string("Laser: "); print_byte(sfp_read_reg(sfp, 234)); print_byte(sfp_read_reg(sfp, 235)); write_char('\n'); - print_string("State: "); print_byte(sfp_read_reg(sfp, 238)); write_char('\n'); + return true; + if (!sfp_read_block(sfp, 224, 16)) + return false; + print_string("Temp: "); print_byte(sfp_buf[0]); print_byte(sfp_buf[1]); write_char('\n'); + print_string("Vcc: "); print_byte(sfp_buf[2]); print_byte(sfp_buf[3]); write_char('\n'); + print_string("TX Bias: "); print_byte(sfp_buf[4]); print_byte(sfp_buf[5]); write_char('\n'); + print_string("TX Power: "); print_byte(sfp_buf[6]); print_byte(sfp_buf[7]); write_char('\n'); + print_string("RX Power: "); print_byte(sfp_buf[8]); print_byte(sfp_buf[9]); write_char('\n'); + print_string("Laser: "); print_byte(sfp_buf[10]); print_byte(sfp_buf[11]); write_char('\n'); + print_string("State: "); print_byte(sfp_buf[14]); write_char('\n'); + + return true; } @@ -777,13 +784,14 @@ void parse_sfp(void) print_string(" - empty\n"); continue; } - sfp_i2c_fail = 0; - print_string(" - Rate: "); print_byte(sfp_read_reg(slot, 12)); - print_string(" Encoding: "); print_byte(sfp_read_reg(slot, 11)); + if (!sfp_read_block(slot, 11, 2)) { + print_string(" - I2C read failed on this slot\n"); + continue; + } + print_string(" - Rate: "); print_byte(sfp_buf[1]); + print_string(" Encoding: "); print_byte(sfp_buf[0]); write_char('\n'); - sfp_print_info(slot); - sfp_print_measurements(slot); - if (sfp_i2c_fail) + if (!sfp_print_info(slot) || !sfp_print_measurements(slot)) print_string("I2C read failed on this slot\n"); } return; diff --git a/httpd/page_impl.c b/httpd/page_impl.c index 19796ed..4101031 100644 --- a/httpd/page_impl.c +++ b/httpd/page_impl.c @@ -177,10 +177,12 @@ void reg_to_html_long(register uint16_t reg) void send_sfp_info(uint8_t sfp) { // This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields - for (uint8_t i = 20; i < 60; i++) { - if (i >= 36 && i < 40) // Skip Non-ASCII codes + for (uint8_t i = 16; i < 64; i++) { + if (!(i & 0xf)) + sfp_read_block(sfp, i, 16); + if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes continue; - uint8_t c = sfp_read_reg(sfp, i); + uint8_t c = sfp_buf[i & 0xf]; if (c && c != 0xa0) // a0 is the byte read from a non-existant I2C EEPROM char_to_html(c); } @@ -193,32 +195,10 @@ void sfp_send_data(uint8_t slot, uint8_t reg, uint8_t len) if (len > 16) return; - if (reg & 0x80) { // Configure SFP readings address (0x51) as I2C device address - reg &= 0x7f; - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | (len - 1) & 0xf, 0x51 >> 5, (0x51 << 3) & 0xff); - } else { - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | (len - 1) & 0xf, 0x50 >> 5, (0x50 << 3) & 0xff); - } + sfp_read_block(slot, reg, len); - reg_read_m(RTL837X_REG_I2C_CTRL); - sfr_mask_data(1, 0xfc, i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2); - reg_write_m(RTL837X_REG_I2C_CTRL); - - REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); - - // Execute I2C Read - reg_bit_set(RTL837X_REG_I2C_CTRL, 0); - - // Wait for execution to finish - do { - reg_read_m(RTL837X_REG_I2C_CTRL); - } while (sfr_data[3] & 0x1); - - for (uint8_t i = 0; i < len; i++) { - if (!(i & 0x3)) - reg_read_m(RTL837X_REG_I2C_OUT + i); - byte_to_html(sfr_data[3 - (i & 0x3)]); - } + for (uint8_t i = 0; i < len; i++) + byte_to_html(sfp_buf[i]); } diff --git a/rtl837x_common.h b/rtl837x_common.h index 5432343..9fe2c0c 100644 --- a/rtl837x_common.h +++ b/rtl837x_common.h @@ -149,8 +149,8 @@ void sleep(uint16_t t); void write_char_no_syslog(char c); void write_char(char c); void print_reg(uint16_t reg); -uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) __banked; -extern __xdata uint8_t sfp_i2c_fail; +bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) __banked __reentrant; +extern __xdata uint8_t sfp_buf[16]; void reg_bit_set(uint16_t reg_addr, char bit); void reg_bit_clear(uint16_t reg_addr, char bit); uint8_t reg_bit_test(uint16_t reg_addr, char bit); @@ -169,7 +169,7 @@ void tcpip_output(void); uint8_t read_flash(uint8_t bank, __code uint8_t *addr); void get_random_32(void); void read_reg_timer(__xdata uint32_t * tmr); -void sfp_print_info(uint8_t sfp); +bool sfp_print_info(uint8_t sfp); bool gpio_pin_test(uint8_t pin); void set_sys_led_state(uint8_t state); void sds_read(uint8_t sds_id, uint8_t page, uint8_t reg); diff --git a/rtl837x_pins.c b/rtl837x_pins.c index ac0cea3..bee9818 100644 --- a/rtl837x_pins.c +++ b/rtl837x_pins.c @@ -129,39 +129,53 @@ void gpio_output_setup(uint8_t pin, __xdata uint8_t initial_val) __banked{ /* - * Read a register of the EEPROM via I2C + * Read up to 16 consecutive registers of the EEPROM via I2C into sfp_buf */ -uint8_t sfp_read_reg(uint8_t slot, uint8_t reg) __banked +bool sfp_read_block(uint8_t slot, uint8_t reg, uint8_t len) __banked __reentrant { - if (reg & 0x80) { // Configure SFP readings address (0x51) as I2C device address - reg &= 0x7f; - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x51 >> 5, (0x51 << 3) & 0xff); - } else { - REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, 0x1 << (I2C_MEM_ADDR_WIDTH-16) | 0, 0x50 >> 5, (0x50 << 3) & 0xff); - } + uint8_t dev; + uint8_t val; - reg_read_m(RTL837X_REG_I2C_CTRL); - sfr_mask_data(1, 0xfc, i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2); - reg_write_m(RTL837X_REG_I2C_CTRL); + len--; + if (len > 15) + return false; + + dev = (reg & 0x80) ? 0x51 : 0x50; // 0x51 holds the diagnostics, 0x50 the module data + reg &= 0x7f; REG_WRITE(RTL837X_REG_I2C_IN, 0, 0, 0, reg); - // Execute I2C Read - reg_bit_set(RTL837X_REG_I2C_CTRL, 0); + REG_WRITE(RTL837X_REG_I2C_CTRL, 0x00, + 0x1 << (I2C_MEM_ADDR_WIDTH - 16) | len, + (dev >> 5) | i2c_bus_from_scl_pin(machine.sfp_port[slot].i2c.scl) << 5 + | i2c_bus_from_sda_pin(machine.sfp_port[slot].i2c.sda) << 2, + ((dev << 3) & 0xff) | 0x1); - // Wait for execution to finish do { - reg_read_m(RTL837X_REG_I2C_CTRL); - } while (sfr_data[3] & 0x1); + reg_read(RTL837X_REG_I2C_CTRL); + } while (SFR_DATA_0 & 0x1); - /* Bit 1 is the controller's own failure indication, which the vendor SDK - * looks at and this did not. Without it an unacknowledged address comes - * back as an ordinary byte and the caller cannot tell it from data. */ - if (sfr_data[3] & 0x2) { - sfp_i2c_fail = 1; - return 0xff; + if (SFR_DATA_0 & 0x2) + return false; + + for (uint8_t i = 0; i <= len; i++) { + switch (i & 0x3) { + case 0: + reg_read(RTL837X_REG_I2C_OUT + i); + val = SFR_DATA_0; + break; + case 1: + val = SFR_DATA_8; + break; + case 2: + val = SFR_DATA_16; + break; + default: + val = SFR_DATA_24; + break; + } + sfp_buf[i] = val; } - reg_read_m(RTL837X_REG_I2C_OUT); - return sfr_data[3]; + return true; } diff --git a/rtlplayground.c b/rtlplayground.c index 6926ae5..420ec9e 100644 --- a/rtlplayground.c +++ b/rtlplayground.c @@ -139,7 +139,7 @@ __xdata char sfp_module_vendor[2][17]; __xdata char sfp_module_model[2][17]; __xdata char sfp_module_serial[2][17]; __xdata uint8_t sfp_options[2]; -__xdata uint8_t sfp_i2c_fail; /* set by sfp_read_reg() when the controller flags a failed transfer */ +__xdata uint8_t sfp_buf[16]; /* scratch for one I2C transaction, the controller reads at most 16 bytes */ __xdata uint8_t sfp_speed[2]; __xdata uint8_t sfp_quirks[2]; __xdata bool button_last; @@ -1188,36 +1188,46 @@ static inline uint8_t sfp_rate_to_sds_config(register uint8_t rate) } -void sfp_print_info(uint8_t sfp) +bool sfp_print_info(uint8_t sfp) { // This loops over the Vendor-name, Vendor OUI, Vendor PN and Vendor rev ASCII fields - for (uint8_t i = 20; i < 60; i++) { - if (i >= 36 && i < 40) // Skip Non-ASCII codes + for (uint8_t i = 16; i < 64; i++) { + if (!(i & 0xf) && !sfp_read_block(sfp, i, 16)) + return false; + if (i < 20 || i >= 60 || (i >= 36 && i < 40)) // Skip Non-ASCII codes continue; - uint8_t c = sfp_read_reg(sfp, i); + uint8_t c = sfp_buf[i & 0xf]; if (c) write_char(c); } print_string("\n"); + + return true; } // Normalize strings from EEPROM by removing any trailing spaces; this allows simpler comparisons -void sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t length) __reentrant +bool sfp_read_field(__xdata char *dst, uint8_t sfp, uint8_t start, uint8_t length) __reentrant { - dst[length] = '\0'; + if (!sfp_read_block(sfp, start, length)) + return false; - for (uint8_t i = 0; i < length; i++) - dst[i] = sfp_read_reg(sfp, start + i); + dst[length] = '\0'; + memcpy(dst, sfp_buf, length); while (length > 0 && dst[--length] == ' ') dst[length] = '\0'; + + return true; } -void sfp_get_info(uint8_t sfp) +bool sfp_get_info(uint8_t sfp) { - sfp_read_field(sfp_module_vendor[sfp], sfp, 20, 16); - sfp_read_field(sfp_module_model[sfp], sfp, 40, 16); - sfp_read_field(sfp_module_serial[sfp], sfp, 68, 16); + if (!sfp_read_field(sfp_module_vendor[sfp], sfp, 20, 16)) + return false; + if (!sfp_read_field(sfp_module_model[sfp], sfp, 40, 16)) + return false; + + return sfp_read_field(sfp_module_serial[sfp], sfp, 68, 16); } void sfp_apply_quirks(uint8_t sfp) __reentrant @@ -1236,7 +1246,7 @@ void sfp_apply_quirks(uint8_t sfp) __reentrant if (!(sfp_options[sfp] & 0x40)) { // The module reports that DDM is not implemented, but try a dummy read to confirm // 0xff would mean a failed I2C read or an impossible (per spec) voltage greater than 6.5V - if (sfp_read_reg(sfp, 226) != 0xff) { + if (sfp_read_block(sfp, 226, 1) && sfp_buf[0] != 0xff) { sfp_options[sfp] |= 0x40; } } @@ -1260,6 +1270,45 @@ void setup_sfp_gpio(void) } } +static bool sfp_module_read(uint8_t sfp) +{ + uint8_t rate; + + // Read Reg 11: Encoding, see SFF-8472 and SFF-8024 + // Read Reg 12: Signalling rate (including overhead) in 100Mbit: 0xd: 1Gbit, 0x67:10Gbit + delay(100); // Delay, because some modules need time to wake up + if (!sfp_read_block(sfp, 11, 2)) + return false; + + rate = sfp_buf[1]; + if (sfp_speed[sfp] == SFP_SPEED_100M) + rate = 0x1; + else if (sfp_speed[sfp] == SFP_SPEED_1G) + rate = 0xc; + else if (sfp_speed[sfp] == SFP_SPEED_2G5) + rate = 0x19; + else if (sfp_speed[sfp] == SFP_SPEED_10G) + rate = 0x69; + print_string(" Rate: "); print_byte(rate); // Normally 1, but 0 for DAC, can be ignored? + print_string(" Encoding: "); print_byte(sfp_buf[0]); + print_string(" Module: "); + if (!sfp_print_info(sfp)) + return false; + print_string("\n"); + + if (!sfp_read_block(sfp, 92, 1)) + return false; + sfp_options[sfp] = sfp_buf[0]; + if (!sfp_get_info(sfp)) + return false; + + sfp_apply_quirks(sfp); + sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate)); + + return true; +} + + void handle_sfp(void) { for (uint8_t sfp = 0; sfp < machine.n_sfp; sfp++) { @@ -1267,29 +1316,10 @@ void handle_sfp(void) if (sfp_pins_last & (0x1 << (sfp << 2))) { sfp_pins_last &= ~(0x01 << (sfp << 2)); print_string("\n Slot: "); write_char('1' + sfp); - // Read Reg 11: Encoding, see SFF-8472 and SFF-8024 - // Read Reg 12: Signalling rate (including overhead) in 100Mbit: 0xd: 1Gbit, 0x67:10Gbit - delay(100); // Delay, because some modules need time to wake up - sfp_i2c_fail = 0; - uint8_t rate = sfp_read_reg(sfp, 12); - if (sfp_speed[sfp] == SFP_SPEED_100M) - rate = 0x1; - else if (sfp_speed[sfp] == SFP_SPEED_1G) - rate = 0xc; - else if (sfp_speed[sfp] == SFP_SPEED_2G5) - rate = 0x19; - else if (sfp_speed[sfp] == SFP_SPEED_10G) - rate = 0x69; - print_string(" Rate: "); print_byte(rate); // Normally 1, but 0 for DAC, can be ignored? - print_string(" Encoding: "); print_byte(sfp_read_reg(sfp, 11)); - print_string(" Module: "); sfp_print_info(sfp); - print_string("\n"); - sfp_options[sfp] = sfp_read_reg(sfp, 92); - sfp_get_info(sfp); - sfp_apply_quirks(sfp); - if (sfp_i2c_fail) - print_string("SFP: an I2C read failed, the module data above may be wrong\n"); - sds_config(machine.sfp_port[sfp].sds, sfp_rate_to_sds_config(rate)); + if (!sfp_module_read(sfp)) { + print_string("SFP: an I2C read failed, retrying on the next poll\n"); + sfp_pins_last |= 0x01 << (sfp << 2); + } } } else { if (!(sfp_pins_last & (0x1 << (sfp << 2)))) {