diff --git a/doc/howto_leds.md b/doc/howto_leds.md
new file mode 100644
index 00000000..dfc8ee9e
--- /dev/null
+++ b/doc/howto_leds.md
@@ -0,0 +1,572 @@
+# LEDs HOWTO using firmware function tracing
+
+This is a tutorial to show how to figure out the LED configuration for
+unmanaged switches (on managed ones one can just print out the registers
+from the command line) without using an I2C port on the device together
+an I2C-capable microcontroller. You will only need to have a SOIC clamp
+and will have to run ghidra to do a bit of reversing.
+
+The additional advantage of this method is that it not only gives the LED
+configuration but also the full PHY configuration and SDS configuration e.g.
+in case of additional PHYs being used on the device.
+
+We will use the ZoeRax HG0402XG V1.1 as an example device.
+
+The first step is to read out the flash with a SOIC clamp.
+Save the image as
+`zoerax_hg0402xg.bin`
+
+The next step is to use a decompiler such as ghidra to identify the
+functions used to write or read registers, SDS or to the PHY.
+
+
+
+First, load the code in BANK0 into ghidra. The type of code is
+8051 default big-endian. The code starts at offset 0x0002 in the file
+and has length 0x3ffd
+
+
+Add BANK1 into ghidra by using File->Add to program...
+and add the code starting at 0x4000 in `zoerax_hg0402xg.bin` with length
+0xc000. Repeat for BANK2 (starts at 0x10000) and BANK3 (starts at 0x1c0000).
+They both have length 0xc000.
+
+
+
+Now, find the read_reg function. An example of the read-register function
+is given below. Note the critical bit, namely the execution of the
+read-function of an ASIC register via an SFR:
+```
+CODE1::4dc9 75 a0 01 MOV SFR_EXEC_GO,#0x1
+```
+
+This sequence of bytes is very specific. Replace 0x01 with 0x03 and you have
+the regster write SFR, 0x05 reads the SerDes, 0x07 writes it. 0x09 reads
+from the PHY and 0x0b writes to it.
+
+
+```
+ **************************************************************
+ * FUNCTION *
+ **************************************************************
+ undefined reg_read()
+ undefined
+ reg_read
+ CODE1::4da9 ab 07 MOV R3,BANK0_R7 = ??
+ CODE1::4dab aa 06 MOV R2,BANK0_R6 = ??
+ CODE1::4dad e4 CLR A
+ CODE1::4dae ff MOV R7,A
+ CODE1::4daf fe MOV R6,A
+ CODE1::4db0 fd MOV R5,A
+ CODE1::4db1 fc MOV R4,A
+ CODE1::4db2 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4db5 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4db8 e4 CLR A
+ CODE1::4db9 ff MOV R7,A
+ CODE1::4dba fe MOV R6,A
+ CODE1::4dbb fd MOV R5,A
+ CODE1::4dbc fc MOV R4,A
+ CODE1::4dbd 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4dc0 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4dc3 ea MOV A,R2
+ CODE1::4dc4 f5 a2 MOV SFR_A_ADDRH,A = ??
+ CODE1::4dc6 eb MOV A,R3
+ CODE1::4dc7 f5 a3 MOV SFR_A_ADDRL,A = ??
+ CODE1::4dc9 75 a0 01 MOV SFR_EXEC_GO,#0x1 <<<<<<<< Executes READ REG
+ LAB_CODE1__4dcc XREF[1]: CODE1::4dcf(j)
+ CODE1::4dcc af a1 MOV R7,SFR_EXEC_STATUS = ??
+ CODE1::4dce ef MOV A,R7
+ CODE1::4dcf 70 fb JNZ LAB_CODE1__4dcc
+ CODE1::4dd1 af a4 MOV R7,DAT_SFR_a4 = ??
+ CODE1::4dd3 fc MOV R4,A
+ CODE1::4dd4 fd MOV R5,A
+ CODE1::4dd5 fe MOV R6,A
+ CODE1::4dd6 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4dd9 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4ddc 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4ddf 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
+ CODE1::4de2 c0 00 PUSH BANK0_R0
+ CODE1::4de4 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4de7 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
+ CODE1::4dea 78 18 MOV R0,#0x18
+ CODE1::4dec 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
+ CODE1::4def d0 00 POP BANK0_R0
+ CODE1::4df1 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
+ CODE1::4df4 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4df7 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4dfa af a5 MOV R7,DAT_SFR_a5 = ??
+ CODE1::4dfc e4 CLR A
+ CODE1::4dfd fc MOV R4,A
+ CODE1::4dfe fd MOV R5,A
+ CODE1::4dff fe MOV R6,A
+ CODE1::4e00 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4e03 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4e06 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e09 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
+ CODE1::4e0c c0 00 PUSH BANK0_R0
+ CODE1::4e0e 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4e11 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
+ CODE1::4e14 78 10 MOV R0,#0x10
+ CODE1::4e16 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
+ CODE1::4e19 d0 00 POP BANK0_R0
+ CODE1::4e1b 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
+ CODE1::4e1e 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e21 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4e24 af a6 MOV R7,DAT_SFR_a6 = ??
+ CODE1::4e26 e4 CLR A
+ CODE1::4e27 fc MOV R4,A
+ CODE1::4e28 fd MOV R5,A
+ CODE1::4e29 fe MOV R6,A
+ CODE1::4e2a 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4e2d 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4e30 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e33 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
+ CODE1::4e36 c0 00 PUSH BANK0_R0
+ CODE1::4e38 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4e3b 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
+ CODE1::4e3e 78 08 MOV R0,#0x8
+ CODE1::4e40 12 05 86 LCALL FUN_CODE_0586 undefined FUN_CODE_0586()
+ CODE1::4e43 d0 00 POP BANK0_R0
+ CODE1::4e45 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
+ CODE1::4e48 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e4b 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4e4e af a7 MOV R7,DAT_SFR_a7 = ??
+ CODE1::4e50 e4 CLR A
+ CODE1::4e51 fc MOV R4,A
+ CODE1::4e52 fd MOV R5,A
+ CODE1::4e53 fe MOV R6,A
+ CODE1::4e54 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4e57 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4e5a 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e5d 12 05 dd LCALL read_xmem_32_b undefined read_xmem_32_b()
+ CODE1::4e60 90 00 c9 MOV DPTR,#0xc9
+ CODE1::4e63 12 05 f9 LCALL FUN_CODE_05f9 undefined FUN_CODE_05f9()
+ CODE1::4e66 12 05 48 LCALL FUN_CODE_0548 undefined FUN_CODE_0548()
+ CODE1::4e69 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e6c 12 06 15 LCALL FUN_CODE_0615 undefined FUN_CODE_0615()
+ CODE1::4e6f 90 00 c5 MOV DPTR,#0xc5
+ CODE1::4e72 02 05 dd LJMP read_xmem_32_b undefined read_xmem_32_b()
+ -- Flow Override: CALL_RETURN (CALL_TERMINATOR)
+
+```
+
+INSERT ghidra_search_read_reg.png
+
+Identify all the read and write function to the HW of the SoC using the find
+in memory function.
+
+The next task is to adapt the intercept routines in a special assembler
+file. Don't worry, you do not need to understand assembler to do this.
+
+```
+; Assembler file to intercept register writes/reads and PHY writes/reads
+; assemble with as31
+; Compile with
+; as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
+; ./injector zoerax_hg0402xg.bin RTLPlayground/rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
+; Start at 0x3b00, where there is always some space in BANK 0
+ .equ valptr, 0x199
+
+
+ .equ SFR_DATA_0, 0xa7
+ .equ SFR_DATA_8, 0xa6
+ .equ SFR_DATA_16, 0xa5
+ .equ SFR_DATA_24, 0xa4
+
+; Intercept Register Write
+ .org 0x4d62
+ lcall reg_write
+ nop
+
+; Intercept Register Read
+ .org 0x4dad
+ lcall reg_read_head
+ .org 0x4e6f
+ lcall reg_read_tail
+
+; Intercept PHY write
+ .org 0x522c
+ lcall phy_write
+
+; Intercept PHY read
+ .org 0x526f
+ lcall phy_read_head
+ .org 0x5295
+ ljmp phy_read_tail
+
+; Intercept SDS write
+ .org 0x503c
+ lcall sds_write
+ nop
+
+; Intercept SDS read
+ .org 0x5061
+ lcall sds_read_head
+ nop
+ .org 0x507b
+ ljmp sds_read_tail
+
+;
+; BEGIN actual code at unused end of BANK0
+;
+ .org 0x3b00
+
+;
+; print the address of a register write. The address is in R6/R7. A and r4 can be scratched
+;
+reg_write:
+ mov a, #'R'
+ acall putc
+
+ mov a, r6
+ acall wbyte
+
+ mov a, r7
+ acall wbyte
+
+ mov a, #'-'
+ acall putc
+
+ mov dptr, #valptr
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #valptr + 1
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #valptr + 2
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #valptr + 3
+ movx a, @dptr
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+
+ mov a, r7
+ mov r5, a
+ mov a, r6
+ mov r4, a
+ ret
+[...]
+```
+
+The full assembler file can be found here: XXXXXXXXXX
+
+
+Once you have assembled the code, inject the bytes into the existing image,
+creating a patched version:
+```
+$ as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
+Begin Pass #1
+Begin Pass #2
+./injector zoerax_hg0402xg.bin rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
+Input file size: 1048576
+Bytes read:: 1048576
+Patched 417 bytes.
+```
+
+Now flash `zoerax_hg0402xg_patched.bin` onto device.
+
+Connect a serial adapter and plug in power. You will see:
+
+```
+$ picocom -b 9600 /dev/ttyUSB0
+picocom v3.1
+
+port is : /dev/ttyUSB0
+flowcontrol : none
+baudrate is : 9600
+parity is : none
+databits are : 8
+stopbits are : 1
+escape is : C-a
+local echo is : no
+noinit is : no
+noreset is : no
+hangup is : no
+nolock is : no
+send_cmd is : sz -vv
+receive_cmd is : rz -vv -E
+imap is :
+omap is :
+emap is : crcrlf,delbs,
+logfile is : none
+initstring : none
+exit_after is : not set
+exit is : no
+
+Type [C-a] [C-h] to see available commands
+Terminal ready
+[The printout only starts once the serial peripherial has been set up. Initially its timing is off:]
+�.Ӛ�҂���������30-00005555 r6330:00005555 R6330-00005515 [RTL837X_REG_SMI_MAC_TYPE]
+[RTL837X_REG_SMI_PORT_POLLING: ]
+r6334:00000000 R6334-000000f0
+r6454:00000000 R6454-00007000
+
+[Read SoC-Revision]
+r000c:00200000 R000c-002a0000 r000c:202a6818 r000c:202a6818 R000c-20206818
+p001e.000d:ffff R02f8-0000ffff R02f4-0000fffa P000001.1e00000d:fffa p001e.000d:ffff p001e.000d:ffff
+R02f8-0000ffff R02f4-0000fff0 P000001.1e00000d:fff0
+
+[RTL837X_REG_LED_MODE being configured:]
+r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
+
+[RLDP Leds configured via RTL837X_REG_LED_RLDP_X:]
+r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
+
+[RTL837X_REG_LED_GLB_IO_EN: ]
+r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
+
+[RTL837X_PIN_MUX_0, makes serial go out of sync:]
+r7f8c:30000000 R7f8c-30000000 r7f8c:300��.Ӛ�҂���������30-00005555
+
+r6330:00005555 R6330-00005515 r6334:00000000 R6334-000000f0
+
+r6454:00000000 R6454-00007000
+
+[Read SoC-Revision (again)]
+r000c:00200000 R000c-002a0000 r000c:202a6818 r000c:202a6818 R000c-20206818
+p001e.000d:ffff R02f8-0000ffff R02f4-0000fffa P000001.1e00000d:fffa p001e.000d:ffff p001e.000d:ffff
+R02f8-0000ffff R02f4-0000fff0
+P000001.1e00000d:fff0
+
+#### LED configuration ########
+[RTL837X_REG_LED_MODE being configured (final):]
+r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
+
+[More RTL837X_REG_LED_RLDP]
+r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
+
+[RTL837X_REG_LED_GLB_IO_EN]
+r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
+
+[RTL837X_PIN_MUX_0
+r7f8c:30000000 R7f8c-30000000 r7f8c:30000000 R7f8c-38000000
+
+[RTL837X_LED_PORT_SET_SEL]
+r654c:00000000 R654c-00010000
+
+[RTL837X_REG_LED1_0_SET0]
+R6548-00000141
+
+[RTL837X_REG_LED3_2_SET0]
+r6544:01411000 R6544-01410174
+
+[RTL837X_REG_LED1_0_SET1]
+r6540:00440070 R6540-0044017f
+
+[RTL837X_REG_LED3_0_SET1]
+r6528:00000000 R6528-000f0000
+
+[SDS 0 being configure:]
+r7b20:00000bff R7b20-000003ff r7b20:000003ff R7b20-000003fa r7b20:000003fa R7b20-000003fa
+Q002110:4480 Q002113:0400 Q002118:6d02 Q00211b:424e Q00211d:0002 Q00361c:1390
+Q003614:003f Q003610:0200 Q002e04:0080 Q002e06:0408 Q002e07:020d Q002e09:0601
+Q002e0b:222c Q002e0c:a217 Q002e0d:fe40 Q002e15:f5c1 Q002e16:0443 Q002e1d:abb0
+Q000612:5078 Q000706:9401 Q000708:9401 Q00070a:9401 Q00070c:9401 Q001f0b:0003
+Q000603:c45c Q00061f:2100 q001f0b:0003 Q001f0b:000f q002000:0000 Q002000:0030
+q002000:0030 Q002000:0010 q002000:0010 Q002000:0050 q002000:0050 Q002000:00d0
+q002000:00d0 Q002000:0cd0 q002000:0cd0 Q002000:04d0 q002000:04d0 Q002000:04d0
+q002000:04d0 Q002000:0cd0 q002000:0cd0 Q002000:00d0 q002000:00d0 Q002000:00d0
+q002000:00d0 Q002000:0050 q002000:0050 Q002000:0010 q002000:0010 Q002000:0010
+q002000:0010 Q002000:0030 q002000:0030 Q002000:0000 q001f00:0000 Q001f00:000b
+q001f00:000b Q001f00:0000
+
+[SDS 1 being configured]
+r7b20:000003fa R7b20-000003fa r7b20:000003fa R7b20-0000035a r7b20:0000035a R7b20-0000035a
+Q012110:4480 Q012113:0400 Q012118:6d02 Q01211b:424e Q01211d:0002 Q01361c:1390
+Q013614:003f Q013610:0200 Q012e04:0080 Q012e06:0408 Q012e07:020d Q012e09:0601
+Q012e0b:222c Q012e0c:a217 Q012e0d:fe40 Q012e15:f5c1 Q012e16:0443 Q012e1d:abb0
+Q010612:5078 Q010706:9401 Q010708:9401 Q01070a:9401 Q01070c:9401 Q011f0b:0003
+Q010603:c45c Q01061f:2100 q011f0b:0003 Q011f0b:000f q012000:0000 Q012000:0030
+q012000:0030 Q012000:0010 q012000:0010 Q012000:0050 q012000:0050 Q012000:00d0
+q012000:00d0 Q012000:0cd0 q012000:0cd0 Q012000:04d0 q012000:04d0 Q012000:04d0
+q012000:04d0 Q012000:0cd0 q012000:0cd0 Q012000:00d0 q012000:00d0 Q012000:00d0
+q012000:00d0 Q012000:0050 q012000:0050 Q012000:0010 q012000:0010 Q012000:0010
+q012000:0010 Q012000:0030 q012000:0030 Q012000:0000 q011f00:0000 Q011f00:000b
+q011f00:000b Q011f00:0000 q012100:4902 Q012100:4906 q013605:0000 Q013605:4000
+Q011f02:001f q011f15:0096 q002100:4902 Q002100:4906 q003605:0000 Q003605:4000
+Q001f02:001f q001f15:00a6
+
+r0a90:000000f3 R0a90-000000fc P0000f0.1f00a610:2858 r5fd4:0002914a
+R5fd4-001a914a r1538:00000e33 R1538-00000e37 r1538:00000e37 R1538-00000e37
+r1538:00000e37 R1538-00000f37 r1638:00000e33 R1638-00000e37 r1638:00000e37
+R1638-00000e37 r1638:00000e37 R1638-00000f37 r1738:00000e33 R1738-00000e37
+r1738:00000e37 R1738-00000e37 r1738:00000e37 R1738-00000f37 r1838:00000e33
+R1838-00000e37 r1838:00000e37 R1838-00000e37 r1838:00000e37 R1838-00000f37
+r1938:00000e33 R1938-00000e37 r1938:00000e37 R1938-00000e37 r1938:00000e37
+R1938-00000f37 r1a38:00000e33 R1a38-00000e37 r1a38:00000e37 R1a38-00000e37
+r1a38:00000e37 R1a38-00000f37 r0b7c:000000d8 R0b7c-000000f8 R7124-00001050
+R7128-00001050 R712c-00001050 R7130-00001050 R7134-00001050 R7138-00001050
+R713c-00001050 R7140-00001050 R7144-00001050 R7148-00001050 r6040:00000030
+R6040-00000031 p041f.a436:8195 P000010.1f00a436:0005 p041f.a438:02d4
+P000010.1f00a436:801e p041f.a438:0000 p041f.b820:0000 P000010.1f00b820:0010
+p041f.b800:00c0 p041f.a436:801e P000010.1f00a436:8023 p041f.a438:0000
+P000010.1f00a438:1802 p041f.a436:8025
+
+[PHY with ID 5 being patched] P000010.1f00a436:b82e p041f.a438:0000
+P000010.1f00a438:0001 p041f.b820:0010 P000010.1f00b820:0090
+P000010.1f00a436:a016 P000010.1f00a438:0000 P000010.1f00a436:a012
+P000010.1f00a438:0000 P000010.1f00a436:a014 P000010.1f00a438:1800
+P000010.1f00a438:8010 P000010.1f00a438:1800 P000010.1f00a438:8013
+P000010.1f00a438:1800 P000010.1f00a438:8026 P000010.1f00a438:1800
+P000010.1f00a438:802a P000010.1f00a438:1800 P000010.1f00a438:802e
+P000010.1f00a438:1800 P000010.1f00a438:802e P000010.1f00a438:1800
+P000010.1f00a438:802e P000010.1f00a438:1800 P000010.1f00a438:802e
+...
+```
+Where:
+```
+r: read-reg
+R: write-reg
+q: read SDS
+Q: write SDS
+p: read PHY
+P: write SDS
+```
+
+The LED configuration has been marked above. We now write exactly those registers
+into the registers of `rtlplayground.c` on startup:
+
+```
+[...]
+ set_sys_led_state(SYS_LED_ON);
+
+ // [RTL837X_REG_LED_MODE being configured (final):]
+ // r6520:0021fdb0 R6520-0021e7b0 r6520:0021e7b0 R6520-0021e6b0
+ REG_SET(RTL837X_REG_LED_MODE, 0x0021e6b0);
+
+ // [More RTL837X_REG_LED_RLDP]
+ // r65f8:00000018 R65f8-0000001b R65fc-fffff000 r6600:00000000 R6600-0000000f
+ REG_SET(0x65f8, 0x0000001b);
+ REG_SET(0x65fc, 0xfffff000);
+ REG_SET(0x6600, 0x0000000f);
+
+ // [RTL837X_REG_LED_GLB_IO_EN]
+ // r65dc:5fffff00 R65dc-7fffff00 r65dc:7fffff00 R65dc-77ffff00
+ REG_SET(0x65dc, 0x77ffff00);
+
+ // [RTL837X_PIN_MUX_0
+ // r7f8c:30000000 R7f8c-30000000 r7f8c:30000000 R7f8c-38000000
+ REG_SET(0x7f8c, 0x38000000);
+
+ // [RTL837X_LED_PORT_SET_SEL]
+ // r654c:00000000 R654c-00010000
+ REG_SET(0x654c, 0x00010040);
+
+ // [RTL837X_REG_LED1_0_SET0]
+ // R6548-00000141
+ REG_SET(0x6548, 0x00000141);
+
+ // [RTL837X_REG_LED3_2_SET0]
+ // r6544:01411000 R6544-01410174
+ REG_SET(0x6544, 0x01410174);
+
+ // [RTL837X_REG_LED1_0_SET1]
+ // r6540:00440070 R6540-0044017f
+ REG_SET(0x6540, 0x0044017f);
+
+ // [RTL837X_REG_LED3_0_SET1]
+ // r6528:00000000 R6528-000f0000
+ REG_SET(0x6528, 0x000f0000);
+
+ cmd_editor_init();
+[...]
+```
+
+Create an initial configuraiton for your device by copying over an existing one
+for the same device type, i.e. same port-configuraiton and same SoC,
+give it a new name. Make sure that the led_mux_custom and led_mux fields are not
+used, because our device does not customize the LED muxes.
+
+Compile and flash onto your device.
+
+Now, boot the switch and issue the `leds`command, which will print out:
+```
+RTL837X_PIN_MUX_0: 0x38000000
+RTL837X_REG_LED_GLB_IO_EN: 0x77ffff00
+RTL837X_REG_LED1_0_SET0: 0x00000141
+RTL837X_REG_LED3_2_SET0: 0x01410174
+RTL837X_REG_LED1_0_SET1: 0x0044017f
+RTL837X_REG_LED3_2_SET1: 0x18000040
+RTL837X_REG_LED1_0_SET2: 0x00000000
+RTL837X_REG_LED3_2_SET2: 0x00000000
+RTL837X_REG_LED1_0_SET3: 0x00000000
+RTL837X_REG_LED3_2_SET3: 0x00000000
+RTL837X_REG_LED3_0_SET1: 0x000f0000
+RTL837X_REG_LED3_0_SET3: 0x00000000
+RTL837X_LED_PORT_SET_SEL: 0x00010040
+RTL837X_REG_LED_GLB_MUX_1: 0x3fffffff
+RTL837X_REG_LED_GLB_MUX_2: 0x0c3fffff
+RTL837X_REG_LED_GLB_MUX_3: 0x1245038d
+RTL837X_REG_LED_GLB_MUX_4: 0x19616554
+RTL837X_REG_LED_GLB_MUX_5: 0x2079d71a
+RTL837X_REG_LED_GLB_MUX_6: 0x000238a1
+RTL837X_REG_LED_GLB_ACTIVE: 0x3ffbedff
+RTL837X_REG_LED_MODE: 0x0021e6b0
+LED pad Configuration:
+00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b
+3f 3f 3f 3f 3f 3f 3f 3f 0f 0c 0d 0e 10 11 12 14 15 16 18 19 1a 1c 1d 1e 20 21 22 23
+LED-set Configuration:
+LED-ID 0 1 2 3
+SET 0: 000141 000000 000174 000141
+SET 1: 0f017f 000044 000040 001800
+SET 2: 000000 000000 000000 000000
+SET 3: 000000 000000 000000 000000
+Port 3: SET 1: ( 2G5 TWO_1G 1G 500M 100M 10M LINK ACT 10G TWO_5G 5G TWO_2G5), ( 1G LINK), ( LINK), ( COL DUPLEX),
+Port 4: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
+Port 5: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
+Port 6: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
+Port 7: SET 0: ( 2G5 LINK ACT), (), ( 1G 100M 10M LINK ACT), ( 2G5 LINK ACT),
+Port 8: SET 1: ( 2G5 TWO_1G 1G 500M 100M 10M LINK ACT 10G TWO_5G 5G TWO_2G5), ( 1G LINK), ( LINK), ( COL DUPLEX),
+
+```
+Together with the GPIO information, this will give you the following machine configuration:
+```
+_code const struct machine machine = {
+ .machine_name = "ZOERAX HG0402XG",
+ .isRTL8373 = 0,
+ .min_port = 3,
+ .max_port = 8,
+ .n_sfp = 2,
+ .log_to_phys_port = {0, 0, 0, 5, 1, 2, 3, 4, 6},
+ .phys_to_log_port = {4, 5, 6, 7, 3, 8, 0, 0, 0},
+ .is_sfp = {0, 0, 0, 1, 0, 0, 0, 0, 2},
+ // Left SFP port (5)
+ .sfp_port[0].pin_detect = GPIO30_ACL_BIT3_EN,
+ .sfp_port[0].pin_los = GPIO_NA,
+ .sfp_port[0].pin_tx_disable = GPIO_NA,
+ .sfp_port[0].sds = 1,
+ .sfp_port[0].i2c = I2CBUS( GPIO39_I2C_SDA4, GPIO40_I2C_SCL3_MDC1 ),
+ // Right SFP port (6)
+ .sfp_port[1].pin_detect = GPIO50_I2C_SCL2_UART1_TX,
+ .sfp_port[1].pin_los = GPIO_NA,
+ .sfp_port[1].pin_tx_disable = GPIO_NA,
+ .sfp_port[1].sds = 0,
+ .sfp_port[1].i2c = I2CBUS( GPIO41_I2C_SDA3_MDIO1, GPIO40_I2C_SCL3_MDC1 ),
+ .reset_pin = GPIO_NA,
+ .high_leds = { .mux = LED_27 | LED_29, .enable = LED_28_SYS | LED_29 },
+ .port_led_set = { 0, 0, 0, 1, 0, 0, 0, 0, 1},
+ /* On Ethernet: Left led is orange, right green
+ * 100M and 1G give right led green with activity
+ * 2.5GBit adds solid left orange LED
+ * LEDs for the SFP-ports are the same as for the Ethernet ports
+ */
+ .led_sets = { { LEDS_2G5 | LEDS_LINK | LEDS_ACT,
+ 0,
+ LEDS_1G | LEDS_100M | LEDS_10M | LEDS_LINK | LEDS_ACT,
+ LEDS_2G5 | LEDS_LINK | LEDS_ACT
+ },
+ { LEDS_2G5 | LEDS_TWO_PAIR_1G | LEDS_1G | LEDS_500M | LEDS_100M | LEDS_10M |
+ LEDS_LINK | LEDS_ACT | LEDS_10G | LEDS_TWO_PAIR_5G | LEDS_5G | LEDS_TWO_PAIR_2G5,
+ LEDS_1G | LEDS_LINK,
+ LEDS_LINK,
+ LEDS_COL | LEDS_DUPLEX
+ }
+ }
+
+};
+```
+
+
diff --git a/doc/images/ghidra_add_block.png b/doc/images/ghidra_add_block.png
new file mode 100644
index 00000000..274b3a34
Binary files /dev/null and b/doc/images/ghidra_add_block.png differ
diff --git a/doc/images/ghidra_open.png b/doc/images/ghidra_open.png
new file mode 100644
index 00000000..9fc95db1
Binary files /dev/null and b/doc/images/ghidra_open.png differ
diff --git a/doc/images/ghidra_search_read_reg.png b/doc/images/ghidra_search_read_reg.png
new file mode 100644
index 00000000..214312a0
Binary files /dev/null and b/doc/images/ghidra_search_read_reg.png differ
diff --git a/rwintercept_zoerax_hg0402xg.asm b/rwintercept_zoerax_hg0402xg.asm
new file mode 100644
index 00000000..997cda66
--- /dev/null
+++ b/rwintercept_zoerax_hg0402xg.asm
@@ -0,0 +1,410 @@
+; Assembler file to intercept register writes/reads and PHY writes/reads
+; assemble with as31
+; Compile with
+; as31 -Fbyte -Orwintercept_zoerax_hg0402xg.hex rwintercept_zoerax_hg0402xg.asm
+; ./injector zoerax_hg0402xg.bin RTLPlayground/rwintercept_zoerax_hg0402xg.hex zoerax_hg0402xg_patched.bin
+; Start at 0x3b00, where there is always some space in BANK 0
+ .equ valptr, 0xc7
+
+
+ .equ SFR_DATA_0, 0xa7
+ .equ SFR_DATA_8, 0xa6
+ .equ SFR_DATA_16, 0xa5
+ .equ SFR_DATA_24, 0xa4
+
+; Intercept Register Write
+ .org 0x4d62
+ lcall reg_write
+ nop
+
+; Intercept Register Read
+ .org 0x4dad
+ lcall reg_read_head
+ .org 0x4e6f
+ lcall reg_read_tail
+
+; Intercept PHY write
+ .org 0x522c
+ lcall phy_write
+
+; Intercept PHY read
+ .org 0x526f
+ lcall phy_read_head
+ .org 0x5295
+ ljmp phy_read_tail
+
+; Intercept SDS write
+ .org 0x503c
+ lcall sds_write
+ nop
+
+; Intercept SDS read
+ .org 0x5061
+ lcall sds_read_head
+ nop
+ .org 0x507b
+ ljmp sds_read_tail
+
+;
+; BEGIN actual code at unused end of BANK0
+;
+ .org 0x3b00
+
+;
+; print the address of a register write. The address is in R6/R7. A and r4 can be scratched
+;
+reg_write:
+ mov a, #'R'
+ acall putc
+
+ mov a, r6
+ acall wbyte
+
+ mov a, r7
+ acall wbyte
+
+ mov a, #'-'
+ acall putc
+
+ mov dptr, #valptr
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #valptr + 1
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #valptr + 2
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #valptr + 3
+ movx a, @dptr
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+
+ mov a, r7
+ mov r5, a
+ mov a, r6
+ mov r4, a
+ ret
+
+;
+; print the address of a register read The address is in R2/R3. Registers 4-7 are scratch
+;
+reg_read_head:
+ mov a, #'r'
+ acall putc
+
+ mov a, r2
+ acall wbyte
+
+ mov a, r3
+ acall wbyte
+
+ mov a, #':'
+ acall putc
+ clr a
+ mov r7, a
+ mov r6, a
+ mov r5, a
+ mov r4, a
+ ret
+
+reg_read_tail:
+ mov dptr, #0xc5
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #0xc5 + 1
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #0xc5 + 2
+ movx a, @dptr
+ acall wbyte
+ mov dptr, #0xc5 + 3
+ movx a, @dptr
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+
+ mov dptr,#0xc5
+ ret
+
+
+;
+; print phy write options
+;
+phy_write:
+ mov a, #'P'
+ acall putc
+
+ mov a, r4
+ acall wbyte
+
+; phymask
+ mov a, r6
+ acall wbyte
+ mov a, r7
+ acall wbyte
+
+ mov a, #'.'
+ acall putc
+
+; dev_id?
+ mov a, r5
+ acall wbyte
+
+; x_phywrite_opt
+ mov dptr, #0xf1
+ movx a, @dptr
+ acall wbyte
+
+; The register going to SFR_C2 and SFR_C3
+ mov a, r2
+ acall wbyte
+ mov a, r3
+ acall wbyte
+
+ mov a, #':'
+ acall putc
+
+ mov dptr, #0xf2
+ movx a, @dptr
+ acall wbyte
+
+
+ mov dptr, #0xf3
+ movx a, @dptr
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+
+ mov dptr, #0xeb
+ mov r4, #0
+ ret
+
+;
+; print phy read options
+;
+phy_read_head:
+ mov r1, 4 ; actually r4
+
+ mov a, #'p'
+ acall putc
+
+ mov a, r7
+ acall wbyte
+
+ mov a, r5
+ acall wbyte
+
+ mov a, #'.'
+ acall putc
+
+ mov a, r2
+ acall wbyte
+ mov a, r3
+ acall wbyte
+
+ mov a, #':'
+ acall putc
+
+ mov r1, 7
+ mov a, r2
+ ret
+
+phy_read_tail:
+ mov r6, 4
+ mov a, r6
+ acall wbyte
+ mov a, r7
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+ mov r4, 6
+ ret
+
+
+;
+; print SDS write options
+;
+sds_write:
+ mov a, r4
+ mov r2, a ; R2 is scratch
+
+ mov a, #'Q'
+ acall putc
+
+
+ mov a, r7
+ acall wbyte
+ mov a, r5
+ acall wbyte
+ mov a, r3
+ acall wbyte
+
+ mov a, #':'
+ acall putc
+
+ mov dptr, #0xe1
+ movx a, @dptr
+ acall wbyte
+
+
+ mov dptr, #0xe2
+ movx a, @dptr
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+
+ mov a, r7
+ mov r1, a
+ mov a, r5
+ mov r7, a
+ mov a, r2
+ mov r4, a
+ ret
+
+;
+; print SDS read options
+;
+sds_read_head:
+ mov a, r4
+ mov r1, a ; R1 is scratch
+
+ mov a, #'q'
+ acall putc
+
+ mov a, r7 ; sds_index bit
+ acall wbyte
+ mov a, r5 ; SFR_94 (2 x r5 | r7)
+ acall wbyte
+ mov a, r3 ; SFR_93
+ acall wbyte
+
+ mov a, #':'
+ acall putc
+
+ mov a, r1
+ mov r4, a ; R1 is scratch
+
+ mov a, r7
+ mov r1, a
+ mov a, r5
+ mov r7, a
+
+ ret
+
+;
+; print SDS read result
+;
+sds_read_tail:
+ mov a, r4
+ mov r6, a ; R6 is returned and preserved
+
+ mov a, r7 ; = r7 = SFR_DATA_8
+ acall wbyte
+ mov r7, SFR_DATA_0
+ mov a, r7 ; SFR_DATA_0
+ acall wbyte
+
+ mov a, #' '
+ acall putc
+
+ mov a, r6 ; R6 is returned and preserved
+ mov r4, a
+ ret
+
+;
+; Write a byte as hex to serial
+;
+ nop
+ nop
+wbyte:
+ mov r4, a
+ swap a
+ anl a, #0x0f
+ mov dptr,#hex
+ movc a, @a+dptr
+
+ jb ET0, wbyte1
+ ret
+wbyte1:
+ jbc TI, wbyte2
+ sjmp wbyte1
+wbyte2:
+ mov SBUF,a
+
+ mov a, r4
+ anl a, #0x0f
+ mov dptr,#hex
+ movc a, @a+dptr
+
+wbyte3:
+ jbc TI, wbyte4
+ sjmp wbyte3
+wbyte4:
+ mov SBUF,a
+
+ ret
+
+;
+; Intercept isr_ext0
+;
+isr_ext0:
+ mov a, #'i'
+ acall putc
+ mov r0,#0x51
+ inc @r0
+ ret
+
+;
+; Intercept isr_ext1
+;
+isr_ext1:
+ mov a, #'I'
+ acall putc
+ mov r0,#0x25
+ mov a, @r0
+ ret
+;
+; Intercept isr_ext2
+;
+isr_ext2:
+ mov a, #'J'
+ acall putc
+ mov r0, #0x1e
+ mov a, #0xa
+ ret
+
+;
+; Intercept isr_ext3
+;
+isr_ext3:
+ mov a, #'j'
+ acall putc
+ mov r0,#0x1e
+ mov a,#0x14
+ ret
+
+;
+; Write a character to serial
+;
+ nop
+ nop
+putc:
+ jb ET0, putc1
+ ret
+putc1:
+ jbc TI,putc2
+ sjmp putc1
+putc2:
+ mov SBUF,a
+ ret
+
+hex:
+ .byte "0123456789abcdef"
+ .end