Random notes on the DreamPlug

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.

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.)

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_VIRTnot 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):

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:

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.

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:

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 SheevaPlug and
in /dev/mtd0 at offset 0xc0000 on the
DreamPlug. 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, OpenOCDshould 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:

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:

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 2012-03-18]

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.