Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7258b32ead
|
||
|
|
12062d6bca
|
||
|
|
704624878e
|
@@ -0,0 +1,9 @@
|
|||||||
|
ChromeOS
|
||||||
|
rootfs
|
||||||
|
devicetree
|
||||||
|
Depthcharge
|
||||||
|
earlycon
|
||||||
|
Coreboot
|
||||||
|
MIPI
|
||||||
|
GPIO
|
||||||
|
defconfig
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
interface OgData {
|
||||||
|
image?: string
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
url?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
link: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// Unique key per component instance so multiple BlogLink usages (even of the
|
||||||
|
// same URL) don't share/collide their async data state.
|
||||||
|
const instanceId = useId()
|
||||||
|
const { data: og } = await useAsyncData<Partial<OgData>>(
|
||||||
|
`blog-link-${instanceId}`,
|
||||||
|
() => $fetch('/api/ogimage', { params: { url: props.link } }),
|
||||||
|
{ default: () => ({}) }
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a
|
||||||
|
:href="props.link" target="_blank" rel="noopener noreferrer"
|
||||||
|
class="block ml-auto mr-auto mt-2 mb-2 sm:max-w-[55%] max-w-[75%] rounded-xl overflow-hidden border border-slate-200 hover:border-slate-400 transition-colors bg-white group no-underline"
|
||||||
|
>
|
||||||
|
|
||||||
|
<img
|
||||||
|
v-if="og?.image" :src="og.image" :alt="og?.title || props.link"
|
||||||
|
class="w-full max-h-[320px] object-cover block" loading="lazy"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div v-if="og?.title || og?.description" class="p-4">
|
||||||
|
<h3 v-if="og?.title" class="text-lg font-semibold text-slate-800 group-hover:underline">{{ og.title }}</h3>
|
||||||
|
<p v-if="og?.description" class="text-sm text-slate-500 mt-1 line-clamp-2">{{ og.description }}</p>
|
||||||
|
<p class="text-xs text-slate-400 mt-2 pb-1 leading-relaxed truncate">{{ props.link }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Fallback when no metadata could be retrieved -->
|
||||||
|
<div v-else class="p-4">
|
||||||
|
<h3 class="text-lg text-center font-semibold text-slate-800 group-hover:underline break-all">{{ props.link }}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="$slots.default" class="ml-auto mr-auto mt-2 w-fit max-w-[80%] slot pb-4">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.slot > * {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="ml-auto mr-auto mt-2 mb-2 p-2 pl-4 bg-slate-100 rounded sm:max-w-[85%] max-w-[95%] border-l-2 border-slate-400 slot">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto mr-auto mt-2 w-fit max-w-[80%] slot-desc">
|
||||||
|
<slot name="description" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.slot :deep(blockquote) {
|
||||||
|
margin: 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.slot :deep(blockquote > *) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.slot-desc > * {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="ml-auto mr-auto mt-2 mb-2 p-2 rounded-xl bg-slate-100 sm:max-w-[85%] max-w-[95%] overflow-x-auto slot">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto mr-auto mt-2 w-fit max-w-[80%] slot-desc">
|
||||||
|
<slot name="description" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.slot :deep(table) {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.slot :deep(th),
|
||||||
|
.slot :deep(td) {
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.slot :deep(th) {
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.slot :deep(tr:nth-child(even) td) {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.slot-desc > * {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
video: string,
|
||||||
|
poster?: string,
|
||||||
|
controls?: boolean | string,
|
||||||
|
autoplay?: boolean | string,
|
||||||
|
loop?: boolean | string,
|
||||||
|
muted?: boolean | string,
|
||||||
|
}>(), {
|
||||||
|
poster: undefined,
|
||||||
|
controls: true,
|
||||||
|
autoplay: false,
|
||||||
|
loop: false,
|
||||||
|
muted: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const parseBool = (value?: boolean | string) => value === true || value === "true"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<video
|
||||||
|
class="object-contain sm:max-w-[85%] max-w-[95%] m-auto"
|
||||||
|
:src="props.video"
|
||||||
|
:poster="props.poster"
|
||||||
|
:controls="parseBool(props.controls)"
|
||||||
|
:autoplay="parseBool(props.autoplay)"
|
||||||
|
:loop="parseBool(props.loop)"
|
||||||
|
:muted="parseBool(props.muted)"
|
||||||
|
preload="metadata"
|
||||||
|
playsinline
|
||||||
|
/>
|
||||||
|
<div class="ml-auto mr-auto mt-2 w-fit max-w-[80%] slot">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.slot > * {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,488 @@
|
|||||||
|
---
|
||||||
|
date: '2026-08-30'
|
||||||
|
title: 'Running Ubuntu on the Lenovo IdeaPad Duet'
|
||||||
|
tags:
|
||||||
|
- ubuntu
|
||||||
|
- u-boot
|
||||||
|
- arm
|
||||||
|
- chromebook
|
||||||
|
---
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
<!-- more -->
|
||||||
|
|
||||||
|
## Lenovo IdeaPad Duet
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/lenovo-ideapad-duet.jpg"}
|
||||||
|
*The 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](https://www.lenovo.com/fr/fr/p/laptops/lenovo/chromebooks/lenovo-ct-x636/zziczctct1x))
|
||||||
|
::
|
||||||
|
|
||||||
|
I bought the device a while ago, during my first year at ENS Rennes.
|
||||||
|
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](https://postmarketos.org/) supported it. That meant a real Linux distribution, based on [Alpine Linux](https://alpinelinux.org/), 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](https://wiki.postmarketos.org/wiki/Lenovo_IdeaPad_Duet_Chromebook_(google-krane)) and the [installation guide](https://wiki.postmarketos.org/wiki/Category:ChromeOS). 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.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/duet-depthcharge.jpg"}
|
||||||
|
*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](https://vhaudiquet.fr/blog/joining-canonical-riscv/). 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:
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/fdisk.jpg"}
|
||||||
|
*Running fdisk on /dev/mmcblk0, printing the partition table*
|
||||||
|
::
|
||||||
|
|
||||||
|
Ok. 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:
|
||||||
|
|
||||||
|
::BlogCode
|
||||||
|
```console
|
||||||
|
# mount /dev/mmcblk0p3 /mnt
|
||||||
|
# ls /mnt/boot
|
||||||
|
Nothing, so /dev/mmcblk0p2 was mounted on /boot
|
||||||
|
# rm -rf /mnt/*
|
||||||
|
Removing all files on the old postmarketOS rootfs
|
||||||
|
# curl -L -O https://cdimage.ubuntu.com/.../ubuntu-base-26.04-base-arm64.tar.gz
|
||||||
|
Downloading the Ubuntu 'base' image, a minimal Ubuntu rootfs
|
||||||
|
# tar -C /mnt -xzf ubuntu-base-26.04-base-arm64.tar.gz
|
||||||
|
Extracting that base Ubuntu rootfs at /dev/mmcblk0p3
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
With that, I was ready to `chroot` into `/mnt` to enjoy my new Ubuntu system.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/ubuntu-chroot.jpg"}
|
||||||
|
*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.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/ubuntu-rootfs-booted.jpg"}
|
||||||
|
*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 kernel
|
||||||
|
|
||||||
|
### Understanding the boot flow
|
||||||
|
|
||||||
|
Clearly, 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.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/duet_firmware_chain.svg"}
|
||||||
|
Firmware boot chain for Chromebooks. The boot ROM contains the FSBL, which loads Coreboot, which has [upstream support for the Duet](https://github.com/coreboot/coreboot/blob/main/src/mainboard/google/kukui/panel_krane.c). Then, Coreboot loads the Arm Trusted Firmware (not relevant to this analysis), which then loads [Depthcharge](https://chromium.googlesource.com/chromiumos/platform/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?
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/duet_emmc_layout_today.svg"}
|
||||||
|
Depthcharge booting internal storage. It only cares about the first, mysterious "ChromeOS kernel" partition. That actually contains a [FIT image](https://fitspec.osfw.foundation/), 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 needed
|
||||||
|
|
||||||
|
Although this would be very easy, it would unfortunately not work. See, in this bootflow, 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](https://ubuntu.com/hardware/docs/image-cookbook/explanation/bootflow/)**.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/duet_planned_efi_path.svg"}
|
||||||
|
*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-Boot
|
||||||
|
|
||||||
|
### Our target
|
||||||
|
|
||||||
|
Our 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](https://chromium.googlesource.com/chromiumos/third_party/hdctools/+/main/docs/ccd.md#suzyq-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 so sure what to do with that, but I figured that either we could use Coreboot/Depthcharge code to rewrite a panel driver, or directly use the framebuffer that Coreboot had already set up. So I gave Claude that suggestion.
|
||||||
|
|
||||||
|
Immediately, it started with a wonderful idea: instead of trying to make that work inside U-Boot directly, just write an assembly stub that shows colors on the screen. It also figured out how to read Coreboot's "LBIO" table to get the framebuffer details. I asked it for a detailed prompt for an agent to implement that, and it wrote one.
|
||||||
|
|
||||||
|
### krane-fb-stub
|
||||||
|
|
||||||
|
I rebooted the Duet, installed [OhMyPi](https://omp.sh/) and connected it to [GLM-5.3-Flash](https://z.ai/blog/glm-5.3-flash). I then gave it the prompt Claude had prepared, and it started implementing the stub and investigating all available kernel/firmware logs on-device.
|
||||||
|
|
||||||
|
::BlogQuote
|
||||||
|
> # Task: Build and test a minimal ARM64 "hello framebuffer" stub for a Lenovo
|
||||||
|
> # IdeaPad Duet (MT8183 / Krane)
|
||||||
|
>
|
||||||
|
> ## Goal
|
||||||
|
>
|
||||||
|
> Produce the smallest possible standalone ARM64 binary that depthcharge
|
||||||
|
> (the ChromeOS firmware bootloader) can load as if it were a Linux kernel,
|
||||||
|
> which does nothing but locate the already-initialized boot-splash framebuffer
|
||||||
|
> and fill it with solid colors at distinct checkpoints. This validates the
|
||||||
|
> whole depthcharge → custom-payload pipeline before any real U-Boot
|
||||||
|
> bring-up work begins. No U-Boot involved yet — this is a from-scratch
|
||||||
|
> freestanding binary, as small and dependency-free as possible.
|
||||||
|
|
||||||
|
#description
|
||||||
|
*Extract of the prompt fed to my local OhMyPi + GLM-5.3-Flash agent*
|
||||||
|
::
|
||||||
|
|
||||||
|
The agent happily started writing ARM assembly as the entry point, then C code for the actual "color display" thing, i.e. parsing Coreboot tables and device tree, finding the framebuffer, and utility code for showing colors.
|
||||||
|
|
||||||
|
The flow went like this: the agent would make a build, then bundle it into a Depthcharge image and flash it to internal storage. Then I would reboot into it, see what happened, and report back to the agent. It took a total of 10 tries for us to get a fully working stub.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/omp-stub.jpg"}
|
||||||
|
*OhMyPi testing the framebuffer stub in QEMU, mid-implementation (**not** part of the prompt, by the way)*
|
||||||
|
::
|
||||||
|
|
||||||
|
There were small issues, like wrong register layouts, which required going back and comparing the Coreboot and Depthcharge trees. Debugging was hard at first, because "black screen, no backlight" could mean anything, from no payload at all, to any part of the display driver being wrong.
|
||||||
|
|
||||||
|
Eventually, it figured out the real issue. Depthcharge was actually blanking the screen, killing the backlight, and disabling the display. That is a real problem, that the agent chose to circumvent by adding a *revival* step: exactly undoing what Depthcharge did, right after handoff.
|
||||||
|
|
||||||
|
Afterwards, debugging got easier: I started seeing backlight patterns (blinks) that I could describe to the agent as feedback. Soon enough, there were colors on the screen.
|
||||||
|
|
||||||
|
Finally, the stub was complete, and showed the color sequence imagined by Claude: red (early init), yellow (DTB parsed), green (LBIO parsed), blue (all good). This was actually completely faked by the agent, as early init, DTB and LBIO are all required to find the framebuffer, so it just parsed all and showed all colors in a sequence. In any case, drawing to the framebuffer now works!
|
||||||
|
|
||||||
|
::BlogVideo{video="/img/duet-ubuntu/duet-stub.mp4"}
|
||||||
|
*The final stub booted on the Duet*
|
||||||
|
::
|
||||||
|
|
||||||
|
### U-Boot using the framebuffer
|
||||||
|
|
||||||
|
So, this framebuffer revival hack is a bit ugly; but I still do not have a serial cable and I would really like to be able to see U-Boot logs on the screen, and now we have a way to print to the screen, so let's go with that.
|
||||||
|
|
||||||
|
With the stub done, I asked the agent to prepare a prompt for a new session to get U-Boot working on the board, including graphical output reusing our work from the stub.
|
||||||
|
|
||||||
|
::BlogQuote
|
||||||
|
> # Task: bring up mainline U-Boot on the Lenovo IdeaPad Duet (google,krane sku176, MT8183)
|
||||||
|
>
|
||||||
|
> You are taking over a bring-up that just passed its first milestone: a minimal bare-metal payload ("krane-fb-stub") boots via depthcharge and draws to the panel. Your job is to replace it with mainline U-Boot as the payload, with a working framebuffer console, in a form that is upstreamable to mainline U-Boot later.
|
||||||
|
>
|
||||||
|
> ## Current state
|
||||||
|
> U-Boot shallow clone: /home/vhaudiquet/u-boot (mainline main, ~2026-08). SoC support exists: arch/arm/mach-mediatek/mt8183/. Reference target: configs/mt8183_pumpkin_defconfig (CONFIG_POSITION_INDEPENDENT=y, MTK serial, mtk-sd MMC, WDT, USB). dts/upstream/src/arm64/mediatek/mt8183-kukui-krane-sku176.dts exists; there is NO krane/kukui defconfig or board dir yet — that is your starting point.
|
||||||
|
|
||||||
|
#description
|
||||||
|
*Extract of the agent prompt for the new session, for U-Boot bring-up*
|
||||||
|
::
|
||||||
|
|
||||||
|
This was unexpectedly a **lot** harder than the framebuffer stub. It took 27 tries before U-Boot ran properly on the device.
|
||||||
|
|
||||||
|
The first flash went in the worst possible way, i.e. the screen was pitch black again. This is really bad, as I cannot know anything about what is wrong from the observation.
|
||||||
|
|
||||||
|
I suggested the agent to use the same kind of debugging we did before: show colored bands on the screen for every step. One in the stub (which was kept at first for the revival step), one as first step in U-Boot, etc. This helped a lot to pinpoint the issues to the specific lines of code involved.
|
||||||
|
|
||||||
|
It turned out that the issue was very early in U-Boot (the agent added something like 13 checkpoints, and I only saw the second one). Actually, the steps were a bit more embarrassing, with errors made by the agent thinking something was already in U-Boot when it was in the stub, and other issues, but we ended up finding the big one: an alignment problem.
|
||||||
|
|
||||||
|
There are two separate alignment requirements in play here, and they can't both be satisfied at the same address:
|
||||||
|
|
||||||
|
- depthcharge's boot protocol hands control to a fixed offset: `0x40` bytes into the payload. No matter what. On this device that offset is not a multiple of 4096.
|
||||||
|
- U-Boot, built as position-independent code, requires its entry point to be on a 4096-byte boundary. Its startup code literally checks this and spins forever if it doesn't hold, because the relocation math it does immediately afterward assumes it.
|
||||||
|
|
||||||
|
The only way to fix that seems to be keeping a shim loader, that can have its entry point at the address that Depthcharge wants, and that will jump to U-Boot afterwards, which can be anywhere in the payload. So this is what we did.
|
||||||
|
|
||||||
|
Everything after that was faster, and the issues that were left were all small (missing environment variable in defconfig for the board, reset that turned out to be a panic and a crash in the video driver's memory mapping, as U-Boot was running out of page-table space). Eventually, we got rid of all those issues, and U-Boot finally showed up on the display.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/first-uboot-prompt.jpg"}
|
||||||
|
*U-Boot finally appearing on the Duet. It shows up in portrait instead of landscape, with all the diagnostic color bands from earlier.*
|
||||||
|
::
|
||||||
|
|
||||||
|
### Console and USB keyboard
|
||||||
|
|
||||||
|
Nice! We have U-Boot running. We can remove all of those silly diagnostic bands.
|
||||||
|
|
||||||
|
The console appears in portrait, and given that the device has an attached keyboard, it makes more sense to display it in landscape. So we need to add a `rot=3` parameter for the screen orientation by default.
|
||||||
|
|
||||||
|
Once we have this, we also need to rebuild U-Boot enabling all UEFI-support related configurations. Then, time to try and boot!
|
||||||
|
Wait, I can't type anything?
|
||||||
|
|
||||||
|
Right, so we first need to make the keyboard work in the U-Boot console, which will help with debugging.
|
||||||
|
All we need is to enable a pre-init sequence that will run the USB set-up (even though the keyboard attaches via pogo pins, those pogo pins actually carry USB data lines).
|
||||||
|
|
||||||
|
Then we can start typing. Let me type 'help' to look at the commands and... weird, the console does not scroll. The text overwrites itself in a weird way? That was really puzzling. I reopened the agent and explained the problem, since it didn't look like a straightforward issue.
|
||||||
|
|
||||||
|
It turns out that it was not a straightforward issue at all, as it was actually an upstream bug. I prepared an [upstream patch that fixes scrolling on a weirdly rotated screen](https://lore.kernel.org/u-boot/20260831163056.3854693-1-valentin.haudiquet@canonical.com/T/#u) (i.e. screens where `rot=1` or `rot=3`). This is literally a one-character fix, that the agent pointed out. Nice to run into that and fix it!
|
||||||
|
|
||||||
|
Now we can finally try booting.
|
||||||
|
|
||||||
|
### Booting the eMMC
|
||||||
|
|
||||||
|
Let's go! `bootefi bootmgr` is the magic command that starts the EFI boot manager and tries to boot the EFI entries. Typing that, and... nothing. Right, we don't have any EFI bootable executable yet on the EFI partition... the Ubuntu chroot does not contain grub and it was never installed properly. So let's reboot into the chroot from the postmarketOS USB stick and install Grub:
|
||||||
|
|
||||||
|
::BlogCode
|
||||||
|
```
|
||||||
|
$ sudo apt install grub-efi-arm64
|
||||||
|
... installing grub package ...
|
||||||
|
$ sudo grub-install --target=arm64-efi --efi-directory=/boot/efi \
|
||||||
|
--boot-directory=/boot
|
||||||
|
... installing grub to /boot and /boot/efi ...
|
||||||
|
```
|
||||||
|
::
|
||||||
|
|
||||||
|
Now let's try booting again. This time, `bootefi bootmgr` takes us to Grub!
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/first-grub-console.jpg"}
|
||||||
|
*The Grub prompt appearing for the first time on the Duet. Sadly, that's not a Grub menu... something went wrong when Grub tried to load its configuration.*
|
||||||
|
::
|
||||||
|
|
||||||
|
After investigating in Grub console, I can manually make it load `grub.cfg`; something seems wrong at handoff time, and Grub cannot access the eMMC. Maybe it is because U-Boot is still using it, so Grub cannot directly access it, and then Grub fully loads without its config, U-Boot lets go of the eMMC, and Grub can now access it, but too late. In any case, we can manually progress to a menu.
|
||||||
|
|
||||||
|
The only issue is that the menu was in portrait. After changing Grub config, we can finally get to a clean landscape Grub menu!
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/first-grub-menu-landscape.jpg"}
|
||||||
|
*The Grub menu, finally working for the first time on the Duet.*
|
||||||
|
::
|
||||||
|
|
||||||
|
Finally, let's boot into Ubuntu! I see some kernel logs, in portrait of course, and... what's happening? Why is the display suddenly corrupting itself? Oh. Right. Our display framebuffer is a hack.
|
||||||
|
|
||||||
|
Remember the "revival" hack to reuse the framebuffer from Coreboot for U-Boot and Grub?
|
||||||
|
Well, it seems that the kernel did not like it. After the end of the earlycon which uses that same EFI-provided framebuffer,
|
||||||
|
the kernel tries to set up the display for itself, finding it in this weird hacked state. At least that's my theory.
|
||||||
|
In any case, the display shows weird things: blinking bands, logs disappearing, lines appearing here and there. Something is wrong.
|
||||||
|
|
||||||
|
Given the display hack and the fact that there was no way I was gonna see kernel logs on there, it felt only natural to try and implement a *real* panel driver for U-Boot and drop the hack once and for all. So that is what I (or rather, the agent) did.
|
||||||
|
|
||||||
|
## U-Boot panel driver
|
||||||
|
|
||||||
|
So.... we have to write a real panel driver. How do we do that? This was also the end of the weekend, and I was starting to feel tired. But I was set on starting the real panel driver work when convenient for me.
|
||||||
|
|
||||||
|
I had some time on weekday evenings and the next weekend, but not enough of course to try and write a driver myself. So I started another agent with that mission:
|
||||||
|
|
||||||
|
::BlogQuote
|
||||||
|
> # Task: proper MT8183 display pipeline bring-up (MIPI DSI host + PHY + BOE TV101WUM-NL6 panel driver) for U-Boot on the Lenovo IdeaPad Duet (google,krane sku176)
|
||||||
|
> You are taking over an in-progress U-Boot port on this machine. U-Boot already boots on the device with a working rotated vidconsole. Today's display path is a "scanout" driver that revives the display pipeline left running by the boot firmware (coreboot + depthcharge) instead of bringing it up itself. Your job is to add a real display pipeline: a MediaTek MT8183 MIPI DSI host driver, the MIPI TX PHY, and a proper driver for the panel (BOE TV101WUM-NL6), so U-Boot brings the panel up from scratch and no longer depends on firmware handoff.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
This also meant going back into the dark: no screen output at all if something failed. Back to blinking screens and dark panels. After 3 flashes like that, it was clear this wasn't going to work. Debugging a real display driver with blinking lights alone is just not realistic.
|
||||||
|
|
||||||
|
Remember the SuzyQable I ordered? Well, it fortunately arrived around that time. It's not a cable per se, but rather a small SuzyQ adapter that you plug any regular USB-C cable into for the host connection.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/suzyq-adapter-2.jpg"}
|
||||||
|
*The SuzyQ adapter. It plugs into the device's USB-C port and connects to a host computer with a USB-C cable, giving serial access.*
|
||||||
|
::
|
||||||
|
|
||||||
|
This required a small amount of work to bring up serial support in U-Boot: just changing the baudrate in the defconfig to 115200. Not obvious at first, but `pumpkin`, the reference `mt8183` board in the U-Boot tree, sets the baudrate to `921600`, and that setting had been carried over to `kukui_krane`.
|
||||||
|
|
||||||
|
After that, serial helped spot the issue with the panel driver: the agent had forgotten to set a property that gets read during initialization, leading to a NULL pointer dereference. An easy fix. Once that was done, the panel driver worked.
|
||||||
|
I would have expected this to be much harder, but with serial the issues were easy to spot, and the agent mostly adapted work from the existing Coreboot panel driver (and the kernel one), which made the task easy for it (him?).
|
||||||
|
|
||||||
|
Technical details on the panel driver and the whole U-Boot saga are available in the "Cleaning up, technical details" section below. Before that, let's boot the kernel.
|
||||||
|
|
||||||
|
## Kernel
|
||||||
|
|
||||||
|
### A missing module
|
||||||
|
|
||||||
|
Finally, we can try booting our Ubuntu 7.0 kernel!
|
||||||
|
Booting it with Grub went fine
|
||||||
|
|
||||||
|
### The hang: fixing with an agent
|
||||||
|
|
||||||
|
, but the kernel output on screen got stuck once the boot console went down.
|
||||||
|
It seems that the U-Boot display driver keeps control of the screen, so when the kernel wants to take over and bring the display up itself, it can't. That is ok, it is also something we can fix directly. We still have the rest of the logs over serial now (with the SuzyQ), so let's follow... wait... why did it stop there?
|
||||||
|
|
||||||
|
So it turns out that the kernel 'hangs' in the middle of initrd, sometimes before, sometimes after pivoting root and showing the login prompt. I've had a true nightmare debugging this. I was pretty sure it was a U-Boot issue (something going wrong in the handoff), but the agents were convinced that there were upstream kernel bugs everywhere. We tried many things in random order, including a lot of changes that weren't necessary.
|
||||||
|
|
||||||
|
This ate a lot of my evenings and got nowhere.
|
||||||
|
|
||||||
|
In the end I had the new OpenAI model, GPT-6 Astra, take a look, and maybe because it was new (so its knowledge cutoff was later), it found a real issue: [an upstream kernel patch not present in the 7.0 kernel, "pmdomain: mediatek: Fix mt8183 hang on boot"](https://lkml.org/lkml/2026/7/29/1310). At that moment I thought Astra was magic and everything was solved. I was about to find out that I was wrong.
|
||||||
|
|
||||||
|
To try it, I tasked an agent with rebuilding the 7.0 kernel for me with that patch on top and the Ubuntu default configs. Somehow, that didn't work at all: the kernel didn't boot to initrd properly. Kernel builds were also very slow on the device, and I had to rebuild all the modules, which was even slower. That was not fun at all.
|
||||||
|
|
||||||
|
I decided to switch to the development release, Ubuntu 26.10 Stonking Stingray. It has a 7.2 kernel packaged, which is more recent, contains that patch, and maybe other fixes introduced along the way. Upgraded the kernel package, rebooted... and... it's just like the 7.0 kernel. Same hang. Astra was wrong.
|
||||||
|
|
||||||
|
So, this patch might be useful, but the hang that it fixes seems very specific and might not be the one I was experiencing. It still allowed me to switch to a more recent kernel, and to consider the fact that there might be multiple bugs involved causing multiple different kinds of hang. That might be a reason why the agent went in all kinds of random directions and each fix did not seem to change anything.
|
||||||
|
|
||||||
|
### Getting (a bit more) involved
|
||||||
|
|
||||||
|
I decided to change my approach and get a bit more involved in the fixing process.
|
||||||
|
I started reading a full boot log from a hang, looking for every error or weird stuff in **chronological order**.
|
||||||
|
|
||||||
|
The first weird thing was in the U-Boot startup log over serial:
|
||||||
|
|
||||||
|
::BlogCode
|
||||||
|
```
|
||||||
|
U-Boot 2026.10-rc2-00071-g7670f264dc3a (Sep 10 2026 - 21:09:40 +0200)
|
||||||
|
|
||||||
|
CPU: MediaTek MT8183
|
||||||
|
DRAM: 2 GiB
|
||||||
|
Core: 47 devices, 14 uclasses, devicetree: separate
|
||||||
|
MMC: mmc@11230000: 0, mmc@11240000: 1
|
||||||
|
Loading Environment from nowhere...
|
||||||
|
Loading Environment from nowhere... OK
|
||||||
|
In: serial,usbkbd
|
||||||
|
Out: serial,vidconsole
|
||||||
|
Err: serial,vidconsole
|
||||||
|
Net: No ethernet found.
|
||||||
|
```
|
||||||
|
#description
|
||||||
|
*U-Boot banner and initial logs. Do you see something weird?*
|
||||||
|
::
|
||||||
|
|
||||||
|
U-Boot only reports 2 GiB of DRAM! We had some issues around memory reservation tables and the EFI memory map before, so this might be real. Could be harmless, but might be a problem, so let's fix it. That one was easy to hand to an agent. In parallel I had other agents working on fixing the slow display bring-up, and the slow serial console.
|
||||||
|
|
||||||
|
The agent fixed this the right way (reading Coreboot tables to find the actual amount of memory on the board), and also the wrong way (adding a config override to 4 GiB in defconfig for `kukui-krane`). Given that `kukui-krane` only exists in 4 GiB variants (to my knowledge), I decided to still keep the defconfig entry, but verified (with logs) that the Coreboot read worked:
|
||||||
|
|
||||||
|
::BlogCode
|
||||||
|
```
|
||||||
|
DRAM: DRAM: coreboot tables at 0xffed9000 report 4096 MiB of usable RAM
|
||||||
|
4 GiB
|
||||||
|
```
|
||||||
|
#description
|
||||||
|
*U-Boot reporting 4 GiB RAM after reading the Coreboot tables*
|
||||||
|
::
|
||||||
|
|
||||||
|
I spotted other small issues in the logs and tasked the agent with fixing them one by one, each in a new session.
|
||||||
|
That took a bit more time and involvement, since I was analyzing everything and verifying the work each time.
|
||||||
|
|
||||||
|
### Ubuntu works (?)
|
||||||
|
|
||||||
|
Afterwards, I rebooted, wanting to retry a full chain, capture a new log, and address the remaining errors.
|
||||||
|
To my surprise, after rebooting once, twice, and multiple times: boot worked flawlessly every time, and I could actually use the system. Finally.
|
||||||
|
|
||||||
|
::BlogImage{image="/img/duet-ubuntu/ubuntu-stonking.jpg"}
|
||||||
|
*Ubuntu 26.10 development release, with the Ubuntu 7.2 generic kernel, actually running on the Duet. Finally. All of this for a single 'Kernel' line change... :)*
|
||||||
|
::
|
||||||
|
|
||||||
|
I started using the system, connected to Wi-Fi (which worked fine!), cleaned up patches, ran an update and... oh. Stuck again. During `apt upgrade`. I rebooted, ran the `dpkg` command to fix up the upgrade, and... stuck again. Always while `dracut` was generating the initramfs. That's a problem.
|
||||||
|
|
||||||
|
I tried multiple things, but it turns out that the machine still seemingly hangs in some CPU-heavy tasks, or long-running tasks.
|
||||||
|
At this point, it's a bit hard to reproduce: you have to use the machine for 20+ minutes before it shows up. Specific operations like `apt upgrade` seem to trigger it every time, though. I tried looking online, and it seems that the LKML has recent patches for that SoC.
|
||||||
|
|
||||||
|
Particularly, ["ASoC: mediatek: mt8183: Fix clock error handling"](https://lkml.org/lkml/2026/8/28/313) seems to aim at fixing some clock errors, most likely the ones that I see in red in the kernel boot log every time. This patch was sent only 2 weeks ago (!!) at the time of writing of this article, so it is definitely not in 7.2. Maybe this will improve things?
|
||||||
|
|
||||||
|
For now, I don't want to try rebuilding another kernel. I want to try and cleanup everything in the U-Boot code, which I first did with agents again. Adversarial reviewers found style issues, stale comments, etc. They also addressed some display performance issues. I made sure to clean up stale configs and build caches, then to rebuild with the `kukui-krane` defconfig. Below is a video of the final boot flow for Ubuntu on the device.
|
||||||
|
|
||||||
|
::BlogVideo{video="/img/duet-ubuntu/full-boot.mp4"}
|
||||||
|
*Full boot chain on the display. Depthcharge kills the display, U-Boot brings it back up, Grub is loaded, EFI `exitBootServices` kills the display again, to allow the kernel to bring it back up again. Could be optimized, but working.*
|
||||||
|
::
|
||||||
|
|
||||||
|
## Cleaning up, technical details
|
||||||
|
|
||||||
|
*(Sorry, this one is a bit technical, and maybe not that interesting. Skip it if you don't care, stay if you want a deep dive into the U-Boot changes)*
|
||||||
|
|
||||||
|
After all of those debugging steps, the U-Boot tree was a bit of a mess: 43 commits on top of main. **43**.
|
||||||
|
|
||||||
|
That is a lot, so I started refactoring, squashing, dropping, and cleaning up all of this. After a heavy pass, that also caught issues (performance-wise, potential bugs in the driver, or stylistic errors, all through this 'static analysis'), I ended up with **20** commits. Still a lot, but more manageable. Here they are:
|
||||||
|
|
||||||
|
::BlogTable
|
||||||
|
| # | Commit |
|
||||||
|
| --- | --- |
|
||||||
|
| 1 | arm: mediatek: add MT8183 kukui krane board support |
|
||||||
|
| 2 | scripts: Makefile.lib: prefer upstream DT includes with OF_UPSTREAM |
|
||||||
|
| 3 | serial: mtk: leave the sample count alone in low-speed mode |
|
||||||
|
| 4 | video: console: fix scrolling and cursor position on rotated consoles |
|
||||||
|
| 5 | video: console: report damage when showing the cursor |
|
||||||
|
| 6 | efi: console: re-sync the vidconsole cursor and accept stdout lists |
|
||||||
|
| 7 | boot: image-fdt: treat duplicate region reservation as a no-op |
|
||||||
|
| 8 | clk: mediatek: mt8183: add MMSYS display clock gates |
|
||||||
|
| 9 | gpio: add MediaTek MT8183 GPIO driver |
|
||||||
|
| 10 | phy: add MediaTek MT8183 MIPI TX D-PHY driver |
|
||||||
|
| 11 | video: add MediaTek MT8183 MIPI DSI host driver |
|
||||||
|
| 12 | video: add MT8183 cold display bring-up for krane with BOE panel |
|
||||||
|
| 13 | video: mt8183: quiesce the display pipeline at ExitBootServices |
|
||||||
|
| 14 | arm: mediatek: mt8183: enlarge the page-table budget for dynamic display mappings |
|
||||||
|
| 15 | arm: mediatek: mt8183: determine the real DRAM size from the previous bootloader |
|
||||||
|
| 16 | mmc: mtk-sd: bounce DMA above 4 GiB and keep U-Boot inside the 32-bit window |
|
||||||
|
| 17 | usb: xhci: bound the interrupt-in event wait to unblock the console |
|
||||||
|
| 18 | usb: xhci-mtk: shut the T-PHY down on remove |
|
||||||
|
| 19 | arm64: dts: mt8183: krane: describe the SSUSB block for U-Boot's xhci driver |
|
||||||
|
| 20 | board: mediatek: mt8183: finish the krane configuration for EFI boot |
|
||||||
|
|
||||||
|
#description
|
||||||
|
*Full commit series added to U-Boot at the end of the experimentation, after a cleanup phase.*
|
||||||
|
::
|
||||||
|
|
||||||
|
This is all the changes that the agents added, grouped together when that made sense (for example, there were 8 commits for the DSI driver, with fix-up stuff and performance improvements, so they all got squashed together to make a final commit for the DSI driver). As you can see, there are also changes that are there not directly for the board itself, but to fix a more generic bug, like ["video: console: fix scrolling and cursor position on rotated consoles", sent upstream already](https://lore.kernel.org/u-boot/20260831163056.3854693-1-valentin.haudiquet@canonical.com/T/#u). Some of those changes might not be useful at all though, or even break other boards, so this will need a manual review. Let's do that.
|
||||||
|
|
||||||
|
First, we need to walk through the generic upstream bug candidates, that have virtually nothing to do with MT8183:
|
||||||
|
|
||||||
|
- **#2**: a real bug in the U-Boot machinery for `OF_UPSTREAM` devicetree usage (which is used to sync the DTS of the board with the one in the upstream kernel). Specific case of the SoC having both a legacy and an upstream dt-bindings header. Really looks like a genuine bug, will be **sent as PATCH RFC upstream** after verifications.
|
||||||
|
- **#4**: already **sent upstream** to fix scrolling on weirdly rotated screens
|
||||||
|
- **#5**: reporting video "damage" when the cursor is set visible. Damage was missing for this specific function, so when having the video console in "only refresh damaged parts" mode, showing the cursor would not work until the next update. Will be **sent upstream** as PATCH after verification.
|
||||||
|
- **#6**: another simple bug fix, related to EFI console output string. This is just a re-sync of the cursor in the EFI console with the one in the U-Boot video console. This will also need evaluation to see if it is really needed, but another candidate for a standalone upstream patch.
|
||||||
|
- **#7**: seems like a **wrong** patch. It is a change to treat a second reservation of the same region for the same purpose as a no-op. That seems like shadowing a real issue that the reservation function was called twice, and will need further investigation why (and maybe a patch). Moving on, **dropping it**.
|
||||||
|
- **#17**: also seems a bit weird. This was added by the agent when investigating serial typing slowness when video was on, and it somehow seems to address a USB keyboard typing slowness which never existed, by creating a new, smaller USB driver timeout kind for waiting over an interrupt. **Dropping it** seems fine.
|
||||||
|
|
||||||
|
Then, we can tackle the MediaTek fixes (not specific to MT8183 SoC):
|
||||||
|
- **#3**: this one was hard to understand for me at first. It only adds a
|
||||||
|
`if(samplecount <= 1) return;`
|
||||||
|
line in the MediaTek serial driver function that sets the baud rate. The agent added a 5 lines comment referencing our Coreboot firmware in this generic MediaTek driver, so something was wrong. But the commit itself was in fact right, it seems: the function is writing unitialized data into serial controller registers without this. I'm not sure why this was never seen before, but it might be because on most boards, those registers have null initial value, and it happens that in this particular case it would write a zero. But with my board that has serial controller already initialized from Coreboot, something went wrong with that and it caused an issue. That seems like a nice catch, that will **need further testing before being forwarded upstream**.
|
||||||
|
- **#18**: a simple one-line change to call `xhci_mtk_phy_shutdown()` on driver removal, so that the kernel can bring up the driver on its own later. Simple fix to verify and **send upstream**.
|
||||||
|
|
||||||
|
Now, we are left with the **board enablement** patches, that we want to send as one or multiple patch series upstream:
|
||||||
|
- **#1**: defining the **defconfig** for our board and renaming `mt8183_pumpkin.c` to `mt8183.c`, as it becomes generic
|
||||||
|
- **#8**: there were **missing clocks** in the driver for the SoC, notably the specific clock that the display DSI driver will use. This adds support for those.
|
||||||
|
- **#9**: this adds the missing **GPIO driver** for the SoC, which is needed to then be able to **reset/power enable the display**. Adapted from Depthcharge.
|
||||||
|
- **#10**: this adds the "MIPI TX D-PHY driver" for the SoC. That gets a bit technical. "MIPI", "Mobile Industry Processor Interface", is an alliance (ARM, Intel, Nokia, TI) that standardize interfaces in mobile SoCs. So this driver is for a specific MIPI standard, for a Transmitter (TX), that does something on the physical layer (PHY), with D being the Roman numeral 500, for the speed of that transmitter: 500 Mbps. This driver was ported from the Linux kernel by the agent. This is the **physical layer for the transmitter that will be used by the next driver**.
|
||||||
|
- **#11**: this adds the "MIPI DSI host driver" for the SoC. DSI being **Display Serial Interface**, the protocol standard, defined by MIPI again, to drive displays. This specifies how pixels, commands (e.g. brightness), and video timing signals are sent to the screen.
|
||||||
|
- **#12**: finally, this commits **unites them all** to add the display driver. It uses #9 to power on the display, then the DSI interface from #11 (that uses the #10 PHY driver and #8 clocks) to **drive that specific "boe_tv101wum" panel** present on the Duet. Adapted from Coreboot and verified against Linux kernel.
|
||||||
|
- **#13**: this **shuts down the display when EFI `ExitBootServices()` is called**, i.e. right before the kernel starts, to allow it to take over from an uninitialized display, the same kind of state that Depthcharge would have left the display in. This might need a change, as there might be a way to tell the kernel in what state we left the display, for it to use it for earlycon, and take over after. This **will need more investigation**.
|
||||||
|
- **#14**: this one is another that left me a bit puzzled. It defines a `get_page_table_size()` function, that returns `SZ_256K`, inside the `mt8183.c` file. That is it. This is actually a function that is called during the generic initialization path, and the issue is that the default page table budget is not enough to map the Coreboot table and the framebuffer for the display. That seems to be the standard pattern to account for that, present in Apple platform files as well, so I left it as-is, but will need to check a bit more before trying to send it upstream.
|
||||||
|
- **#15**: this one allows detecting the real memory present on the board from the Coreboot tables, instead of relying on the static "2 GiB" from the devicetree. The code is in the generic init path for the MT8183, which seems wrong to me, as this has a lot of Coreboot-specific paths. So I think this should be abstracted away in a Coreboot table driver that can extract information, and multiple different paths to check whether this driver should be used. **It will need a bit more work**.
|
||||||
|
- **#19**: this commit adds a U-Boot-specific overlay for the `krane` devicetree, describing the Super Speed USB node of the device. Linux uses the more generic "MTU3" MediaTek driver for the USB node, which has multiple capabilities (Dual-Role OTG). U-Boot lacks this driver, so we manually add only the Super Speed USB part that is nested under the `mtu3` node in the upstream devicetree and thus invisible to U-Boot.
|
||||||
|
- **#20**: this is a weird commit that **enables everything that was added before** (video, usb, PHY and DSI drivers, etc) in the board **defconfig**. This will go or stay like this depending on how the series is split for upstream.
|
||||||
|
|
||||||
|
I guess this will need to be split between basic board support and video drivers. I don't want to waste the time of the maintainers with "AI slop" and code that does not follow the right conventions, so I will take extra care to read the U-Boot contributing documentation, and make sure all the patches are in a good shape before sending them. I also don't think they have an AI policy at the moment, but I plan anyway to rewrite all the patches myself with the AI output as input, and will of course disclose that all of this was AI-assisted.
|
||||||
|
|
||||||
|
## A bootable Ubuntu image
|
||||||
|
|
||||||
|
Now that we understand most of the work, we can crystallize this into a bootable Ubuntu image, embedding this modified U-Boot in a first Depthcharge partition. The goal of this is that anyone can download the image, put it on a USB stick, and boot it using Depthcharge "USB boot" option, to get into a real Ubuntu on the Duet.
|
||||||
|
|
||||||
|
This might seem a bit hard, but it is actually very easy for me as I did this a couple times already for RISC-V boards for my job at Canonical, so I know exactly which tools to use to produce such an image. We need [ubuntu-image](https://github.com/canonical/ubuntu-image) and a **gadget file** that describes the partition of the image we want to build, as well as an **image definition** file that describes the version, packages and stuff we want in the image. See this [tutorial](https://ubuntu.com/hardware/docs/image-cookbook/tutorial/create_image/) for more details.
|
||||||
|
|
||||||
|
I decided to create a Git repo with everything to create such an image: `uboot/` with all the patches, `krane-shim-loader/` with the shim loader and the script to create the Depthcharge payload from it and U-Boot, `image/` with the files described above as well as custom script to make the Depthcharge partition work well and test the image. This was also easily done, assisted by an agent.
|
||||||
|
|
||||||
|
::BlogLink{link="https://github.com/vhaudiquet/krane"}
|
||||||
|
::
|
||||||
|
|
||||||
|
If you own a Lenovo IdeaPad Duet, go to the repo, download the image in Releases, burn it to a USB stick and try it!
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
### Using AI for hardware bring-up/debugging
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Next steps
|
||||||
|
|
||||||
|
Obviously, the immediate next step is to try and upstream all this U-Boot work, so that the bug fixes help everyone and support for the device is added upstream, which should both let anyone play with it and provides maintenance for the future.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
coreboot, fix errors, upstreaming
|
||||||
@@ -38,6 +38,10 @@ export default defineNuxtConfig({
|
|||||||
],
|
],
|
||||||
|
|
||||||
content: {
|
content: {
|
||||||
|
experimental: {
|
||||||
|
sqliteConnector: 'native',
|
||||||
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
markdown: {
|
markdown: {
|
||||||
highlight: {
|
highlight: {
|
||||||
|
|||||||
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 219 KiB |
@@ -0,0 +1,24 @@
|
|||||||
|
<svg width="100%" viewBox="0 0 680 380" role="img" style="" xmlns="http://www.w3.org/2000/svg"><title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Current eMMC layout and what depthcharge actually boots</title><desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Depthcharge loads partition 1, a postmarketOS-packed kernel image, which then mounts the Ubuntu root filesystem on partition 3 with mismatched kernel modules; partition 2 is unused at boot time.</desc>
|
||||||
|
<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></marker></defs>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="260" y="30" width="160" height="50" rx="8" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="55" text-anchor="middle" dominant-baseline="central" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Depthcharge</text></g>
|
||||||
|
<line x1="340" y1="80" x2="340" y2="118" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"><rect x="40" y="118" width="600" height="205" rx="20" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="140" text-anchor="middle" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:auto">eMMC storage (mmcblk0)</text>
|
||||||
|
<text x="340" y="158" text-anchor="middle" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:auto">Custom 3-partition layout</text></g>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="60" y="180" width="160" height="110" rx="10" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="140" y="218" text-anchor="middle" dominant-baseline="central" style="fill:rgb(99, 56, 6);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">p1 · kernel</text>
|
||||||
|
<text x="140" y="252" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">pmOS FIT image</text></g>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="240" y="180" width="160" height="110" rx="10" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="320" y="218" text-anchor="middle" dominant-baseline="central" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">p2 · /boot</text>
|
||||||
|
<text x="320" y="252" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Inactive at boot</text></g>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="420" y="180" width="200" height="110" rx="10" stroke-width="0.5" style="fill:rgb(225, 245, 238);stroke:rgb(15, 110, 86);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="520" y="218" text-anchor="middle" dominant-baseline="central" style="fill:rgb(8, 80, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">p3 · rootfs</text>
|
||||||
|
<text x="520" y="252" text-anchor="middle" dominant-baseline="central" style="fill:rgb(15, 110, 86);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Modules for wrong kernel</text></g>
|
||||||
|
<path d="M140,290 L140,312 L520,312 L520,290" fill="none" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="330" y="304" text-anchor="middle" style="fill:rgb(82, 81, 78);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:auto">kernel mounts root</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 190 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
<svg width="100%" viewBox="0 0 680 436" role="img" style="" xmlns="http://www.w3.org/2000/svg"><title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">MT8183 Duet firmware boot chain</title><desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Four sequential firmware stages: Boot ROM, coreboot, TF-A BL31, then depthcharge.</desc>
|
||||||
|
<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></marker></defs>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="40" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="58" text-anchor="middle" dominant-baseline="central" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Boot ROM</text>
|
||||||
|
<text x="340" y="76" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Loads first-stage bootloader</text></g>
|
||||||
|
<line x1="340" y1="96" x2="340" y2="140" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="140" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="158" text-anchor="middle" dominant-baseline="central" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Coreboot</text>
|
||||||
|
<text x="340" y="176" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">DRAM, PMIC, panel init</text></g>
|
||||||
|
<line x1="340" y1="196" x2="340" y2="240" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="240" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(241, 239, 232);stroke:rgb(95, 94, 90);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="258" text-anchor="middle" dominant-baseline="central" style="fill:rgb(68, 68, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">TF-A BL31</text>
|
||||||
|
<text x="340" y="276" text-anchor="middle" dominant-baseline="central" style="fill:rgb(95, 94, 90);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Secure monitor firmware</text></g>
|
||||||
|
<line x1="340" y1="296" x2="340" y2="340" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="340" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(250, 238, 218);stroke:rgb(133, 79, 11);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="358" text-anchor="middle" dominant-baseline="central" style="fill:rgb(99, 56, 6);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Depthcharge</text>
|
||||||
|
<text x="340" y="376" text-anchor="middle" dominant-baseline="central" style="fill:rgb(133, 79, 11);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Verifies and loads kernel</text></g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.3 KiB |
@@ -0,0 +1,22 @@
|
|||||||
|
<svg width="100%" viewBox="0 0 680 436" role="img" style="" xmlns="http://www.w3.org/2000/svg"><title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Planned U-Boot and EFI boot path, not yet flashed to internal storage</title><desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">Depthcharge would chainload U-Boot instead of Linux directly, U-Boot provides a UEFI environment, which chainloads GRUB or an EFI-stub kernel, which then boots a matched Ubuntu kernel.</desc>
|
||||||
|
<defs><marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></marker></defs>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="40" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="58" text-anchor="middle" dominant-baseline="central" style="fill:rgb(60, 52, 137);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">U-Boot (in p1)</text>
|
||||||
|
<text x="340" y="76" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Chainloaded via depthcharge</text></g>
|
||||||
|
<line x1="340" y1="96" x2="340" y2="140" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="140" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="158" text-anchor="middle" dominant-baseline="central" style="fill:rgb(60, 52, 137);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">UEFI environment</text>
|
||||||
|
<text x="340" y="176" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">bootefi bootmgr</text></g>
|
||||||
|
<line x1="340" y1="196" x2="340" y2="240" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="240" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(238, 237, 254);stroke:rgb(83, 74, 183);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="258" text-anchor="middle" dominant-baseline="central" style="fill:rgb(60, 52, 137);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">GRUB / EFI stub</text>
|
||||||
|
<text x="340" y="276" text-anchor="middle" dominant-baseline="central" style="fill:rgb(83, 74, 183);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Loads matching kernel</text></g>
|
||||||
|
<line x1="340" y1="296" x2="340" y2="340" marker-end="url(#arrow)" style="fill:none;stroke:rgb(137, 135, 129);color:rgb(11, 11, 11);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||||
|
<rect x="210" y="340" width="260" height="56" rx="8" stroke-width="0.5" style="fill:rgb(225, 245, 238);stroke:rgb(15, 110, 86);color:rgb(11, 11, 11);stroke-width:0.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||||
|
<text x="340" y="358" text-anchor="middle" dominant-baseline="central" style="fill:rgb(8, 80, 65);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:14px;font-weight:500;text-anchor:middle;dominant-baseline:central">Ubuntu kernel</text>
|
||||||
|
<text x="340" y="376" text-anchor="middle" dominant-baseline="central" style="fill:rgb(15, 110, 86);stroke:none;color:rgb(11, 11, 11);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"anthropic-sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;font-size:12px;font-weight:400;text-anchor:middle;dominant-baseline:central">Mounts p3 cleanly</text></g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 552 KiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
@@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* Server route that fetches a given URL and extracts its OpenGraph / Twitter
|
||||||
|
* card metadata (image, title, description) so the BlogLink component can
|
||||||
|
* render a rich, full-sized link preview.
|
||||||
|
*
|
||||||
|
* GET /api/ogimage?url=https://example.com/article
|
||||||
|
*
|
||||||
|
* Returns: { image?: string, title?: string, description?: string, url: string }
|
||||||
|
* Never throws: on any error it returns an empty object so the build does not
|
||||||
|
* break when a linked page is unreachable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface OgData {
|
||||||
|
image?: string
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decode the most common HTML entities found in meta tags. */
|
||||||
|
function decodeHtmlEntities(value: string): string {
|
||||||
|
return value
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/�*39;/g, "'")
|
||||||
|
.replace(/�*27;/gi, "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the `content` of a `<meta>` tag whose `property` or `name` matches
|
||||||
|
* one of the given keys (case-insensitive). Handles attributes in any order.
|
||||||
|
*/
|
||||||
|
function extractMeta(html: string, keys: string[]): string | undefined {
|
||||||
|
const lowered = keys.map(k => k.toLowerCase())
|
||||||
|
const metaRegex = /<meta\b[^>]*>/gi
|
||||||
|
let match: RegExpExecArray | null
|
||||||
|
while ((match = metaRegex.exec(html)) !== null) {
|
||||||
|
const tag = match[0]
|
||||||
|
const propMatch = tag.match(/(?:property|name)\s*=\s*["']([^"']+)["']/i)
|
||||||
|
if (propMatch && lowered.includes(propMatch[1].toLowerCase())) {
|
||||||
|
const contentMatch = tag.match(/content\s*=\s*["']([^"']+)["']/i)
|
||||||
|
if (contentMatch) return decodeHtmlEntities(contentMatch[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fallback: extract the document <title>. */
|
||||||
|
function extractTitle(html: string): string | undefined {
|
||||||
|
const m = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)
|
||||||
|
return m ? decodeHtmlEntities(m[1].trim()) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve a possibly-relative URL against a base URL. */
|
||||||
|
function resolveUrl(base: string, url: string): string {
|
||||||
|
try {
|
||||||
|
return new URL(url, base).toString()
|
||||||
|
} catch {
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineCachedEventHandler(async (event): Promise<Partial<OgData>> => {
|
||||||
|
const { url } = getQuery(event)
|
||||||
|
if (typeof url !== 'string' || !url) return {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const html = await $fetch<string>(url, {
|
||||||
|
responseType: 'text',
|
||||||
|
// Return the body even for 4xx/5xx responses (e.g. GitHub 404 pages
|
||||||
|
// still carry useful og:image metadata), instead of throwing.
|
||||||
|
ignoreResponseError: true,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (compatible; BlogLinkOgImageBot/1.0; +https://vhaudiquet.fr)',
|
||||||
|
Accept: 'text/html,application/xhtml+xml',
|
||||||
|
},
|
||||||
|
timeout: 15000,
|
||||||
|
retry: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof html !== 'string' || html.length === 0) return {}
|
||||||
|
|
||||||
|
const image = extractMeta(html, ['og:image', 'og:image:url', 'og:image:secure_url', 'twitter:image'])
|
||||||
|
const title = extractMeta(html, ['og:title', 'twitter:title']) || extractTitle(html)
|
||||||
|
const description = extractMeta(html, ['og:description', 'twitter:description'])
|
||||||
|
|
||||||
|
return {
|
||||||
|
image: image ? resolveUrl(url, image) : undefined,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
url,
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Network failure, DNS error, non-HTML response, timeout, etc.
|
||||||
|
return { url }
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
maxAge: 60 * 60 * 24, // cache resolved metadata for 24h
|
||||||
|
name: 'ogimage',
|
||||||
|
group: 'ogimage',
|
||||||
|
// Cache key based only on the target URL, so unrelated query params (e.g.
|
||||||
|
// cache-busting ones) don't create duplicate entries.
|
||||||
|
getKey: (event) => String(getQuery(event).url || ''),
|
||||||
|
})
|
||||||