Never Trust the PCB
After always doubting UART pinout, this is another story about how assuming the PCB must work as designed wasted a lot of time.
This time the device is Sirivision SR-ST3808F, a managed switch based on Realtek RTL9303 with 512 MiB DDR3, 16 MiB SPI-NOR and 8 SFP+ ports. I bought the first one on July 26, 2024 and used it as the core switch below BPI-R4 and above lots of 2.5G switches at my home, then bought a second one on August 5, 2026.
I began development on OpenWrt port and made the first device tree on August 11, 2026. The hardware problem was not found until September 10, and the final patch was prepared on September 11. So an apparently simple derivative port consumed about a month, mostly trying to make software initialize a chip which the PCB permanently held in reset.
Freeing the old switch for development
Although I wanted to bring it to OpenWrt after SR-ST3408F support was merged, taking the core switch apart repeatedly for development was not practical.
I bought my second SR-ST3808F during a recent sale and it arrived on Friday, August 7, 2026. From the outside the two switches looked almost the same. Both were about 195 mm long and 40 mm high, but the new case was about 165 mm wide instead of 159 mm and carried a Sirivision logo. Peeking inside also revealed the same major components in roughly the same places. That made it natural to assume this was another production of the same board design.
I then examined its WebUI and found several differences from the old firmware. Both switches provide built-in firmware backup and restore, so I backed them up, restored the new firmware onto the old switch, then restored the old firmware onto the new switch. Both combinations worked.
The switch only has a one-year warranty, and one of its case screws is covered by a warranty-void label. The old switch was already outside that warranty, so breaking its label and opening it cost me nothing.
Late that night I restored the new firmware on the new switch and put it into service as my home core switch. Friday ended with the old switch finally free for OpenWrt development, flashing, probing and soldering, before a free weekend in which I did not yet have to worry about the later investigation.
Ironically, the replacement also became the one comparison sample I did not want to touch. It was new, still within its one-year warranty, and carrying my home network. Opening it meant both taking the core switch offline and breaking the label over its screw, deliberately voiding the warranty on known-good hardware.
Two interchangeable software stacks
The versions were not merely newer and older in every field:
| 2024 unit | 2026 unit | |
|---|---|---|
| U-Boot version | 3.6.8.55120 |
3.6.11.55242 |
| U-Boot build date | 2024-05-14 | 2025-11-28 |
U-Boot automatically runs rtk network on |
yes | no |
| U-Boot DAC support | no | yes |
| U-Boot RTL8261N support | yes | no |
| Firmware version | 3.5.0.22 |
3.5.0.10 |
| Firmware build date | 2024-07-23 | 2025-11-28 |
| Firmware DAC fixups | no | yes |
| Firmware OSPF support | no | yes |
The later firmware’s numerically lower version suggests 3.5.0.10 was a product starting point rather than a globally increasing release number. Either firmware could boot either board, while the loaders merely differed in which interfaces they could bring up as a working network link. At this point, that interchangeability only reinforced my assumption that the two switches shared the same hardware design.
An almost working device tree
At first this looked like an easy port. SR-ST3808F and the already supported SR-ST3408F share the same SoC and much of the surrounding design. On August 11, I made the initial attempt by duplicating the SR-ST3408F support and replacing the board-specific parts.
The stock image did not contain a hidden device tree. It used a Linux 3.18.24 kernel with Realtek’s legacy platform code, an initramfs containing separate module and application SquashFS images, and proprietary board modules loaded after boot. I extracted those files and decoded the unstripped board_conf.ko symbol and relocation tables. In particular, portConf_9303_8xge, fiberConf_9303_8xge, opticalConf_9303_8xge, detectConf_9303_8xge and boardGpio_8xge supplied the relationships which a modern device tree had to express.
Together with U-Boot and kernel logs, that recovered the important mappings:
- RTL9303 switch ports 0, 8, 16, 20, 24, 25, 26 and 27 go to front-panel SFP ports 1 to 8;
- I2C channels 0 to 7 read the corresponding SFP EEPROMs;
- one serial LED is used for each port;
- RTL8231 provides three signals for every SFP cage:
TX_DISABLE,MOD_DEF0andLOS; - GPIO 7 on RTL9303 is the reset button;
- the firmware partition starts at
0x300000and uses image magic0x93000000.
The RTL8231 layout was especially regular. Its GPIOs are used in groups of three:
| SFP port | TX_DISABLE | MOD_DEF0 | LOS |
|---|---|---|---|
| 1 | 0 | 1 | 2 |
| 2 | 3 | 4 | 5 |
| 3 | 6 | 7 | 8 |
| 4 | 9 | 10 | 11 |
| 5 | 12 | 13 | 14 |
| 6 | 21 | 22 | 23 |
| 7 | 24 | 25 | 26 |
| 8 | 27 | 28 | 29 |
The gaps also match the functions reserved by RTL8231, so this did not look like a guess. PCB traces from the SFP cages go to the expected pins, and the vendor firmware reports exactly the same assignments.
There was an unrelated distraction around DAC cables almost immediately. On the same afternoon the old loader could establish a working network link through an RTL8261N-based 10G RJ45 module but not a DAC cable, while the new loader could establish one through a DAC cable but not that RJ45 module. TFTP worked once the corresponding loader had a functioning network link. OpenWrt could identify a DAC but could not always bring it up at 10 Gbps because the vendor’s dac50cm, dac100cm and dac300cm modes also apply SerDes tuning. I created a small follow-up branch on top of the initial attempt just to record that DAC TODO, but it was not the reason normal optical modules failed and did not belong in the board-support patch.
The actual blocker was much simpler:
rtl8231-expander realtek-aux-mdio:00: RTL8231 not present or ready 0x0 != 0x37
Looking for a software bug
RTL8231 can use serial, SMI or MDIO mode. This board should use it as an MDIO-controlled GPIO expander, and OpenWrt accesses it through the RTL9303 auxiliary MDIO controller.
By August 18 I was following the vendor software more literally. The RTL8231 datasheet defaults to address 31 while the extracted board profile explicitly selected address 0, so I tried both and changed the auxiliary MDIO handling accordingly. I kept that interpretation in a separate branch and briefly tried a patch which could continue past a failed READY_CODE check.
The high-level vendor commands were misleading. rtk ext-devInit 0 and rtk ext-devInit 31 both printed success, while rtk ext-pinGet called every tested pin invalid. Reading the matching SDK sources explained why: the external-device wrappers discarded several lower-level failures and returned success unconditionally. Even the stock kernel message RTL8231 probe (unit 0): (found) only meant that the RTL9303 family selected an RTL8231 access backend; it did not mean an external chip had acknowledged a transaction.
A warning I ignored
Another week passed. From August 24 through 28 I deliberately stepped away from the SR-ST3808F and worked on adding support for an FG-8GT-1SX in another switch firmware project, hoping a smaller, unrelated port would clear my head. Instead, that board produced serial output but accepted no input. I assumed RX initialization was wrong and spent more than a day flashing software revisions before probing the pads with an oscilloscope. The PCB had swapped UART RX and 3.3 V on the SoC side of two resistor footprints. I finalized that support change on September 1 and wrote the preceding post about it.
That should have made me distrust the SR-ST3808F PCB immediately. It did not. I treated the UART incident as one vendor’s isolated layout error, while continuing to grant this board’s RTL8231 circuit the benefit of the doubt. In retrospect, my attempt to take a fresh breath had supplied exactly the warning I still failed to apply.
Probing below the drivers
I did not completely leave the SR-ST3808F alone during that week. I preserved the old OpenWrt attempt and moved into the vendor binaries, making synthetic MIPS ELF wrappers for vendor firmware, following vendor U-Boot’s register access in a disassembler, and writing small static MIPS programs which queried /dev/rtcore, the rtdrv socket API and the internal and external GPIO interfaces. Several apparent interfaces were stubs returning ENOTTY; others copied zero from the RTL9303 indirect-access register without proving that a peripheral had replied.
The raw controller test was more decisive. U-Boot’s mw.l and md.l were enough to set EXT_GPIO_EN at switch register 0xc600 and drive the indirect transaction register at 0xc620. I scanned all 32 PHY addresses and all 32 RTL8231 registers: 1,024 reads after a power cycle. Every transaction completed with zero data and no command-failure bit. I also wrote the chip-enable bit and the documented self-clearing software-reset bit at addresses 0 and 31, with no change. This narrowed the possibilities to a part held in hardware reset, a wrong hardware strap or mode, broken bus wiring, or a dead or missing part. It still did not identify which one.
I restarted the board support from current OpenWrt sources on September 2 and kept polishing that apparently “working” configuration through September 7. The basic switch ports, flash, LEDs and I2C buses worked, but RTL8231 still did not. I was three and a half weeks into the port and still treated the PCB wiring as a known fact while every software theory failed.
For the forum question I published an experimental version separately from the intended support. That tree contained commented out RTL8231 nodes and attributes to demostrate what need to absent if I don’t skip READY_CODE check, otherwise they would block SFP interfaces to be fullying functional.
If I preseve all intended RTL8231 nodes and attributes, I need to skip READY_CODE check instead, which would bring RTL8231 “online” only in the software perspective:
gpiochip1: 37 GPIOs, parent: mdio_bus/realtek-aux-mdio:00
gpio-0 (|tx-disable) out lo
gpio-1 (|mod-def0 ) in lo ACTIVE LOW
gpio-2 (|los ) in lo
...
Every input remained zero regardless of whether a module was inserted. In particular, MOD_DEF0 is active-low and should change when a module is plugged in, but it was stuck. The vendor’s show tech-support command reported the same impossible values for all external GPIOs.
For a while this still looked like some missing initialization. The vendor firmware handled modules well enough, and both its U-Boot and kernel contained RTL8231 support. But the decoded board profile configured the eight FB_EXIST_n and FB_LOS_n signals on RTL8231 while independently configuring eight hardware-I2C EEPROM readers at address 0x50; U-Boot exposed the same per-port I2C primitive. That independent path could explain how the product remained useful while all three dedicated GPIO signals per cage were dead. The preserved configuration and status output do not prove the exact polling or fallback control flow in the WebUI, which would require a controlled unplug and replug test while recording both I2C and status results.
Measuring the chip itself
The RTL8231 datasheet describes pin 36, RESET, as an input with a typical 75 kohm internal pull-up. Left alone, it should rise towards 3.3 V. On my switch it stayed around 0.02 to 0.04 V in U-Boot, the stock firmware and OpenWrt.
Other pins also looked like an uninitialized chip. For example, GPIO 10 and GPIO 13 both serve as MOD_DEF0 inputs for empty cages, but measured about 3.32 V and 0.15 V respectively. MDC was high while MDIO was at 0 V. All five address pins were around 0.06 V. No software environment changed this behaviour.
At that point the likely explanations were a dead RTL8231, a bad solder joint, or a PCB holding it in reset. I had bought the development switch almost two years earlier, so a failed chip did not seem impossible. I posted all the measurements to the OpenWrt forum on September 8 while still expecting either a software detail or one failed chip.
The forum discussion suggested checking whether some SoC GPIO controlled reset. I followed that suggestion and enumerated all 24 RTL9303 GPIOs, driving every possible output state with enough delay to watch the meter. The RTL8231 reset pin stayed around 22.8 mV, and the discussion did not produce a missing software initialization.
Before voiding the warranty I tried the comparison which did not require opening the new switch. I temporarily removed it from the network, and its vendor U-Boot reported meaningful external GPIO values. The same OpenWrt device tree initialized its RTL8231 at MDIO address 0 immediately. Together with the firmware and loader cross-flashing on August 7, this made a software difference very unlikely. The remaining possibilities were a failed component or some hidden hardware change.
With no bigger clue coming from the forum, I finally removed the new switch from my home network again, accepted losing the remaining warranty, broke the label and opened it.
Only then could I put the PCBs side by side and find that both really were marked ST3808F_V1.1. The important visible distinction was the production batch: the broken board was marked 2411, while the working one was marked 2550. Confirming the same PCB revision made the remaining tiny component differences much more interesting.
Even with both boards open, through Wednesday I still suspected an ordinary component failure. Either some resistor / capacitor could be shorting reset to ground, or the RTL8231 itself could have a broken internal pull-up. I removed both components to test them separately and damaged the tiny capacitor near RTL8231’s reset pin, C209 in the process. That evening I ordered two replacement RTL8231s and a pack of different 0402 capacitors online, not knowing the exact capacity of C209.
Tracing the reset net
On Thursday I made an ugly standalone test of the removed RTL8231 rather than wait for the replacements. I soldered a cheap adjustable 3~12V DC source, intended for powering fans, directly to VCC1 on pin 19 and GND1 on pin 13. Both pins are on the east side of the package. With only power and ground connected, its reset pin rose to about 3.3 V. The chip and its internal pull-up were good.
That result finally forced me back to the PCB. Something outside the RTL8231 had to be pulling reset low. A continuity check from pin 36 first led to the pads for the capacitor I had removed, C209. A capacitor on a reset net was unsurprising because it could provide a power-on reset delay, but it did not explain why the line never rose.
The board was arranged roughly like this:
Board Rear Board Front Power
--------------+--------------------------------------------+----------
|Console TI-MAX3232C |
|PWR&SYS-LEDs R102 DC-4PIN-Header|
|SFP1-4-LEDs TI-HC164 TI-LV08A |12V Supply
|SFP5-8-LEDs |
|SFP1 |
|SFP2 |
|SFP3 SMD2520 RAM:Micron-D9SHD|
|SFP4 SoC:RTL9303 |
LDO |SFP5 74HC125D |
|SFP6 SPI:WinBound-W25Q128JVSIQ|
C209 |SFP7 74HC125D |
RTL8231 |SFP8 SPI-6PIN-EmptyHeader|AC-Socket
|ResetButton |
--------------+--------------------------------------------+----------
Examining the PCB again and again, I finally found that the trace did not simply end at C209. From the pad of C209 shared with the reset net, it travelled across almost the entire rear of the PCB from south to north. It was mixed into a dense field of unrelated and nearly parallel traces under the solder mask. I had to follow it in short sections with continuity measurements; whenever I picked up a neighbouring trace, I had to go back to the last confirmed point and try again.
The resistor which should not exist
Near the north end the trace finally crossed through the PCB into that cluster of components. Repeating the same continuity checks on the front narrowed the reset net to one side of a small resistor marked R102 on the old board.
I then checked the same position on the new board. The footprint was there, but R102 was not fitted. Until then I had treated the identical ST3808F_V1.1 markings, successful firmware cross-flashing and matching major components as evidence that the two PCBs were electrically the same. Here was a concrete production difference, directly on the reset net, between the board whose RTL8231 never started and the board whose RTL8231 worked.
The voltages made the difference harder to dismiss. With the old board powered, both sides of the fitted R102 sat near ground. On the powered new board, the two bare pads were completely different: the west side crossed the PCB and led south towards C209 and RTL8231 sat near 3.3 V, while the east side led into the nearby logic remained near ground. Without R102, the working board kept those two levels apart. With R102 fitted, the old board joined them and pulled the reset net low.
That changed the question from how software should initialize the chip to what the extra resistor was doing. On both boards, the east side of R102 went to the northwest of a nearby TI chip marked LV08A.
Looking up the LV08A then supplied the missing meaning: it contains four 2-input positive AND gates, and pin 8 on the northwest corner is the output of its third gate. The recovered circuit was:
LV08A
+----------------+
unconnected 3A --|9 8 |-- 3Y -- R102 --+-- RTL8231 RESET
| AND | |
3.3 V 3B --|10 | C209
+----------------+ |
GND
C209 provides the normal power-on reset pulse. R102 adds another path from the reset line to output 3Y of the AND gate.
Input 3B is pulled to about 3.3 V through R106 and R96. There is also an unpopulated U11 position which looks intended for a debug button or switch to pull it low. Input 3A, however, is connected to nothing. On the old board it measured about 0.05 V, and about 850 ohm to the LV08A ground while powered off, similar to other floating input pins on LV08A. It was not controlled by any RTL9303 GPIO.
With 3A behaving as low and 3B high, 3Y remains low. Through R102 it therefore holds RTL8231 reset low forever. This was not a faulty chip and no driver could release it.
The intended circuit is uncertain. Maybe the designer treated the AND gate as an OR gate; maybe a pull-up for 3A was forgotten; maybe the channel was supposed to work as a buffer for the optional debug button. Leaving a CMOS input floating would be wrong in every version.
Sirivision’s production fix was wonderfully direct: do not solder R102.
| Component | 2411 batch | 2550 batch |
|---|---|---|
| PCB silkscreen | ST3808F_V1.1 |
ST3808F_V1.1 |
| Case width | about 159 mm | about 165 mm |
| LDO | AZ1117H-ADJTRE1, marking EH11A |
AZ1117H-ADJTRG1, marking GH17K |
| Internal PSU | TPT36S1203A, 12 V 3 A |
FC042X01-120333, 12 V 3.33 A |
| R102 | fitted | not fitted |
| RTL8231 reset | about 0.02 V | about 3.3 V |
| RTL8231 | held in reset | working |
Repairing what I had broken
Removing R102 disconnects the broken gate output and allows the RTL8231 internal pull-up to release reset. I had proved the fault, but I could not simply solder the original chip back onto the board: the standalone test had left all of its east-side pins except VCC1 and GND1 bent beyond practical reuse. I had to wait for the replacements already in transit.
They arrived on the following Saturday, September 12. I soldered the first new RTL8231 onto the old board, with R102 removed and C209 still absent, and the switch worked. I then tried to restore the power-on reset circuit. Because I did not know C209’s original value, I chose a 22 pF capacitor from the 0402 pack.
That package was difficult to solder by hand, and I nearly destroyed the pads. After the attempt the RTL8231 stopped working again. I could not tell whether the capacitor value was wrong or my soldering had joined pads which should have remained separate, but that replacement chip was no longer usable. Removing the new C209 finished off its already damaged pads, and I removed the first replacement RTL8231 as well.
I soldered in the second new RTL8231 and left both R102 and C209 absent. The switch finally worked consistently. R102 must not connect the low gate output to reset; C209’s power-on reset pulse, on the other hand, can be omitted because the RTL8231 starts correctly with its reset pin released by the internal pull-up.
Disconnect both the AC cable and DC connection before opening the switch or touching R102. The internal power supply exposes mains wiring inside the case. Right after finding R102 difference my elbow was shocked by AC on new switch when measuring R102 pads live on it.
What went into OpenWrt
There were two possible ways to represent this in OpenWrt: create a second device profile without RTL8231 for the bad batches, or describe the hardware as it was intended and let old boards fail to probe the expander.
I chose a single profile. There is no PCB revision or model marking which reliably distinguishes them: both are ST3808F_V1.1, and R102 is only a production BOM difference. More importantly, omitting RTL8231 would preserve the accidental vendor workaround but lose TX_DISABLE, MOD_DEF0 and LOS on every port. The board with R102 removed behaves like the later production board and provides the hardware experience the design clearly intended.
The experimental changes which skipped READY_CODE, modified the common RTL8231 driver and guessed alternative MDIO addresses were all dropped. The cleaned-up single board-support patch was submitted as OpenWrt pull request #25111.
The knowledge on how to identify and fix the problem was submitted as part of the commit message so one could flash and possibly fix the switch by reading the commit message alone.
TL;DR: For an old board, in kernel log, the symptom is:
rtl8231-expander realtek-aux-mdio:00: RTL8231 not present or ready 0x0 != 0x37
And SFP EEPROM reads through ethtool -m lan1 to lan8 will also fail even with a module inserted.
To fix it, power everything off, inspect whether R102 is fitted, and remove it before treating the problem as software.
The actual lesson
The first device tree from August 11 was already close to the final one submitted one month later. The decisive hardware investigation took about two days once I seriously allowed the PCB itself to be wrong. Most of the preceding three and a half weeks was spent changing software around a peripheral which was physically prevented from starting.
Vendor firmware is not proof that every chip it names actually works. An independent software path can hide a dead hardware path for years. Identical PCB silkscreen is not proof of an identical assembled circuit either; a single omitted resistor can be the real hardware revision.
And when a chip with an internal pull-up has its reset pin sitting near GND, stop patching its driver. Measure and trace that pin first.
The detailed measurements and discussion leading to the R102 discovery are archived in the OpenWrt forum thread.