Running ‘hello world’ on Netis WF2419D router

September 19, 2015 in Code Snippets, DIY, Hardware, Microcontroller, Programming, RTL8196C, Tips & Tricks, Tutorial by admin

hardwareThis started as a quick fun project to do for breaking a bit apart from the usual daily stuff and mainly consist of building a ‘hello world’ application, install it on the modem’s flash and run it, instead of modem’s own firmware. The guinea pig will be a Netis WF2419D router I got cheaply some while ago, and just gathers the dust in the house.

If you wanna play with your modem, please note: You can render your modem unusable (this will, for sure, at least erase parts the existing data from the flash, leaving your modem unable to perform it’s modem duties). While probably there is a way of recovering from this (reinstalling the original firmware), if you manage overwrite the bootloader section of flash, it will become a paper weight (probably can be recovered by interfacing it with a JTAG or maybe removing the flash and copying data into it from another router’s flash). Anyway I take no responsability for your actions, broken modem, burned down house or whatever problem might happen because of this post.

Opening up the modem and getting out the circuit board, it’s packed with the following:

  • Realtek RTL 8196C SoC
  • RTL8192CE WLAN chip
  • Winbond W9812G6JH Ram (16 MB as 2M x 4Banks x 16 Bits SDRAM)
  • EON 4 MByte SPI flash

Looking at the board, there is a rather obvious line of 5 pins almost yelling ‘serial port here!’

board-top

After couple of tries, and searches on the net for various close enough routers, managed to figure out the correct pinout for Rx, Tx, Gnd.

rxtx

 

The serial port has to be set to 38400 8N1. Enters minicom and after after running

sudo minicom --device /dev/ttyACM0 -b 38400

(where /dev/ttyACM0 is the serial port of my ttl to UART converter) and resetting the router .. magic appears in the terminal:

<Probe SPI #0 > 
1.Manufacture ID=0x1c 
2.Device ID=0x30 
3.Capacity ID=0x16 
4.EON SPI (4 MByte)! 
5.Total sector-counts = 1024(sector=4KB)
6.spi_flash_info[0].device_size = 22
01_8198_SFCR(b8001200) =1f800000
01_8198_SFCR2(b8001204) =bb08000
01_8198_SFCSR(b8001208) =c8000000
---RealTek(RTL8196C)at 2013.08.21-12:58+0800 version v1.1c [16bit](390MHz)
....

followed by various messages displayed by the router booting up. Eventually ends up with a busybox command prompt, accessible without any password. Some information displayed using the available commands:

# cat meminfo
 total: used: free: shared: buffers: cached:
Mem: 11100160 9465856 1634304 0 712704 3182592
Swap: 0 0 0
MemTotal: 10840 kB
# cat cpuinfo 
system type : Philips Nino
processor : 0
cpu model : R3000 V0.0
BogoMIPS : 389.12
wait instruction : yes
microsecond timers : no
tlb_entries : 32
extra interrupt vector : no
hardware watchpoint : no
VCED exceptions : not available
VCEI exceptions : not available
ll emulations : 0
sc emulations : 0

Next step, check if is possible to update the firmware somehow.
It seems like is running RealTek bootloader, which can be entered either by pressing Esc at startup, or pressing the reset button while powering up the router. Doing so, ended up with this:

=============================
<Probe SPI #0 >
1.Manufacture ID=0x1c
2.Device ID=0x30
3.Capacity ID=0x16
4.EON SPI (4 MByte)!
5.Total sector-counts = 1024(sector=4KB)
6.spi_flash_info[0].device_size = 22
01_8198_SFCR(b8001200) =1f800000
01_8198_SFCR2(b8001204) =bb08000
01_8198_SFCSR(b8001208) =c8000000
---RealTek(RTL8196C)at 2013.08.21-12:58+0800 version v1.1c [16bit](390MHz)
---Escape booting by user
Set 8196C PHY Patch OK
<RealTek>

by pressing ?, a nice help is displayed with various commands:

<RealTek>help
----------------- COMMAND MODE HELP ------------------
HELP (?) : Print this help message
D <Address> <Len>
DB <Address> <Len>
DW <Address> <Len>
EW <Address> <Value1> <Value2>...
EB <Address> <Value1> <Value2>...
CMP: CMP <dst><src><length>
IPCONFIG:<TargetAddress>
AUTOBURN: 0/1
LOADADDR: <Load Address>
J: Jump to <TargetAddress>
FLW <dst_ROM_offset><src_RAM_addr><length_Byte> <SPI cnt#>: Write offset-data to SPI from RAM
PHYR: PHYR <PHYID><reg>
PHYW: PHYW <PHYID><reg><data>
<RealTek>

In order to upload a new firmware, the router will start a tftp server on a specific address (192.168.1.6 by default, however it can be changed with ipconfig <TargetAddress>). I’ve lost couple of hours trying to figure out why my router is not doing it, but without much success. After searching all over the internet, it seems like it should work, but it does not. So, I let it go, hoping there is an another way. And there is, even if not the fastest one.

The D, DB and DW commands dump memory values. Their counterpart EW and EB set memory zones. So if you want to overwrite various RAM memory zones, you can actually do it by issuing a series of DB or DW commands.
To transfer the memory in flash, you’ll have to have it in RAM anyways, even with tftp updates. Once it is in ram, you can issue the FLW command to write it into flash.

CAUTION: Do NOT write under 0x10000 in flash, as the bootloader and various configuration variables reside there. Do it and your board will probably become paperweight!!

Anyways, back to our horses. The flashing will occur like this: We’ll compile the application (but probably a stand-alone flat binary with a fix entry address). We’ll transform then this binary image to a minicom script, issuing for each byte the corresponding DB or DW sequence. Once it is in RAM we can jump to it and see if working. (later on we can flash it maybe to flash and having it run on router’ startup).

The program building this script can be downloaded from here: buildscript.c.

Next step, finding datasheets and a suitable binary.

A suitable toolchain was found after searching on openwrt forums (I think), and came together with the SDK itself, which made things a bit easier. Download can be found at:

http://sourceforge.net/projects/rtl819x/

The specific file I’ve downloaded was rtl819x-sdk-v2.5_2.5.1_2.5.2_boa.tar.gz although I see no reason why it should not work with the other download. The toolchain is located in the rtl819x-sdk-v2.5_2.5.1_2.5.2_boa/rtl819x/toolchain/rsdk-1.3.6-5281-EB-2.6.30-0.9.30/bin folder, so this has to be added to your current path, or the executable files from there made somehow available.

First, we need to create a linker file, which will output flat binary instead of elf. But for this we need to know where it will be in the memory, since the entry point will be at a fixed address. Eyeballing the API sources, it turned out that the RAM address (at least in initial RAM testing) was 0x80000000, so I gave an extra 0x10000 bytes for safetly (and since the boot code was displaying that does not find sys image at that offset), so I ended up eventually with this linker script:

OUTPUT_FORMAT(binary)
virt = 0x80010000;
phys = 0x80010000;
SECTIONS
{ .text virt : AT(phys)
 { code = .;
 *(.text)
 *(.rodata)
 *(.rodata*)
. = ALIGN(4096);
 d_code = .;
 *(.dtext) /* discardable code */
 
 . = ALIGN(4096); 
 }
 .data : AT(phys + (data - code))
 { 
 data = .;
 __CTOR_LIST__ = .;
 LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)
 *(.ctors)
 LONG(0)
 __CTOR_END__ = .;
__DTOR_LIST__ = .;
 LONG((__DTOR_END__ - __DTOR_LIST__) / 4 - 2)
 *(.dtors)
 LONG(0)
 __DTOR_END__ = .; 
 
 *(.data)
. = ALIGN(4096);
 *(.sdata)
 *(.sdata2)
. = ALIGN(4096);
 d_data = .;
 *(.ddata) /* discardable data */
. = ALIGN(4096); 
 }
.bss : AT(phys + (bss - code))
 { 
 edata = .;
 bss = .;
 *(.bss)
 *(COMMON)
 . = ALIGN(4096); 
 }
end = .; 
}

And quickly made an empty function ‘kernel’ doing nothing at all, except returning:

void entry() 
{
}

Compiling it:

$ rtl819x-sdk-v2.5_2.5.1_2.5.2_boa/rtl819x/toolchain/rsdk-1.3.6-5281-EB-2.6.30-0.9.30/bin/mips-linux-gcc -fno-pic -mno-abicalls -I./ -c start.c
$ rtl819x-sdk-v2.5_2.5.1_2.5.2_boa/rtl819x/toolchain/rsdk-1.3.6-5281-EB-2.6.30-0.9.30/bin/mips-linux-ld -T kernel.ld -nostdlib -nodefaultlibs -e entry start.o -o kernel.elf

(while the file is kernel.elf, it is actually a flat binary)

and convert it to minicom script for uploading:

$ tools/bs kernel.bin kernel.scr
Input file is kernel.bin
Output file is kernel.scr
Converted to script 8210 (0x00002012) bytes, to be written at address 0x80010000

where tools/bs is the buildscript discussed earlier.

Flashing is done with this command line:

sudo minicom --device /dev/ttyACM0 -b 38400 -S kernel.scr

After a quite long period (about 1 minute or so) image is transfered to board’s RAM, at 0x80000000. We can execute it with j 80000000:

<RealTek>
<RealTek>j 80010000
---Jump to address=80010000
<RealTek>

What did just happen? Hopefully the boot loader called our empty function, which returned, so we are back in the boot loader. Just to be sure, let’s make the function never return, just to see everything is ok, and our code is actually executing:

void entry() 
{
    while(1) {
    }
}

After running the previos build sequences and flashing the image to RAM and jumping to it, we get this:

<RealTek>j 80010000
---Jump to address=80010000

Yep, seems legit. We have an infinite loop. Let’s try to send some message on the serial port. Digging in the SDK source, is not really difficult to find serial port initialization and putchar:

void prom_console_init(void)
{
    /* 8 bits, 1 stop bit, no parity. */
     REG8(UART0_LCR) = CHAR_LEN_8 | ONE_STOP | PARITY_DISABLE;
    /* Reset/Enable the FIFO */
     REG8(UART0_FCR) = FCR_EN | RXRST | TXRST | CHAR_TRIGGER_14;
    /* Disable All Interrupts */
     REG8(UART0_IER) = 0x00000000;
    /* Enable Divisor Latch */
     REG8(UART0_LCR) |= DLAB;
    /* Set Divisor */
     REG8(UART0_DLL) = (SYSCLK / (BAUDRATE * 16) - 1) & 0x00FF;
     REG8(UART0_DLM) = ((SYSCLK / (BAUDRATE * 16) - 1) & 0xFF00) >> 8;
    /* Disable Divisor Latch */
     REG8(UART0_LCR) &= (~DLAB);
}
int prom_putchar(char c)
{
     unsigned int busy_cnt = 0;
    do
     {
        /* Prevent Hanging */
        if (busy_cnt++ >= 30000)
        {
           /* Reset Tx FIFO */
           REG8(UART0_FCR) = TXRST | CHAR_TRIGGER_14;
           return 0;
        }
     } while ((REG8(UART0_LSR) & LSR_THRE) == TxCHAR_AVAIL);
    /* Send Character */
     REG8(UART0_THR) = c;
    return 1;
}

Note: Since we are generating a flat binary, we have to make sure our entry function is the first one in the file, otherwise for some reason things end up broken.
Note2: These will require platform.h to be included, as it contains various macros and definitions.

Having these, let’s display hello world:

void entry()
{
    prom_console_init();
    prom_putchar('h');
    prom_putchar('e');
    prom_putchar('l');
    prom_putchar('o');
    prom_putchar(' ');
    prom_putchar('w');
    prom_putchar('o');
    prom_putchar('r');
    prom_putchar('l');
    prom_putchar('d');
    prom_putchar('!');
    while(1) {
   }
}

After recompiling, flashing it in the RAM, and jumping to it, we get this:

<RealTek>j 80010000
---Jump to address=80010000helo world!

Success. We displayed hello world.

Rebooting the router will revert to it’s original purpose.

NOTE: After this point, your router won’t start up with it’s routing firmware, but instead with your hello world!!!!. make sure you really want this.

