Random notes on the DreamPlug

[Initially written on , mostly rewritten on ]

This page contains some random notes, mostly for myself but which might be useful to others, on the Marvell DreamPlug (and incidentally some of its older cousins the GuruPlug and SheevaPlug) and on getting some things to work with it mainly around the boot process. This page is not expected to be of interest to inexperienced users.

The main information site about these computers was the PlugWiki at http://plugcomputer.org/plugwiki/index.php/Main_Page, which is now dead (but Archive.org remembers parts of it); there is some information here by Debian and here by ArchLinux which might still be useful (see also this page).

Note that an important difference between the DreamPlug and the GuruPlug is that the GuruPlug has an internal 512MB NAND flash memory for persistent storage whereas the DreamPlug has a smaller amount (2MB) of NOR memory to store the bootloader, and an internal 4GB SD card that is accessed through the USB bus (and can be changed if necessary by opening the plug). There are other differences, but this is the main reason why the boot and upgrade process is not the same in both plugs. (See here for a general introduction about NAND and NOR.)

Table of contents

How to speak to the serial port

Connect the JTAG adapter to the DreamPlug using the UART interface on both sides and do something like this (on the computer to which the JTAG adapter is connected by USB):

sudo modprobe ftdi-sio
sudo chown $USER /dev/ttyUSB0
screen /dev/ttyUSB0 115200

How to get a useful kernel

Look here for example (I'm pointing at the 4.2.21 kernel because that's the most recent longterm stable kernel as of 2016-09-17, but of course, another version may be more appropriate), and look for the kirkwood kernels or configs.

There used to be a number of patches that were in various ways useful, but now this seems to be irrelevant, and the DreamPlug will work fine with a vanilla kernel.

