Intention

My original intention was to try and learn developing for the ARM
family of microcontroller cores, especially ARM7TDMI. I could have
bought a development board for hundreds of EUR, but soon found that
there are consumer products available off the shelves that provide
almost the same functionality for less money.

After some searching and looking at pictures that people took from
inside their devices, it appeared that a large number of DSL routers and
WLAN access points are built around ARM7 or ARM9 cores. Many use
chips from Samsung (e.g. S3C4510 with ARM7TDMI embedded)
and Conexant (e.g. CX82100 with ARM940T; or CX84200 with ARM7TDMI,
formerly made by ADMtek, called ADM5106). The Samsung chips are
best documented, while information about Conexant is rare.

Another thing to look for is the availability of some method to
upload and run selfmade code on the device. Often the boards in the
routers provide the ARM standard JTAG connector (2x7 pin header).
But from documentation available on the net, I learned that ZyXEL
routers come loaded with a "BootExtension" that can be used to
up- and download firmware via serial console port, and even to
inspect and modify RAM contents using simple AT commands (like
those AT commands used to control Hayes modems).

Finally I bought a Netgear RT311 from eBay (EUR 16!). It is
almost identical to ZyXEL Prestige 310, and very similar to
devices like Netgear RT314, MR314, and other ZyXEL Prestige.
The most important technical specs (from my point of view) are:

Samsung S3C4510X01 controller, running at 50 MHz

1 MByte flash memory (1x Intel TE28F800B3B, 16x512 kBit)
The firmware knows about the TE28F160C3B (16 MBit == 2 MByte),
but I can't see on the board
if A19 is connected (maybe even A20 or A21 are).
Probably it is possible to replace the flash chip with a bigger one.

4 MByte SDRAM (2x EliteMT M12L16161C)
There are to similar places unpopulated, not even resistors and
capacitors are present, but it is probably possible to add another
two SDRAM chips there. That would result in 8 MByte memory.

1 RS232 console port

1 Ethernet WAN (10 MBps) via RTL8019AS MAC+PHY

1 Ethernet LAN (10/100 MBps) using S3C4510's MAC and LSI 80225/B PHY

1 JTAG 2x7 pin header (unpopulated)

Software: ZyNOS BootBase, BootExtension, HTP and RAS

This is a list of devices that probably use ZyNOS BootExtension as
well; (please send corrections and updates, especially I'd like to know
which ones have a serial console port or at least are prepared to have one)

ZyXEL Prestige 100, 128, 202; D-Link DI-106; Netgear RT328, RH348

ZyXEL Prestige 310, Netgear RT311, Teledat T-DSL Router

ZyXEL Prestige 314, Netgear RT314, Teledat 400

ZyXEL Prestige 316, Netgear MR314

Other Netgear: FR314, RP114, RP614, RP344

The ZyNOS ("ZyXEL Network OS"?) firmware in these routers is based on
Kadak AMX RTOS, the same
OS that was basis for PalmOS up to version 4.x. I don't know if that's
still the case for newer devices that don't use an ARM core anymore.

Accessing the router

Using the console port

Too lazy to build a JTAG cable, I tried the serial console port first.
Use a normal serial cable (not a Nullmodem cable) to connect it to your
PC. The initial baud rate can be configured permanently to any standard
rate you want up to 115200 bps; so you might need several tries until
you get a message after power-on similar to this one:

Unless you hit a key, the RAS code boots and the router just works as
it was meant to. But IF you hit a key, you're in debug mode. That is,
you're talking to ZyXEL's "BootExtension" code. Try typing the command
"ATHE" and you get this output:

Now the above list does mention a lot of useful commands, but I thought
there were commands to write to RAM? And what is this obscure "DebugFlag"?

At least the ATDU command allowed me to dump the whole RAM contents and
then disassemble them (using arm-objdump). Locating the code that processes
the ATEN and ATSE commands took me some time, but, well, learning ARM
assembly language was one of my goals when I bought the device ;-)

The DebugFlag and how to change it

