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
|
|
@ -836,6 +836,81 @@ yield(void)
|
|||
sched();
|
||||
release(&ptable.lock);
|
||||
}
|
||||
// Which device the root filesystem is on.
|
||||
//
|
||||
// It was `#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 is the one thing standing
|
||||
// between this kernel and its own acceptance test, which begins "boot on real
|
||||
// hardware from a USB stick".
|
||||
//
|
||||
// A probe rather than a boot argument, because the answer is discoverable and
|
||||
// asking someone to name a device number is a worse interface than looking.
|
||||
// schfs_kmount() is already written to be used this way -- 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"
|
||||
// -- so each candidate costs one scan and answers yes or no. A failed probe
|
||||
// leaves the slot unmounted and reusable, which is what makes a loop over
|
||||
// candidates legal at all.
|
||||
//
|
||||
// **The order is preference, not confidence.** A ramdisk root is an explicit
|
||||
// build choice and wins outright. After it, USB comes before the fixed disks:
|
||||
// a stick that is plugged in was plugged in on purpose, and a machine with
|
||||
// both should boot the thing the person is holding.
|
||||
uint rootdev;
|
||||
|
||||
static int
|
||||
rootdev_usable(uint dev)
|
||||
{
|
||||
if (dev == RAMDISK_DEV)
|
||||
return ramdisk_present();
|
||||
if (dev == USBDISK_DEV)
|
||||
return usb_storage_present();
|
||||
// iderw() panics on an absent drive rather than failing, so a probe has to
|
||||
// ask first. That is the whole reason ide_disk_present() exists.
|
||||
return ide_disk_present((int)dev);
|
||||
}
|
||||
|
||||
static void
|
||||
rootdev_mount(void)
|
||||
{
|
||||
static const struct { uint dev; const char *what; } cand[] = {
|
||||
#ifdef RAMDISK_ROOT
|
||||
{ RAMDISK_DEV, "ramdisk" },
|
||||
#endif
|
||||
{ USBDISK_DEV, "USB disk" },
|
||||
{ 1, "IDE disk 1" },
|
||||
{ 0, "IDE disk 0" },
|
||||
};
|
||||
|
||||
for (int i = 0; i < (int)NELEM(cand); i++) {
|
||||
if (!rootdev_usable(cand[i].dev))
|
||||
continue;
|
||||
schfs_kmount(cand[i].dev);
|
||||
if (schfs_kmounted()) {
|
||||
rootdev = cand[i].dev;
|
||||
kinfo("root: %s (device %d)\n", cand[i].what, cand[i].dev);
|
||||
return;
|
||||
}
|
||||
kinfo("root: %s (device %d) carries no SchFS volume\n",
|
||||
cand[i].what, cand[i].dev);
|
||||
}
|
||||
|
||||
// Name what was looked at. "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.
|
||||
// Plain %s and no width. cprintf() has no field-width specifier -- it
|
||||
// prints "%-11s" literally and then reads the *next* argument for the %d,
|
||||
// which is how the first version of this message came out as
|
||||
// `%-11s device 1596400`. See print_int() in kernel/core/console.c.
|
||||
cprintf("root: no SchFS volume on any candidate device. Looked at:\n");
|
||||
for (int i = 0; i < (int)NELEM(cand); i++)
|
||||
cprintf(" device %d, %s: %s\n", cand[i].dev, cand[i].what,
|
||||
rootdev_usable(cand[i].dev) ? "present, but no SchFS on it"
|
||||
: "not present");
|
||||
panic("no root filesystem");
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
forkret(void) {
|
||||
|
|
@ -860,17 +935,10 @@ forkret(void) {
|
|||
iinit();
|
||||
mountinit();
|
||||
|
||||
// SchFS is the root filesystem.
|
||||
//
|
||||
// Mounted here rather than in main() because mounting reads blocks,
|
||||
// bread() sleeps, and sleeping needs a process context.
|
||||
//
|
||||
// schfs_kmount() reports failure by leaving the volume unmounted rather
|
||||
// than by returning -- it is a probe elsewhere, where "not SchFS" is an
|
||||
// ordinary answer. Here it is fatal, so ask afterwards.
|
||||
schfs_kmount(ROOTDEV);
|
||||
if (!schfs_kmounted())
|
||||
panic("schfs root: no SchFS volume on the root device");
|
||||
// SchFS is the root filesystem, on whichever device turns out to have
|
||||
// one. Here rather than in main() because mounting reads blocks, bread()
|
||||
// sleeps, and sleeping needs a process context.
|
||||
rootdev_mount();
|
||||
// Re-point pid 1's cwd at the real root, now that there is one.
|
||||
//
|
||||
// sinit() sets cwd = namei("/") back in main(), which is before this runs.
|
||||
|
|
|
|||
Loading…
Reference in a new issue