However, these recent kernels require a recent U-Boot. Indeed, if booting from Marvell U-Boot or Debian U-Boot version <2011.12-3, then the kernel needs to be configured with CONFIG_ARM_PATCH_PHYS_VIRT not set, and CONFIG_PHYS_OFFSET set to 0x0 (or else it will hang after Uncompressing Linux... done, booting the kernel.). Apparently this is due to L2 cache needing to be disabled for the kernel decompressor to work (I have no idea how things got so seriously fscked up that having L2 cache enabled — something which ought to be completely transparent — can break things, nor how that is related to the two options I just mentioned, but I don't really want to know). Leaving CONFIG_ARM_PATCH_PHYS_VIRT unset is a bad idea, and incompatible with device trees, so the best solution is to use a recent U-Boot. See below on how to upgrade U-Boot.

The story about the device tree

Initially, the support for every ARM system had to be coded separately in the Linux kernel. Each system would receive a number, the machine identifier, and the bootloader would pass this identifier to the kernel so as to activate the proper bit of code. The machine identifiers of the GuruPlug and the DreamPlug are 2659=0xa63 and 3550=0xdde respectively. However, the DreamPlug appeared at a time when this whole mechanism was being phased out, which led to complications: while the GuruPlug was supported under identifier 0xa63 if the kernel was compiled with CONFIG_MACH_GURUPLUG, on the other hand, support for the DreamPlug under the machine identifier 0xdde, and the CONFIG_MACH_DREAMPLUG config variable, never made its way into the mainline kernel. (The patches adding this are here [note that .boot_params near the end needs to be replaced by .atag_offset just as in the neighboring file guruplug-setup.c] and here, but as I said, they were never merged.) To work around this, the U-Boot provided by Marvell would pass an incorrect machine identifier on the DreamPlug (pretending it was a GuruPlug), a quick and dirty workaround which mostly worked, but required extra patches on the kernel (to account for the differences between the two plugs, especially with regards to flash memory devices). But this is all obsolete anyway.

If using a recent U-Boot, the correct way to pass hardware information to the kernel is now not to use the obsolete machine identifier mechanism, but a device tree blob. All the necessary support is in the kernel: compilation should create a file called arch/arm/boot/dts/kirkwood-dreamplug.dtb (or arch/arm/boot/dts/kirkwood-guruplug-server-plus.dtb for the GuruPlug), which should be passed to the kernel by U-Boot in a way similar to the passing of the initial ramdisk (see below). So just remember to place this file somewhere U-Boot can find it.

(There is an optional hack, called CONFIG_ARM_APPENDED_DTB, which lets you append the blob to the kernel zImage instead of passing it separately: it is simpler if you want to avoid changing the boot config, but since U-Boot has to be replaced anyway for the reasons explained above concerning the L2 cache, there is little reason to use this hack on the DreamPlug.)

How to (cross-)build the kernel

Here are some potentially useful command lines (which, of course, should be adapted intelligently) to create a cross-compiler for arm(el) on an x86/x86_64 Debian system and install it under /opt/arm-linux-gnueabi-tools (and /opt/arm-linux-gnueabi for the system libraries):

mkdir /opt/arm-linux-gnueabi
mkdir /opt/arm-linux-gnueabi-tools
dpkg-deb -x libc6_2.19-18+deb8u1_armel.deb /opt/arm-linux-gnueabi
dpkg-deb -x libc6-dev_2.19-18+deb8u1_armel.deb /opt/arm-linux-gnueabi
dpkg-deb -x linux-libc-dev_3.16.7-ckt11-1+deb8u5_armel.deb /opt/arm-linux-gnueabi
(cd /opt/arm-linux-gnueabi/usr ; tar cf - *) | (cd /opt/arm-linux-gnueabi ; tar xf -)
rm -rf /opt/arm-linux-gnueabi/usr
ln -s . /opt/arm-linux-gnueabi/usr
# Similarly, need to move the contents of
# /opt/arm-linux-gnueabi/include/arm-linux-gnueabi
# to its parent and ditto for /opt/arm-linux-gnueabi/lib/arm-linux-gnueabi
# (and replace them by symlinks .).
cd /tmp ; apt-get source binutils/jessie
cd binutils-2.25
./debian/rules patch
mkdir /tmp/binutils-build
cd /tmp/binutils-build
/tmp/binutils-2.25/configure --target=arm-linux-gnueabi --prefix=/opt/arm-linux-gnueabi-tools --enable-shared --enable-plugins --with-sysroot=/opt/arm-linux-gnueabi
make -j4 && make install
cd /tmp ; apt-get source gcc-4.8/jessie
cd gcc-4.8-4.8.4
DEB_CROSS_NO_BIARCH=yes ./debian/rules patch
mkdir /tmp/gcc-build
cd /tmp/gcc-build
/tmp/gcc-4.8-4.8.4/src/configure --target=arm-linux-gnueabi --prefix=/opt/arm-linux-gnueabi-tools --with-sysroot=/opt/arm-linux-gnueabi --enable-languages=c
make -j4 && make install

After this, something like the following might be used to compile the kernel, from a directory where it has been extracted:

PATH=/opt/arm-linux-gnueabi-tools/bin:$PATH
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabi-
export PATH ARCH CROSS_COMPILE
make menuconfig
make -j4 uImage

To create a proper Debian package instead, use something like this:

PATH=/opt/arm-linux-gnueabi-tools/bin:$PATH
export PATH
day="`date +%Y%m%d`"
make-kpkg --arch armel --cross-compile arm-linux-gnueabi- --revision=0custom.${day} --append-to-version=-dreamplug --config menuconfig configure
CONCURRENCY_LEVEL="4" make-kpkg --arch armel --cross-compile arm-linux-gnueabi- --revision=0custom.${day} --append-to-version=-dreamplug --rootcmd fakeroot kernel_image kernel_headers

This will create packages with names such as linux-image-4.4.21-dreamplug_0custom.20160917_armel.deb and linux-headers-4.4.21-dreamplug_0custom.20160917_armel.deb which can be installed with dpkg -i; after they have been installed, the uImage file can be created on the target using something like this: mkimage -A arm -O linux -T kernel -C none -a 0x00008000 -e 0x00008000 -n Linux-4.4.21-dreamplug -d /boot/vmlinuz-4.4.21-dreamplug uImage (where the mkimage program is from the package named uboot-mkimage or u-boot-tools). If using an initial ramdisk, convert it to a uInitrd file loadable by U-Boot with something like: mkimage -A arm -O linux -T ramdisk -C none -a 0x01100000 -n Initramfs-4.4.21-dreamplug -d /boot/initrd.img-4.4.21-dreamplug uInitrd (note that, unlike the kernel image and initial ramdisk, the device tree blob does not have to be converted using mkimage).

Beware of this bug in Debian kernel-package version 12.036+nmu2, however.

Booting (example)

Learn how to use U-Boot. I suggest instructions such as the following:

ext2load usb 0:2 0x0c00000 /boot/kirkwood-dreamplug.dtb
ext2load usb 0:2 0x6400000 /boot/uImage
ext2load usb 0:2 0x1100000 /boot/uInitrd
setenv bootargs console=ttyS0,115200 root=/dev/sda2 rootdelay=10 ro
bootm 0x6400000 0x1100000 0x0c00000

assuming the second partition of the internal SD card (usb 0:2) holds an ext2 or ext3 filesystem where the three files named above are, respectively, the device tree blob, the kernel uImage and the uInitrd for the initial ramdisk.

Here's what the entire U-Boot environment might look like:

baudrate=115200
bootcmd=${x_bootcmd_usb}; ${x_bootcmd_fdt}; ${x_bootcmd_kernel}; ${x_bootcmd_initrd}; setenv bootargs ${x_bootargs} ${x_bootargs_root}; bootm 0x6400000 0x1100000 0x0c00000;
bootdelay=3
eth1addr=something
ethact=egiga0
ethaddr=something
stderr=serial
stdin=serial
stdout=serial
x_bootargs=console=ttyS0,115200
x_bootargs_root=root=/dev/sda2 rootdelay=10 ro
x_bootcmd_fdt=ext2load usb 0:2 0x0c00000 /boot/kirkwood-dreamplug.dtb
x_bootcmd_initrd=ext2load usb 0:2 0x1100000 /boot/uInitrd
x_bootcmd_kernel=ext2load usb 0:2 0x6400000 /boot/uImage
x_bootcmd_usb=usb start

How to use or install a different U-Boot

The DreamPlug comes from a version of U-Boot compiled by Marvell which, as explained above, is incapable of booting a recent kernel, doesn't know about device tree blobs, and doesn't even pass the correct (obsolete) machine identifier for the DreamPlug. The best thing to do is throw it away. Sadly, upgrading U-Boot isn't so simple.

From Linux

One way to do this is from Linux on the device itself, provided one has a working kernel which correctly supports the NOR flash used on the DreamPlug (but this may run into a chicken-and-egg problem). If the kernel says something like this at boot on the DreamPlug:

[    1.186000] m25p80 spi0.0: mx25l1606e (2048 Kbytes)
[    1.190915] 3 ofpart partitions found on MTD device spi0.0
[    1.196449] Creating 3 MTD partitions on "spi0.0":
[    1.201890] 0x000000000000-0x000000080000 : "u-boot"
[    1.208287] 0x000000100000-0x000000110000 : "u-boot env"
[    1.215203] 0x000000180000-0x000000190000 : "dtb"

and there is a /dev/mtd0 device, then U-Boot can be flashed with simply flashcp /usr/lib/u-boot/dreamplug/u-boot.kwb /dev/mtd0 (assuming the new U-Boot raw binary is in /usr/lib/u-boot/dreamplug/u-boot.kwb, which is where Debian puts it).

I think the equivalent command for the GuruPlug would be flash_erase /dev/mtd0 0 4 ; nandwrite /dev/mtd0 /usr/lib/u-boot/dreamplug/u-boot.kwb but I didn't check and my GuruPlugs are all retired now.

While I'm at it, the U-Boot environment is in /dev/mtd1 on the DreamPlug and in /dev/mtd0 at offset 0xc0000 on the GuruPlug. It is a 4kB block consisting of 4092 bytes of data (null-terminated strings of the form name=value) preceded by a 4-byte checksum which is just the CRC32 of the 4092 next bytes. Given such an environment, use flashcp uboot-env.bin /dev/mtd1 on the DreamPlug, or flash_erase /dev/mtd0 0xc0000 1 ; nandwrite -s 0xc0000 /dev/mtd0 uboot-env.bin on the GuruPlug (again, this is untested).

From U-Boot itself

U-Boot can be upgraded from U-Boot itself.

First, load it into memory, say at offset 0x0900000 with something like ext2load usb 0:2 0x0900000 /boot/u-boot.kwb (or tftpboot 0x0900000 /boot/u-boot.kwb if using TFTP, or whatever you use to load a file into U-Boot).

Then flash it using the following lines for the DreamPlug (to write to NOR):

sf probe 0
sf erase 0x0 0x100000
sf write 0x0900000 0x0 size

where the last argument on the third line is the size of the file (which is printed by the load command), or at least some upper bound on that size.

For the GuruPlug:

nand erase 0x0 0x80000
nand write 0x0900000 0x0 0x80000

where 0x80000 at the end of both lines is an upper bound on the size of the file which is a multiple of 0x20000 (the block size of the NAND).

From OpenOCD

If the device is bricked, OpenOCD should be able to write to its RAM and restart a good U-Boot, which can then reflash itself (or do whatever U-Boot can do).

First connect the JTAG adapter as explained in the manual (page 6): connect the JTAG interface on the adapter and on the plug, and connect the adapter to some computer through USB. The adapter appears as USB identifier 9e88:9e8f. Make sure you have the proper permissions on the device (/dev/bus/usb/xxx/yyy where the bus and device numbers xxx and yyy are shown by lsusb).

Note that the JTAG interface is incredibly fickle. If things don't work at the first try, try to disconnect it and reconnect it (at both ends!). There seem to be many things that can go wrong, and I haven't been able to identify them all, but a badly wired JTAG connexion is definitely one.

Also, use a version of OpenOCD between 0.5.0 and 0.7.0 inclusive. Versions 0.8.0 and 0.9.0 can't seem to connect to the interface. See this bug-report for details.

Once ready, run openocd -f /usr/share/openocd/scripts/board/sheevaplug.cfg and telnet to localhost port 4444 to get to the OpenOCD console. Here is what the output should look like when everything is properly connected and OpenOCD is able to control the plug's processor:

Open On-Chip Debugger 0.7.0 (2016-09-16-10:42)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.sourceforge.net/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
adapter speed: 2000 kHz
trst_and_srst separate srst_gates_jtag trst_push_pull srst_open_drain connect_deassert_srst
adapter_nsrst_delay: 200
jtag_ntrst_delay: 200
dcc downloads are enabled
Warn : use 'feroceon.cpu' as target identifier, not '0'
sheevaplug_load_uboot
Info : clock speed 2000 kHz
Info : JTAG tap: feroceon.cpu tap/device found: 0x20a023d3 (mfg: 0x1e9, part: 0x0a02, ver: 0x2)
Info : Embedded ICE version 0
Info : feroceon.cpu: hardware has 1 breakpoint/watchpoint unit

On the other hand, if you get messages like Error: JTAG scan chain interrogation failed: all zeroes and Error: feroceon.cpu: IR capture error; saw 0x00 not 0x01 and Error: unexpected Feroceon EICE version signature then things are not all right. But in this case you should still be able to use the reset command, and it should still work, so if the problem is not from a badly wired JTAG connexion, you can try to reset the processor until OpenOCD announces that it found a feroceon.cpu tap/device as shown above.

There are definitely a number of factors which can cause OpenOCD to succeed or fail to control the plug's processor, and I have been unable to identify them all: a badly wired JTAG is one (it will fail repeatedly, no matter how often reset is run), but sometimes the first connexion fails, and after a reset it works again. (I noticed that when the plug is sitting idle at the U-Boot prompt, OpenOCD seems to control it at the first try, whereas if it is running Linux, at least one reset is necessary. I really don't understand what it going on.)