The ATENx[,y] command obviously wants a "password" y. If you used it without a
password, it always sets the DebugFlag to zero, regardless of the value x.
"Officially", the password shall be a hex value, derived from the output of
the ATSE command. Now after understanding the code, it became clear that
ATSE initializes an internal variable (the "seed") from the ARM's TCNT0
timer value, and outputs this value together with the last three octets
of the device's (LAN) MAC address.

Further digging in the code revealed how to compute the password from the
ATSE output. Only the first three octets (the "seed") and least significant
3 bits of the (LAN) MAC address are important:

But this is still utterly complex. Just don't use ATSE, and the seed will
be just zero after booting. Then, only the 3 least significant bits of the
(LAN) MAC address determine the password.
You can gather the MAC address from
the ATSH output. It is always the same unless you change the router...

MAC Addressof LAN port

Password y for ATENx,y

...0 or ...8

10F0A563

...1 or ...9

887852B1

...2 or ...A

C43C2958

...3 or ...B

621E14AC

...4 or ...C

310F0A56

...5 or ...D

1887852B

...6 or ...E

8C43C295

...7 or ... F

C621E14A

New possibilities if DebugFlag is set

On my RT311, I can now enable the DebugFlag using

ATEN1,1887852B

And now when the DebugFlag is set, the ATHE instantly show some more
possibilities (only the new ones listed here):

Booting uCLinux

Please note: The following text is slightly out-of-date. In 2.4.24-uc0, a patch from me was integrated that adds
better support for the router. A description is here in uclinux CVS. It adds the "Support ZyXEL BootExtension" configuration option which you should check!

To build a kernel for the router, a few minor changes have to be made
in the kernel source tree. First, uncomment these lines in the top-level
Makefile to declare that you're building a kernel for the "armnommu"
architecture, and that you're using a cross-compiler:

ARCH := armnommu
CROSS_COMPILE = arm-elf-

We're going to build a "SNDS-100" kernel. That is the name of a
well-documented development board that has the S3C4510 processor
onboard. But a few definitions need to be changed to make it run
on the router, because the router is a little different from the
SNDS-100:

RAM and Flash size are different.
In the arch/armnommu/config.in, modify the memory
settings in the section if [ "$CONFIG_BOARD_SNDS100" = "y" ]:

We want to upload and start the kernel at a specific
address in RAM.
In the arch/armnommu/Makefile, modify the address
in the section ifeq ($(CONFIG_BOARD_SNDS100),y):

TEXTADDR = 0x00020000

You may change the baud rate of initial console to something other
than 19200 in drivers/char/serial_samsung.c, at the top of
serial_console_setup:

int baud = 115200;

Note: To build a compressed image (zImage), more changes are
required because the Makefiles in arch/armnommu/boot/compressed
aren't properly prepared to produce big-endian code. Therefore we
stay with an uncompressed image for now...

After making the above changes, go ahead and configure the kernel
using "make menuconfig" or "make xconfig". For the first try, use
the following settings:

Do a "make clean dep Image". The result hopefully is a
file arch/armnommu/boot/Image. This is the kernel
(uncompressed) that can now be uploaded to the router.
Determine its size in hex, so we can issue the correct
upload command at the router later.

Now boot the router, enter Debug mode and enable the DebugFlag
(see above). Issue the ATUP command to upload the kernel
at address 0x20000. Assuming the Image file size is 429540
bytes, i.e. 0x68DE4 bytes in hex, the command would be this:

ATUP20000,68DE4

Once the Xmodem upload is complete, make sure you have the
serial speed (baud rate) set to 19200 bps (e.g. by issuing the
command ATBA2) (or whatever is configured in drivers/char/serial_samsung.c), and start the kernel at 0x20000:

Big Endian vs. Little Endian

My router is set to run in Big Endian mode (BE). This introduces a lot of
problems, because support for Big Endian targets is still work in
progress in uClinux for armnommu. Some of my patches to get this completely
working have been integrated in uClinux 2.4 CVS in January 2004, especially
regarding the checksumming code in these files:

I'm yet working on patches for the s3c4510 ethernet driver - only two
lines need change for Big Endian, but activation of the PHY also needs
to be integrated and that'll take time until I have a clean patch:

Network devices

