Running Ubuntu on the Lenovo IdeaPad Duet | Valentin Haudiquet
Pangram verdict · v3.3
We believe this text is mainly human-written, with some AI content.
AI likelihood · overall
HumanArticle text · 1,674 words · 1 segments analyzed
I own a Lenovo IdeaPad Duet Chromebook, a ChromeOS 2-in-1 ARM64 tablet. I had been running ChromeOS, then postmarketOS, which suddenly broke. Time for some firmware hacking to try and get Ubuntu to run there!Lenovo IdeaPad DuetThe Lenovo IdeaPad Duet: a 10.1" tablet with a plug-and-play detachable keyboard, a 1920x1200 screen, an ARM64 MediaTek MT8183 SoC and 4 GiB of RAM (Image credits: Lenovo)I bought the device a while ago, during my first year at engineering school. It was cheap (~100€), used but in really nice condition. I love 10" laptops for their portability, and this one could even work with a stylus for note-taking, a workflow I wanted to try (turns out it wasn't really for me).I had also never used ChromeOS, and was really curious. At the time I was developing Android apps, and being able to code natively and run the app on-device sounded exciting. It turned out to be disappointing because the performance in ChromeOS was not really there, and I had to run everything Linux through a container, which felt awkward. I quietly stopped using the device.A while later, I realized that postmarketOS supported it. That meant a real Linux distribution, based on Alpine Linux, with any DE (GNOME, KDE, etc.) and proper Linux tooling and performance. I had to try.Installing postmarketOS was really easy, thanks to the effort they put into the wiki and the installation guide. Basically: put the device in 'developer mode', download an image, write it to a USB stick and boot from it using the 'developer mode' menu. You then land in a live postmarketOS image you can use to install to the eMMC.The ChromeOS firmware 'developer mode' menu, which allows you to select boot media, USB or internal storage. Mine is in French. 'Start legacy BIOS' does nothing here; I suspect this only works on x86 Chromebooks, and they just reused the same firmware.So far, so good: they even have images with preinstalled DEs like GNOME. I'm a heavy GNOME user on my main computer, so I went with that one. Unfortunately, it turned out to be a mistake. GNOME can be quite resource-hungry, which didn't suit the device's weak CPU and 4 GiB of RAM. It was a bit slow, and there was too much friction for it to be really usable. I also find GNOME ill-suited to a "minimalist" workflow - it tends to go all out. Thinking about it now, a tiling window manager with a terminal-centric workflow would have surely been better for the device.I kept using it from time to time anyway. Eventually I updated to postmarketOS 26.06, and then the device refused to reboot.I managed to boot the old 25.12 version, but even writing 26.06 to a USB stick and booting it does not seem to work. Something must have changed between the two versions that silently breaks boot on the Duet. While investigating, I realized that all the necessary drivers were now in the mainline kernel. I also now work at Canonical on Ubuntu development, doing bring-up of RISC-V devices. Given the state of the Duet, an Ubuntu bring-up seemed genuinely feasible. So... I have a weekend. Let's do it!Installing Ubuntu?At this point, I have almost no idea how postmarketOS boots on the device, what the boot flow looks like, and whether anything will work with Ubuntu. One of the easiest ways to get 'something that looks like Ubuntu' is to replace the postmarketOS 26.06 rootfs with an Ubuntu rootfs. So that's what I did.I booted into my postmarketOS 25.12 USB stick. First, I inspected the state of the internal eMMC storage:Running fdisk on /dev/mmcblk0, printing the partition tableOk. So there's a mysterious "ChromeOS Kernel" partition, a /boot EFI partition, and then the rootfs partition. An easy way to then install the Ubuntu rootfs is to just replace that one. Which is exactly what I did, in the dumbest possible way:With that, I was ready to chroot into /mnt to enjoy my new Ubuntu system.Chrooted from the postmarketOS 25.12 USB stick into /dev/mmcblk0p3 with the new Ubuntu rootfs. I even installed some packages, like fastfetch! Notice the 6.12.87-mt81 kernel: that's the postmarketOS kernel from the USB stick.Ok, this is all very nice, but living inside a chroot forever and having to boot from USB just to mount the internal storage doesn't feel great at all. Time to see if we can boot into this new rootfs! I expected this to fail, since I assumed something was wrong with the new postmarketOS 26.06 kernel, but to my surprise it worked flawlessly. Which means the 26.06 issue must have been in the rootfs startup programs, silently crashing something.Booted from internal storage into the Ubuntu rootfs on /dev/mmcblk0p3. Notice the new 6.18.44-mt81 kernel: that's the postmarketOS kernel from the 26.06 installation, on the /dev/mmcblk0p1 or p2 boot partitions.As you can see, the device now calls itself localhost, so I'd have to do some setup to finish configuration; but I had already created my user account in the chroot before. Another issue: the new rootfs lacks the firmware for the postmarketOS kernel, so Wi-Fi no longer works. A simple fix is to take the 26.06 image, extract the /lib/firmware/6.18.../* tree and copy it into the rootfs, which would restore Wi-Fi.But that means staying with the postmarketOS kernel, unable to update. I did not want that, I want full Ubuntu running, including the kernel.So I did the simplest thing: I installed the linux-generic package from the archive. It copied vmlinuz and initrd into /dev/mmcblk0p2, the /boot partition. I also copied the upstream DTB for the device into the /boot partition, so I could hardcode it on the kernel command line if needed. I expected everything to crash, nothing to work, since I didn't know the bootloader and assumed the kernel wouldn't get the right DTB. I rebooted.To my surprise, the machine booted normally. Better yet: I ran fastfetch again and everything was identical, including the kernel version.Wait, what? I had just overwritten that kernel in the /boot partition. Is that partition not used at all?Using the Ubuntu kernelUnderstanding the boot flowClearly, I needed to take a step back and actually understand how the machine boots, as trying random stuff was not going to work anymore. So I read some resources and talked with Claude, giving him (it?) the URLs and asking for a boot flow explanation.Turns out the boot flow is a bit complicated on Chromebooks. Here are the schematics that Claude generated when explaining the chain to me. I find them quite clear.Firmware boot chain for Chromebooks. The boot ROM contains the FSBL, which loads Coreboot, which has upstream support for the Duet. Then, Coreboot loads the ARM Trusted Firmware (not relevant to this analysis), which then loads Depthcharge, the bootloader for ChromeOS devices.So Depthcharge, the ChromeOS bootloader, is also what shows the 'Developer Mode' menu that lets me pick between USB and internal storage at boot. This is really interesting. Now, what is the next step? What does Depthcharge actually load?Depthcharge booting internal storage. It only cares about the first, mysterious "ChromeOS kernel" partition. That actually contains a FIT image, which here is basically a bundle of kernel, initrd and device tree.That clears up the mystery! The /boot partition was never actually used at boot, only by postmarketOS to prepare the Depthcharge FIT image (using the mkdepthcharge tool). So installing my new kernel there was not enough. I would also have to re-generate the Depthcharge image.EFI environment neededAlthough this would be very easy, it would unfortunately not work. See, in this boot flow, there was never EFI-capable firmware involved. Neither EDK2 nor U-Boot is used: Depthcharge loads and boots the kernel directly from the FIT image. That works fine for the postmarketOS kernel, but not for the Ubuntu kernel, which is an EFI executable and expects an EFI environment. Ubuntu only boots via UEFI.So, we need a UEFI-capable firmware. The easiest to get working would be U-Boot: EDK2 can be harder to work with, and U-Boot already has support for the mt8183 SoC. That support is only present as a config for the 'Pumpkin' reference development board of the SoC, so we will need to do some work into U-Boot to be able to boot it. Our goal is to chainload U-Boot via Depthcharge, in order to get a UEFI environment, that will be able to boot Grub which will then boot our Ubuntu kernel:Target boot flow after Depthcharge.Let's get started hacking U-Boot! Wait, but do we even know how to print something to the screen? How are we going to debug this? We can't expect U-Boot with no drivers at all for the board to work right away printing debug logs... and bringing up a screen is not easy. Let's think this through.U-BootOur targetOur goal is to have U-Boot chainloaded as a Depthcharge payload, for it to then be able to access the eMMC and load Grub that is there. Grub will then use the UEFI storage drivers, i.e. U-Boot drivers exposed over the EFI API, to load the kernel and initrd.U-Boot already has support for the mt8183, so we won't need to write the drivers, which makes this work possible. Unfortunately, we will still need board-specific adaptations for the Lenovo IdeaPad Duet. I'll also refer to it by its codename kukui-krane (kukui being the codename for the mt8183-based chromebooks, krane for the specific Lenovo IdeaPad Duet).Realistically, we will need some form of logging to debug our U-Boot build. Two options:Using a serial output on the board. This is actually possible, with what is called a SuzyQable which enables 'CCD', Closed-Case Debug, and gives you a UART interface on Chromebooks. Unfortunately, I don't have such a cable (I've ordered one).Using the screen directly. Harder, because we would need to write a driver for that specific panel, or the specific framebuffer as initialized by Coreboot. It is also something that we ultimately want: proper U-Boot support for the device. So let's go!The Coreboot source tree has a krane_panel.c file with some of the panel initialization code. I was not