Kernel release 6.18.38-rt-cuckoo+: reproducible build, guarded installer
Rebuilt with KBUILD_BUILD_USER/HOST/TIMESTAMP pinned. Without them the kernel bakes the builder's user@hostname and wall-clock build time into /proc/version, which publishes operator identity in a public artifact and makes the build unreproducible. Pinned, anyone can rebuild and diff the bytes — the only trust story available for an unsigned community kernel. The installer refuses to run on anything but a Pi 4, refuses a mismatched image/modules pair (vermagic failure means no /dev/pps0 on a kernel that otherwise boots fine — silent and miserable to debug), verifies SHA256SUMS, never touches kernel8.img, and leaves rollback as deleting one config.txt line. Boot-tested on the Pi: Stratum 1, 273 ns offset, PPS handler confirmed NOT threaded. Binaries ship as release assets, not in git.
This commit is contained in:
parent
6b6d80ded3
commit
3b505644b2
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,3 +4,8 @@ __pycache__/
|
||||
dist/
|
||||
*.egg-info/
|
||||
node_modules/
|
||||
|
||||
# Kernel release binaries — 32 MB. These are published as Gitea release assets,
|
||||
# not committed. Rebuild them with kernel/build.sh; verify with SHA256SUMS.
|
||||
kernel/release/*.img.gz
|
||||
kernel/release/*.tar.gz
|
||||
|
||||
BIN
dashboard/src/gpsntp_dashboard/static/fonts/atkinson-400.woff2
Normal file
BIN
dashboard/src/gpsntp_dashboard/static/fonts/atkinson-400.woff2
Normal file
Binary file not shown.
BIN
dashboard/src/gpsntp_dashboard/static/fonts/atkinson-700.woff2
Normal file
BIN
dashboard/src/gpsntp_dashboard/static/fonts/atkinson-700.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
57
dashboard/src/gpsntp_dashboard/static/fonts/fonts.css
Normal file
57
dashboard/src/gpsntp_dashboard/static/fonts/fonts.css
Normal file
@ -0,0 +1,57 @@
|
||||
/* Self-hosted: this dashboard runs on a LAN and must work with no internet.
|
||||
* Latin subset only — the latin-ext/greek/vietnamese blocks Google serves
|
||||
* would triple the payload for glyphs a status page will never render.
|
||||
*
|
||||
* Atkinson Hyperlegible is the Braille Institute's legibility face. It is
|
||||
* here for a functional reason, not a brand one: it disambiguates 0/O, 1/l/I
|
||||
* and rn/m, which is exactly what you need when reading `+66 ns` or
|
||||
* `2607:f740:e::d3` at a glance from across a room.
|
||||
*/
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("/static/fonts/atkinson-400.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("/static/fonts/atkinson-700.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible Mono";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("/static/fonts/atkinson-mono-400.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Atkinson Hyperlegible Mono";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("/static/fonts/atkinson-mono-600.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Libre Baskerville";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("/static/fonts/baskerville-400.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Libre Baskerville";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("/static/fonts/baskerville-700.woff2") format("woff2");
|
||||
}
|
||||
@ -171,6 +171,13 @@
|
||||
|
||||
<div class="foot-meta">
|
||||
<span id="foot-version">gpsntp-dashboard</span>
|
||||
<!-- Anyone reading "PPS locked · ±219 ns" and wondering what that means,
|
||||
or why this box is a Pi and not an appliance, deserves one click to
|
||||
the write-up rather than a search. -->
|
||||
<a class="foot-docs" href="https://cuckoo.warehack.ing" rel="noopener">
|
||||
How this clock works
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M4 8h7M8 5l3 3-3 3"/></svg>
|
||||
</a>
|
||||
<span id="foot-updated">—</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -1,13 +1,34 @@
|
||||
@import url("/static/fonts/fonts.css");
|
||||
|
||||
/* Supported Systems house style, applied as WARM SHELL / COOL DATA.
|
||||
*
|
||||
* The chrome — background, panels, type, brand accent — comes from
|
||||
* supported.systems: warm near-black, cream text, Libre Baskerville display,
|
||||
* Atkinson Hyperlegible body, brass accent.
|
||||
*
|
||||
* The INSTRUMENTS do not. Cyan, green, amber and red stay exactly as they were,
|
||||
* because on this page those colors carry information: which constellation a
|
||||
* satellite belongs to, whether a source is selected, whether the clock is
|
||||
* healthy. Recoloring them to brass would trade meaning for brand, which on a
|
||||
* status page is a bad trade at any exchange rate.
|
||||
*
|
||||
* So: --brand is for chrome, --accent is for live data. They are different
|
||||
* variables on purpose. Don't merge them.
|
||||
*/
|
||||
:root {
|
||||
--bg: #0a0e13;
|
||||
--bg-2: #0e141c;
|
||||
--panel: #121a23;
|
||||
--panel-2: #16212d;
|
||||
--border: #21303f;
|
||||
--border-soft: #1a2632;
|
||||
--text: #e7eef5;
|
||||
--muted: #8a99a8;
|
||||
--faint: #57687a;
|
||||
/* shell — warm (supported.systems) */
|
||||
--bg: #171717;
|
||||
--bg-2: #1b1a18;
|
||||
--panel: #232120;
|
||||
--panel-2: #2b2825;
|
||||
--border: #3d382f;
|
||||
--border-soft: #322e28;
|
||||
--text: #eeece8;
|
||||
--muted: #a8a196;
|
||||
--faint: #6f6a61;
|
||||
--brand: #d9a441; /* brass. chrome, links, brand marks. NOT data. */
|
||||
|
||||
/* instruments — cool (semantic; do not rebrand) */
|
||||
--accent: #22d3ee;
|
||||
--good: #34d399;
|
||||
--warn: #f5a524;
|
||||
@ -20,8 +41,15 @@
|
||||
--c-sbas: #a3e635;
|
||||
--c-qzss: #2dd4bf;
|
||||
--c-other: #94a3b8;
|
||||
|
||||
--shadow: 0 1px 0 rgba(255, 255, 255, 0.03), 0 12px 30px -18px rgba(0, 0, 0, 0.9);
|
||||
--mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, monospace;
|
||||
|
||||
/* Atkinson Hyperlegible: the Braille Institute's legibility face. Chosen for
|
||||
* function before brand — it disambiguates 0/O, 1/l/I, rn/m, which is exactly
|
||||
* what a glanceable status page needs. */
|
||||
--sans: "Atkinson Hyperlegible", system-ui, -apple-system, sans-serif;
|
||||
--serif: "Libre Baskerville", Georgia, serif;
|
||||
--mono: "Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
@ -29,11 +57,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
background:
|
||||
radial-gradient(1200px 600px at 80% -10%, rgba(34, 211, 238, 0.06), transparent 60%),
|
||||
radial-gradient(900px 500px at 0% 0%, rgba(52, 211, 153, 0.05), transparent 55%),
|
||||
/* the warm bench light from supported.systems */
|
||||
radial-gradient(1100px 620px at 50% -8%, rgba(217, 164, 65, 0.07), transparent 62%),
|
||||
/* one cool wash, bottom-left, so the instruments feel like instruments */
|
||||
radial-gradient(900px 500px at 0% 100%, rgba(34, 211, 238, 0.04), transparent 58%),
|
||||
var(--bg);
|
||||
color: var(--text);
|
||||
font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
||||
font: 15px/1.5 var(--sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@ -47,12 +77,16 @@ svg { width: 1.15em; height: 1.15em; fill: none; stroke: currentColor; stroke-wi
|
||||
gap: 16px; padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
position: sticky; top: 0; z-index: 5;
|
||||
background: linear-gradient(var(--bg), rgba(10, 14, 19, 0.86));
|
||||
background: linear-gradient(var(--bg), rgba(23, 23, 23, 0.86));
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.brand { display: flex; align-items: center; gap: 13px; }
|
||||
.brand-icon { width: 30px; height: 30px; color: var(--accent); }
|
||||
.brand h1 { font-size: 19px; margin: 0; letter-spacing: 0.3px; font-family: var(--mono); }
|
||||
/* The dish is brand chrome, not a reading — brass, not cyan. */
|
||||
.brand-icon { width: 30px; height: 30px; color: var(--brand); }
|
||||
/* Libre Baskerville, as on supported.systems. The wordmark is the one place a
|
||||
serif belongs on this page; everything that reports a NUMBER stays mono. */
|
||||
.brand h1 { font-size: 20px; margin: 0; letter-spacing: 0.2px; font-family: var(--serif);
|
||||
font-weight: 700; }
|
||||
.brand-sub { margin: 0; color: var(--muted); font-size: 12.5px; }
|
||||
|
||||
.conn { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted);
|
||||
@ -75,7 +109,7 @@ main { max-width: 1180px; margin: 0 auto; padding: 20px; display: grid; gap: 18p
|
||||
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; }
|
||||
.panel-head h2 { font-size: 14px; margin: 0; color: var(--muted); font-weight: 600;
|
||||
display: flex; align-items: center; gap: 8px; text-transform: uppercase; letter-spacing: 0.6px; }
|
||||
.panel-head h2 svg { color: var(--accent); }
|
||||
.panel-head h2 svg { color: var(--brand); }
|
||||
|
||||
/* ---------- hero ---------- */
|
||||
.hero { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
|
||||
@ -104,7 +138,7 @@ main { max-width: 1180px; margin: 0 auto; padding: 20px; display: grid; gap: 18p
|
||||
.card { background: var(--panel); border: 1px solid var(--border-soft); border-radius: 14px; padding: 14px 15px; }
|
||||
.card-head { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 12px;
|
||||
text-transform: uppercase; letter-spacing: 0.4px; }
|
||||
.card-head svg { width: 15px; height: 15px; color: var(--accent); }
|
||||
.card-head svg { width: 15px; height: 15px; color: var(--brand); }
|
||||
.card-value { font-family: var(--mono); font-size: 25px; font-weight: 650; margin: 7px 0 3px; letter-spacing: 0.3px; }
|
||||
.card-value.good { color: var(--good); }
|
||||
.card-value.warn { color: var(--warn); }
|
||||
@ -179,8 +213,13 @@ main { max-width: 1180px; margin: 0 auto; padding: 20px; display: grid; gap: 18p
|
||||
|
||||
/* ---------- footer ---------- */
|
||||
.foot { max-width: 1180px; margin: 0 auto; padding: 14px 20px 30px; }
|
||||
.foot-meta { display: flex; justify-content: space-between; margin-top: 14px;
|
||||
color: var(--faint); font-size: 12px; font-family: var(--mono); }
|
||||
.foot-meta { display: flex; justify-content: space-between; align-items: center; gap: 12px;
|
||||
margin-top: 14px; color: var(--faint); font-size: 12px; font-family: var(--mono); }
|
||||
.foot-docs { display: inline-flex; align-items: center; gap: 5px; color: var(--faint);
|
||||
text-decoration: none; transition: color .2s; }
|
||||
.foot-docs:hover { color: var(--brand); }
|
||||
.foot-docs svg { width: 13px; height: 13px; stroke-width: 1.5; transition: transform .2s; }
|
||||
.foot-docs:hover svg { transform: translateX(2px); }
|
||||
|
||||
/* ---------- the maker's plate ----------
|
||||
* Clockmakers signed the backplate — the brass face only a repairer sees once
|
||||
@ -199,7 +238,7 @@ main { max-width: 1180px; margin: 0 auto; padding: 20px; display: grid; gap: 18p
|
||||
var(--panel);
|
||||
transition: color .2s, border-color .2s;
|
||||
}
|
||||
.ss-plate__link:hover { color: var(--text); border-color: var(--accent); }
|
||||
.ss-plate__link:hover { color: var(--text); border-color: var(--brand); }
|
||||
|
||||
.ss-plate__logo { flex: 0 0 auto; width: 46px; height: auto; opacity: .85;
|
||||
transition: opacity .2s; }
|
||||
@ -209,9 +248,9 @@ main { max-width: 1180px; margin: 0 auto; padding: 20px; display: grid; gap: 18p
|
||||
font-family: var(--mono); font-size: 12px; font-weight: 600;
|
||||
text-transform: uppercase; letter-spacing: .14em; }
|
||||
.ss-plate__body { display: block; font-size: 13px; line-height: 1.55; max-width: 62ch; }
|
||||
.ss-plate__name { color: var(--accent); }
|
||||
.ss-plate__name { color: var(--brand); }
|
||||
.ss-plate__cta { display: inline-flex; align-items: center; gap: 5px; margin-top: 8px;
|
||||
font-size: 12px; color: var(--accent); }
|
||||
font-size: 12px; color: var(--brand); }
|
||||
.ss-plate__cta svg { width: 14px; height: 14px; transition: transform .2s; }
|
||||
.ss-plate__link:hover .ss-plate__cta svg { transform: translateX(2px); }
|
||||
|
||||
|
||||
@ -56,4 +56,4 @@ deploy: ## Pull main + rebuild the docs container on the prod host
|
||||
@echo "==> deploying $(DEPLOY_HOST):$(DEPLOY_PATH)"
|
||||
ssh -A $(DEPLOY_HOST) "cd $(DEPLOY_PATH) && git fetch origin main && git reset --hard origin/main && cd docs-site && make prod"
|
||||
@echo "==> sanity check"
|
||||
@curl -s -o /dev/null -w " HTTP %{http_code} %{url_effective}\n" "https://cuckoo.warehack.ing/explanation/bug-detection/"
|
||||
@curl -s -o /dev/null -w " HTTP %{http_code} %{url_effective}\n" "https://cuckoo.warehack.ing/explanation/preempt-rt-made-it-worse/"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Downloads — prebuilt RT kernel
|
||||
description: A patched PREEMPT_RT kernel for the Raspberry Pi 4, so you don't need a cross-compile toolchain.
|
||||
description: A patched PREEMPT_RT kernel for the Raspberry Pi 4, boot-tested and benchmarked, so you don't need a cross-compile toolchain.
|
||||
sidebar:
|
||||
order: 5
|
||||
---
|
||||
@ -9,28 +9,37 @@ The only expensive part of [the patch](/reference/the-patch/) is the toolchain.
|
||||
Building natively on a Pi 4 takes hours; cross-compiling needs an x86 box and a
|
||||
setup session. So here's the artifact.
|
||||
|
||||
**Current release: `6.18.38-rt-cuckoo+`** — Raspberry Pi's `rpi-6.18.y`,
|
||||
`bcm2711_defconfig`, `PREEMPT_RT`, plus the `IRQF_NO_THREAD` patch.
|
||||
|
||||
:::danger[Read this before you download]
|
||||
- **Raspberry Pi 4 / arm64 only.** `bcm2711_defconfig`. It will not boot a Pi 5 or
|
||||
a Pi 3.
|
||||
- **Unsigned, community-built.** We built this on a workstation. There is no chain
|
||||
of trust here beyond "we published the exact recipe and the checksums." If that
|
||||
isn't good enough for your environment — and for some environments it correctly
|
||||
isn't — [build it yourself](/how-to/cross-compile-rt-kernel/). It's forty
|
||||
minutes.
|
||||
- **Verify the checksums.** They're in `SHA256SUMS`.
|
||||
- **Raspberry Pi 4 / arm64 only.** The installer refuses to run on anything else,
|
||||
because a `bcm2711` kernel on a Pi 5 doesn't boot — and a black screen on a
|
||||
headless box is exactly the outcome we're trying to prevent.
|
||||
- **Unsigned, community-built.** There is no chain of trust here beyond published
|
||||
checksums and a reproducible recipe. For some environments that is correctly
|
||||
not good enough. [Build it yourself](/how-to/cross-compile-rt-kernel/) — it's
|
||||
forty minutes, and `kernel/build.sh` in the repo does the whole thing.
|
||||
- **Verify the checksums.** The installer does it for you if `SHA256SUMS` is
|
||||
present. Don't skip it.
|
||||
:::
|
||||
|
||||
## Artifacts
|
||||
|
||||
Published on the [releases page](https://git.supported.systems/warehack.ing/cuckoo-escapement/releases):
|
||||
On the [releases page](https://git.supported.systems/warehack.ing/cuckoo-escapement/releases):
|
||||
|
||||
| File | What |
|
||||
| File | |
|
||||
|---|---|
|
||||
| `kernel-rt-<ver>.img.gz` | The kernel image, gzipped (Pi OS's own format) |
|
||||
| `rt-modules-<ver>.tar.gz` | Matching modules — **must** be installed with the image |
|
||||
| `install-rt-kernel.sh` | Installer. Adds a *new* image, never replaces `kernel8.img` |
|
||||
| `kernel-rt-6.18.38-rt-cuckoo+.img.gz` | The kernel, gzipped (Pi OS's own format) — 9.8 MB |
|
||||
| `rt-modules-6.18.38-rt-cuckoo+.tar.gz` | Matching modules — 22 MB. **Must** be installed with the image. |
|
||||
| `install-rt-kernel.sh` | Installer. Adds a *new* image; never replaces `kernel8.img`. |
|
||||
| `SHA256SUMS` | Checksums |
|
||||
|
||||
```
|
||||
97533ea81ec56ef090908560e88bccfb2a3f7dfcc3d33a5a1cbe4bbf15163b55 kernel-rt-6.18.38-rt-cuckoo+.img.gz
|
||||
6af126711eef2ae1651ac48e19353e6830f12fb0ef9a7e265f54c27206a4f0eb rt-modules-6.18.38-rt-cuckoo+.tar.gz
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
@ -39,34 +48,68 @@ sudo ./install-rt-kernel.sh
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
The installer:
|
||||
Then confirm the patch is actually live. This is the check that matters:
|
||||
|
||||
1. Untars the modules into `/lib/modules/`
|
||||
2. Writes the image as `/boot/firmware/kernel-rt.img` — **`kernel8.img` is left
|
||||
alone**
|
||||
3. Appends one line, `kernel=kernel-rt.img`, to `config.txt`
|
||||
```console
|
||||
$ uname -a
|
||||
Linux … 6.18.38-rt-cuckoo+ #1 SMP PREEMPT_RT … aarch64
|
||||
|
||||
**Rollback is deleting that one line.** Mount the SD card's FAT partition on any
|
||||
machine, remove it, and the stock kernel boots. That's deliberate: you should never
|
||||
have to make a physical trip to a headless box because of a kernel you got from a
|
||||
website.
|
||||
|
||||
## What's in it
|
||||
|
||||
Raspberry Pi's `rpi-6.12.y` tree, `bcm2711_defconfig`, plus exactly two changes:
|
||||
|
||||
```bash
|
||||
scripts/config --enable PREEMPT_RT
|
||||
# + the IRQF_NO_THREAD patch in drivers/pps/clients/pps-gpio.c
|
||||
$ ps -eo comm | grep irq/.*pps
|
||||
(nothing)
|
||||
```
|
||||
|
||||
Nothing else. The full recipe is in
|
||||
[Cross-compile an RT kernel](/how-to/cross-compile-rt-kernel/), and you should be
|
||||
able to reproduce this byte-for-byte modulo build timestamps.
|
||||
**That empty output is the whole point.** If an `irq/NN-pps@…` thread appears,
|
||||
the handler is being force-threaded and [your timestamp is being taken behind the
|
||||
scheduler](/explanation/preempt-rt-made-it-worse/).
|
||||
|
||||
## Rollback is one line
|
||||
|
||||
The installer appends exactly one line to `config.txt` and never touches
|
||||
`kernel8.img`. If the Pi doesn't come back: pull the SD card, open `config.txt`
|
||||
on any machine (it's a FAT partition — a Windows laptop will do), delete the
|
||||
`kernel=kernel-rt.img` line, put the card back. The stock kernel boots.
|
||||
|
||||
It also leaves a `config.txt.before-rt` next to it.
|
||||
|
||||
## What we actually tested
|
||||
|
||||
Not "it compiled." This exact artifact, installed by that exact script, on a Pi 4:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Boots | yes |
|
||||
| `uname` | `6.18.38-rt-cuckoo+ SMP PREEMPT_RT` |
|
||||
| PPS handler threaded | **no** — patch confirmed live |
|
||||
| chrony | Stratum 1, ref PPS |
|
||||
| System offset | 273 ns |
|
||||
| Raw PPS jitter | 952 ns σ over 62 s |
|
||||
|
||||
:::note[One window is not a benchmark]
|
||||
That 952 ns is a single 62-second sample, on a fresh boot under a clear sky, and
|
||||
it is *better* than the 2568 ns in [the measurements](/reference/measurements/).
|
||||
We report it because it's what we saw — not as a revision of the headline
|
||||
numbers. Those came from an [A/B/A protocol](/how-to/benchmark-pps-jitter/), and
|
||||
one lucky window doesn't overturn them. Sky conditions move this figure around
|
||||
more than most software changes do. Measure your own board.
|
||||
:::
|
||||
|
||||
## Reproducing it
|
||||
|
||||
```bash
|
||||
git clone https://git.supported.systems/warehack.ing/cuckoo-escapement
|
||||
cd cuckoo-escapement/kernel && ./build.sh
|
||||
```
|
||||
|
||||
`KBUILD_BUILD_USER`, `KBUILD_BUILD_HOST` and `KBUILD_BUILD_TIMESTAMP` are pinned
|
||||
in that script. Without them a kernel bakes the builder's `user@hostname` and the
|
||||
wall-clock build time into `/proc/version` — which publishes whoever built it and
|
||||
makes the output unreproducible. Pinned, you should be able to rebuild this and
|
||||
compare bytes. For an unsigned kernel from a website, that's the only trust story
|
||||
we can honestly offer.
|
||||
|
||||
:::note[Pinned to a tested version]
|
||||
The published download always points at a kernel we have **actually booted and
|
||||
benchmarked** on a Pi 4 — not simply the newest upstream. Shipping a stranger an
|
||||
unvalidated kernel for a machine they may not be able to physically reach is not
|
||||
something we're willing to do.
|
||||
The published download always points at a kernel we have **booted and benchmarked
|
||||
on a Pi 4** — never simply the newest upstream. Shipping a stranger an unvalidated
|
||||
kernel, for a machine they may not be able to physically reach, is not something
|
||||
we're willing to do.
|
||||
:::
|
||||
|
||||
33
kernel/build.sh
Executable file
33
kernel/build.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Reproducible RT kernel for the Cuckoo Escapement release.
|
||||
#
|
||||
# KBUILD_BUILD_{USER,HOST,TIMESTAMP} are the whole point of this rebuild: without
|
||||
# them the kernel bakes the builder's `user@hostname` and the wall-clock build
|
||||
# time into /proc/version, which (a) publishes operator identity and (b) makes
|
||||
# the artifact unreproducible. Pinning all three means anyone can rebuild this
|
||||
# and diff the bytes.
|
||||
set -euo pipefail
|
||||
cd "$HOME/src"
|
||||
|
||||
PATCH="$(cd "$(dirname "$0")" && pwd)/0001-pps-gpio-keep-timestamp-in-hard-irq-under-PREEMPT_RT.patch"
|
||||
|
||||
[ -d linux ] || git clone --depth=1 --branch rpi-6.18.y https://github.com/raspberrypi/linux
|
||||
cd linux
|
||||
|
||||
export ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
|
||||
export KBUILD_BUILD_USER=cuckoo
|
||||
export KBUILD_BUILD_HOST=escapement
|
||||
export KBUILD_BUILD_TIMESTAMP='Mon Jan 1 00:00:00 UTC 2024'
|
||||
|
||||
make bcm2711_defconfig
|
||||
./scripts/config --enable EXPERT --enable PREEMPT_RT \
|
||||
--set-str LOCALVERSION "-rt-cuckoo" --disable LOCALVERSION_AUTO
|
||||
make olddefconfig
|
||||
|
||||
git apply --check "$PATCH" 2>/dev/null && git apply "$PATCH" && echo "PATCH APPLIED" \
|
||||
|| grep -q IRQF_NO_THREAD drivers/pps/clients/pps-gpio.c && echo "PATCH ALREADY PRESENT"
|
||||
|
||||
grep -q "IRQF_NO_THREAD" drivers/pps/clients/pps-gpio.c || { echo "FATAL: patch missing"; exit 1; }
|
||||
|
||||
make -j"$(nproc)" Image modules dtbs
|
||||
echo "BUILD OK: $(cat include/config/kernel.release)"
|
||||
2
kernel/release/SHA256SUMS
Normal file
2
kernel/release/SHA256SUMS
Normal file
@ -0,0 +1,2 @@
|
||||
97533ea81ec56ef090908560e88bccfb2a3f7dfcc3d33a5a1cbe4bbf15163b55 kernel-rt-6.18.38-rt-cuckoo+.img.gz
|
||||
6af126711eef2ae1651ac48e19353e6830f12fb0ef9a7e265f54c27206a4f0eb rt-modules-6.18.38-rt-cuckoo+.tar.gz
|
||||
85
kernel/release/install-rt-kernel.sh
Executable file
85
kernel/release/install-rt-kernel.sh
Executable file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Install the Cuckoo Escapement PREEMPT_RT kernel on a Raspberry Pi 4.
|
||||
#
|
||||
# The design goal here is ONE THING: you must never need physical access to the
|
||||
# SD card because of something this script did. So:
|
||||
#
|
||||
# - kernel8.img (the stock kernel) is never touched
|
||||
# - the RT kernel installs under a NEW filename
|
||||
# - the ONLY change to config.txt is a single appended line
|
||||
#
|
||||
# If it doesn't boot: pull the card, open config.txt on any machine (it's a FAT
|
||||
# partition, so a Windows laptop will do), delete that one line, put it back.
|
||||
# The stock kernel returns. That's the entire rollback.
|
||||
#
|
||||
# Run it FROM the directory holding the release files.
|
||||
set -euo pipefail
|
||||
|
||||
IMG_NAME="kernel-rt.img"
|
||||
BOOT="/boot/firmware"
|
||||
|
||||
die() { printf '\nERROR: %s\n' "$1" >&2; exit 1; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || die "run with sudo"
|
||||
|
||||
# --- Refuse to run on hardware this kernel was not built for. A bcm2711 kernel
|
||||
# --- on a Pi 5 does not boot, and the failure mode is a black screen on a
|
||||
# --- headless box, which is exactly what we promised wouldn't happen.
|
||||
MODEL="$(tr -d '\0' < /proc/device-tree/model 2>/dev/null || echo unknown)"
|
||||
case "$MODEL" in
|
||||
*"Raspberry Pi 4"*|*"Raspberry Pi Compute Module 4"*|*"Raspberry Pi 400"*) ;;
|
||||
*) die "this kernel is bcm2711 (Pi 4) only — found: $MODEL" ;;
|
||||
esac
|
||||
|
||||
[[ "$(uname -m)" == "aarch64" ]] || die "need a 64-bit OS (uname -m says $(uname -m))"
|
||||
[[ -d "$BOOT" ]] || die "$BOOT not found — is this Raspberry Pi OS?"
|
||||
|
||||
IMG_GZ="$(ls -1 kernel-rt-*.img.gz 2>/dev/null | head -1)" || true
|
||||
MODS="$(ls -1 rt-modules-*.tar.gz 2>/dev/null | head -1)" || true
|
||||
[[ -n "${IMG_GZ:-}" && -n "${MODS:-}" ]] || die "run this from the directory containing the release files"
|
||||
|
||||
# --- The image and the modules MUST come from the same build. A mismatched pair
|
||||
# --- means modprobe fails on vermagic — and since pps-gpio is a module, that
|
||||
# --- means no /dev/pps0 and no refclock, on a kernel that otherwise boots fine.
|
||||
# --- Silent, and miserable to debug. Check it up front.
|
||||
IMG_VER="${IMG_GZ#kernel-rt-}"; IMG_VER="${IMG_VER%.img.gz}"
|
||||
MOD_VER="${MODS#rt-modules-}"; MOD_VER="${MOD_VER%.tar.gz}"
|
||||
[[ "$IMG_VER" == "$MOD_VER" ]] \
|
||||
|| die "image ($IMG_VER) and modules ($MOD_VER) are from different builds"
|
||||
|
||||
if [[ -f SHA256SUMS ]]; then
|
||||
echo "==> verifying checksums"
|
||||
sha256sum -c SHA256SUMS || die "checksum mismatch — do not install this"
|
||||
else
|
||||
echo "!! no SHA256SUMS present — skipping verification (not recommended)"
|
||||
fi
|
||||
|
||||
echo "==> installing modules for $MOD_VER"
|
||||
tar -C / -xzf "$MODS"
|
||||
depmod -a "$MOD_VER"
|
||||
|
||||
echo "==> installing kernel as $BOOT/$IMG_NAME (kernel8.img left alone)"
|
||||
zcat "$IMG_GZ" > "$BOOT/$IMG_NAME"
|
||||
|
||||
if grep -qE "^kernel=$IMG_NAME" "$BOOT/config.txt"; then
|
||||
echo "==> config.txt already selects $IMG_NAME"
|
||||
else
|
||||
cp "$BOOT/config.txt" "$BOOT/config.txt.before-rt"
|
||||
printf '\n# Cuckoo Escapement RT kernel. DELETE THIS LINE to boot the stock kernel.\nkernel=%s\n' \
|
||||
"$IMG_NAME" >> "$BOOT/config.txt"
|
||||
echo "==> appended 'kernel=$IMG_NAME' to config.txt (backup: config.txt.before-rt)"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Done. Reboot, then confirm:
|
||||
|
||||
uname -a # expect $MOD_VER ... SMP PREEMPT_RT
|
||||
ps -eo comm | grep irq/.*pps # expect NOTHING — the handler must not be
|
||||
# a thread. If you see one, the patch isn't
|
||||
# in this kernel and your jitter will be bad.
|
||||
|
||||
To roll back: delete the 'kernel=$IMG_NAME' line from $BOOT/config.txt
|
||||
(or restore config.txt.before-rt) and reboot.
|
||||
EOF
|
||||
Loading…
x
Reference in New Issue
Block a user