Netgear® DM111P hacking

This document describes the process of building a Debian-based custom firmware image for the DM111P ADSL2+ Ethernet modem. The original intent was simply to adapt the original firmware to provide IPv6 connectivity -- however digging a bit into the problem revealed a large optimisation window, be it by removing services that I deemed unnecessary, or by adding other, more useful services (such as SNMP).

We will adopt an incremental approach here : first we'll describe the device in some details, then we'll cover the compilation and booting of a custom kernel, then we'll examine a possible root filesystem layout, and finally we'll step through the creation of a full firmware image, which will be flashed. Needless to say, the whole setup will be based on GNU/Linux :-).

Description of the Netgear® DM111P

General description

The DM111P acts as an ADSL2+ modem by bridging the (ATM-based) ADSL connection to its integrated Ethernet 10/100 port  there are several ways to do this, which you can configure using the integrated web interface  basically, either you let the "modem" handle the PPP connection (PPPoA Bridging or PPPoE Bridging), or you handle the connection from the router to which the modem is attached (RFC 2684 Bridging). Which one you choose is a t your discretion, but will generally depend on your ISP, the router you have, and the functionality you want (e.g., if the DM111P itself does not support IPv6, the you'll have to negotiate your PPP session from the router, and hence use RFC 2684 bridging). Note that if you have the choice, PPPoA encapsulation is generally the way to go since it allows for a full Ethernet MTU.

The board is based on a Broadcom BCM6338 chipset, which combines an ADSL2+ transceiver, an Ethernet controller, and a MIPS32 CPU. The main components on this board are shown on the schema below.

Schema of the DM11P board

We'll be particularly interested in the serial connector, which allows one to have access to Broadcom's Common Firmware Environment (CFE) -- this is the topic of the next section. Documentation about the BCM6338 also mentions JTAG capabilities, but I haven't seen any connectors which could provide JTAG access, so just forget it for now.

Accessing the serial console

The serial interface is a 3.3V TTL, left-to-right pin assignment being Vcc, Ground, Tx, and Rx. To be able to connect to it from a standard PC serial interface, you'll need a 3.3V serial to RS-232 level converter, which can be purchased, assembled from a kit, or built from scratch.

If you wish to build the level converter yourself, you'll need a MAX3232 and five 0,1µF capacitors -- for reference, a schema is shown below.

3.3V serial to RS-232 level converter

Once you're done, you should be able to get a console connection : fire up minicom -- the serial port setup is 115200 bauds, 8 bits data, no parity, 1 stop bit. If everything is OK, you should see the DM111P booting :

CFE version 1.0.37-0.6.8 for BCM96338 (32bit,SP,BE)
Build Date: Tue Apr 10 10:08:13 CST 2007 (michaelc)

Flash Config: CS0(1fc00008,1f),Base(bfc00000),Size(2MB)
Ethernet Network Device: Internal PHY
Auto-negotiation timed-out

Board IP address                : 192.168.0.1  
Host IP address                 : 192.168.0.2  
Gateway IP address              :   
Run from flash/host (f/h)       : f  
Default host run file name      :   
Default host flash file name    : bcmModelName_fs_kernel  
Boot delay (1-9 seconds)        : 1  
Board Id Name                   : RTA1320  
Psi size in KB                  : 24
Number of MAC Addresses (1-32)  : 1  
Ethernet MAC Address            : 00:14:6c:2b:94:31  
Memory size in MB               : 8

==== Press space key to stop auto run (1 seconds) ====
Auto run second count down(before hit space key):
Code Address: 0x80010000, Entry Address: 0x80171018
Decompression OK!
Entry at 0x80171018
Closing network.
Starting program at 0x80171018
Linux version 2.6.8.1 (compiled by michaelc) (gcc version 3.4.2) #1 Fri Jun 1 12:20:59 CST 2007
man/device id ec/2277
FLASH_BASE bfc00000,blk 0
Total Flash size: 2048K with 39 sectors NVRAM @0 block
Scratch pad is not used for this flash part.
CPU revision is: 00029010
Determined physical RAM map:
 memory: 007a0000 @ 00000000 (usable)
On node 0 totalpages: 1952
  DMA zone: 1952 pages, LIFO batch:1
  Normal zone: 0 pages, LIFO batch:1
  HighMem zone: 0 pages, LIFO batch:1
Built 1 zonelists
Kernel command line: root=31:0 ro noinitrd
brcm mips: enabling icache and dcache...
Primary instruction cache 16kB, physically tagged, 2-way, linesize 16 bytes.
Primary data cache 8kB 2-way, linesize 16 bytes.
PID hash table entries: 32 (order 5: 256 bytes)
Using 120.000 MHz high precision timer.
Dentry cache hash table entries: 2048 (order: 1, 8192 bytes)
Inode-cache hash table entries: 1024 (order: 0, 4096 bytes)
Memory: 6096k/7808k available (1226k kernel code, 1692k reserved, 181k data, 68k init, 0k highmem)
Calibrating delay loop... 239.20 BogoMIPS
Mount-cache hash table entries: 512 (order: 0, 4096 bytes)
Checking for 'wait' instruction...  unavailable.
NET: Registered protocol family 16
Can't analyse prologue code at 801411fc
1.parse options inodes 764 block 764
PPP generic driver version 2.4.2
NET: Registered protocol family 24
Using noop io scheduler
bcm963xx_mtd driver v1.0
rootfs_addr 0bfc10100
brcmboard: brcm_board_init entry
bcm963xx_serial driver v2.0
NET: Registered protocol family 2
IP: routing cache hash table of 512 buckets, 4Kbytes
TCP: Hash tables configured (established 512 bind 1024)
NET: Registered protocol family 1
NET: Registered protocol family 17
NET: Registered protocol family 8
NET: Registered protocol family 20
VFS: Mounted root (squashfs filesystem) readonly.
Freeing unused kernel memory: 68k freed
init started:  BusyBox v1.00 (2005) multi-call binary
Algorithmics/MIPS FPU Emulator v1.5
tmpfs size 262144
1.parse options inodes 773 block 64


BusyBox v1.00 (2005) Built-in shell (msh)
Enter 'help' for a list of built-in commands.


Loading drivers and kernel modules... 

atmapi: module license 'Proprietary' taints kernel.
blaadd: blaa_detect entry
adsl: adsl_init entry
Broadcom BCMPROCFS v1.0 initialised
Internal PHY
Broadcom BCM6338A2 Ethernet Network Device v0.3 Jun  1 2007 12:19:47
Config Internal PHY Through MDIO
BCM63xx_ENET: Auto-negotiation timed-out
BCM63xx_ENET: 10 MB Half-Duplex (assumed)
eth0: MAC Address: 00:14:6C:2B:94:31

pSdramPHY=0xA07FFFF8, 0x5344 0x14882240
AdslCoreHwReset: AdslOemDataAddr = 0xA07FB404

==>   Netgear DM111 ADSL2+ Modem Software Version: 3.30j_A2pB021g.d19b    <==

pvc2684d: Interface "nas_8_35" created sucessfully

pvc2684d: Communicating over ATM 0.8.35, encapsulation: LLC

ip_tables: (C) 2000-2002 Netfilter core team
ip_conntrack version 2.1 (61 buckets, 0 max) - 368 bytes per conntrack
ip_conntrack_pptp version 2.1 loaded
ip_nat_pptp version 2.0 loaded
ip_conntrack_h323: init 
ip_nat_h323: initialize the module!
ip_conntrack_rtsp v0.01 loading
ip_nat_rtsp v0.01 loading
ip_nat_pptp version 2.0 unloaded
ip_conntrack_pptp version 2.1 unloaded
unregistering port 554
board_ioctl: boot complete!

As indicated, you can force CFE to give you a prompt by hitting 'Space', after which you should be able to do some useful things, such as booting an alternate kernel via TFTP or re-flashing a bricked modem.

Firmware Layout

From time to time, Netgear provides firmware images to update the modem (see there). Careful examination of those files leads to a quite common firmware layout, which is used in many SOHO modem/routers. The schema below summarises the global layout of a firmware image :

+---------------------------------+ 0x00000000
|                                 |
|         Broadcom Header         | Fixed size = 256 bytes
|                                 |
+---------------------------------+ 0x00000100
|                                 |
|         CFE boot loader         | Size in header (cfeLen)
|                                 |
+---------------------------------+ 0x00000100 + cfeLen
|                                 |
|         SquashFS Root           | Size in header (rootfsLen)
|                                 |
+---------------------------------+ 0x00000100 +cfeLen + rootfsLen
|                                 |
|         Compressed kernel       | Size in header (kernelLen)
|                                 |
+---------------------------------+ 

The Broadcom header

The Broadcom header is well-defined, and extensively described in the OpenWrt bcmTag.h header file. The following table explains the different fields and reports the respective values in images provided by Netgear :

NameLengthDescriptionFW version 3.28RFW version 3.29UFW version 3.30J
tagVersion4Header version666
signature_120Company infoN/A??
signature_214Additional infoN/A??
chipId6Chip Id.N/A??
boardId16Board Id.RTA 1320RTA 1320RTA 1320
bigEndian2Big endian flag111
totalImageLen10Size of the image (minus header)161385016175091630076
cfeAddress12CFE boot loader address (if not null)0xbfc000000xbfc000000xbfc00000
cfeLen10CFE boot loader size619926220062264
rootfsAddress12Root filesystem address (if not null)0xbfc101000xbfc101000xbfc10100
rootfsLen10Root filesystem size108544010854401097728
kernelAddress12Kernel address (if not null)0xbfd191000xbfd191000xbfd1c100
kernelLen10Kernel size466418469869470084
dualImage2Dual image flag000
inactive2Inactive image flag000
reserved?Reserved???
imageValidationToken20Image checksum0x0fcde49b0xf91c2b950xa5d3bca7
tagValidationToken20Header checksum0x783295dc0xc4fe2f990x6b937861

The contents of the signature_1, signature_2, chipId, and reserved fields are not yet totally clear : the first three fields seem to be unused in firmware versions anterior to 3.30j, but used afterwards, as is the case for the first 48 bytes of the field. Following those bytes, however, it seems we have a significant 4-byte field at offset 50 (whose meaning is unknown to me), and a very important field at offset 54, which is the checksum of the root filesystem and the kernel sections combined -- this checksum will be checked at boot-time, so if you don't include it, the modem will simply refuse to boot.

I will probably have to examine Netgear's host utilities for more details; if you're curious, the full fields contained in version 3.30J are listed below :

$ ./bcm_fw_extract -v cfe-dm111-v330j_a2pb021g 
[0x00] Header version: 6
[0x04] Vendor signature (1): 
        69 80 8e ea 10 c5 ca c8 45 31 9a dc 0f 8b a9 f5 
        00 e1 6d cb 
[0x18] Vendor signature (2): 
        b4 ae 26 aa a9 bc c8 e5 df 12 d7 b8 5b 2e 
[0x26] Chip Id.: 
        bf 9d f6 c6 ec 5a 
[0x2c] Board Id.: RTA1320
[0x3c] Big endian ?: 1
[0x3e] Total img size: 0x0018df7c
[0x48] CFE address: 0xbfc00000
[0x54] CFE size: 0x0000f338
[0x5e] Root FS address: 0xbfc10100
[0x6a] Root FS size: 0x0010c000
[0x74] Kernel address: 0xbfd1c100
[0x80] Kernel size: 0x00072c44
[0x8a] Dual img ?: 0
[0x8c] Inactive img ?: 0
[0x8e] Reserved: 
        e7 cf c5 35 8d 5e c1 2e 63 34 bf 70 6c 57 19 c1 
        b2 31 5f f0 7b b9 f4 1c 00 00 00 00 00 00 00 00 
        00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
        00 00 35 00 00 00 7f 59 32 a6 00 00 00 00 00 00 
        00 00 00 00 00 00 00 00 00 00 
        - RK checksum (0x36): 0x7f5932a6
[0xd8] Header checksum: 0x6b937861
[0xec] Image checksum: 0xa5d3bca7

The image checksum is a standard 1-complemented CRC32 computed over the whole image, header excluded. As for the header checksum, it is computed over the header as whole, itself excluded. Those checksums are verified by CFE and/or the HTTP flash utilities, so don't forget to update them should you want to modify some parts of the firmware image.

Using the information contained in the header, we can easily extract the different sections of the image. I wrote a little tool that does just that -- it is available in the tools directory. Example usage :

$ ./bcm_fw_extract -a -d 3.30J cfe-dm111-v330j_a2pb021g
[0x00] Header version: 6
[0x2c] Board Id.: RTA1320
[0x3c] Big endian ?: 1
[0x3e] Total img size: 0x0018df7c
[0x48] CFE address: 0xbfc00000
[0x54] CFE size: 0x0000f338
[0x5e] Root FS address: 0xbfc10100
[0x6a] Root FS size: 0x0010c000
[0x74] Kernel address: 0xbfd1c100
[0x80] Kernel size: 0x00072c44
[0x8a] Dual img ?: 0
[0x8c] Inactive img ?: 0
Dumping Broadcom header... (offset 0x00000000, sz 0x00000100, crc 0x8a24fb98) done.
Dumping CFE boot loader... (offset 0x00000100, sz 0x0000f338, crc 0x4991f98f) done.
Dumping root filesystem... (offset 0x0000f438, sz 0x0010c000, crc 0xdf4aeffa) done.
Dumping kernel... (offset 0x0011b438, sz 0x00072c44, crc 0x0568def0) done.

The CFE bootloader

CFE stands for Common Firmware Environment and is generally the bootloader used on Broadcom boards. The sources for recent versions are provided on Broadcom's website, but the CFE embedded in Netgear routers (at least in the DM111P) is a special version customised to be able to boot an LZMA-compressed kernel (see below). The source for the modified version does not seem to be available, as Netgear's sources only contain binary versions of the CFE.

We can however suppose that it contains already published code -- running strings on the extracted bootloader reveals interesting messages, which can be found in a published 2.6.8.1 kernel patch :

$ strings -12 3.30J/bootldr.bin 
PANIC: out of memory!
!Error allocating memory for LitDecoder m_Coders!
!Error in allocating memory for reverseBitTreeDecoder2!
        xError in allocating memory for bitTreeDecoder!
Error in allocating memory for reverseBitTreeDecoder!
0CodeReal: invalid data

Since the above-mentioned patch is GPL code, it seems likely that Netgear infringes the GPL by not publishing source code for the modified bootloader they are using.

The root filesystem

The filesystem image is a modified SquashFS image, which is LZMA-compressed (cf. the SquashFS LZMA homepage for more information.). However it seems the SquashFS version used by Netgear is not compatible with the standard tools from the SquashFS LZMA project :

$ unsquashfs -v | head -n 1
unsquashfs version 1.5 (2007/10/31)
$ unsquashfs -s 3.30J/rootfs.bin 
Reading a different endian SQUASHFS filesystem on 3.30J/rootfs.bin
Can't find a SQUASHFS superblock on 3.30J/rootfs.bin

The message regarding endianness being purely informative, we have to conclude that Netgear is using a different SquashFS and/or LZMA implementation. Fortunately, the kernel patch mentioned above gives us a hint at the LZMA implementation that is being used. Quickly patching unsquashfs to use this implementation yields something functional (thanks to the fine OpenBox4 folks for what appears to be the original implementation) :

$ ./lzma_unsquash -s 3.30J/rootfs.bin 
Reading a different endian SQUASHFS filesystem on 3.30J/rootfs.bin
Found a valid big endian SQUASHFS 2:0 superblock on 3.30J/rootfs.bin.
Creation or last append time Fri Jun  1 06:23:29 2007
Filesystem is not exportable via NFS
Inodes are decompressed
Data is compressed
Check data is not present in the filesystem
Fragments are not present in the filesystem
Always_use_fragments option is not specified
Duplicates are removed
Filesystem size 1071.33 Kbytes (1.05 Mbytes)
Block size 65536
Number of fragments 0
Number of inodes 351
Number of uids 2
Number of gids 1

Combining this with the image extraction code above, we are now able to extract the entire filesystem :-) :