The LSI80225 PHY for the LAN interface, driven by the S3C4510X's internal EMAC,
has to explictly
enabled by driving I/O pin #6 low (IOPMOD[6]=1 and IOPDATA[6]=0).
Then, the drivers/net/s3c4510.c with
small fixes for Big Endian mode will just run fine. Haven't done any
stress tests however...

The second (WAN) network interface uses a RTL8019AS MAC with integrated
PHY. This one has to be enabled by driving I/O pin #2 low
(IOPMOD[2]=1 and IOPDATA[2]=0). Its interrupt is connected to the
S3C4510X's external interrupt line #3; the setting IOPCON[19:15]=11101
is usable.

The RTL8019AS is connected in external I/O bank 1 using a bus width
of 16 bit (EXTDBWTH[22:23]=10). If I/O base address was set to 0x3F00000
(REFEXTCON[9:0]=0x3F0), the registers appear starting at 0x3F045C0.

To match the big endian host,
the MSB of the RTL8019AS data bus is connected to the LSB of the
S3C4510X data bus. Therefore the RTL8019AS cannot be accessed
with the S3C4510 bus width set to 8 bit (the LSB of RTL8019AS data,
e.g. register contents, wouldn't be visible to the S3C4510).

In 16 bit mode however, the (byte-wide) registers appear in address
space on half-word boundaries, not byte boundaries, and the mapping
in the ne.c / 8390.c driver code has to be tweaked. The address on
S3C4510 bus appears at the RTL8019AS shifted right by one;
i.e. if you want to read a value from 0x2E0, you have to
access 0x5C0 instead.

The LEDs

The TEST LED is controlled by I/O pin #1. It lights when IOPMOD[1]=1
and IOPDATA[1]=0.
The other LEDs are probably hardwired to the respective output pins from
the RTL8019AS, power supply, and WAN PHY.

Make a root filesystem etc.

This is on my to-do list. In the meantime, I decoded the various
structures used by the BootExtension - see the following section.
Here the intention was to become able to construct a valid firmware
image, without ZyNOS RasCode but including my own kernel instead.
The presented information should be sufficient to achieve this now.

ZyNOS BootExtension data

Memory map

The flash is visible in the ARM7's address space at 0x02000000 and
also at 0x06000000. You can get a directory of its contents (as well
as the proposed destinations in RAM) using the ATMP command:

The first two sections in $ROM, i.e. BootBas and DbgArea,
cannot be changed with the standard upload commands.
ATLC targets the 2x8kByte DbgArea+RomDir2 at offset 0x4000,
and ATUR puts the received data into flash starting at offset 0x8000.

Data structures

Firmware images, named "ROMIO image" in the output of ATMP, always
begin with a 0x30 bytes header. It contains the information described
in the following table. Note that numbers are stored big-endian, i.e.
the most significant byte appears at the lowest address!

Offset

Size[bytes]

Meaning

0

4

Start address of boot code in RAM

4

2

All zero (0)

6

3

Three ascii chars "SIG"

9

1

The value 0x03(?)

10

4

Size[bytes] of data following this header

14

4

All zero (0)

18

1

Flags (0x40?)

20

2

Expected checksum of data following this header

22

2

All zero (0)

24

15

ASCIIZ Code version

39

4

Absolute location of memMapTab memory map table

43

5

All zero (0)

The memory map, which is parsed e.g. when you issue the ATMP command,
consists of a 0x18 bytes header, followed by the table entries
(each 0x18 bytes), and ASCII text for the "$USER" section (exactly
what ATMP prints at the end following the $USER title).
The header looks like this:

Offset

Size[bytes]

Meaning

0

2

Number of memMapTab entries following

2

4

Location of start of $USER section

6

4

Location behind end of $USER section

10

2

Expected checksum of the memMapTab data following the header, including $USER section

12

12

All zero (0)

Each entry in the memMapTab describes an "object" somewhere in memory:

When MSB is set in the type byte, this means the entry is within the
$RAM section, otherwise it is for $ROM. This is a complete list of
types as ATMP displays them:

Type

Section

Name (as ATMP says)

1

$ROM

ROMIMG

4

$ROM

ROMBIN

5

$ROM

ROMDIR

7

$ROM

ROMMAP

128

$RAM

RAM

129

$RAM

RAMCODE

130

$RAM

RAMBOOT

Where ROMBIN entries point to, you'll find another 0x30 bytes header
describing the data immediately following that header, as shown below.
The Size given in the memMapTab for these entries specifies the space
reserved for the data, which may be more than the actual size
as specified in the header there:

Offset

Size[bytes]

Meaning

0

6

All zero (0)

6

3

Three ascii chars "SIG"

9

1

Object type (0x04 for ROMBIN)

10

4

Size[bytes] of uncompressed data

14

4

Size[bytes] of compressed data

18

1

Flags (0xE0?)

20

2

Expected checksum of uncompressed data

22

2

Expected checksum of compressed data

24

15

ASCIIZ Code version

39

9

All zero (0)

If the flags have bit 7 set (0x80), this means the following data is stored
in compressed form. Compressed data is stored as contiguous chunks of LZW
encoded data; each starting with a halfword (16 bit) specifying the size of
the chunk when uncompressed (2048 except for the last chunk), followed by a
halfword specifying the size of the compressed data in the chunk, and
finally followed by the compressed data itself. If you concatenate the data
parts of all chunks, you can decompress them using the LZW demo code from
Mark Nelson. And that code also would allow you to compress your own data
to build your own firmware...

Note for the future: I took a look at firmware of a newer device, the
ZyAIR B-2000 V.2. The firmware still uses the same structure with HTPCode
and RasCode and memMapTable etc., albeit compressed data there is not
any longer stored LZW encoded as described above, but simply compressed
with bzip2. You can run bunzip2 immediately on the data following the
SIG header to get the uncompressed code.

Finally, while looking at all the data, I found the info at the
end of the BootBase area that defines some of the info displayed by the
ATSH command:

Offset (hex)

Size[bytes]

Meaning

0x3F72

21

SNMP MIB level & OID

0x3F87

1

Zero (0)

0x3F88

8

Values 0x00020000 and 0x00100000(?)

0x3F90

32

ASCIIZ Vendor name

0x3FB0

32

ASCIIZ Product Model

0x3FD0

4

RAS ROM address

0x3FD4

2

Zero (0)

0x3FD6

1

0x05 (System type? Flash type?)

0x3FD7

1

Zero (0)

0x3FD8

22

Other feature bits

0x3FF6

1

Main feature bits

0x3FF7

1

Zero (0)

0x3FF8

6

MAC Address

0x3FFE

1

Default Country Code

0x3FFF

1

BootExtension DebugFlag

Computing checksums

The following code yields correct checksums as used in the ROMIO,
ROMBIN and memMapTab structures (might be written prettier, but this
is similar to what the BootExtension code actually does):

Hardware notes

2-pin header J4

This connects I/O "p0" of S3C4510 to ground. When connected, the
router at startup doesn't ask to press "any" key to enter Debug
Mode, but to press "ESC" key. More important is that it starts HTPCode
(that is the Hardware Test Program) afterwards instead of RasCode
(the router application).

2x7-pin header JP1

That's a standard ARM JTAG header. This is its pinout:

JTAG signal

Pin No.

Pin No.

JTAG signal

TVcc (3.3V)

1

2

GND

nTRST

3

4

GND

TDI

5

6

GND

TMS

7

8

GND

TCK

9

10

GND

TDO

11

12

nRESET (not connected!)

TVcc

13

14

GND

As in the SNDS100 reference board circuit, nRESET at the JTAG header
isn't connected to anything, but the nTRST causes both nTRST and
nRESET to be asserted. Thus if you
own a router without RESET button, you could add one yourself
(short nTRST and GND).

Unfortunately this means that asserting nTRST
too long would cause a full system reset. I ran into problems with
this when I tried the openwince-jtag tools. The armtool from Erwin
Authried (midori Linux distribution) works better and I was able
to upload an Image to the router with it (3 times faster than with
a 115200 bps serial Xmodem upload), but it ended up having the
bytes reversed (again: little vs. big endian hassles...)

2x6-pin header S1

This probably could be populated with a switch to change the wiring
on the LAN port, to emulate a cross-connected Ethernet cable.

I/O pin assignments

These have been mentioned in the text above already, but I'll
summarize them in the following table. IOPMOD and IOPDATA registers
are used to access the I/O pins.