Anyway, assuming we have a working or workable JTAG connexion, inside the OpenOCD console, you can run cd /usr/lib/u-boot/dreamplug (replacing by a directory where uboot.elf resides) and then reset ; init ; sheevaplug_load_uboot : upon success, OpenOCD should display something like this:

JTAG tap: feroceon.cpu tap/device found: 0x20a023d3 (mfg: 0x1e9, part: 0x0a02, ver: 0x2)
target state: halted
target halted in ARM state due to debug-request, current mode: Supervisor
cpsr: 0x000000d3 pc: 0xffff0000
MMU: disabled, D-Cache: disabled, I-Cache: disabled
233373 bytes written at address 0x00600000
downloaded 233373 bytes in 1.664493s (136.921 KiB/s)
verified 233373 bytes in 0.850263s (268.039 KiB/s)

and the plug will be running the new U-Boot (just for once, but it can be used to flash itself, or flash a different U-Boot, as explained above).

I don't think the ELF U-Boot image loaded into memory by OpenOCD can be used directly as such — but if you have no other way of getting the U-Boot image inside memory before flashing it, you can also use OpenOCD to do this, e.g., reset ; init ; sheevaplug_init ; load_image uboot.elf ; verify_image uboot.elf ; load_image u-boot.kwb 0x0900000 bin ; resume 0x0600000 (and then, flash U-Boot from inside itself assuming it resides at offset 0x0900000).

