Packing a battery-first kernel into an HTC Desire Z boot.img

Before I built a kernel for my HTC Desire Z, I wrote a plan for it, and the plan was confident about the end. Build the kernel and its modules, dump the boot partition, take the image apart, swap the kernel, put it back, flash it, check the frequency table, measure idle current. The middle of that, everything between "dump" and "flash", was a single comment line. I wrote that line. I have some notes for the man who wrote it.

The phone is a 2010 Android slider from HTC, running CyanogenMod 7 (Android 2.3), and like every Android phone of its age it boots its kernel out of a small partition of its own. Two parts ago I built a battery-first kernel for it: no overclocking, an 806 MHz ceiling, a lower voltage table. The last part was the Wi-Fi it broke. This one is the step in between that I skipped: how a freshly built zImage becomes something the phone will boot, and which lines of the plan survived contact with it.

The plan said zImage and modules#

The build line in the plan asked for two targets, zImage modules. Every build I actually ran asked for one, zImage: the first try, every retry after the four build failures, and the run that finally linked. Nobody noticed, because the zImage is the only build output the boot image needs. The modules live on /system, the phone already had a set of them for the old kernel, and nothing complains about a missing module until something tries to load it. Twelve minutes after boot I noticed Wi-Fi was gone, and the last part is the rest of that.

So the first line of the plan that did not survive was the first line of the plan.

Finding the boot partition, three ways#

Android boot images on this phone live in eMMC partitions, and step one is a raw copy of the right one. I asked the phone three times where that was. The by-name directory under /dev/block/platform did not exist. /proc/mtd, which lists raw-flash partitions, answered with a header and no rows, which is correct for a phone without raw flash and helps nobody. A header and no rows, /proc/mtd. You could have said eMMC. The third file, /proc/emmc, had the table:

dev:        size     erasesize name
mmcblk0p17: 00040000 00000200 "misc"
mmcblk0p21: 0087f400 00000200 "recovery"
mmcblk0p22: 00400000 00000200 "boot"
mmcblk0p25: 22dffe00 00000200 "system"
mmcblk0p27: 12bffe00 00000200 "cache"
mmcblk0p26: 442ffe00 00000200 "userdata"
mmcblk0p28: 014bfe00 00000200 "devlog"
mmcblk0p29: 00040000 00000200 "pdata"

Boot is mmcblk0p22, 0x400000 bytes, 4 MiB. The plan, it turns out, had mmcblk0p22 written into its dd command all along. I asked three files anyway, which is roughly my relationship with plans.

$ adb shell "su -c 'dd if=/dev/block/mmcblk0p22 of=/sdcard/boot.img bs=4096'" 2>&1 | tr -d '\r'
1024+0 records in
1024+0 records out
4194304 bytes transferred in 0.118 secs (35544949 bytes/sec)

What abootimg found inside#

A boot image is a small header, the compressed kernel, a ramdisk (the tiny root filesystem with init and its scripts) and a kernel command line. abootimg, a tool for taking these images apart, read the copy I pulled off the phone:

Android Boot Image Info:

* file name = /tmp/current-boot.img 

* image size = 4194304 bytes (4.00 MB)
  page size  = 2048 bytes

* Boot Name = ""

* kernel size       = 2093120 bytes (2.00 MB)
  ramdisk size      = 144684 bytes (0.14 MB)

* load addresses:
  kernel:       0x04008000
  ramdisk:      0x05000000
  tags:         0x04000100

* cmdline = no_console_suspend=1 console=null

* id = 0xe62bea90 0xf1e0c2e6 0x0cd09fab 0xeba1fce0 0x75a4dfaf 0x00000000 0x00000000 0x00000000

The plan said to unpack, swap and repack. abootimg has a shorter way: -u updates an existing image in place, and -k names the new kernel. I made a copy of the original and ran that on the copy, so the ramdisk, the load addresses, the page size and the command line all stayed exactly what HTC and CyanogenMod had put there. The ramdisk matters more than it looks. It holds init.rc, and among much else init.rc is what makes /vendor point at /system/vendor, which the Wi-Fi fix in the last part leaned on.

The repacked image, read back with abootimg -i, changed in one line:

* kernel size       = 1905820 bytes (1.82 MB)
  ramdisk size      = 144684 bytes (0.14 MB)

187,300 bytes smaller than CyanogenMod's kernel. I am not handing all of that to the options I stripped. The two kernels come from different commits, I do not know which compiler built CyanogenMod's, and I have no third build to referee. Same ramdisk size, same command line, same load addresses: as diffs go, a reassuring one.

Flashing it, with the backup in /tmp#

fastboot is the bootloader's USB flashing mode, and on this phone, which has its security flag off, it writes the boot partition without arguing. After one warning about a verified-boot footer, a format this 2010 phone predates, its report was three lines:

Sending 'boot' (4096 KB)                           OKAY [  0.704s]
Writing 'boot'                                     OKAY [  0.680s]
Finished. Total time: 1.404s

For those 1.4 seconds the old boot partition existed as two files: one in /tmp on my workstation, one on the SD card of the phone being flashed. I copied the workstation one into the project's ROM folder right after the flash, not before, and I kept very still about that order until the phone booted. It did, on the first try, and reported back the four frequencies I had asked for, 245.76 to 806.4 MHz, and the conservative governor I would take back half an hour later. The undervolt part has that readout and the governor story.

The step the plan gave one line#

The plan's last step was the shortest of all: test, and measure idle current. The test part was a few cats. The measuring part turned out to need a battery file I had not known about and a tile on my own home screen, and it took the next two parts to do properly. The plan also came with a table of estimated battery gains for each change, which I have still not measured against the stock kernel, so I will not repeat them here.

I keep the plan. It was right about the partition number and the order of the steps, and wrong about almost everything it thought was one line. The rollback image is still in the ROM folder. I have not needed it yet, and with this phone I keep the stress on yet.

Filed under: android, kernel, htc-desire-z, cyanogenmod, power-management

Sources#

  • Kali- kernel for MSM7x30, commit c5198968 -- the tree the kernel was built from
  • HTC Desire Z (vision), CyanogenMod 7.2.0 -- direct device testing: /proc/emmc, dd of mmcblk0p22, abootimg -i before and after abootimg -u -k, fastboot flash boot; the stock boot image is kept as a rollback copy