How the protocol was recovered from NETGEAR's stock ReadyNAS firmware. This is the methodology, so it can be repeated for other ReadyNAS models / front boards.
TrueNAS SCALE has no driver for NETGEAR's front board. The ReadyNAS
BIOS/bootloader writes Booting... to the LCD's GDDRAM during boot, and since an
LCD retains its image without active driving, that frame just stays there
forever under TrueNAS. (In the stock kernel, readynas_lcd_init is what writes
the "Booting.." string.) Nothing is broken — the panel simply needs a driver.
- Download the official ReadyNAS OS 6 firmware
.imgfor the RN426. - Unpack it: the
.imgis a 16 KBinfo::header followed by a tar containingkernel(a bzImage),initrd.gz, androot.tlz(an lzma'd tar rootfs). - Extract
vmlinuxfrom the bzImage (decompress the embedded xz/gzip payload). - The kernel has no symbol table, but it has an embedded kallsyms table.
Reconstruct an ELF with symbols using
vmlinux-to-elf:Nowvmlinux-to-elf vmlinux vmlinux.elfnm -n vmlinux.elfandobjdump -dwork normally.
nm/strings surface the relevant functions. The driver supports two display
types; the RN426 uses the SSD130x "oled" (SPI) path, not the HD44780 one:
| Symbol | What it does |
|---|---|
oled_probe |
reads the per-model config (gpiochip label + 6 pin numbers), claims the gpiod lines |
spi_send |
bit-bangs one SPI byte over 4 gpiod descriptors (CS/CLK/MOSI/DC) |
init_oled |
pulses RESET, sends a 33-byte init table, sets EN, backlight on |
oled_data_write |
writes a character (font-rendered) |
readynas_lcd_init |
registers the driver and writes "Booting.." |
Disassembling spi_send reveals the 4-wire bit-bang (MSB-first, latch on rising
CLK). init_oled reads its 33 init bytes from a .rodata table — dump it to get
the SSD1305 init sequence. Note it has no display-on; 0xAF is sent
elsewhere.
oled_probe loads a config struct (gpiochip label + pins at offsets
0x10..0x24). Scanning .rodata for structs whose label points to a gpiochip
name and is followed by valid pin numbers yields the per-model configs:
name='rn426_8' chip=gpio_dnv.0 pins=[30, 28, 7, 8, 17, 31]
But those pin numbers are in NETGEAR's custom gpio_dnv driver numbering,
which does not match mainline pinctrl-denverton. So you cannot just load
mainline pinctrl and use offsets 30/28/7/8/17/31 — you'll drive the wrong pads
(this wasted real effort; the symptom was "no display change").
Instead, decode gpio_dnv's own mapping:
dnv_gpio_set(chip, off, val)callsdnv_gpio_reg(chip, off, 5)and sets bit 0 of the returned register → confirms output isPADCFG_DW0bit 0.dnv_gpio_regcomputesaddr = mmio_base + reg_base[5] + table5[off], wheretable5(a.rodataarray) holds each pad's PADCFG offset. Foroff >= 14it's cleanly(off-14)*8; offsets 3–13 get+0x30000, which maps the North community (0xFDC20000) to the South one (0xFDC50000, since0xFDC50000 - 0xFDC20000 = 0x30000).
Reading table5 for the RN426 pins and adding the community base + PADBAR
(read live from the hardware: 0x400) gives the physical PADCFG_DW0
addresses used by this driver. From there, drive bit 0 (level) and clear bit 9
(GPIOTXDIS) directly via /dev/mem — bypassing the gpiochip numbering
entirely. See lcd-protocol.md for the resulting table.
| Symbol | What it does |
|---|---|
i2cfb_button_depressed / i2cfb_reporter |
i2c_smbus_read_byte_data(client, 0x04) → button bitmap |
button_irq_init |
sets up a GPIO IRQ from the MCU (interrupt-driven reads) |
So buttons = MSP430 reg 0x04 bitmap over SMBus. The bit→button mapping was then
confirmed empirically by holding each button and watching reg 0x04
(see buttons-protocol.md).
- GPIO chardev v1 ABI is disabled on this TrueNAS kernel
(
CONFIG_GPIO_CDEV_V1=n):GPIO_GET_LINEHANDLEreturnsEINVAL. Use the v2 ABI (GPIO_V2_GET_LINE) or, as here, just/dev/mem. - Mainline
pinctrl-denvertonline offsets ≠ stockgpio_dnvoffsets. Don't trust the firmware's pin numbers against a mainline gpiochip. - i801 is SMBus-only — no raw i2c (
i2ctransferfails); use SMBus ops. - i2c bus numbering swaps across reboots (i801 ↔ iSMT); find i801 by name.
- MSP430
reg 0x02gates button scanning and only resets on a cold power-cycle — seebuttons-protocol.md. - The unhidden P2SB is needed for the SoC GPIO MMIO; the BIOS re-hides it on every boot, so the driver un-hides it at startup.