diff options
Diffstat (limited to 'content/blog/kobo_clara-custom-distro.md')
| -rw-r--r-- | content/blog/kobo_clara-custom-distro.md | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/content/blog/kobo_clara-custom-distro.md b/content/blog/kobo_clara-custom-distro.md new file mode 100644 index 0000000..f7d40e1 --- /dev/null +++ b/content/blog/kobo_clara-custom-distro.md | |||
| @@ -0,0 +1,265 @@ | |||
| 1 | +++ | ||
| 2 | title = "Kobo Clara HD Custom Linux Distro/RootFS" | ||
| 3 | date = 2021-07-22 | ||
| 4 | draft = false | ||
| 5 | +++ | ||
| 6 | |||
| 7 | These are just some notes I made when creating my own mini-distro after | ||
| 8 | wanting something more custom than just using buildroot or making the | ||
| 9 | official firmware more slim. For people other than me, I suggest | ||
| 10 | looking through (C)LFS or running postmarketOS instead once this | ||
| 11 | reader's pull request[1] gets integrated into upstream. | ||
| 12 | |||
| 13 | Two things that'll greatly help with this is having serial terminal | ||
| 14 | access with the four uart pins near the top right in the back of the | ||
| 15 | reader, near the uSD card slot (I don't connect the 5V pin as my reader | ||
| 16 | doesn't really turn on anything other than the power LED). I suggest | ||
| 17 | maybe soldering female pin headers to there to make your life easier | ||
| 18 | (you can later cut out a hole in the back cover or desolder the headers | ||
| 19 | once you're done). Other than that, I suggest installing QEMU with ARM | ||
| 20 | userspace to test programs that you have built or running them on a | ||
| 21 | separate ARM device like a Raspberry Pi. | ||
| 22 | |||
| 23 | ## Prelude | ||
| 24 | Ever since I learnt that the official firmware for the Clara was just | ||
| 25 | using a modified Linux kernel with busybox as coreutils and many other | ||
| 26 | libraries, I just knew that I had to minimize it. I also saw that it | ||
| 27 | was using glibc for it's libc, which I really dislike as statically | ||
| 28 | linking C programs against it was a pain in my experience, compared to | ||
| 29 | something like musl and uclibc. It's also much larger than them and I | ||
| 30 | don't use any of glibc extensions so it seemed like a waste of space to | ||
| 31 | me. | ||
| 32 | |||
| 33 | Initially when I replaced Nickel with Plato, I was able to shave about | ||
| 34 | 100 MiB after I removed /usr/local (which contains Nickel, Qt and a few | ||
| 35 | other things), from 189 MiB to 74 MiB, but I still wanted to make it | ||
| 36 | smaller. | ||
| 37 | |||
| 38 | Using buildroot, I was able to get it under 2 MiB (!!) which was a | ||
| 39 | little less than half the size of an uncompressed armhf Alpine Linux | ||
| 40 | minirootfs (4.9M for 3.14). With Busybox, it was pretty much working | ||
| 41 | out of the box, with serial terminal access! But waiting around 15 | ||
| 42 | minutes for the toolchain to build each time I wanted to change | ||
| 43 | something in the rootfs took way too long, although it could've been | ||
| 44 | minimized if I used ccache with a fairly large cache size. I still | ||
| 45 | found that it compiled and installed a lot of things I wouldn't be | ||
| 46 | using (particularly in /usr) even after disabling almost all of the | ||
| 47 | third-party packages. | ||
| 48 | |||
| 49 | I've uploaded the config file and the resulting rootfs for | ||
| 50 | buildroot 2021.05. The root password by default is changeme. | ||
| 51 | EDIT 2022-10-21: gone, build it yourself | ||
| 52 | |||
| 53 | Of course the rootfs I got from buildroot nor me making the official | ||
| 54 | firmware smaller is the point of this article, and the actual point is | ||
| 55 | making one yourself! (or rather what I did to make my own) | ||
| 56 | |||
| 57 | ## Cross-toolchain | ||
| 58 | For now as of July 22, 2021, I'm using my distro (Void Linux)'s | ||
| 59 | packaged cross toolchain for armhf musl, but eventually I would be | ||
| 60 | using my own. | ||
| 61 | |||
| 62 | I'm not compiling off of the device itself as it would be somewhat slow | ||
| 63 | for bigger programs, which is currently primarily the Linux kernel, | ||
| 64 | U-Boot, and the toolchain itself, considering that the ereader's CPU | ||
| 65 | (Freescale i.MX 6SLL) is a single core running up to 1 GHz. Including | ||
| 66 | the development tools and headers would also take up more space on the | ||
| 67 | device itself, and since the terminal can currently only be accessed | ||
| 68 | through it's serial/uart pins, I don't think it's ideal. | ||
| 69 | |||
| 70 | TODO: include steps to create own toolchain (probably based off of gcc | ||
| 71 | 4.7.3 as that doesn't require c++) | ||
| 72 | |||
| 73 | ## Building the rootfs | ||
| 74 | Assuming you made a new filesystem on your rootfs's partition, it'll | ||
| 75 | likely be empty with no directories you'd expect to find on a regular | ||
| 76 | distro. So you'll just have to make them. | ||
| 77 | cd /path/to/rootfs | ||
| 78 | mkdir bin dev etc proc sbin | ||
| 79 | |||
| 80 | Your binaries would usually go in /bin, the uSD card, ttymxc0, and | ||
| 81 | other devices would go in /dev, felker init's default program/script to | ||
| 82 | execute is usually in /etc/rc, /proc is optional but I have it mounted | ||
| 83 | to see what is currently mounted through /proc/mounts (or mount(1) | ||
| 84 | without any arguments) as well as to see my disk usage through df(1). | ||
| 85 | /sbin is there to place the init in as /sbin/init is the default init | ||
| 86 | path the kernel looks at. | ||
| 87 | |||
| 88 | ## toybox | ||
| 89 | Now on to the main part of the distro, the userspace. I intend to keep | ||
| 90 | it fairly minimal so I've chosen to use toybox along with a slightly | ||
| 91 | modified version of felker (musl dev)'s init[2], as well as dash[3] as | ||
| 92 | the main shell since toybox doesn't include one as of 0.8.5 (though | ||
| 93 | it'll probably be there by 1.0). I'll also be statically linking all | ||
| 94 | the programs that'll be used so I wouldn't have to worry about shared | ||
| 95 | libraries not being included/copied over, and also including LTO for | ||
| 96 | slightly faster binaries. Originally, I tried going with sinit, sbase, | ||
| 97 | and ubase but I was having trouble getting serial terminal access with | ||
| 98 | getty to /dev/ttymxc0 (the default serial tty, at least with the | ||
| 99 | vendor kernel). I didn't have this problem with busybox's and toybox's | ||
| 100 | getty however. My config for toybox was also about 81K smaller than my | ||
| 101 | trimmed sbase-box and ubase-box (352K compared to 267K+166K) where I | ||
| 102 | removed programs that I won't use from ${BIN} in their respective | ||
| 103 | Makefiles. | ||
| 104 | EDIT 2022-10-21: also gone | ||
| 105 | |||
| 106 | First I suggest exporting some environment variables to set the | ||
| 107 | toolchain used as well as enabling static linking and LTO. | ||
| 108 | |||
| 109 | export CROSS_COMPILE="arm-linux-musleabihf-" # change to your cross-tc | ||
| 110 | export CC="${CROSS_COMPILE}gcc" | ||
| 111 | export LDFLAGS="--static" | ||
| 112 | export CFLAGS="-flto -static" | ||
| 113 | export ARCH=arm # for compiling the linux kernel | ||
| 114 | |||
| 115 | To compile toybox, get the source from | ||
| 116 | https://landley.net/toybox/downloads/ (or clone the upstream repo). | ||
| 117 | Then run make menuconfig (optionally with make defconfig before it) and | ||
| 118 | change it as you see fit. Personally, I disabled most of the programs I | ||
| 119 | wouldn't use and kept only the ones that'll help with fixing a problem. | ||
| 120 | Finally, make sure to run make. | ||
| 121 | |||
| 122 | make defconfig | ||
| 123 | make menuconfig | ||
| 124 | make | ||
| 125 | |||
| 126 | To move it to your rootfs and set it's symlinks, you could probably run | ||
| 127 | make install after setting PREFIX to your rootfs's /bin directory, but | ||
| 128 | I did it manually. | ||
| 129 | |||
| 130 | # automatic (didn't test, check README) | ||
| 131 | make PREFIX=/path/to/rootfs/bin/ install | ||
| 132 | |||
| 133 | # (semi?) manual | ||
| 134 | cp toybox /path/to/rootfs/bin | ||
| 135 | |||
| 136 | # add symlinks if doing manual and you want them | ||
| 137 | cd /path/to/rootfs/bin | ||
| 138 | for prog in $(qemu-arm ./toybox); do ln -s toybox "$prog"; done | ||
| 139 | |||
| 140 | ## dash | ||
| 141 | Also as of toybox 0.8.5, a shell still isn't included (probably would | ||
| 142 | be included by 1.0 according to scripts/install.sh as well as a few | ||
| 143 | other programs like gzip), so a separate shell would need to be built. | ||
| 144 | Any can be used but dash would be shown as an example as I was able to | ||
| 145 | get a static binary without too much trouble. | ||
| 146 | |||
| 147 | First obtain the source[3] and cd into its | ||
| 148 | untarred directory. Assuming your CC and CFLAGS are set, you can run | ||
| 149 | these steps: | ||
| 150 | |||
| 151 | autoreconf -fiv | ||
| 152 | ./configure --host=$CROSS_COMPILE --with-libedit | ||
| 153 | make | ||
| 154 | ${CROSS_COMPILE}strip src/dash | ||
| 155 | |||
| 156 | As this is going to be used as the main shell, I've decided to just | ||
| 157 | copy it to /bin/sh in the rootfs directory, though copying it there but | ||
| 158 | as /bin/dash and /bin/sh being symlinked to dash is also an option. | ||
| 159 | |||
| 160 | cp src/dash /path/to/rootfs/bin/sh | ||
| 161 | # or | ||
| 162 | cp src/dash /path/to/rootfs/bin | ||
| 163 | cd /path/to/rootfs/bin | ||
| 164 | ln -s dash sh | ||
| 165 | |||
| 166 | ## felker's init | ||
| 167 | The init is just a single file that you can get from felker's site[2] | ||
| 168 | or the gist on github[7]. I haven't had a good experience with the | ||
| 169 | default startup program (/etc/rc) as a shell script with execve() run | ||
| 170 | on it so I'd change it to execvp() and remove the third (specifies | ||
| 171 | environment). To compile and install the init, all you need to do is | ||
| 172 | run: | ||
| 173 | |||
| 174 | $CC $CFLAGS -o init init.c | ||
| 175 | cp init /path/to/rootfs/sbin | ||
| 176 | |||
| 177 | Instead of /etc/rc being a shell script, you can also make a C program | ||
| 178 | that does whatever you think is needed for a proper startup. I'll still | ||
| 179 | use a shell script though which is linked here. | ||
| 180 | EDIT 2022-10-21: you get the idea, it's gone. | ||
| 181 | |||
| 182 | ## /etc/passwd | ||
| 183 | Copying the rootfs's contents to your device's/uSD card's root | ||
| 184 | partition and then turning the device on should now work with a login | ||
| 185 | prompt shown in the serial terminal. However, you probably wouldn't be | ||
| 186 | able to login to any user. So you'll have to create a file at | ||
| 187 | /path/to/rootfs/etc/passwd. For an empty password to root, you can use | ||
| 188 | this, though I suggest setting a password as soon as you login: | ||
| 189 | |||
| 190 | # in rootfs's /etc/passwd | ||
| 191 | root::0:0:root:/root:/bin/sh | ||
| 192 | |||
| 193 | With the passwd file created/updated, you should now be able to login | ||
| 194 | to root after the rootfs is copied to your uSD card. Your rootfs so far | ||
| 195 | should now be around 550-560K, which is much much smaller than the | ||
| 196 | original firmware's, though it'll likely be much larger to maybe a few | ||
| 197 | megabytes once a proper reader software is added. | ||
| 198 | |||
| 199 | ## Custom Linux Kernel | ||
| 200 | WARNING: I haven't actually gotten the kernel to load in u-boot yet. It | ||
| 201 | just hangs in the "Starting kernel ..." step and the init doesn't get | ||
| 202 | loaded, so I'm assuming the kernel itself isn't either. If anyone out | ||
| 203 | there has gotten a custom kernel working in the Kobo Clara HD, please | ||
| 204 | send me an email or message on xmpp. | ||
| 205 | |||
| 206 | UPDATE Jul 28, 2021: Gave up on it as I just couldn't get any kernels I | ||
| 207 | built (both vendor and akemnade's mainline) to boot. But neither did | ||
| 208 | postmarketOS boot beyond the initial initramfs messages without the log | ||
| 209 | file being created. So I'll revisit this for later. | ||
| 210 | |||
| 211 | EDIT 2022-10-21: I have gotten this working, but have been unable to | ||
| 212 | get Plato build for musl, so I will have to either continue fighting | ||
| 213 | with the crab or create my own with fbink, as that still works. | ||
| 214 | Separate article on this later. | ||
| 215 | |||
| 216 | My next big step is compiling my own kernel for the Clara HD. With the | ||
| 217 | default configuration built for the vendor kernel, it appears to be | ||
| 218 | about 3M, so my goal is to build a kernel that is smaller than that | ||
| 219 | while retaining only the functionality that I need. I'm also not going | ||
| 220 | to include networking support as that is unneeded for my purposes, but | ||
| 221 | I suggest just keeping it if you're unsure. The wifi driver for the | ||
| 222 | Kobo Clara HD is available as an out-of-tree driver[8]. | ||
| 223 | |||
| 224 | You should first obtain the kernel source, with two main options, the | ||
| 225 | vendor kernel[9] and the mainline kernel (with akemnade's | ||
| 226 | patches)[10]. For the latter, you need to clone the repo and switch to | ||
| 227 | the latest kobo/drm-merged branch (kobo/merged-5.13 as of July 25, | ||
| 228 | 2021). | ||
| 229 | |||
| 230 | After you've got them and assuming the CROSS_COMPILE and ARCH | ||
| 231 | environment variables are set, you'd want to configure the kernel. | ||
| 232 | |||
| 233 | I had a hard time compiling the vendor kernel with many things | ||
| 234 | disabled, so I've kept my config somewhat similar to the default | ||
| 235 | config. The config I used is available here (EDIT: dead). | ||
| 236 | |||
| 237 | make menuconfig | ||
| 238 | make zImage | ||
| 239 | |||
| 240 | Assuming it compiles properly and arch/arm/boot/zImage exists, all | ||
| 241 | that's needed to is to write it to your uSD card at the 1M offset. | ||
| 242 | dd if=/path/to/kernel/zImage of=/path/to/uSDdev bs=512 seek=2048 | ||
| 243 | |||
| 244 | ## Custom U-Boot | ||
| 245 | I have not done this yet, nor really plan to, but if you do manage to | ||
| 246 | compile the Kobo's vendored u-boot source, then all you'd have to do to | ||
| 247 | install it is: | ||
| 248 | |||
| 249 | dd if=u-boot-file of=/dev/mmcblk0 bs=128k count=1 seek=6 | ||
| 250 | |||
| 251 | If I remember correctly, this command was included in an older | ||
| 252 | firmware's startup script/rcS for updating udev, and it should still | ||
| 253 | work. | ||
| 254 | |||
| 255 | ## Links | ||
| 256 | [1]: https://gitlab.com/postmarketOS/pmaports/-/merge_requests/2334 | ||
| 257 | [2]: https://ewontfix.com/14 | ||
| 258 | [3]: https://git.kernel.org/pub/scm/utils/dash/dash.git | ||
| 259 | [4]: https://github.com/akemnade/linux/tree/kobo/merged-5.13 | ||
| 260 | [5]: https://misc.andi.de1.cc/kobo/uboot-env.txt | ||
| 261 | [6]: https://misc.andi.de1.cc/kobo/ | ||
| 262 | [7]: https://gist.github.com/rofl0r/6168719/raw/183525e0f0007169a49392b21ceee5b507e3aee8/init.c | ||
| 263 | [8]: https://github.com/jwrdegoede/rtl8189ES_linux/tree/rtl8189fs | ||
| 264 | [9]: https://github.com/kobolabs/Kobo-Reader/blob/master/hw/imx6sll-clara/kernel.tar.bz2 | ||
| 265 | [10]: https://github.com/akemnade/linux/tree/kobo/drm-merged-5.12 | ||
