wip: duet (3)
This commit is contained in:
Vendored
+7
@@ -1,2 +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>
|
||||
+46
-13
@@ -306,10 +306,14 @@ Technical details on the panel driver and the whole U-Boot saga are available in
|
||||
|
||||
## Kernel
|
||||
|
||||
### Fixing with an agent
|
||||
### A missing module
|
||||
|
||||
Finally, we can try booting our Ubuntu 7.0 kernel!
|
||||
Booting it with Grub went fine, but the kernel output on screen got stuck once the boot console went down.
|
||||
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.
|
||||
@@ -390,6 +394,8 @@ For now, I don't want to try rebuilding another kernel. I want to try and cleanu
|
||||
|
||||
## 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:
|
||||
@@ -426,30 +432,57 @@ This is all the changes that the agents added, grouped together when that made s
|
||||
|
||||
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.
|
||||
- **#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:
|
||||
- #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:
|
||||
|
||||
- **#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
|
||||
@@ -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 || ''),
|
||||
})
|
||||
Reference in New Issue
Block a user