The journey to running Nerves on a Kobo Clara e-reader

For a few years now, I’ve been hesitant to buy an e-reader. I dislike that most of them have closed ecosystems. I was waiting for a “hacker-friendly” device and had high hopes for the PineNote. Unfortunately, the price tag of the PineNote and the difficulty of getting my hands on one left me without a “cheap” alternative. The PineNote is also much more than an e-reader and significantly larger, while I was looking for an open, pocket-sized device.

Kobo is a Toronto-based company that has released several e-reading devices over the past decade, and they have had a small, active hacker community building apps and plugins for their products. Initially, their devices shipped with an internal SD card you could replace with another operating system after opening the device, but later models now run their software on eMMC. Since they offered a cheap 6-inch entry model, the Clara (BW and Color), I decided to give it a try and ordered the Clara Color.

Kobo Clara Color

Ever since I started porting Buildroot and Nerves to exotic devices (running Nerves on Android e-waste), each time I get a consumer device in my hands, the first question that pops into my head is:

Will it run Nerves?

This topic is about answering that question and documenting my journey to understand how the device works and how to access relevant information so I can maybe build a Nerves firmware for it. I have no clue if I will make it, but documenting the process and progress might be interesting to some people here.

I’ll be adding posts to this topic, at random intervals, with my progress in the hope of helping demystify what it takes to get Nerves and Elixir running on consumer products and to inspire others to try to do the same.

10 Likes

Getting to know Clara

My first step is to figure out what information I can get from the device without opening it. The back cover shows that it’s a N367B model.

Since Kobo apparently has partnered with ifixit, I was able to find some guides showing the devices motherboards. There are two models the N367 and N367B.

We can clearly see a UART port on the top-left side of the N367 motherboard. A UART port is an asynchronous serial interface that uses separate TX (transmission) and RX (reception) lines for bidirectional communication.

On the right side of N367B motherboard, there seems to be something similar but without the same markings, so it would need some further investigation later on.

Why am I trying to figure out if there is a UART port somewhere? Because on most devices, this is where boot messages get printed. You can see what happens in the boot chain and, for linux based systems, the kernel messages, just like when your computer starts. This gives a lot of valuable information to create a bootable system later on. Some manufacturers use a UART port to flash the device before they leave the factory.

So, as finding a suitable UART port would require further investigation on my model and I didn’t want to open it up right away, I started trying to figure out if there was a way to stop the boot process and put the device in either fastboot mode or something else. Usually, you need to find a sequence of actions to perform when the device boots to get there.

Fastboot mode

I tried pressing the power button multiple seconds before pluging the usb cable, different combinations of pluging, unplugging, pressing the button etc… But it was not it.

Some devices sometimes require the host to “poll” the device with a fastboot command for them to interrupt their booting process. So,here’s how I managed to get it into fastboot.

  1. Turn off and unplug the Clara from your computer
  2. Launch the fastboot getvar all command on your computer
  3. Plug the Clara to your computer
  4. Quickly hold the power button until you see information in your host console
  5. Release the power button
  6. The Clara is now in fastboot mode

This is what you should get in your terminal:

➜  ~ fastboot getvar all
< waiting for any device >
(bootloader) 	hwcfg.PCB: [0] PCB=0x71
(bootloader) 	max-download-size: 0x1e00000
(bootloader) 	version: 0.5
all: Done!!
Finished. Total time: 0.003s

It doesn’t tell us much for now, but having the Clara in fastboot is useful if we want to flash existing partitions, or if we want to try a new boot image without flashing the emmc and potentially bricking it.

To get out of fastboot mode, just press the power button approximately 10 seconds.

Investigating the filesystem

When plugging the Clara to your computer, you can mount it as a mass storage device and put books on it. So let’s see what’s on that partition:

➜  KOBOeReader ls
fonts
➜  KOBOeReader ls -la
total 8364
drwxr-xr-x   7 marc marc    8192 jan  1  1970 .
drwxr-x---+  3 root root    4096 nov 15 12:13 ..
drwxr-xr-x   2 marc marc    8192 nov 14 10:19 .adobe-digital-editions
drwxr-xr-x   3 marc marc    8192 aoû 28 11:10 fonts
drwxr-xr-x  11 marc marc    8192 nov 15 12:13 .kobo
drwxr-xr-x  49 marc marc    8192 nov 14 11:36 .kobo-images
➜  KOBOeReader 

