Debricking the Nook

Setup

I decided to write up the story of how I debricked the Nook (recovered a unit killed by a downgrade to firmware 1.0.0), so nobody would think “that bastard Nomad found which buttons to hold to debrick it, but won’t share.” What follows is a wall of text interesting only to hardware people and Linux folks. Normal people are better off not reading it 🙂

Opening

It all started when a bricked unit arrived with a request to try to fix it. Classic symptoms – the message “Restoring to Factory update” and then silence… After about three days of soldering, reassembling ribbon cables, and meditating over logs and source, I understood exactly what our problem looked like. The device boots u-boot, which doesn’t care at all what memory it’s working with, then turboboot is supposed to load (an intermediate kernel that knows how to do the update/rollback), and only after that the full kernel and system. So, turboboot from version 1.0.0 (and the main kernel right after it) chokes during MMC initialization and fails to pick up the NAND memory – it uses the MMC 4.3 or 4.4 protocol, support for which only appeared in Linux kernel 2.6.36, and for the B&N 1.4 and 1.5 firmwares it was backported onto our kernel. Incidentally, that’s exactly why 1.5.0k doesn’t start on newer Nooks either – same error there.
By that point, by hook or by crook, I’d managed to run my own code on the bricked Nook, but ran into a silly situation – the system had never created a device node for the NAND, so there was nothing to fix. Maybe if I were a super-specialist in processors and hardware I could write code to address the NAND directly, but that’s uncharted territory for me. Another idea was to take the UBoot code, rip out the MMC handling, and write my own program from it. A rough estimate of the effort – no less than a week of full-time work, including studying someone else’s code and the philosophy behind it, debugging, and testing. I spent many hours trying to build an MMC driver as a module that would unload the code already in the kernel and replace it (that’s not as crazy as it sounds – I did exactly that with the touchscreen driver), but it turned out that support for our hardware was nailed straight into the kernel internals, and doing this turned out to be very, very hard (even setting aside “minor” things like the impossibility of looking up and unloading a bus by name, since the find_bus code has been commented out in Linux for ages).

Middlegame

The next day I learned about a wonderful feature called kexec. In short, it’s the ability to boot another kernel directly from a currently running one. I assumed that’s exactly how turboboot itself worked, but was cruelly disappointed – it turns out there’s some proprietary code in there for switching to another kernel, and the kernel itself was compiled without kexec support at all. After digging through a pile of forums, I found a project where people had tried to implement kexec as a loadable module. It was a bold idea, but as far as I could tell the project was never really finished and only worked on the Motorola Milestone and the DroidX. The most unpleasant part was that the module itself had been built for a different kernel (2.6.29 or older), and with ARMv7 code at that (our processor is ARMv6). Not to mention that the syscall table lookup in there was commented out with a hardcoded address that had nothing to do with us, and the code itself, once uncommented, didn’t really search for anything properly. I honestly can’t say anymore how much I had to backport and rework using improvised means, but the module did eventually build, learned to find syscall_table, and I managed to load its .ko file straight into the running turboboot kernel.
At this stage I still had to fiddle a bit more (improvised means again) with the kexec-tools executable to actually get it running, and it turned out that any attempt to load a kernel simply rebooted the device… Essentially, the module was half-baked and unfinished, and this whole direction was a dead end. With that pessimistic thought I went to sleep.
The next day, while doing my regular job, I suddenly wondered – what if the reboot isn’t a bug, but intended behavior? And that’s exactly what it was – one of the WM8350 drivers, on being unloaded, decided it was time to restart the system and cut the power 🙂 I couldn’t do anything about the driver itself, so I had to rework the code that unloads all the drivers so that this particular one would be skipped. I had a feeling this would cause more trouble later, but decided to risk it. As a result, the device stopped rebooting and instead just said “Bye!” and hung 🙂

Stalemate

My disappointment didn’t last long, and by trial and error I found that despite claiming support for uImage and zImage, kexec only actually loads uncompressed kernels. That could have been the end of the story, but it turned out that all my self-built kernels, along with the factory 1.4-1.5 ones, made it as far as GPIO initialization and hung there… A bit further along, the kernel extracted from turboboot loaded, but then crashed while loading the LCD screen driver, and overall it wouldn’t have been much use anyway. Over the next day or two (it felt like an eternity) I built my own kernels, stripped out everything I could (why would we need e-ink or sound when all we need is to reflash the thing?), and commented out the spots where it hung. In general you’re not supposed to do this – a kernel without GPIO shouldn’t even boot – but here I got lucky and built a super-stripped kernel that could literally only talk to MMC (I had already backported support for protocol 4.3 from newer kernel versions). At the same time I built my own ramdisk image for this kernel and wrote a script that, on boot, would flash turboboot onto the MMC card and then stop. There was a small hitch here with placing turboboot – due to some bugs the script couldn’t determine the MMC card’s size (a botched backport? some quirk of running under kexec?), so I calculated the exact placement myself and hardcoded a fixed number into the script. This was very risky – being off by even one byte would have completely bricked the device – but everything went fine. A new turboboot got flashed onto the device, one that could see the internal memory. This was almost a full victory – all that remained was to convince the new turboboot to roll back to the factory firmware. It could now see the NAND, and it correctly unpacked the factory image onto it… except that image turned out to be version 1.0.0…

Endgame

I don’t know how 1.0.0 ended up there (probably a B&N mistake when building the Refurbished Nook), but the upshot was that the new turboboot loads, and then loads the old kernel, which still can’t see the NAND. Worse, at this stage I could no longer execute my own code, and the Nook wouldn’t boot and kept periodically rolling back to firmware 1.0.0. And since this factory firmware didn’t include a turboboot.img, there was no way back to the previous state either, so the situation looked genuinely bad. Deciding not to get discouraged, I read carefully through the logs and noticed that the combination of the 1.5 turboboot and the 1.0.0 kernel tries, on every startup, to load the file /init (the Android bootloader), and that if you insert an SD card, it looks for the file there too. Letting out a triumphant whoop, I put a script there to load the kexec_load.ko module and call kexec, but immediately ran headfirst into another wall of bugs: the 1.0.0 kernel differed from the one bundled inside turboboot 1.0.0 and had no support for /proc/iomem. The device needs this to report which addresses physical memory sits at, and without it kexec simply can’t work. The addresses for this memory were nowhere near the 0-0x1000000 range you might expect, but fortunately I dug up the right addresses buried in mountains of logs and hardcoded them into the kexec code itself.
My mini-kernel booted, but this time, instead of flashing turboboot onto the NAND, I flashed the entire bravo_update.dat from firmware 1.5.0n. Of course, I had to calculate the addresses in advance with a calculator, but by this point a mistake wasn’t nearly as terrifying, and overall everything went smoothly – the device rolled back to the “factory” 1.5.0n and became fully functional.

Conclusion

As you can see, there was nothing especially complicated about this whole operation, and anyone who wants to can follow the same path without much trouble – write the necessary patches, build the kexec module, the necessary ramdisks and scripts, and learn to run code on a bricked Nook. I’d only be glad if someone did 🙂
Until that happens, I’m ready to fix any number of bricked Nooks for a small fee. Write to [email protected] and we’ll work it out.
Once I get tired of this, I’ll release my kexec-mod, kexec-tools, MMC module sources, and so on. Might be useful to someone.

Originally published 2011-03-27.