root: find the filesystem instead of being told at build time, and fix dmesg
`#define ROOTDEV 1` -- IDE disk 1 -- so a kernel could only ever root on the
disk it was *built* expecting. Booting the same image from a USB stick was
impossible by construction, and that was the one thing standing between this
kernel and its own acceptance test, which begins "boot on real hardware from a
USB stick".
rootdev_mount() probes. schfs_kmount() was already written for exactly this --
its own comment says "the uberblock scan doubles as the probe: no valid
uberblock means this is simply not a SchFS volume, which is not an error worth
reporting loudly" -- and a failed probe leaves the slot unmounted and reusable,
which is what makes a loop over candidates legal at all. So the change is one
function and one call site; the only real use of ROOTDEV was `schfs_kmount()`.
Order is preference, not confidence: a RAMDISK_ROOT build wins outright, then
USB, then IDE 1, then IDE 0. USB before the fixed disks because a stick that is
plugged in was plugged in on purpose. iderw() panics on an absent drive rather
than failing, so each candidate is checked with ide_disk_present() /
usb_storage_present() first -- which is what those predicates are for.
Verified with **no IDE disk attached at all**: boots, mounts the stick, writes
a file, and the file is still there on the next boot.
[ 0.000021] root: USB disk (device 3)
And when nothing matches, it names what it looked at, because "no root
filesystem" with no list is the least useful thing a kernel can say at the one
moment somebody needs to know which disk it expected:
root: no SchFS volume on any candidate device. Looked at:
device 3, USB disk: not present
device 1, IDE disk 1: not present
device 0, IDE disk 0: present, but no SchFS on it
**That message is also how dmesg turned out to be broken**, because it did not
appear in `dmesg` and should have. `dmesg` printed *nothing at all*, on a
system whose boot banner says "or dmesg once you are in" and whose entire
quiet-boot design is "route the detail into the kernel log and say how to read
it". The log was there the whole time -- `cat /proc/kmsg` has always worked --
but the one command anybody would reach for could not see it.
Two separate faults, each alone enough:
- **sys_syslog() was `return 0`.** dmesg does not read /proc/kmsg; it calls
klogctl(10) for the buffer size and klogctl(3) to read. 0 to the first one
means "the log is zero bytes", and a stub that answers 0 is
indistinguishable from a machine that logged nothing. Implemented for the
actions that mean something here; the rest return -EINVAL rather than 0,
because a console log level this kernel does not have is not a request that
succeeded.
- **With that fixed, dmesg was still empty.** It parses every line with
`sscanf(msg, "<%u>[%llu.%llu] %n", ...)` and silently drops anything that
does not match. The ring's `[ 0.00] text` already matches everything
after the priority -- scanf skips the leading spaces -- so the only missing
thing was the `<6>`. klog_render_syslog() adds it on the way out rather
than storing it: the ring is also what a person reads with `cat
/proc/kmsg` and what PSOGM points at after a crash, and an ABI detail
belongs at the boundary that has the ABI.
Also: the first version of the no-root diagnostic printed
%-11s device 1596400
because cprintf() has no field-width specifier -- it prints "%-11s" literally
and then reads the next argument for the %d. Plain %s now, with the note.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fdbd9bcf95
commit
7e88cea151
6 changed files with 307 additions and 21 deletions
|
|
@ -168,6 +168,44 @@ It does **not** mount the root filesystem, and cannot: mounting reads blocks,
|
|||
`forkret()`, the first time any process is scheduled — guarded by a `static int
|
||||
first`.
|
||||
|
||||
### Which device the root is on is decided there, not at build time
|
||||
|
||||
`ROOTDEV` used to be `#define ROOTDEV 1` — IDE disk 1 — so a kernel could only
|
||||
ever root on the disk it was *built* expecting. Booting the same image from a
|
||||
USB stick was impossible by construction, and that was the one thing standing
|
||||
between this kernel and its own acceptance test, which begins "boot on real
|
||||
hardware from a USB stick".
|
||||
|
||||
`rootdev_mount()` probes instead. `schfs_kmount()` was already written to be
|
||||
used this way — *"the uberblock scan doubles as the probe: no valid uberblock
|
||||
means this is simply not a SchFS volume, which is not an error worth reporting
|
||||
loudly"* — and a failed probe leaves the slot unmounted and reusable, which is
|
||||
what makes a loop over candidates legal at all.
|
||||
|
||||
| order | device | |
|
||||
|---|---|---|
|
||||
| 1 | `RAMDISK_DEV` | only in a `RAMDISK_ROOT` build, where it is an explicit choice |
|
||||
| 2 | `USBDISK_DEV` | a stick that is plugged in was plugged in on purpose |
|
||||
| 3 | IDE 1 | what the old constant named |
|
||||
| 4 | IDE 0 | usually the ESP, so usually not SchFS |
|
||||
|
||||
`iderw()` *panics* on an absent drive rather than failing, so each candidate is
|
||||
checked with `ide_disk_present()` / `usb_storage_present()` first. That is the
|
||||
whole reason those predicates exist.
|
||||
|
||||
When nothing matches, the panic names what it looked at, because "no root
|
||||
filesystem" with no list is the least useful thing a kernel can say at the one
|
||||
moment somebody needs to know which disk it expected:
|
||||
|
||||
```
|
||||
root: no SchFS volume on any candidate device. Looked at:
|
||||
device 3, USB disk: not present
|
||||
device 1, IDE disk 1: not present
|
||||
device 0, IDE disk 0: present, but no SchFS on it
|
||||
```
|
||||
|
||||
The chosen device is reported in `dmesg` as `root: USB disk (device 3)`.
|
||||
|
||||
That is also where PID 1's working directory is re-pointed at the real root,
|
||||
and the reason is worth keeping:
|
||||
|
||||
|
|
@ -233,12 +271,31 @@ that routing, and the whole boot is still there afterwards:
|
|||
|
||||
```
|
||||
# dmesg | head -4
|
||||
--- boot 1 ---
|
||||
[ 0.00] e820: 1017 MB usable in 8 regions, top 1006 MB (22 entries)
|
||||
[ 0.00] acpi: 2 cpus, lapic at ffff8000fee00000, ioapic id 0
|
||||
[ 0.00] pci: 7 devices
|
||||
[ 0.000000] e820: 1017 MB usable in 8 regions, top 1002 MB (21 entries)
|
||||
[ 0.000000] klog: 128 KB at ffff800000737000, nothing recoverable was there
|
||||
[ 0.000000] acpi: 4 cpus, lapic at ffff8000fee00000, ioapic id 0
|
||||
[ 0.000000] console: JetBrains Mono, 10x21 cells, 128 by 38
|
||||
```
|
||||
|
||||
**`dmesg` printed nothing at all until 2026-09-22**, which made that promise in
|
||||
the banner a lie for as long as it had been there. Two separate things were
|
||||
wrong, and each alone was enough:
|
||||
|
||||
- `sys_syslog()` was `return 0`. `dmesg` does not read `/proc/kmsg`; it calls
|
||||
`klogctl(10)` for the buffer size and `klogctl(3)` to read, and 0 to the
|
||||
first one means "the log is zero bytes". A stub that answers 0 is
|
||||
indistinguishable from a machine that logged nothing.
|
||||
- With that implemented, `dmesg` was *still* empty. It parses each line with
|
||||
`sscanf(msg, "<%u>[%llu.%llu] %n", ...)` and **silently drops** anything that
|
||||
does not match. The ring's `[ 0.00] text` already matches everything
|
||||
after the priority — scanf skips the leading spaces — so the only missing
|
||||
thing was the `<6>`.
|
||||
|
||||
`klog_render_syslog()` adds it on the way out rather than storing it, because
|
||||
the ring is also what a person reads with `cat /proc/kmsg` and what PSOGM
|
||||
points at after a crash. An ABI detail belongs at the boundary that has the
|
||||
ABI.
|
||||
|
||||
The rule for `kinfo` against `cprintf` is not how interesting a line is —
|
||||
everything was interesting to somebody once. It is: **would you want to see
|
||||
this on a machine that is working?** `pci: 7 devices` is yes the first time and
|
||||
|
|
|
|||
Loading…
Reference in a new issue