This kobo dot folder seems interesting, let’s check what’s in it:

➜  KOBOeReader ls -la .kobo
total 576
drwxr-xr-x 11 marc marc   8192 nov 15 12:13 .
drwxr-xr-x  7 marc marc   8192 jan  1  1970 ..
-rw-r--r--  1 marc marc     25 nov 14 10:19 affiliate.conf
drwxr-xr-x  2 marc marc   8192 nov 14 11:34 assets
drwxr-xr-x  2 marc marc   8192 nov 14 11:35 audiobook
-rw-r--r--  1 marc marc  19456 nov 15 12:06 BookReader.sqlite
drwxr-xr-x  2 marc marc   8192 nov 14 10:19 certificates
drwxr-xr-x  2 marc marc   8192 nov 14 11:34 custom-dict
-rw-r--r--  1 marc marc     75 nov 14 10:19 device.salt.conf
drwxr-xr-x  2 marc marc   8192 nov 14 11:35 dict
drwxr-xr-x  2 marc marc   8192 nov 14 11:36 dropbox
-rw-r--r--  1 marc marc   3072 nov 14 11:34 fonts.sqlite
drwxr-xr-x  2 marc marc   8192 nov 14 11:34 guide
drwxr-xr-x  2 marc marc   8192 nov 14 11:34 kepub
drwxr-xr-x  2 marc marc   8192 nov 14 10:19 Kobo
-rw-r--r--  1 marc marc 427008 nov 15 12:13 KoboReader.sqlite
-rw-r--r--  1 marc marc    105 nov 14 10:19 ssh-disabled
-rw-r--r--  1 marc marc     82 nov 15 12:12 version

Hang on… What is this ssh-disabled file…

To enable ssh:
- Rename this file to ssh-enabled
- Reboot the device
- Connect via: ssh root@<device_ip>

Ok, this is exciting :star_struck: Having ssh access as root would allow us to gather everything we need to run buildroot and Nerves on this thing.

SSH access

After renaming that file and ejecting the device, getting it’s IP from my dhcp server lease list, I was, as promised, able to connect to the Clara as root… :confetti_ball:

[root@kobo ~]# uname -a
Linux kobo 4.9.77 #1 SMP PREEMPT d226bc7bf-20250103T160218-B0103160930 armv7l GNU/Linux
[root@kobo ~]# cat /proc/cpuinfo 
processor	: 0
Processor	: ARMv7 Processor rev 4 (v7l)
model name	: ARMv7 Processor rev 4 (v7l)
BogoMIPS	: 15.60
Features	: half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm aes pmull sha1 sha2 crc32 
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xd03
CPU revision	: 4

Hardware	: MediaTek MT8110 board
Revision	: 0000
Serial		: 1234567890ABCDEF
[root@kobo ~]# ls -la /dev/disk/by-partlabel/
total 0
drwxr-xr-x    2 root     root           280 Nov 15 12:21 .
drwxr-xr-x    7 root     root           140 Nov 15 12:21 ..
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 UBOOT -> ../../mmcblk0p2
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 bl2 -> ../../mmcblk0p1
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 boot_a -> ../../mmcblk0p4
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 hwcfg -> ../../mmcblk0p6
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 ntxfw -> ../../mmcblk0p7
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 nvram -> ../../mmcblk0p3
lrwxrwxrwx    1 root     root            16 Nov 15 12:21 recovery -> ../../mmcblk0p11
lrwxrwxrwx    1 root     root            16 Nov 15 12:21 system_a -> ../../mmcblk0p10
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 tee_a -> ../../mmcblk0p5
lrwxrwxrwx    1 root     root            16 Nov 15 12:21 userdata -> ../../mmcblk0p12
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 vendor -> ../../mmcblk0p9
lrwxrwxrwx    1 root     root            15 Nov 15 12:21 waveform -> ../../mmcblk0p8
[root@kobo ~]# 

