mlainez
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.

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.
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #blog-post
- #ai
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming











Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
mlainez
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.
fastboot getvar allcommand on your computerThis is what you should get in your terminal:
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:
This
kobodot folder seems interesting, let’s check what’s in it:Hang on… What is this
ssh-disabledfile…Ok, this is exciting
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…
This tells us already quite a lot of things:
I now need to understand a bit better how all of this is wired up, and also which drivers are used.
lsmoddoesn’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!lawik
Love this! Following with a lot of interest
Samjowen
Me too! This is proper hacker stuff
mlainez
Time for more exploration
With root access to the ereader, I can get more info and copy what I need to the
userdatapartition 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:
I would also like to get a dump of several partitions so I can dig into them later if needed. I use the
ddcommand for this.For the
system_apartition, 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.It worked without any warnings so I could dump
mmcblk0p10without issues.Understand the partition table
There is still something I’d like to know. Some partitions have
_asuffixes. This makes it seem like there is some kind of A/B partitioning going on but I’m not sure. There might be someempty spacebetween 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
fdiskthat the Kobo ships with doesn’t support GPT partition tables…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=24entries, which is more than enough.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]
Two gaps:
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]
So, our friend is clear, there doesn’t seem to be any significant empty space on the disk. The
_asuffix is just a naming convention they used. There is arecoverypartition that exists which has the same size assystem_a, which is likely used whensystem_ais 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.
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
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:
mlainez
Figuring out what happens at boot
Kobo releases the bootloader source code as well as the kernel sources it uses for their devices here.
After downloading and extracting both, I started investigating a bit more. The first thing I looked at is the bootloader config, and more specifically for any signs of secureboot config options.
But for this, I first need to know which config file I need to look into. I know from
cat /proc/cpuinfothat my SOC is of the mediatek MT811x family but I have no clue yet what I should be looking into in this list:Maybe I can find something useful in how the device is initialized, and since I have root access, it shouldn’t be that hard. The device uses busybox init and has this in its
/etc/inittabfile:Here’s what
/etc/init.d/rcSactually does. I only kept the relevant bits:mmcblk0p6mmcblk0p10in the case of amt8113t-ntxplatform, and p1 in other cases. Since p10 is actually mounted, it means our platform is identified asmt8113t-ntx)1070 250 150 600for our system)Kobo.tgzandKoboRoot.tgzupdate packages from onboard storage, which is how updates are distributed by Kobo and the rcS flashes the partitionsIt does quite a lot of things actually, but it at least gives us the indication that we are running on an MT8113T SOC, so we can conclude that the u-boot config file we should be looking into is
mt8113_tp1_emmc_defconfig. But let’s make absolutely sure of this.Looking into partition contents
When we listed the partitions above, we discovered that there was a
boot_apartition. Since this would likely be where the devicetree and kernel is located, there is probaby some information we can get from it. So let’s dump it usingdd, transfer it to my computer and try to look at what’s inside usinghexdump.It shows clearly that it is a U-Boot FIT image and it likely contains the kernel and devicetree indeed
With
uboot-tools, we can actually show much more valuable information.What this tells us:
aud8113tp1-E60T00-D0x00-8113T.dtbSo our device is most likely an
MT8113 TP1and since our Kobo reader has emmc, I think we can be certain that the U-Boot defconfig we should be looking into ismt8113_tp1_emmc_defconfig.In the file, we can find that these two options have been activated:
This tells us that U-Boot will verify the FIT image signature and its contents before booting it. That means it’s unlikely we will be able to boot another FIT image while keeping the same U-Boot on the Kobo.
Since we have the source code, we could change it’s config, rebuild it and try to flash it, but if the first level bootloader also checks the signature of the U-Boot image, we will just brick it.
There is only one thing left to do to figure that part out…
Opening it up and finding a serial port
I was pleasantly surprised to find a UART port just there on the left of the motherboard, waiting to be used
By just inserting some jumper wires in the holes, I could connect the ground and RX to my FDTI serial dongle. I just needed to figure out the baudrate now.
Once more, having root access to the device made this step easy:
There is a bunch of stuff we can ignore, the most important part for us is
console=ttyS0,921600n1. So the baudrate is 921600. I just needed to startminicomand press the Kobo’s power button.The entire boot log is quite long but here are some of the interesting parts:
So, it seems like the FIT image is verified before U-Boot is even started, which seems to be consistent with what is documented in the mediatek secureboot documentation.
It’s unlikely that with my current knowledge, I can bypass secure boot, even with access to a serial port. It might be that there are exploits existing on Mediatek SOCs and shady tools I could use to try to find a way around it, but I couldn’t find something “off the shelf” to use.
At this point, the first picture that came to my head was the “Directed by Robert B. Weide” meme video… Then it got me thinking.
A way forward
There are two main blockers to figure out:
Let’s look at our options for each of them.
Inappropriate kernel config
Nerves requires additional kernel modules in order to work. It can be for filesystems (F2FS, Squashfs) or to set up ethernet over USB. As listed above,
lsmoddoesn’t give any output besides the wifi and BT drivers, which means that all modules were built-in the kernel.A lot of Android kernels are locked like this. They usually don’t allow you to load external, unsigned modules. But I can check two things to see if that’s the case here:
The good news here is that module support is indeed activated in the kernel config:
It was expected since the wifi module is loaded in the
rcSscript. And we can also see that:Which means that this kernel can load any external, unsigned modules
All I need to do is compile a module on my computer, send it to the Kobo over ssh and try to load it.
After installing the toolchain, and configuring the kernel menuconfig, I was able to have a
f2fs.komodule, ready to be tested:And it loaded without any issue on the Kobo
This gives me a way to extend the current kernel with additional modules.
Finding space in the partition table
One option is to resize the userdata partition and make space for a Nerves system but I would like to keep using the Kobo as an ereader AND boot Nerves when I want/need it. So I need a more “creative” plan.
During my work running Nerves on Android e-waste, I’ve used “subpartitions” for the Fairphone 2. It means that one of the existing partitions was flashed with an entire disk image with its own partition table. Using tools like
kpartxin an initramfs allowed me to mount the Nerves system in the subpartitions.I could do something similar here. I could “use” the existing Kobo system as if it were an initramfs, and, by modifying its init script, mount a Nerves system in a binary file stored in the userdata partition.
In order to see if that would work, I can test this with a buildroot system, built with the Kobo toolchain and see if I can
pivot_rootto it from the Kobo system.If it works (I don’t see why it wouldn’t), then my next steps would be:
I’ve got my work cut out for me…
mlainez
Getting to a working buildroot system
Nerves can be seen as a “layer” on top of a Buildroot system. Buildroot is a build system that let’s you create a Linux root filesystem from scratch. It can also build the kernel and bootloader for you and packages it all in a nice image you can flash on an SD Card.
In order to have a working Linux device, you need at least three things:
The root filesystem is also refered to as the “userspace”.
On our Kobo, we already have a bootloader and a kernel that we cannot change (because of Secure Boot), so the only thing we need is a root filesystem that the kernel will mount to run its
initscript.We also need a way to avoid booting on the Kobo OS and boot our own system instead. And we want to do that as soon as we can.
What happens at boot
Once the kernel kicks in, it mounts the partition that has the rootfs, usually passed as
root=in the kernel command line, and will try to run/initor/sbin/initfrom it. It will run this script as PID1, meaning as the first process.On our device, the cmdline has
root=/dev/mmcblk0p10which means the kernel will mount/dev/mmcblk0p10as/and run/sbin/init.We need to replace this
/sbin/initby something else, so that it will allow us to “switch” to our alternative root filesystem and execute that system’s/sbin/initinstead.This is actually what happens in a Nerves system. Instead or running the buildroot system’s
/sbin/init, Nerves subsitutes it witherlinit, which gets ran as PID1.Replacing Kobo’s /sbin/init
We need our replacement init script to do several basic things:
/sbin/initas PID1I’ve written this init script to do exactly that.
Here’s an extract of it, showing the most relevant parts. If a touch is detected on the screen during boot, we first try to mount our image file containing the alternative rootfs, then we use
pivot_rootto use that system’s root as/and have the old root mounted in.oldroot. We then move all the relevant mounts to the new system, unmount the old root and then execute the alternative rootfs/sbin/initas PID1.Building an alternative rootfs
Now, we just need an image to boot from… You can find the buildroot external tree I used here.
The first step is to get to a system that compiles… And this is not always a walk in the park. Some of the issues along the way were due to the provided toolchain, kernel source, and Mediatek specific quirks.
1. Getting the kernel to compile (in order to have new modules in the rootfs)
The kernel is released as a
.tar.zst-part-aaand.tar.zst-part-abwhich is not really what buildroot expects. Moreover, inside this archive, there is also the Wifi driver source. So in order for buildroot to be able to use this, some gymnastics is needed in a Makefile extension.2. Getting the Wifi drivers to compile
Next to that, still in the same makefile extension, I’ve added a
LINUX_POST_BUILD_HOOKS += LINUX_BUILD_CONNECTIVITY_CMDSas a kernel post build hook to compile the Wifi drivers. but due to some require path issues, I had to create a patch to the wifi driver makefile…3. Building an image
Since I’m not going to have much in my image, only the rootfs, I don’t need a partition table, so Buildroot will just generate a blob with an ext4 rootfs in it that I can directly mount as a loop device.
Running the image for the first time
I created an init script to start usb networking when my system is initialized, it helps debugging in an easy way without the quirky jumper wires when using UART.
The buildroot image needs to be placed in
.buildroot/buildroot-kobo.imgon the userdata partition, so I just need to start the Kobo in it’s original OS, make it act as a mass storage device, and simply copy paste my image in the right folder.I have also created to init scripts to support the E-ink screen that I won’t detail here but after booting (and obviously many trials and errors that wouldn’t fit this article’s narrative), here’s what we have
Probably not the most impressive image you’ve ever seen but this is quite a win
We can connect to it with usb and see that indeed, we are now running buildroot.
Now do Nerves!
The first step to porting Nerves on anything is to first have a working Buildroot system. Now that it’s done, I can move to the next step and try running Nerves on it. I hope I can find workarounds to any future roadblocks ahead of this. But so far so good. I’m running the latest buildroot, on a 4.9 Android kernel full of Mediatek patches so I’m sure I will have to figure some more stuff out.
Will the next picture show a working Nerves system?
mlainez
From a working buildroot image to a booting Nerves System
From my past experience porting Nerves on devices, when you reach the point where you have a working buildroot system, you have made 70% of the work already, and what’s left is to make sure you get a few things ready:
nerves_defconfigwith the proper Nerves specific options and linking relevant parts to the Nerves System BR external tree config files and scriptsfwupconfiguration for your devicefw_env.configfits yourfwupconfigurationAlthough most of this can be “standardized”, there is always something specific about the devices we try to run Nerves on, and it can make it more or less complicated to get to a fully functioning system. But usually, just to see a system boot (not supporting all the board’s features for instance), you don’t need much.
In this case though, we can call my setup a bit “unorthodox”. I’m trying to run Nerves from a loop device created from a binary file in one of the ereader’s partitions… Moreover, I can’t change the kernel, so I’m stuck with a 4.9 Android kernel, and a provided toochain using GCC 4.9. So I expect some compilation issues when using a recent buildroot…
Getting all required packages to compile
Standards matter
The first thing that I stumbled upon when compiling the system were errors like these:
This is due to the fact that our compiler supports the C89 open standard by default for C code, and in that standard, initial declaration in for loops need to be outside the for statement, like this:
So you need to tell the compiler to use another standard. And the way to do that is to override the buildroot packages
.mkfiles in our own nerves_system.Although the solution was the same for several packages, it had to be done differently for some, since all packages makefiles are not exactly the same, may use different variables, etc…
An example of this can be found already in the buildroot system where I had to add eudev to add Wifi support.
Missing headers
Due to the fact the toolchain is using an old GCC 4.9 compiler, there are some features missing, and one of them was the lack of the
getrandomfunction insys/random.h.This function is called by
erlinitin theseedrng.cand it falls back to using/dev/randomin case of an issue (as far as I understood what it does). It means that I can just write a “stub” that will return -1 (fail) with a specific error type, and it should work.Missing constants in sysroot headers
When compiling
Vintage_net, it seemed that some constants were missing, I don’t have much clue what these constants do, but they were apparently added in later kernels. I was glad to have Claude in my life for this one tbh, and he gave me a nice patch to add to my system, in the form of anaf_compat.mkfile.Patching Cairo
Another one I wouldn’t have been capable of figuring out is with Cairo and the fact that compiling with GCC 4.9 with the compile flag
-O3generates invalid instructions. But it seems that changing thi globally was not working so Claude figured out that the issue came from a very specific file and proposed a patch for it, that I still don’t fully understand… I am also not sure why Cairo is in everynerves_defconfigout there.Getting erlang to compile
There were a few options that needed to be added to the C flags, such as
--disable-year2038which is a problem with systems that represent time as 32-bit signed integers.Indeed, the maximum date you can represent using this is January 19, 2038. The solution is to move to 64-bit which we can’t do here… So we need to disable it to avoid compilation errors.
I also had to explicitely tell erlang where to find supporting libraries for the compilation with
erl_xcomp_sysroot.Creating the fwup configuration
Since we are not running in a traditional setup, we don’t have a boot partition. It means out image looks like this:
This simplifies our configuration, so after some cleanup and setting up the environment variables to point to the right loop devices and making changes to the config files in
/etc, I was ready to go to the next step.Changing the init script
My image is now a bit more complex than before, and I need the original Kobo system to be able to read a
squashfspartition. Since there are some modifications I will need at boot, I will also need to useoverlayfsto make sure I can boot.The full scipt can be found here.
The first boot
It was now time to try it out… And after some tweaking in the init script, I finally saw this after manually doing a
modprobe g_etherin my serial console and tryingssh nerves.local.You’ll notice that a few things are not quite right. It seems some things are not read properly, and the application part is not mounted.
Whenever you see these “UNKNOWN”, and
Valid (unknown)on the firmware line, this usually means that you didn’t configure yourfw_env.configproperly, or that theuboot_envpart of your image is somehow corrupted, so you should double check that the partition offsets in yourfwup-common.confand your fw_env.config actually match. In my case, it was a misconfiguration.Fixing the application partition mount issue was also rather easy, I didn’t use the right loop device in my
erlinit.configI made sure the sizes were correct for the uboot_env variables, where Nerves stores information about the platform, architecture, serial, active partition, etc…
I also made a couple of changes to my
erlinit.configto load theg_ethermodule before the Erlang VM starts and fix it with the correct loop device.And after reuploading the image on that userdata partition, there I was, with a booting Nerves system
Next steps
I still have work to do in order to really use this device. The first step is to add Wifi support like I did in my buildroot system, as well as add all I need to be able to manipulate the eInk screen.
There is also a big limitation to my current setup. Using loop devices from files, you can’t rewrite the filesystem when the file is mounted, which means that the usual
mix firmware uploaddoesn’t work and each time I want to update my firmware, I have to boot the ereader on the Kobo OS to mount it as a mass storage device and copy paste the full.imgfile generated withmix firmware.image. It’s cumbersome but it works.Something I can try to fix this is resize the userdata partition on the device to free up space at the end of the eMMC. Then I can create an extra partition and flash a full disk image on it like I did on the fairphone 2 with kpartx. But that’s for later.
My current todo list is:
That’s a fun way to start 2026…
kip
I am thoroughly enjoying this series and I appreciate it’s a material amount of work to document and write it. You tell the story really well too. Thanks for sharing - and some incredibly spelunking.
lawik
I believe Cairo is in some systems to support Scenic by default.
mlainez
I was wrong
In my previous post, I wrote that
I said that it prevented me to use
mix uploadand the usual Nerves firmware update cycle. Well, It turns out that when I created my Nerves System, I made a mistake in myfwp.conf. The problem was that for the Kobo, I only have 2 partitions (rootfs and application), but the standard for Nerves is 3 partitions (+boot).During a firmware update, fwup checks that partitions are at particular offsets, and the default in
fwup.confto do this is to userequire-partition-offset(1, ...)in the upgrade tasks. But partition 1 is our application partition in this case, not the rootfs! And that’s why it was failing.Changing this to
require-partition-offset(0, ...)solved the issue and I can now update my firmware without copying it manually anymoreThis means that running Nerves from an image file mounted as a loop device, although non-standard, is a viable solution on “exotic” devices for which we have root access to.
Before I forget again…
When I explained the packages I had to update in order to make them work with a 4.9 Android kernel, I forgot to talk about a very important one.
In this image, you can see
nerves_uevent not startedin theApplicationspart ofNervesMOTD. The 4.9 kernel can produce UEvent messages that exceed the 64 KB maximum payload size supported by 2-byte packet framing, causing the process to crash when receiving large kernel event messages.The solution was to upgrade the packet framing from 2 bytes to 4 bytes on both the C and Elixir sides, and to add safe deserialization so that any corrupt or truncated frames are gracefully dropped instead of crashing the process. In all honesty, alghouth I understand why it crashed, I don’t think I would have solved it as easily without the help of Claude
I therefore had to create my own fork of NervesUEvent to solve this issue.
Now let’s get some wifi…
The Clara relies on proprietary firmware and binaries that live on the original partitions. And I have no clue how much I can redistribute these in my own libraries, so to avoid taking any risks and since my goal is to run Nerves alongside the original Kobo operating system, I will need to:
The birth of “blob_copy”
Since I need specific proprietary binaries for both wifi and eink support, I decided to create a dedicated project for this, which might even be useful in future work.
BlobCopymounts a block device partition read-only, copies files according to a manifest (supporting individual files, directories via cp -ar, and glob patterns), marks completion with a marker file, and distinguishes critical entries from non-critical ones. Any missing critical entry will stop the execution with an error, a missing non-critical entry will raise a warning.This is an example of how it’s used to support wifi on the Clara Colour:
This ensures that all binaries we need are copied to our rootfs at boot. This means we need to make our rootfs writable in some way, which is not the intended way to deal with your rootfs in Nerves. The only way I found to get it to work is by copying the entire rootfs in a writable tmpfs at boot and to actually mount that tmpfs to
/. this is all handled in my custom kobo-init script:This means that the blobs don’t persist across reboots, which is acceptable in this setup. The source code for BlobCopy can be found here.
Bringup of the wifi device in Elixir
There are two parts we need to cover:
wmt_chrdev_wifi.ko,wlan_drv_gen4m.ko, andwmt_cdev_bt.ko) sources are not part of the kernel source tree and must be in a specific directory on the rootfs for the proprietarywmt_loaderbinaries to work. This is the kind of non-standard proprietary quirks we have to deal with and fortunately, since we have root access to the e-reader, we can understand what happens in the original system and implement it in Elixir.The whole sequence and behavior to have wifi running on the Kobo Clara Color is implemented in a dedicated KoboWifi library. Here are a few details on how it works in practice:
Loading and unloading kernel modules in Elixir
I decided to build another library to handle loading kernel modules in a generic way. I don’t need it for the eink screen support but this could be handy in future work. I called this library KmodLoader. This library is being used in
KoboWifito load the necessary kernel modules or mount specific file systems needed by the modules themselves.Initializing the device with VintageNet
We need to implement
VintageNet.PowerManagerbehaviour so the WiFi init is driven by VintageNet’s lifecycle.On
power_on, it waits for all files to be copied viaKoboWifi.Init.wait_until_ready, then executes the full MediaTek init:/system/etc/firmwaresymlinkwmt_drv.kowithKmodLoader/dev/wmtdetectwmt_loader(also handling its quirk of returning exit code 255 even on success by parsing stdout for “do kernel module init succeed: 0”)/dev/stpwmtwmt_launcheras a supervised daemon/dev/wmtWifi(with up to 5 retries/sys/class/net/.Yeah… I know, this seems like a pretty weird init cycle, thank you Mediatek for making things easy…
On
power_off, modules are unloaded in reverse order.And once all of this is done, we can finally have Wifi on our Kobo Clara Colour with Nerves
Hello
wlan0Wrapup
Getting wifi to work on the Kobo Clara Colour with Nerves meant dealing with a lot of proprietary MediaTek quirks that were never designed to run outside of Android.
Binaries that exit 255 on success because they try to set Android system properties that don’t exist. Kernel modules that live outside the kernel source tree and must sit in a specific directory for a proprietary loader to find them. A wifi enable sequence that involves writing “1” to a character device, hoping for the best… An Android-style /system/etc/firmware symlink that has to exist for the firmware loading daemon to locate its files. And all of this on a read-only SquashFS rootfs that I had to copy into a tmpfs just so I could write the proprietary blobs somewhere
None of this is standard Nerves, and none of it is documented anywhere. I had to figure it out from the stock init scripts with, I must admit, a lot of help from Claude
, then trace through the MediaTek WMT stack, and figure out what each binary actually does by running it and reading its output on the original system.
To make it all work, I ended up creating several projects:
/lib/modules/, another gift from MediaTek…That’s what it takes to run Nerves on hardware that was never meant for it, but honestly, that’s also what makes this fun
It took me a while to continue this series due to the amount of elixir code I had to cleanup, but it’s almost finished now. And from the picture I just shared, you can probably guess what the next post will be about…
Stay tuned for some eink magic in Elixir