$ ./bcm_fw_extract -f -x -d 3.30J cfe-dm111-v330j_a2pb021g
[0x00] Header version: 6
[...]
Dumping root filesystem... (offset 0x0000f438, sz 0x0010c000, crc 0xdf4aeffa) done.
Extracting root filesystem... done.
$ ls -l 3.30J/squashfs-root
total 44
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:23 bin
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:14 CVS
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:23 dev
drwxr-xr-x 10 sjv sjv 4096 2007-06-01 06:23 etc
drwxr-xr-x  3 sjv sjv 4096 2007-06-01 06:21 lib
lrwxrwxrwx  1 sjv sjv   11 2008-03-23 20:07 linuxrc -> bin/busybox
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:23 mnt
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:23 proc
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:23 sbin
drwxr-xr-x  3 sjv sjv 4096 2007-06-01 06:23 usr
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:23 var
drwxr-xr-x  2 sjv sjv 4096 2007-06-01 06:21 webs

The kernel

Examining the kernel image reveals that it is LZMA-compressed, and that there is a Broadcom-specific header prepended to it. The format of this header is quite simple (and originally described at SkayaWiki) : a 4-byte value representing the address at which the kernel will be loaded, a second 4-byte value holding the kernel entry point, i.e. the code that gets executed after decompression, and a third value being the size of the compressed kernel (header stripped). In our case, the header yields the following  :

