The U is for urgghle
Wed, 28 Mar 2018 00:36:42 +0000
A more productive week than the previous one , on the whole.
(Once I got warmed up, at least. I returned from my holiday to find that the entire local network had stopped working because after mucking around with U-boot on the device while I was away, I'd inadvertently let the default openwrt installation on the MT300A start , and it was running a DHCP server. Shouldn't have put it on the LAN, I suppose. One round of reboots later ... but apart from that it has been a productive week)
Let's start by spoiling the ending, so you don't have to read the rest of this post: said MT300A now boots to user space and runs init (and monit). I hope that all that remains is to get the Ethernet working and to build a flashable image.
On our way to that destination, we ... basically, this is another thrilling instalment of "don't trust the bootloader"
This board builds with device tree, but its u-boot has no way to provide a device tree blob at start up, so what we have to do instead is bodge the device tree into the kernel itself.
The actual mechanics of glomming a device tree binary blob onto the kernel image
are fairly straightforward if you have an openwrt build to crib from:
generate the ELF vmlinux as usual, convert it to a raw binary, compile the DTS
file (which involves preprocessing it with
cpp because - I don't know why -
it contains two different kinds of
include directive and the
dtc tool only
understands one of them) and then use some magic patch-dtb tool to stick
I previously described device tree as "let's pretend that the hardware description was provided to us by open firmware." and insofar as this means the hardware is described by a data file instead of by the effect of running imperative C code then it's definitely good. But when that data file is provided by the kernel source [*] and attached to the kernel image, instead of being passed in by the bootloader as an input to the kernel, things get weird. For example, the default config for the MT300A is that the kernel command line is coming from DT - effectively this means that the kernel provides its own command line. And you may ask yourself: where does that highway go to? It would make sense if the command line had been provided by the user to the bootloader which then merged it into the DT, but in this scenario ... not so much.
[*] splitting hairs here: the particular DTS we're using comes from LEDE and not from the mainline kernel. But that doesn't invalidate my point, which is that it doesn't come from the bootloader.
At this point, and skating over a minor digression where I had accidentally built a kernel that thought it was little endian but using a big endian compiler (tl;dr - didn't work), I had a kernel that booted most of the way to mounting root but then failed.
The reasons it failed were approximately legion in number, or at least that's how it felt. In the order I discovered them :
I. The kernel was configured to get its command line - see above - from DT not
the bootloader, so was ignoring all my
phram options because the hardcoded
command line overrode them.
II. For reasons that no doubt made perfect sense to the people who
were there are the time, the u-boot build on this board doesn't
bootargs anyway. So the kernel was ignoring all my
options because it wasn't even seeing
. Remember, kids - the U in "u-boot" stands for
#undef . (I am
assuming the sources there correspond with the binary that shipped on
my device, to be quite honest I haven't checked this properly for myself).
III. another bit of hardcoded magic ... there is a kernel config option in
LEDE, again enabled by default, that looks through the MTD partition table and
if it finds a partition named
rootfs, sets it up as the root filesystem.
This overrides any
root= option given on the command line. So the openwrt
rootfs on the actual flash was being used instead of the custom root fs in
phram - and failing to work not least because it's a jffs filesystem and I'd set
IV. Whereas the Yun wanted the address of the filesystem image as an offset from 0x80000000, the MT300A wants it offset from 0. I haven't got the MIPS memory map entirely straight in my head but i think I would be using the right words if I said it wants a kuseg memory address not a kseg0 address. Or perhaps it wants a physical address not a kseg0 address. (The same physical RAM is mapped in both places but the cache behaviour is different). Whichever is the right explanation I don't know, but when I fixed that, bingo, it boots!
I don't know if the Yun would work with kuseg addresses too, but next time I plug it back in I'll try it.
Also this week, significantly cleaned up how we provide config
options to the nixwrt kernel
- now we read the appropriate defconfig file(s) into an attrset then
pass that attrset into
overrideConfig is a
function that accepts an attrset of default config options and is
expected to return a customised set. It's a lot prettier than the
rather awful mess of
grep -v it replaces, but it's not
100% perfect because it doesn't know the type of each config option
value - if you have a string value you need to quote that string
yourself (tip: use
builtins.toJSON) but it will do for now.
Probably I should extract it into a function so that I could use the
same code to configure Busybox.
That's about it for this week.
- With luck and a following wind I'll be at the NixOS 18.03 install party at Codenode - I hope to bring along some hardware - so if you're interested and in London, sign up and come along.
- If you'd like to get NixWRT updates more than once a week, I encourage you to follow me on Mastodon