If it doesn't work, double check the JTAG connection. If it still doesn't work, perhaps try adding a small sleep command between reset and init, or lowering the JTAG interface speed with adapter_khz 250 (I'm not sure this is useful, though). I don't think cold rebooting the plug itself is useful, though: reset should work just as well. But I can't claim to understand what's really going on: there's a lot of black magic, and some people have reported failing to unbrick a plug after dozens or hundreds of attempts. Good luck!

The mess with the Wifi

[Written ]

There are two different Marvell Wifi chipsets found on these GuruPlugs and DreamPlugs: the SD8688 (SDIO identifier 0x02df:0x9104) and the SD8787 (SDIO identifier 0x02df:0x9119). My GuruPlugs have the former while my DreamPlugs have the latter; however, the distribution may not be along the GuruPlug/DreamPlug line in all cases: I don't know. Support for them in Linux is very different. The (older) SD8688 is supported in Wifi client mode using the libertas driver in stock kernels or a driver from Marvell called sd8xxx (whose source code was released); and it is also supported in master mode using the uap8xxx driver from Marvell. The newer SD8787 is not the same chip, and apparently needs different drivers: it is also supported in client mode on stock kernels, the driver being then called mwifiex; however, this driver does not support master mode (=access point mode).

The kernel distributed with the DreamPlug does support Wifi master mode, but at the price of a proprietary driver, which is useless not so much for the philosophical reason that it is proprietary, but for the very practical reason that it supports only one kernel version (2.6.33.7-dirty, there's some irony in the word dirty), and that kernel is completely obsolete and contains known security vulnerabilities. Do not use it! So, basically, if you have a newer DreamPlug, you can't use it as a Wifi access point.