diff --git a/boot/uefi/main.c b/boot/uefi/main.c index a5eec96..644d634 100644 --- a/boot/uefi/main.c +++ b/boot/uefi/main.c @@ -112,6 +112,16 @@ serial_putc(char c) __asm__ volatile("outb %0, %1" : : "a"((UINT8)c), "d"((UINT16)0x3F8)); } +/* Diagnostics: only on a verbose boot. + * + * These four lines say where the framebuffer is, where the kernel went and + * where the ACPI tables are. Worth having when a machine will not boot, and + * worth nothing on the thousand boots that work -- and they are the first + * thing on the serial line every time, so they set the tone for everything + * after. vprint() is print() with that decision built in. */ +static void vprint(const CHAR16 *s); +static void vprint_hex(UINT64 v); + static void print(const CHAR16 *s) { @@ -139,6 +149,20 @@ print_hex(UINT64 v) print(buf); } +static void +vprint(const CHAR16 *s) +{ + if (VERBOSE_BOOT) + print(s); +} + +static void +vprint_hex(UINT64 v) +{ + if (VERBOSE_BOOT) + print_hex(v); +} + // A failure here cannot be recovered from and must not be silent: without this // the firmware simply returns to its boot menu and the user is told nothing. static EFI_STATUS @@ -948,10 +972,10 @@ setup_framebuffer(void) // is drawn across the splash. SPLASH_UP = ui_active(); - print(L"schos: framebuffer "); print_hex(fb); - print(L" "); print_hex(info->HorizontalResolution); - print(L"x"); print_hex(info->VerticalResolution); - print(L"\r\n"); + vprint(L"schos: framebuffer "); vprint_hex(fb); + vprint(L" "); vprint_hex(info->HorizontalResolution); + vprint(L"x"); vprint_hex(info->VerticalResolution); + vprint(L"\r\n"); return fb; } @@ -1085,13 +1109,13 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) ui_progress_end(); file->Close(file); - print(L"schos: kernel entry "); print_hex(entry); print(L"\r\n"); + vprint(L"schos: kernel entry "); vprint_hex(entry); vprint(L"\r\n"); // Read while boot services still exist and the system table is guaranteed // valid; written to low memory further down, after the firmware has let go // of it, for the same reason the e820 map is. UINT64 rsdp = find_rsdp(); - print(L"schos: acpi rsdp "); print_hex(rsdp); print(L"\r\n"); + vprint(L"schos: acpi rsdp "); vprint_hex(rsdp); vprint(L"\r\n"); // The memory map, and then out. GetMemoryMap must be the last thing before // ExitBootServices: the key it returns is invalidated by any allocation, @@ -1120,7 +1144,7 @@ EfiMain(EFI_HANDLE image, EFI_SYSTEM_TABLE *systab) if (nent >= E820_MAXENTRIES) print(L"schos: WARNING: memory map truncated, some RAM will be unused\r\n"); - print(L"schos: exiting boot services\r\n"); + vprint(L"schos: exiting boot services\r\n"); st = BS->ExitBootServices(image, mapkey); if (EFI_ERROR(st)) { diff --git a/cmd/resolvconf/main.c b/cmd/resolvconf/main.c index f5d9e50..95700f1 100644 --- a/cmd/resolvconf/main.c +++ b/cmd/resolvconf/main.c @@ -44,6 +44,15 @@ puts_(const char *s) write(1, (void *)s, n); } +static int +same(const char *a, const char *b, int n) +{ + for (int i = 0; i < n; i++) + if (a[i] != b[i]) + return 0; + return 1; +} + static int slurp(const char *path, char *buf, int cap) { @@ -110,10 +119,24 @@ main(int argc, char *argv[]) } // An existing file is somebody's decision until told otherwise. + // + // Saying so every boot is the wrong volume for it. On a machine that has + // been up before, /etc/resolv.conf always exists and always says the same + // thing, so this printed a line about having done nothing on every single + // start -- which is exactly the kind of message that teaches people to + // stop reading their own boot. + // + // Quiet when the file already says what this would have written. It is + // still worth a word when the two *disagree*, because then the nameserver + // has changed and the machine is using the old one. if (!force) { char old[512]; - if (slurp(DST, old, sizeof(old)) > 0) { - puts_("resolvconf: " DST " already exists; -f to replace it\n"); + int on = slurp(DST, old, sizeof(old)); + if (on > 0) { + if (on == n && same(old, buf, n)) + return 0; // already right; nothing to say + puts_("resolvconf: " DST " differs from what DHCP gave;" + " -f to replace it\n"); return 0; } } @@ -130,6 +153,8 @@ main(int argc, char *argv[]) } close(fd); + // Worth one line the first time, because it is the moment names start + // working and somebody may be waiting to know it. puts_("resolvconf: "); write(1, buf, n); return 0; diff --git a/cmd/schinit/main.c b/cmd/schinit/main.c index dd725bc..e39284d 100644 --- a/cmd/schinit/main.c +++ b/cmd/schinit/main.c @@ -102,6 +102,23 @@ ends_with(const char *s, const char *suffix) return seq(s + ls - lf, suffix); } +// How the machine was booted; see /proc/bootmode and kinfo() in the kernel. +static int verbose_boot; + +static void +read_bootmode(void) +{ + char b[16]; + int fd = open("/proc/bootmode", O_RDONLY); + int n; + + if (fd < 0) + return; + n = read(fd, b, sizeof(b) - 1); + close(fd); + if (n > 0) { b[n] = 0; verbose_boot = (b[0] == 'v'); } +} + static void out(const char *s) { @@ -522,6 +539,11 @@ main(int argc, char *argv[]) bootstrap(); + // Only now: /proc is what bootstrap() mounts, and this reads a file in it. + // Before the mount it silently returned "quiet" every time, which made the + // verbose boot quiet in exactly the one process that runs first. + read_bootmode(); + // A reincarnated PID 1 has the children but not the table. // // exec() replaced this program's memory and kept everything else -- the @@ -545,13 +567,21 @@ main(int argc, char *argv[]) reclaim(); } - out("schinit: SchOS process 1"); - if (generation > 1) { - out(", generation "); - outn((int)generation); - out(" (reincarnated)"); + // Quiet unless asked, or unless this is not the first pid 1. + // + // "SchOS process 1" is a line about the system working exactly as designed, + // which is the definition of what a quiet boot leaves out. A *reincarnated* + // pid 1 is the opposite -- it means the last one died and was replaced -- + // so that one prints either way. + if (verbose_boot || generation > 1) { + out("schinit: SchOS process 1"); + if (generation > 1) { + out(", generation "); + outn((int)generation); + out(" (reincarnated)"); + } + out("\n"); } - out("\n"); // Generation 4 is the safety net: a manifest that kills init must not kill // it for ever, so this generation does not read /etc/schinit at all. One @@ -590,7 +620,8 @@ main(int argc, char *argv[]) } publish(); - out("schinit: ready (pid 1)\n"); + if (verbose_boot) + out("schinit: ready (pid 1)\n"); for (;;) { // Two things can happen -- a request arrives, or a child exits -- and diff --git a/cmd/schinitd/main.c b/cmd/schinitd/main.c index 3686b8e..fb7db0b 100644 --- a/cmd/schinitd/main.c +++ b/cmd/schinitd/main.c @@ -89,6 +89,44 @@ out(const char *s) write(1, (void *)s, slen(s)); } +// How the machine was booted, read once. +// +// The routine progress lines below -- one per job started, plus the summary -- +// are the userspace half of the same problem the kernel has: worth reading on +// a boot you are watching, and noise on the several thousand after it. They +// are not deleted, they are held back, and `make qemu` with F8 or any verbose +// boot brings all of them back. +// +// Failures are never held back. A job that could not start, a channel that +// closed, a pid 1 that stopped answering: those print either way, because a +// silent supervisor is worse than a chatty one. +static int verbose_boot; + +static void +read_bootmode(void) +{ + char b[16]; + int fd = open("/proc/bootmode", O_RDONLY); + int n; + + if (fd < 0) + return; // no procfs: assume quiet + n = read(fd, b, sizeof(b) - 1); + close(fd); + if (n > 0) { + b[n] = 0; + verbose_boot = (b[0] == 'v'); + } +} + +// Progress, as opposed to news. +static void +vout(const char *s) +{ + if (verbose_boot) + write(1, (void *)s, slen(s)); +} + static void outn(int v) { @@ -560,6 +598,8 @@ main(void) { struct schinit_hello h; + read_bootmode(); + // Version handshake before anything else. A mismatch is a refusal rather // than a best-effort interpretation -- see the header for why. h.op = SCHINIT_HELLO; @@ -580,8 +620,9 @@ main(void) } load_jobs(); - out("schinitd: "); outn(njobs); out(" job(s) loaded, generation "); - outn((int)h.generation); out("\n"); + vout("schinitd: "); if (verbose_boot) outn(njobs); + vout(" job(s) loaded, generation "); + if (verbose_boot) { outn((int)h.generation); out("\n"); } // Anything PID 1 is already holding is ours again, not something to start. unsigned int highest = adopt(); @@ -593,11 +634,11 @@ main(void) continue; if (j->pid) continue; // adopted; it never stopped running - out("schinitd: starting "); out(j->label); out("\n"); + vout("schinitd: starting "); vout(j->label); vout("\n"); ask_spawn(j); } - out("schinitd: ready\n"); + vout("schinitd: ready\n"); // From here on this program does two things: answer PID 1's heartbeat, and // act on the exits it reports. There is no wait() in this program and diff --git a/cmd/vgpud/main.c b/cmd/vgpud/main.c index 4837148..516b11d 100644 --- a/cmd/vgpud/main.c +++ b/cmd/vgpud/main.c @@ -31,6 +31,26 @@ // --- small output, since lib/ has no printf --------------------------------- +// How the machine was booted. A driver coming up is progress on a boot you are +// watching and noise on every other one; a driver *failing* is neither, and +// stays on puts(). See kinfo() in kernel/core/console.c for the same rule one +// level down. +static int verbose_boot; + +static void +read_bootmode(void) +{ + char b[16]; + int fd = open("/proc/bootmode", O_RDONLY); + int n; + + if (fd < 0) + return; + n = read(fd, b, sizeof(b) - 1); + close(fd); + if (n > 0) { b[n] = 0; verbose_boot = (b[0] == 'v'); } +} + static void puts(const char *s) { @@ -39,6 +59,23 @@ puts(const char *s) write(1, (void *)s, n); } +// Progress, as opposed to news. +static void +vputs(const char *s) +{ + if (verbose_boot) + puts(s); +} + +static void putn(long long v); + +static void +vputn(long long v) +{ + if (verbose_boot) + putn(v); +} + static void putn(long long v) { @@ -375,7 +412,7 @@ bring_up(void) if (width <= 0 || height <= 0) die("the device reports no display"); - puts("vgpud: display is "); putn(width); puts("x"); putn(height); puts("\n"); + vputs("vgpud: display is "); vputn(width); vputs("x"); vputn(height); vputs("\n"); // A 2D resource the size of the screen. struct virtio_gpu_resource_create_2d *c = @@ -431,8 +468,10 @@ bring_up(void) if (docmd(sizeof(*so), sizeof(struct virtio_gpu_ctrl_hdr)) < 0 || !resp_ok()) die("SET_SCANOUT refused"); - puts("vgpud: scanout is ours, framebuffer at physical 0x"); - puthex(fb_paddr, 16); puts("\n"); + if (verbose_boot) { + puts("vgpud: scanout is ours, framebuffer at physical 0x"); + puthex(fb_paddr, 16); puts("\n"); + } } // Push what has been drawn to the host. Two commands, and both are needed: @@ -735,6 +774,8 @@ control_socket(void) int main(int argc, char *argv[]) { + read_bootmode(); + pcifd = open("/dev/pci", O_RDWR); if (pcifd < 0) die("cannot open /dev/pci"); @@ -758,17 +799,19 @@ main(int argc, char *argv[]) if (index_ < 0) nothing_to_do("no virtio-gpu on this machine"); - puts("vgpud: virtio-gpu at "); puthex(info.bus, 2); puts(":"); - puthex(info.slot, 2); puts("."); puthex(info.func, 1); - if (info.driver[0]) { puts(", currently held by "); puts(info.driver); } - puts("\n"); + if (verbose_boot) { + puts("vgpud: virtio-gpu at "); puthex(info.bus, 2); puts(":"); + puthex(info.slot, 2); puts("."); puthex(info.func, 1); + if (info.driver[0]) { puts(", currently held by "); puts(info.driver); } + puts("\n"); + } // The kernel driver is asked to let go here. It puts the VBE framebuffer // back on screen before returning, so there is never a moment with no // display -- see vgpu_release() and vga_fb_restore(). if (ioctl(pcifd, PCIIOC_CLAIM, &index_) < 0) nothing_to_do("claim refused -- the kernel driver would not release it"); - puts("vgpud: claimed\n"); + vputs("vgpud: claimed\n"); // The info is re-read because claiming is what enables memory space and // bus mastering, and a BAR read before that can legitimately be zero. @@ -786,9 +829,9 @@ main(int argc, char *argv[]) draw_test_pattern(); present(); - puts("vgpud: pid "); - putn(getpid()); - puts(" drew a test pattern from userspace\n"); + vputs("vgpud: pid "); + vputn(getpid()); + vputs(" drew a test pattern from userspace\n"); if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'x') { // Exit without releasing, to show what a crashed driver leaves behind: @@ -826,7 +869,7 @@ main(int argc, char *argv[]) sleep(100); } - puts("vgpud: listening on " VGPUD_SOCKET "\n"); + vputs("vgpud: listening on " VGPUD_SOCKET "\n"); for (;;) { int c = accept(srv, 0, 0); if (c < 0) { diff --git a/docs/architecture/boot.md b/docs/architecture/boot.md index 038a2f1..c4185e6 100644 --- a/docs/architecture/boot.md +++ b/docs/architecture/boot.md @@ -207,9 +207,71 @@ framebuffer from the loader. Then everything goes to the UART and --- +## 7. What the boot says + +A working boot prints one line: + +``` +SchOS UEFI loader +Press F8 for boot options... booting + +Welcome to SchOS! (F8 at boot for the full log, or dmesg once you are in) + +root@schos / ยป +``` + +It used to print about thirty โ€” the memory map, the local APIC's calibration, +every PCI device, each driver announcing itself, each job starting. All of that +is genuinely useful, and all of it is useful *once*. Printed on every boot it +trains the person in front of the machine to stop reading their own console, +which is expensive on the day one of those lines changes. + +**Nothing was deleted; it was routed.** `consputc()` writes to the klog ring +before it writes anywhere else, so a line can be kept in full while being held +back from the two places a person is looking. `kinfo()` is `cprintf()` with +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 +``` + +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 +no every time after. `e1000: no DMA memory for the rings` is yes for ever, and +stays a `cprintf`. + +Three levels, then, and the middle one is new: + +| | | +|---|---| +| quiet (default) | the greeting, plus anything that went wrong | +| verbose (F8) | every line above, on the console and the serial line | +| the klog | all of it, always, whichever of the two you booted | + +Userspace follows the same rule through `/proc/bootmode`, which holds one word. +`schinit`, `schinitd` and `vgpud` read it and hold back their progress lines โ€” +but never their failures, because a supervisor that goes quiet when a job dies +is worse than one that chatters. + +**`schinit` reads it after `bootstrap()` and not before**, which is the sort of +detail that only shows up when tested: `/proc` is what `bootstrap()` mounts, so +reading it earlier returned "quiet" every time and made the verbose boot quiet +in the one process that runs first. + ## What it does not do - **No BIOS path.** A machine without UEFI does not boot SchOS. +- **The quiet boot hides a slow one.** With nothing printing between the + loader and the greeting, a boot that stalls for ten seconds looks the same as + one that does not. F8 is the answer and it has to be pressed *before* the + stall, which is the wrong order for finding out. +- **`/proc/bootmode` is read once, at start.** A program that wants to be + quieter or louder later has nowhere to say so, and nothing re-reads it. - **The root device is compiled in.** Booting from a USB disk needs the root device to be selectable, which it is not; the USB mass-storage driver works and a superblock reads back through `bread()`, but nothing can mount it. diff --git a/include/defs.h b/include/defs.h index a22f4c4..a09cfd3 100644 --- a/include/defs.h +++ b/include/defs.h @@ -64,6 +64,10 @@ int proc_reincarnate(int status); void proc_kill_init(void); extern uint init_generation; void cprintf(char*, ...); +// Boot chatter: always in the klog, on the screen only when boot_verbose. +// See the note above kinfo() in kernel/core/console.c for which is which. +void kinfo(char*, ...); +extern int boot_verbose; // A log line: the serial port only, never the display. // diff --git a/kernel/arch/x86_64/acpi.c b/kernel/arch/x86_64/acpi.c index 600a8dd..d11b590 100644 --- a/kernel/arch/x86_64/acpi.c +++ b/kernel/arch/x86_64/acpi.c @@ -281,7 +281,7 @@ acpi_init(void) outb(0x23, inb(0x23) | 1); } - cprintf("acpi: %d cpu%s, lapic at %p, ioapic id %d\n", + kinfo("acpi: %d cpu%s, lapic at %p, ioapic id %d\n", ncpu, ncpu == 1 ? "" : "s", lapic, ioapicid); return 1; } diff --git a/kernel/arch/x86_64/e820.c b/kernel/arch/x86_64/e820.c index 303954b..9c55781 100644 --- a/kernel/arch/x86_64/e820.c +++ b/kernel/arch/x86_64/e820.c @@ -163,7 +163,7 @@ e820_init(void) for (int i = 0; i < nmem_usable; i++) usable += mem_usable[i].end - mem_usable[i].base; - cprintf("e820: %d MB usable in %d region%s, top %d MB (%d entries)\n", + kinfo("e820: %d MB usable in %d region%s, top %d MB (%d entries)\n", (int)(usable / (1024 * 1024)), nmem_usable, nmem_usable == 1 ? "" : "s", (int)(phystop / (1024 * 1024)), count); diff --git a/kernel/arch/x86_64/lapic.c b/kernel/arch/x86_64/lapic.c index 7017be8..743ea6c 100644 --- a/kernel/arch/x86_64/lapic.c +++ b/kernel/arch/x86_64/lapic.c @@ -148,7 +148,7 @@ lapicinit(void) // see the comment above it for why loading a constant here does not work. if (lapic_timer_hz == 0) { calibrate_timers(); - cprintf("cpu0: lapic timer %u Hz, tsc %u MHz, tick = %u counts at %d Hz\n", + kinfo("cpu0: lapic timer %u Hz, tsc %u MHz, tick = %u counts at %d Hz\n", lapic_timer_hz, (uint)(tsc_hz / 1000000), lapic_timer_hz / HZ, HZ); } diff --git a/kernel/core/console.c b/kernel/core/console.c index c4fb6b9..84ae380 100644 --- a/kernel/core/console.c +++ b/kernel/core/console.c @@ -85,19 +85,30 @@ print_int(int64 xx, int base, int sign) consputc(buf[i]); } -void -cprintf(char *fmt, ...) +// Everything in the log; only what matters on the screen. +// +// A boot used to print about thirty lines of diagnostics to the serial line +// and the console, and they are genuinely useful -- once. After that they are +// the first thing you see every time you start the machine, and the message +// that actually matters is buried among them. +// +// So they are not deleted, they are *routed*. consputc() writes to the klog +// ring before anything else, so a line can be kept in full while being held +// back from the two places a person is looking. `dmesg`, /proc/kmsg and the +// next boot's "carried over from the previous boot" all still have it. +// +// Set for the duration of one kinfo() and no longer. Not a mode the system +// sits in: a panic, a driver failing, anything that is not routine still goes +// straight to the screen, because those are lines nobody should have to go +// looking for. +static int cons_logonly; + +static void +vcprintf(char *fmt, va_list ap) { - va_list ap; - int i, c, locking; + int i, c; char *s; - va_start(ap, fmt); - - locking = cons.locking; - if (locking) - acquire(&cons.lock); - if (fmt == 0) panic("null fmt"); @@ -143,9 +154,47 @@ cprintf(char *fmt, ...) break; } } +} +void +cprintf(char *fmt, ...) +{ + va_list ap; + int locking = cons.locking; + + va_start(ap, fmt); + if (locking) + acquire(&cons.lock); + vcprintf(fmt, ap); if (locking) release(&cons.lock); + va_end(ap); +} + +// A line for the log, and for the screen only on a verbose boot. +// +// The rule for choosing between this and cprintf is not "how interesting is +// it" -- everything here was interesting to somebody once. It is: **would you +// want to see this on a machine that is working?** `pci: 7 devices` is a yes +// the first time and a no every time after. `e1000: no memory for its rings` +// is a yes for ever, and stays a cprintf. +void +kinfo(char *fmt, ...) +{ + va_list ap; + int locking = cons.locking; + + va_start(ap, fmt); + if (locking) + acquire(&cons.lock); + if (!boot_verbose) + cons_logonly++; + vcprintf(fmt, ap); + if (!boot_verbose) + cons_logonly--; + if (locking) + release(&cons.lock); + va_end(ap); } // Hand the console over to panic(). @@ -212,6 +261,10 @@ consputc(int c) // three in the morning. See include/klog.h. klog_putc(c); + // Held back from the screen and the wire, kept in the log. See kinfo(). + if (cons_logonly) + return; + if (c == BACKSPACE) { uartputc('\b'); uartputc(' '); uartputc('\b'); if (!cons_serial_only) diff --git a/kernel/core/klog.c b/kernel/core/klog.c index 5147fdb..059aa7c 100644 --- a/kernel/core/klog.c +++ b/kernel/core/klog.c @@ -250,10 +250,10 @@ klog_init(void *base, uint64 bytes) // silently started over" look identical in a file that begins with a boot // divider either way. if (recovered) - cprintf("klog: %d KB at %p, %d bytes carried over from the previous boot\n", + kinfo("klog: %d KB at %p, %d bytes carried over from the previous boot\n", (int)(bytes / 1024), (uint64)base, (int)prev_bytes); else - cprintf("klog: %d KB at %p, nothing recoverable was there\n", + kinfo("klog: %d KB at %p, nothing recoverable was there\n", (int)(bytes / 1024), (uint64)base); } diff --git a/kernel/core/main.c b/kernel/core/main.c index 6d17e5a..2d7935b 100644 --- a/kernel/core/main.c +++ b/kernel/core/main.c @@ -224,6 +224,19 @@ kmain(uint32 fb_addr) netuser_init(); dbg_thread_start(); if (boot_verbose) cprintf("verbose boot: dbg thread started\n"); + + // The one line a working boot prints. + // + // Everything above this is a kinfo(), which means it is in the klog and not + // on the screen -- the whole boot is still there, it is simply not the first + // thing anybody has to read every morning. What is left is the fact that the + // machine came up, and where the rest went if it did not. + // + // A verbose boot has already said thirty other things and does not need a + // greeting on top of them. + if (!boot_verbose) + cprintf("\nWelcome to SchOS! (F8 at boot for the full log, or dmesg once you are in)\n\n"); + mpmain(); } @@ -256,7 +269,7 @@ enable_sse(void) static void mpmain(void) { - cprintf("cpu%d: starting\n", cpunum()); + kinfo("cpu%d: starting\n", cpunum()); enable_sse(); // allow user XMM/SSE instructions idtinit(); // load idt register syscallinit(); // syscall set up diff --git a/kernel/core/time.c b/kernel/core/time.c index 654d5bc..fa5bbc2 100644 --- a/kernel/core/time.c +++ b/kernel/core/time.c @@ -96,7 +96,7 @@ time_init(void) monotonic(&up, &dummy); boot_epoch = now > up ? now - up : now; - cprintf("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n", + kinfo("time: %d-%d-%d %d:%d:%d UTC from the hardware clock\n", (int)year, r.month, r.day, r.hour, r.minute, r.second); } diff --git a/kernel/dev/pci.c b/kernel/dev/pci.c index d0b144f..7bf35b8 100644 --- a/kernel/dev/pci.c +++ b/kernel/dev/pci.c @@ -556,7 +556,7 @@ pci_init(void) } scan_bus(0); - cprintf("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s"); + kinfo("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s"); pci_bind_all(); } diff --git a/kernel/dev/procfs.c b/kernel/dev/procfs.c index 68e00a6..65e13c0 100644 --- a/kernel/dev/procfs.c +++ b/kernel/dev/procfs.c @@ -343,6 +343,7 @@ gen_filesystems(struct sbuf *sb) #define PROC_NETIF 15 #define PROC_KMSG 17 #define PROC_RESOLV 18 +#define PROC_BOOTMODE 19 // Render global file `id` (a PROC_* constant) into `buf`. Returns its length. // @@ -378,6 +379,15 @@ proc_gen_global(int id, char *buf, int cap) // kernel file's include path. case PROC_NETIF: sb.len = net_info(buf, PROC_BUFSZ); break; case PROC_RESOLV: sb.len = net_resolv(buf, PROC_BUFSZ); break; + // How this machine was booted, for the programs that start during one. + // + // schinit and the jobs it runs have the same problem the kernel does -- most + // of what they print is interesting once -- and the same answer only works + // if they can find out which kind of boot this is. One word, so reading it + // is `if (strcmp(buf, "verbose"))` and not a parser. + case PROC_BOOTMODE: + sb_puts(&sb, boot_verbose ? "verbose\n" : "quiet\n"); + break; case PROC_DEVICES: gen_devices(&sb); break; default: return 0; } diff --git a/kernel/dev/uart.c b/kernel/dev/uart.c index 8dc223b..3d9a321 100644 --- a/kernel/dev/uart.c +++ b/kernel/dev/uart.c @@ -40,7 +40,12 @@ uartearlyinit(void) - // Announce that we're here. + // Announce that we're here -- only when asked for. This is the first thing a serial line ever sees from + // SchOS and it says nothing except that the port works -- which is worth a + // line on a boot you are watching and not on every boot for ever. It cannot + // go through kinfo(): it runs before there is a console to route it to. + if (!boot_verbose) + return; for(p="SchOS UART...\n"; *p; p++) uartputc(*p); } diff --git a/kernel/drivers/gpu/vga/vga_fb.c b/kernel/drivers/gpu/vga/vga_fb.c index dcbdd9a..b9b0a22 100644 --- a/kernel/drivers/gpu/vga/vga_fb.c +++ b/kernel/drivers/gpu/vga/vga_fb.c @@ -83,7 +83,7 @@ vga_fb_init(uint64 phys_addr) if (fb_height == 0) fb_height = 600; if (fb_stride == 0) fb_stride = fb_width * (fb_bpp / 8); - cprintf("vga: %dx%d, %d bpp, stride %d, LFB: 0x%x\n", + kinfo("vga: %dx%d, %d bpp, stride %d, LFB: 0x%x\n", fb_width, fb_height, fb_bpp, fb_stride, (uint32)phys_addr); info.ptr = fb; diff --git a/kernel/drivers/gpu/vga/vga_pci.c b/kernel/drivers/gpu/vga/vga_pci.c index c2eab97..2c11d61 100644 --- a/kernel/drivers/gpu/vga/vga_pci.c +++ b/kernel/drivers/gpu/vga/vga_pci.c @@ -59,7 +59,7 @@ vga_pci_probe(struct pci_dev *d) pci_enable(d, PCI_CMD_MEMORY); if (bar == 0) { - cprintf("vga: display controller at %x:%x.%x has no framebuffer aperture\n", + kinfo("vga: display controller at %x:%x.%x has no framebuffer aperture\n", d->bus, d->slot, d->func); return 0; // still ours; nothing more to do } @@ -73,7 +73,7 @@ vga_pci_probe(struct pci_dev *d) (uint32)vbe, (uint32)bar->base); } - cprintf("vga: display controller %x:%x, aperture %x (%d MB)\n", + kinfo("vga: display controller %x:%x, aperture %x (%d MB)\n", d->vendor, d->device, (uint32)bar->base, (uint32)(bar->size / (1024 * 1024))); return 0; diff --git a/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c b/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c index 65f9499..3ef4fe4 100644 --- a/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c +++ b/kernel/drivers/gpu/virtio_gpu/virtio_gpu.c @@ -507,7 +507,7 @@ vgpu_probe(struct pci_dev *d) cprintf("virtio-gpu: no ISR capability; leaving irq %d unclaimed\n", d->irq); g->ready = 1; - cprintf("virtio-gpu: %dx%d, %d KB framebuffer, queue %d, irq %d\n", + kinfo("virtio-gpu: %dx%d, %d KB framebuffer, queue %d, irq %d\n", g->width, g->height, g->fb_bytes / 1024, g->qsize, d->irq); return 0; } @@ -578,7 +578,7 @@ vgpu_release(struct pci_dev *d) g->ready = 0; g->pdev = 0; - cprintf("virtio-gpu: released to userspace; screen is the VBE framebuffer\n"); + kinfo("virtio-gpu: released to userspace; screen is the VBE framebuffer\n"); return 0; } diff --git a/kernel/drivers/net/e1000/e1000.c b/kernel/drivers/net/e1000/e1000.c index b423d71..1917825 100644 --- a/kernel/drivers/net/e1000/e1000.c +++ b/kernel/drivers/net/e1000/e1000.c @@ -347,7 +347,7 @@ e1000_probe(struct pci_dev *d) e->ready = 1; - cprintf("e1000: %x:%x:%x:%x:%x:%x, link %s, irq %d\n", + kinfo("e1000: %x:%x:%x:%x:%x:%x, link %s, irq %d\n", e->mac[0], e->mac[1], e->mac[2], e->mac[3], e->mac[4], e->mac[5], (rd(e, E1000_STATUS) & E1000_STATUS_LU) ? "up" : "down", d->irq); return 0; diff --git a/kernel/fs/procfs.c b/kernel/fs/procfs.c index 9c3c844..67f5e5a 100644 --- a/kernel/fs/procfs.c +++ b/kernel/fs/procfs.c @@ -97,6 +97,8 @@ static const struct global_file globals[] = { // The nameservers DHCP supplied, in resolv.conf's own syntax. See // net_resolv() and cmd/resolvconf. { "resolv.conf", 18 }, + // "quiet" or "verbose" -- how this machine was booted. See cmd/schinitd. + { "bootmode", 19 }, { "kmsg", 17 }, }; #define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0]))) diff --git a/kernel/fs/schfs/kmount.c b/kernel/fs/schfs/kmount.c index 8d587cb..9880bae 100644 --- a/kernel/fs/schfs/kmount.c +++ b/kernel/fs/schfs/kmount.c @@ -533,7 +533,7 @@ kmount_slot(int slot, uint bdev) } m->mounted = 1; - cprintf("schfs: mounted '%s' on device %d, %d MB, generation %d\n", + kinfo("schfs: mounted '%s' on device %d, %d MB, generation %d\n", m->vol.label, bdev, (int)(m->vol.total_blocks * SCHFS_BLKSIZE / (1024 * 1024)), (int)m->vol.generation); diff --git a/kernel/net/net.c b/kernel/net/net.c index 903e6fd..cd6cf66 100644 --- a/kernel/net/net.c +++ b/kernel/net/net.c @@ -272,7 +272,7 @@ net_thread(void) continue; dhcp_start(net_eth_netif(idx)); attached++; - cprintf("net: %s attached as interface %d\n", d->name, idx); + kinfo("net: %s attached as interface %d\n", d->name, idx); } if (attached == 0) { @@ -285,7 +285,7 @@ net_thread(void) } lwip_sys_unlock(); - cprintf("net: lwIP %d.%d.%d up on %d interface%s, requesting an address\n", + kinfo("net: lwIP %d.%d.%d up on %d interface%s, requesting an address\n", LWIP_VERSION_MAJOR, LWIP_VERSION_MINOR, LWIP_VERSION_REVISION, attached, attached == 1 ? "" : "s");