$ hexdump -C 3.30J/kernel.bin | head -n 5
00000000  80 01 00 00 80 17 10 18  00 07 2c 38 5d 00 00 40  |..........,8]..@|
00000010  00 00 00 6f fd ff ff a3  b7 7f 73 46 51 6a 69 37  |...o......sFQji7|
00000020  2f 2a 4c aa 3e ad ff b2  20 92 3f 7a 37 87 f9 f1  |/*L.>... .?z7...|
00000030  b9 c0 6b cd 4e 2c 90 0e  e0 d2 49 1b da a9 d7 5d  |..k.N,....I....]|
00000040  31 8c a1 b8 59 4b d2 c4  1e 36 29 0e 81 8d 81 5f  |1...YK...6)...._|

This means that the bootloader will load the kernel at 0x80010000, and then jump to 0x80171018, which is fully confirmed by the console output above :

[...]
Auto run second count down(before hit space key):
Code Address: 0x80010000, Entry Address: 0x80171018
Decompression OK!
Entry at 0x80171018
Closing network.
Starting program at 0x80171018
Linux version 2.6.8.1 (compiled by michaelc) (gcc version 3.4.2) #1 Fri Jun 1 12:20:59 CST 2007
[...]

Since we now have a functioning LZMA decompressor (note that current LZMA utils do *not* seem to be able to decompress this image), we can extract the kernel image :

$ ./bcm_fw_extract -k -x -d 3.30J cfe-dm111-v330j_a2pb021g
[0x00] Header version: 6
[...]
Dumping kernel... (offset 0x0011b438, sz 0x00072c44, crc 0x0568def0) done.
Extracting kernel... (sz 0x00180015) done.
$ ls -l 3.30J/vmlinux 
-rw-r----- 1 sjv sjv 1572885 2008-03-23 20:07 3.30J/vmlinux

This decompressed kernel is a stripped version of a standard MIPS32 ELF vmlinux image. As for booting a kernel directly via TFTP, I'm still investigating : it seems CFE does recognise the header, but somehow fails at decompression -- this needs further investigation.