So, we have displayed hello world, however when router restarts, our ‘firmware’ is lost. Let’s try to fix this, first by flashing our code into router’s flash.

flw 10000 80010000 2000

where 10000 is the address where we want to store our image, 80010000 is the RAM area from where to store the image and 2000 is the length of our image:

<RealTek>flw 10000 80010000 2000
Write 0x2000 Bytes to SPI flash#1, offset 0x10000<0xbd010000>, from RAM 0x80010000 to 0x80012000
(Y)es, (N)o->y
..
<RealTek>

After restarting the router:

<Probe SPI #0 >
1.Manufacture ID=0x1c
2.Device ID=0x30
3.Capacity ID=0x16
4.EON SPI (4 MByte)!
5.Total sector-counts = 1024(sector=4KB)
6.spi_flash_info[0].device_size = 22
01_8198_SFCR(b8001200) =1f800000
01_8198_SFCR2(b8001204) =bb08000
01_8198_SFCSR(b8001208) =c8000000
---RealTek(RTL8196C)at 2013.08.21-12:58+0800 version v1.1c [16bit](390MHz)
no sys signature at 00010000!
....

woopsie, no cookie for this. Seems like we’re missing some things in our firmware. This took the longest to figure out, but eventually after searching the net and
digging in the sources, turned out there are some headers which the bootloader looks for when booting up. These headers are created by a program called cvimg, which is included in the sdk. I copied it with it’s dependencies (couple of include files) and compiled it. Now the build process is something like this:

$ ../rtl819x-sdk-v2.5_2.5.1_2.5.2_boa/rtl819x/toolchain/rsdk-1.3.6-5281-EB-2.6.30-0.9.30/bin/mips-linux-gcc -fno-pic -mno-abicalls -I./ -c start.c
$ ../rtl819x-sdk-v2.5_2.5.1_2.5.2_boa/rtl819x/toolchain/rsdk-1.3.6-5281-EB-2.6.30-0.9.30/bin/mips-linux-ld -T kernel.ld -nostdlib -nodefaultlibs -e hal_entry start.o -o kernel.elf
$ tools/cvimg linux kernel.elf kernel.bin 0x80010000 0xbd010000
Generate image successfully, length=8194, checksum=0xbb3a
$ tools/bs kernel.bin kernel.scr
Input file is kernel.bin
Output file is kernel.scr
Converted to script 8210 (0x00002012) bytes, to be written at address 0x80010000

Note: The previous program or the build process was not changed in any way, the only addition is the

tools/cvimg linux kernel.elf kernel.bin 0x80010000 0xbd010000

kernel.elf is the entry file to be patched, kernel.bin is the output file 0x80010000 is the start address of the code and 0xbd010000 is the address in flash. This target address is displayed when flashing:

<RealTek>flw 10000 80010000 2012
Write 0x2012 Bytes to SPI flash#1, offset 0x10000<0xbd010000>, from RAM 0x80010000 to 0x80012012
(Y)es, (N)o->y
...
<RealTek>

Now we have it in flash memory of the router. Jumping to it however is not working anymore:

<RealTek>j 80010000
---Jump to address=80010000
Undefined Exception happen.

But when restarting the router, we get this:

<Probe SPI #0 >
1.Manufacture ID=0x1c
2.Device ID=0x30
3.Capacity ID=0x16
4.EON SPI (4 MByte)!
5.Total sector-counts = 1024(sector=4KB)
6.spi_flash_info[0].device_size = 22
01_8198_SFCR(b8001200) =1f800000
01_8198_SFCR2(b8001204) =bb08000
01_8198_SFCSR(b8001208) =c8000000
---RealTek(RTL8196C)at 2013.08.21-12:58+0800 version v1.1c [16bit](390MHz)
Jump to image start=0x80010000...helo world!

Success. Our code is now in flash and runs when the router starts.

From here we can basically program it to do whatever we want, since the datasheets for both the RTL 8196C SoC and the RTL8192CE WLAN chip are available to be downloaded, and so is the SDK together with the toolchain. I’ve ended up with a relatively small (~7.5 x 11cm) board with 16MB ram, 4MB flash and wireless chipset, really cheap (~10-15eu?)