This tells us already quite a lot of things:

  • It’s running an old kernel, probably custom
  • It’s an Armv7 32 bits
  • It seems to rely on AB partitioning and has partitions dedicated to configuration, waveform information for the e-ink screen, etc…

I now need to understand a bit better how all of this is wired up, and also which drivers are used.

[root@kobo ~]# lsmod
wlan_drv_gen4m 1908365 0 - Live 0xbf14a000 (O)
wmt_cdev_bt 16871 0 - Live 0xbf141000 (O)
wmt_chrdev_wifi 12825 1 wlan_drv_gen4m, Live 0xbf138000 (O)
wmt_drv 1059215 4 wlan_drv_gen4m,wmt_cdev_bt,wmt_chrdev_wifi, Live 0xbf000000 (O)

lsmod doesn’t provide much info so I’m assuming there is a lot of stuff compiled within the kernel and some custom logic happening in the init proces. Time to dig into the filesystem and the different partitions! :nerd_face:

14 Likes

Love this! Following with a lot of interest :slight_smile:

2 Likes

Me too! This is proper hacker stuff :nerd_face:

1 Like

Time for more exploration

With root access to the ereader, I can get more info and copy what I need to the userdata partition where the books are stored. This is the partition that gets mounted as a USB mass storage device on my computer so I can easily fetch dumped files from it. It’s mount path on the Kobo is /mnt/onboard.

Let’s get the kernel config:

[root@kobo ~]$ zcat /proc/config.gz > /mnt/onboard/kernel.defconfig

I would also like to get a dump of several partitions so I can dig into them later if needed. I use the dd command for this.

[root@kobo ~]$ dd if=/dev/mmcblk0p4 of=/mnt/onboard/mmcblk0p4.img

For the system_a partition, since this is the booted system, it’s safer to remount it as read-only (when it works) before doing it, since the system can be in constant change depending on how it’s been configured.

[root@kobo ~]$ mount -o remount,ro /

It worked without any warnings so I could dump mmcblk0p10 without issues.

Understand the partition table

There is still something I’d like to know. Some partitions have _a suffixes. This makes it seem like there is some kind of A/B partitioning going on but I’m not sure. There might be some empty space between partitions that would somehow be used after rewriting the GPT partition table. Knowing when partitions start and end would let us see what we want.

Unfortunately, the fdisk that the Kobo ships with doesn’t support GPT partition tables…

[root@kobo ~]$ fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 15 GB, 15678308352 bytes, 30621696 sectors
1898 cylinders, 256 heads, 63 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1    0,0,2       1023,255,63          1 4294967295 4294967295 2047G ee EFI GPT 

It just shows one big partition, which means it was probably not compiled with GPT support enabled and there is nothing else on the Kobo system that can be helpful…

The only thing we can do is dump the GPT partition table from the disk. It should start at the third sector of the disk (a sector is 512 bytes, first one is MBR, second is the GPT header). Each table entry is 128 bytes. Let’s read the first 6 sectors of the table just in case. Since each partition table entry is 128 bytes, we will be reading 6*512/128=24 entries, which is more than enough.

[root@kobo ~]$ dd if=/dev/mmcblk0 bs=512 skip=2 count=6 | hexdump -C
6+0 records in
6+0 records out
3072 bytes (3.0KB) copied, 00000000  af 3d c6 0f 83 84 72 47  8e 79 3d 69 d8 47 7d e4  |.=....rG.y=i.G}.|
00000010  4a 5e ea ad fa 9b fc 47  96 4d 8f 76 be 94 7b 73  |J^.....G.M.v..{s|
00000020  00 04 00 00 00 00 00 00  ff 07 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  62 00 6c 00 32 00 00 00  |........b.l.2...|
00000040  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000080  af 3d c6 0f 83 84 72 47  8e 79 3d 69 d8 47 7d e4  |.=....rG.y=i.G}.|
00000090  6c 11 c8 2c 0f 1d ad 42  bc 97 a2 ab 54 75 e3 6a  |l..,...B....Tu.j|
000000a0  00 08 00 00 00 00 00 00  ff 0f 00 00 00 00 00 00  |................|
000000b0  00 00 00 00 00 00 00 00  55 00 42 00 4f 00 4f 00  |........U.B.O.O.|
000000c0  54 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |T...............|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000100  af 3d c6 0f 83 84 72 47  8e 79 3d 69 d8 47 7d e4  |.=....rG.y=i.G}.|
00000110  ea f1 8f 89 97 05 e8 45  8e d7 74 5f 4b 6e c6 c8  |.......E..t_Kn..|
00000120  00 90 00 00 00 00 00 00  ff 97 00 00 00 00 00 00  |................|
00000130  00 00 00 00 00 00 00 00  6e 00 76 00 72 00 61 00  |........n.v.r.a.|
00000140  6d 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |m...............|
00000150  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|

...

We can see all the partitions we already saw before, and the hex values of their start and end blocks are somewhere in the output. However, we need to translate this, and at this point, we might as well ask our friend Claude for some assistance…

This is what he says based on the partition table hexdump and some additional context I gave him:

[ON] :robot:

# Name Start LBA End LBA Size (sectors) Size (MB) Gap After
1 bl2 1,024 2,047 1,024 0.5 0
2 UBOOT 2,048 4,095 2,048 1.0 32,768
3 nvram 36,864 38,911 2,048 1.0 0
4 boot_a 38,912 137,215 98,304 48 0
5 tee_a 137,216 145,407 8,192 4.0 0
6 hwcfg 145,408 147,455 2,048 1.0 0
7 ntxfw 147,456 149,503 2,048 1.0 0
8 waveform 149,504 169,983 20,480 10.0 0
9 vendor 169,984 268,287 98,304 48 21,504
10 system_a 289,792 2,386,943 2,097,152 1,024 0
11 recovery 2,386,944 4,484,095 2,097,152 1,024 0
12 userdata 4,484,096 30,621,567 26,137,472 12,762.4 129

Two gaps:

  • 16 MB (32,768 sectors) between UBOOT and nvram
  • 10.5 MB (21,504 sectors) between vendor and system_a

Total device size: 30,621,696 sectors (14.6 GB)

Last partition ends at: 30,621,567 sectors

Unallocated space: 129 sectors (64.5 KB)

[OFF] :robot:

So, our friend is clear, there doesn’t seem to be any significant empty space on the disk. The _a suffix is just a naming convention they used. There is a recovery partition that exists which has the same size as system_a, which is likely used when system_a is detected as corrupted by the bootloader.

What Kernel is this?

At first glance, 4.9 is an old kernel. But looking into it’s config, we might find additional information. I have transfered all the files I wanted on my computer to continue my investigations.

➜  cat kernel.defconfig | grep ANDROID
CONFIG_ANDROID_PARANOID_NETWORK=y
CONFIG_ANDROID_DEFAULT_SETTING=y
# CONFIG_USB_G_ANDROID is not set
# CONFIG_ANDROID_LOGGER is not set
# CONFIG_ANDROID_LOW_MEMORY_KILLER is not set
CONFIG_ANDROID_INTF_ALARM_DEV=y
CONFIG_ANDROID=y
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_ANDROID_BINDER_DEVICES="binder,hwbinder,vndbinder"
# CONFIG_ANDROID_BINDER_IPC_32BIT is not set
# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set

Usually, having a bunch of _ANDROID_ config options means it’s an Android kernel and not mainline. This is not a good sign…

I started looking at available resources on Kobo kernels, and since there is a large community of hackers for old Kobo devices, I could find the Kobolabs git repository where Kobo actually releases tarballs of their kernels and toolchain :slight_smile:

It confirmed my assumption that it was an Android kernel, but also that the kernel source includes a lot of Mediatek stuff… The repo also includes the source code for the wifi driver and the u-boot they used.

It means that in order to use a recent mainline kernel, I’d need to port all these changes which is a significant amount of work and I’m far from being a kernel developer. I’m definitely not fluent enough in C and I would need to not only mainline the Mediatek parts, but also the wifi driver part if I don’t want a subpar experience with it.

But even if I managed to do this, there is still a major question that needs answering:

Is it using secure boot?

5 Likes