fastfetch reported 3 seconds of uptime after 15 real ones, `time` claimed
3.12s for a 20-second command, and ping's one-second interval was closer to
six. One cause, not three: `ticks` was advancing at ~14.5 Hz instead of 100.
It was not the timer. virtio-gpu-pci shares IRQ 11 with the e1000 and
registered no interrupt handler at all. PCI INTx is level-triggered, so the
device held the line asserted, the kernel signalled EOI, and it fired again
immediately -- 72,000 interrupts a second, 83% of CPU0, starving the tick.
The e1000's handler read ICR as zero half a million times, correctly
answering "not mine", which is what made it look like a network-free problem.
Reading virtio's ISR byte clears it and drops the line. That is the fix.
irq_dispatch now measures each line's rate against the TSC -- not ticks,
which is the first casualty of a storm -- and names the attached handlers
once above 20000/s. It reproduces the original fault as a single line.
Found along the way, and fixed because they were real:
- lapicinit loaded TICR with xv6's uncalibrated 10000000, whose own comment
admitted it was never measured. calibrate_timers() now measures the LAPIC
and the TSC against the 8254 PIT. (It came back at 999.9 MHz, so this was
not the bug -- but the next machine is not obliged to be 1 GHz.)
- microdelay() was an empty function body. Every microdelay(1000) in the
xHCI reset path and the USB hub power-on sequence returned instantly.
- HZ is a constant in param.h now, not the literal 100 written out at a
dozen ticks-to-seconds conversions. USER_HZ is separate and deliberately
does not follow it: musl answers sysconf(_SC_CLK_TCK) with 100 always, so
times() and /proc/stat scale at the syscall boundary the way Linux does.
- TIOCGWINSZ answered a hardcoded 24x80. gfx_cols()/gfx_rows() have known
the real geometry all along; at 1024x768 the console is 128x48, so every
program that lays out a screen was formatting for the wrong one.
- TCFLSH fell through to the catch-all `return 0`, claiming to discard
input without discarding any.
uptime now advances 5.01 seconds per 5 real seconds. schfs-check passes.
Full investigation, including the two wrong hypotheses and the measurement
flaw that protected them, in docs/history/kernel.md section 13.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
522 lines
18 KiB
C
522 lines
18 KiB
C
// /proc — synthesised kernel status files.
|
|
//
|
|
// Each file is a character device node under major PROCFS_MAJOR, distinguished
|
|
// by its minor number, created by init at boot. Reading one renders the whole
|
|
// text into a scratch buffer and returns the slice starting at the caller's
|
|
// file offset, so a normal read-until-zero loop terminates.
|
|
//
|
|
// SCOPE: these are the *global* files. Per-process directories (/proc/<pid>/…)
|
|
// are not here, because they need something this kernel does not have yet:
|
|
// synthetic directories, i.e. a directory whose entries are generated at
|
|
// lookup and getdents time rather than read from disk. That is a real VFS
|
|
// feature touching iget/ilock/dirlookup/getdents, and it is the natural
|
|
// companion to the SchFS work rather than a bolt-on to the current fs.c.
|
|
// The namespace (which files exist, including one directory per process) is
|
|
// kernel/fs/procfs.c; this file only generates the *content* of the global
|
|
// files. `ps` and `top` work through the former.
|
|
|
|
#include "types.h"
|
|
#include "defs.h"
|
|
#include "irq.h"
|
|
#include "x86.h"
|
|
#include "device.h"
|
|
#include "display.h"
|
|
#include "netdev.h"
|
|
#include "param.h"
|
|
#include "memlayout.h"
|
|
#include "mmu.h"
|
|
#include "spinlock.h"
|
|
#include "sleeplock.h"
|
|
#include "fs.h"
|
|
#include "file.h"
|
|
#include "proc.h"
|
|
#include "uaccess.h"
|
|
#include "errno.h"
|
|
#include "fsstat.h"
|
|
#include "schfs.h"
|
|
|
|
#define PROC_BUFSZ 2048
|
|
|
|
// --- tiny formatting helpers (no snprintf in the kernel) -------------------
|
|
|
|
struct sbuf {
|
|
char *p;
|
|
int len;
|
|
int cap;
|
|
};
|
|
|
|
static void
|
|
sb_putc(struct sbuf *sb, char c)
|
|
{
|
|
if (sb->len < sb->cap - 1)
|
|
sb->p[sb->len++] = c;
|
|
}
|
|
|
|
static void
|
|
sb_puts(struct sbuf *sb, const char *s)
|
|
{
|
|
while (*s)
|
|
sb_putc(sb, *s++);
|
|
}
|
|
|
|
// A string in a fixed-width column, space-padded. cprintf has no width
|
|
// specifier and neither does this buffer, so tables build their own columns.
|
|
// Over-long values are allowed to run past their column rather than be
|
|
// truncated: a misaligned row is easier to read than a mangled name.
|
|
static void
|
|
sb_pad(struct sbuf *sb, const char *s, int width)
|
|
{
|
|
int n = 0;
|
|
while (s[n]) {
|
|
sb_putc(sb, s[n]);
|
|
n++;
|
|
}
|
|
while (n++ < width)
|
|
sb_putc(sb, ' ');
|
|
}
|
|
|
|
// Four lowercase hex digits, zero-padded. PCI and USB identifiers are always
|
|
// written this way, and a lookup table keyed on "8086:100e" does not match
|
|
// "8086:100E" or "8086:10e".
|
|
static void
|
|
sb_puthex4(struct sbuf *sb, uint32 v)
|
|
{
|
|
static const char hex[] = "0123456789abcdef";
|
|
for (int i = 3; i >= 0; i--)
|
|
sb_putc(sb, hex[(v >> (i * 4)) & 0xF]);
|
|
}
|
|
|
|
static void
|
|
sb_putu(struct sbuf *sb, uint64 v)
|
|
{
|
|
char tmp[24];
|
|
int i = 0;
|
|
if (v == 0) { sb_putc(sb, '0'); return; }
|
|
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
|
|
while (i--) sb_putc(sb, tmp[i]);
|
|
}
|
|
|
|
// "<label><value> <unit>\n" — the shape almost every /proc line takes.
|
|
static void
|
|
sb_kv(struct sbuf *sb, const char *label, uint64 v, const char *unit)
|
|
{
|
|
sb_puts(sb, label);
|
|
sb_putu(sb, v);
|
|
if (unit && *unit) { sb_putc(sb, ' '); sb_puts(sb, unit); }
|
|
sb_putc(sb, '\n');
|
|
}
|
|
|
|
// --- the files -------------------------------------------------------------
|
|
|
|
// PHYSTOP is the top of usable physical memory; kalloc hands out everything
|
|
// above the kernel image. Reporting the total honestly and leaving "free" as a
|
|
// share of it is better than the invented constants sysinfo() used to return.
|
|
#define TOTAL_KB (PHYSTOP / 1024)
|
|
|
|
static void
|
|
gen_meminfo(struct sbuf *sb)
|
|
{
|
|
uint64 total = TOTAL_KB;
|
|
uint64 freek = kalloc_free_bytes() / 1024;
|
|
sb_kv(sb, "MemTotal: ", total, "kB");
|
|
sb_kv(sb, "MemFree: ", freek, "kB");
|
|
sb_kv(sb, "MemAvailable: ", freek, "kB");
|
|
sb_kv(sb, "Buffers: ", 0, "kB");
|
|
sb_kv(sb, "Cached: ", 0, "kB");
|
|
sb_kv(sb, "SwapTotal: ", 0, "kB");
|
|
sb_kv(sb, "SwapFree: ", 0, "kB");
|
|
}
|
|
|
|
static void
|
|
gen_uptime(struct sbuf *sb)
|
|
{
|
|
uint t = uptime(); // timer ticks, HZ of them per second
|
|
uint frac = (t % HZ) * 100 / HZ; // hundredths, whatever HZ happens to be
|
|
sb_putu(sb, t / HZ);
|
|
sb_putc(sb, '.');
|
|
sb_putu(sb, frac / 10);
|
|
sb_putu(sb, frac % 10);
|
|
sb_puts(sb, " 0.00\n"); // idle time: not tracked
|
|
}
|
|
|
|
static void
|
|
gen_cpuinfo(struct sbuf *sb)
|
|
{
|
|
// Online CPUs, not present ones -- the same rule Linux follows, and the
|
|
// reason matters: something reading this to size a thread pool would
|
|
// otherwise be told a VM with four processors has four to schedule on, when
|
|
// three of them are enumerated and idle. "cpu cores" stays the present count,
|
|
// which is what that field means.
|
|
// Real values, from CPUID. These used to read "SchOS" and "SchOS virtual
|
|
// CPU" -- a thing to print when you have not asked the processor, and
|
|
// actively unhelpful, because identifying the machine is the one question
|
|
// /proc/cpuinfo exists to answer.
|
|
//
|
|
// Leaf 0 returns the vendor string in EBX:EDX:ECX, in that order, which is
|
|
// not the order anyone expects. Leaves 0x80000002..4 hold the 48-character
|
|
// brand string. Leaf 0x80000000 reports the highest extended leaf, and asking
|
|
// for one the CPU does not implement returns the highest supported leaf's
|
|
// value rather than an error -- so checking first is not optional.
|
|
char vendor[13], brand[49];
|
|
uint32 a, b, c, d;
|
|
|
|
cpuid_leaf(0, &a, &b, &c, &d);
|
|
*(uint32 *)&vendor[0] = b;
|
|
*(uint32 *)&vendor[4] = d;
|
|
*(uint32 *)&vendor[8] = c;
|
|
vendor[12] = '\0';
|
|
|
|
brand[0] = '\0';
|
|
cpuid_leaf(0x80000000, &a, 0, 0, 0);
|
|
if (a >= 0x80000004) {
|
|
for (int leaf = 0; leaf < 3; leaf++) {
|
|
cpuid_leaf(0x80000002 + leaf, &a, &b, &c, &d);
|
|
*(uint32 *)&brand[leaf * 16 + 0] = a;
|
|
*(uint32 *)&brand[leaf * 16 + 4] = b;
|
|
*(uint32 *)&brand[leaf * 16 + 8] = c;
|
|
*(uint32 *)&brand[leaf * 16 + 12] = d;
|
|
}
|
|
brand[48] = '\0';
|
|
}
|
|
|
|
// Intel pads the brand string with leading spaces, which read as a broken
|
|
// field rather than a name.
|
|
const char *model = brand;
|
|
while (*model == ' ')
|
|
model++;
|
|
if (*model == '\0')
|
|
model = "unknown";
|
|
|
|
for (int i = 0; i < ncpu_online; i++) {
|
|
sb_kv(sb, "processor\t: ", i, 0);
|
|
sb_puts(sb, "vendor_id\t: ");
|
|
sb_puts(sb, vendor);
|
|
sb_putc(sb, '\n');
|
|
sb_puts(sb, "model name\t: ");
|
|
sb_puts(sb, model);
|
|
sb_putc(sb, '\n');
|
|
sb_kv(sb, "cpu cores\t: ", ncpu, 0);
|
|
sb_putc(sb, '\n');
|
|
}
|
|
if (ncpu_online < ncpu) {
|
|
sb_kv(sb, "cpus present\t: ", ncpu, 0);
|
|
sb_puts(sb, "note\t\t: additional processors are enumerated but not "
|
|
"started; see docs/plan/roadmap.md\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
gen_version(struct sbuf *sb)
|
|
{
|
|
// The kernel's name, not the system's -- same as "Linux version 6.x" here.
|
|
sb_puts(sb, "SchIFT version 0.1.0 (schift) #1 SMP x86_64\n");
|
|
}
|
|
|
|
static void
|
|
gen_stat(struct sbuf *sb)
|
|
{
|
|
// USER_HZ, like times() -- everything that parses /proc/stat divides by 100.
|
|
uint t = (uint)((uint64)uptime() * USER_HZ / HZ);
|
|
sb_puts(sb, "cpu ");
|
|
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
|
|
// Online, for the same reason as cpuinfo: top and friends count the cpuN
|
|
// lines here to decide how many cores to draw.
|
|
for (int i = 0; i < ncpu_online; i++) {
|
|
sb_puts(sb, "cpu");
|
|
sb_putu(sb, i);
|
|
sb_putc(sb, ' ');
|
|
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
|
|
}
|
|
sb_kv(sb, "processes ", (uint64)proc_num(), 0);
|
|
sb_kv(sb, "procs_running ", 1, 0);
|
|
sb_kv(sb, "btime ", 0, 0);
|
|
}
|
|
|
|
// Everything attached, whatever bus found it. The one place that answers "what
|
|
// is in this machine" across PCI, USB and platform devices at once -- which is
|
|
// the question a device manager is, so this is where one would read from.
|
|
//
|
|
// Columns rather than key/value: this is a table of like things, and a reader
|
|
// scanning for an unclaimed device wants the driver column to line up.
|
|
static void
|
|
gen_devices(struct sbuf *sb)
|
|
{
|
|
sb_puts(sb, "name bus class driver id device\n");
|
|
for (int i = 0; i < device_count(); i++) {
|
|
struct device *d = device_at(i);
|
|
if (!d)
|
|
break;
|
|
// 14 for the name and driver columns: "virtio-gpu0" is eleven characters
|
|
// and overran a ten-wide column, which sb_pad allows on purpose but which
|
|
// made the whole table unreadable.
|
|
sb_pad(sb, d->name, 14);
|
|
sb_pad(sb, dev_bus_name(d->bus), 10);
|
|
sb_pad(sb, dev_class_name(d->class), 10);
|
|
// An unclaimed device is the interesting row, so it gets a visible marker
|
|
// rather than an empty column.
|
|
sb_pad(sb, d->driver ? d->driver : "(none)", 14);
|
|
|
|
// vendor:product, the way lspci and lsusb print it. The kernel has no
|
|
// business resolving these to names -- that is what /usr/share/usb.ids is
|
|
// for, and it belongs to a userspace tool. What the kernel owes is the
|
|
// number, in the form a lookup expects.
|
|
if (d->vendor || d->product) {
|
|
sb_puthex4(sb, d->vendor);
|
|
sb_putc(sb, ':');
|
|
sb_puthex4(sb, d->product);
|
|
sb_putc(sb, ' ');
|
|
} else {
|
|
sb_puts(sb, " ");
|
|
}
|
|
sb_puts(sb, d->desc[0] ? d->desc : "-");
|
|
sb_putc(sb, '\n');
|
|
}
|
|
}
|
|
|
|
static void
|
|
gen_loadavg(struct sbuf *sb)
|
|
{
|
|
// No load tracking; report zeros but a truthful process count.
|
|
sb_puts(sb, "0.00 0.00 0.00 1/");
|
|
sb_putu(sb, (uint64)proc_num());
|
|
sb_puts(sb, " 1\n");
|
|
}
|
|
|
|
// df and mountpoint parse this.
|
|
//
|
|
// It used to be three fixed lines, which was true while there was one
|
|
// filesystem and no way to mount a second. Now `mount -t schfs /dev/hdb /mnt`
|
|
// works, and a mount missing from here is invisible to df -- it printed a
|
|
// header and no row, because df looks the path up in this list before it calls
|
|
// statfs at all.
|
|
static void
|
|
gen_mounts(struct sbuf *sb)
|
|
{
|
|
char path[MAXPATH], source[MAXPATH], type[16];
|
|
uint fsdev;
|
|
|
|
sb_puts(sb, "devtmpfs /dev devtmpfs rw 0 0\n");
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
if (mount_get(i, path, source, type, &fsdev) != 0)
|
|
continue;
|
|
// "/" is emitted last, below, so skip it if it somehow appears here.
|
|
if (path[0] == '/' && path[1] == '\0')
|
|
continue;
|
|
sb_puts(sb, source[0] ? source : "none");
|
|
sb_puts(sb, " ");
|
|
sb_puts(sb, path);
|
|
sb_puts(sb, " ");
|
|
sb_puts(sb, type[0] ? type : "schfs");
|
|
// A mounted snapshot is genuinely read-only, and saying so is what stops
|
|
// anything trying to write to one and reporting a confusing EROFS later.
|
|
sb_puts(sb, SCHFS_IS_SNAP_DEV(fsdev) ? " ro 0 0\n" : " rw 0 0\n");
|
|
}
|
|
|
|
// Root must be last: df keeps the last mount point that prefixes the path
|
|
// it was asked about, so listing / first made `df /` report /proc instead.
|
|
sb_puts(sb, "/dev/root / schfs rw 0 0\n");
|
|
}
|
|
|
|
// Where the time goes in the filesystem and block paths.
|
|
//
|
|
// Cycles, not seconds: the numbers are only ever compared against each other,
|
|
// and converting would mean knowing the TSC frequency, which this kernel does
|
|
// not. What matters is the ratios and the miss counts.
|
|
static void
|
|
gen_fsstat(struct sbuf *sb)
|
|
{
|
|
sb_puts(sb, "bread_calls "); sb_putu(sb, fsstat.bread_calls);
|
|
sb_puts(sb, "\nbread_miss "); sb_putu(sb, fsstat.bread_miss);
|
|
sb_puts(sb, "\nbget_evict_dirty "); sb_putu(sb, fsstat.bget_evict_dirty);
|
|
sb_puts(sb, "\ndisk_reads "); sb_putu(sb, fsstat.disk_reads);
|
|
sb_puts(sb, "\ndisk_writes "); sb_putu(sb, fsstat.disk_writes);
|
|
sb_puts(sb, "\ndisk_kcycles "); sb_putu(sb, fsstat.disk_cycles / 1000);
|
|
sb_puts(sb, "\nread_calls "); sb_putu(sb, fsstat.read_calls);
|
|
sb_puts(sb, "\nread_bytes "); sb_putu(sb, fsstat.read_bytes);
|
|
sb_puts(sb, "\nread_kcycles "); sb_putu(sb, fsstat.read_cycles / 1000);
|
|
sb_puts(sb, "\ngetdents_calls "); sb_putu(sb, fsstat.getdents_calls);
|
|
sb_puts(sb, "\ngetdents_walked "); sb_putu(sb, fsstat.getdents_walked);
|
|
sb_puts(sb, "\ngetdents_kcycles "); sb_putu(sb, fsstat.getdents_cycles / 1000);
|
|
sb_puts(sb, "\ndescents "); sb_putu(sb, fsstat.descents);
|
|
sb_puts(sb, "\nexec_calls "); sb_putu(sb, fsstat.exec_calls);
|
|
sb_puts(sb, "\nexec_kcycles "); sb_putu(sb, fsstat.exec_cycles / 1000);
|
|
|
|
// Occupancy. The peaks are the diagnostic: a peak that climbs across a
|
|
// session and never falls back after the commands finish is a reference
|
|
// leak, and which line it is on says where to look.
|
|
sb_puts(sb, "\ninode_used "); sb_putu(sb, fsstat.inode_used);
|
|
sb_puts(sb, "\ninode_peak "); sb_putu(sb, fsstat.inode_peak);
|
|
sb_puts(sb, " / "); sb_putu(sb, NINODE);
|
|
sb_puts(sb, "\nfile_used "); sb_putu(sb, fsstat.file_used);
|
|
sb_puts(sb, "\nfile_peak "); sb_putu(sb, fsstat.file_peak);
|
|
sb_puts(sb, " / "); sb_putu(sb, NFILE);
|
|
sb_puts(sb, "\nbuf_pinned "); sb_putu(sb, fsstat.buf_pinned);
|
|
sb_puts(sb, "\nbuf_pinned_peak "); sb_putu(sb, fsstat.buf_pinned_peak);
|
|
sb_puts(sb, " / "); sb_putu(sb, NBUF);
|
|
|
|
// The SchFS bracket. `locked 1` with `owner 0` is the wedge: the volume lock
|
|
// is held by something that is not in a transaction, so nothing will ever
|
|
// release it and every later filesystem call sleeps in acquiresleep.
|
|
for (int i = 0; i < SCHFS_VOL_SLOTS; i++) {
|
|
int locked, owner, depth;
|
|
schfs_lockinfo(i, &locked, &owner, &depth);
|
|
if (locked < 0)
|
|
continue;
|
|
sb_puts(sb, "\nschfs"); sb_putu(sb, (uint64)i);
|
|
sb_puts(sb, " locked "); sb_putu(sb, (uint64)locked);
|
|
sb_puts(sb, " tx_owner_pid "); sb_putu(sb, (uint64)owner);
|
|
sb_puts(sb, " tx_depth "); sb_putu(sb, (uint64)depth);
|
|
}
|
|
sb_puts(sb, "\n");
|
|
}
|
|
|
|
static void
|
|
gen_filesystems(struct sbuf *sb)
|
|
{
|
|
sb_puts(sb, "\tschfs\n");
|
|
sb_puts(sb, "nodev\tproc\n");
|
|
sb_puts(sb, "nodev\tdevtmpfs\n");
|
|
}
|
|
|
|
// --- dispatch --------------------------------------------------------------
|
|
|
|
#define PROC_MEMINFO 1
|
|
#define PROC_UPTIME 2
|
|
#define PROC_CPUINFO 3
|
|
#define PROC_VERSION 4
|
|
#define PROC_STAT 5
|
|
#define PROC_LOADAVG 6
|
|
#define PROC_MOUNTS 7
|
|
#define PROC_FILESYSTEMS 8
|
|
#define PROC_SCHFS 9
|
|
#define PROC_SYSRQ 10
|
|
#define PROC_PCI 11
|
|
#define PROC_INTERRUPTS 12
|
|
#define PROC_DEVICES 13
|
|
#define PROC_FSSTAT 14
|
|
#define PROC_NETIF 15
|
|
|
|
// Render global file `id` (a PROC_* constant) into `buf`. Returns its length.
|
|
//
|
|
// Split out of procread() so the /proc *filesystem* (kernel/fs/procfs.c) can
|
|
// use the same generators: that file owns the namespace, this one owns the
|
|
// content, and they have no other business with each other.
|
|
int
|
|
proc_gen_global(int id, char *buf, int cap)
|
|
{
|
|
struct sbuf sb = { buf, 0, cap };
|
|
|
|
switch (id) {
|
|
case PROC_MEMINFO: gen_meminfo(&sb); break;
|
|
case PROC_UPTIME: gen_uptime(&sb); break;
|
|
case PROC_CPUINFO: gen_cpuinfo(&sb); break;
|
|
case PROC_VERSION: gen_version(&sb); break;
|
|
case PROC_STAT: gen_stat(&sb); break;
|
|
case PROC_LOADAVG: gen_loadavg(&sb); break;
|
|
case PROC_MOUNTS: gen_mounts(&sb); break;
|
|
case PROC_FSSTAT: gen_fsstat(&sb); break;
|
|
case PROC_FILESYSTEMS: gen_filesystems(&sb); break;
|
|
// SchFS renders straight into the buffer rather than through sbuf, because
|
|
// it lives in kernel/fs/schfs and must not depend on this file's helpers.
|
|
case PROC_SCHFS: sb.len = schfs_kinfo(buf, PROC_BUFSZ); break;
|
|
// Same arrangement as SchFS above: pci.c renders its own lines so that it
|
|
// does not depend on this file's helpers.
|
|
case PROC_PCI: sb.len = pci_info(buf, PROC_BUFSZ); break;
|
|
// Likewise irq.c: it owns the table, so it renders it.
|
|
case PROC_INTERRUPTS: sb.len = irq_info(buf, PROC_BUFSZ); break;
|
|
// And likewise net.c, which is the only file here allowed to include lwIP's
|
|
// headers -- reaching into the stack from this one would put them on every
|
|
// kernel file's include path.
|
|
case PROC_NETIF: sb.len = net_info(buf, PROC_BUFSZ); break;
|
|
case PROC_DEVICES: gen_devices(&sb); break;
|
|
default: return 0;
|
|
}
|
|
return sb.len;
|
|
}
|
|
|
|
int
|
|
procread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
char buf[PROC_BUFSZ];
|
|
int len = proc_gen_global(ip->minor, buf, PROC_BUFSZ);
|
|
|
|
if (len <= 0 || (int)off >= len)
|
|
return 0; // EOF
|
|
int avail = len - (int)off;
|
|
if (n > avail)
|
|
n = avail;
|
|
memmove(dst, buf + off, n);
|
|
return n;
|
|
}
|
|
|
|
// /proc is read-only, with one deliberate exception.
|
|
//
|
|
// /proc/sysrq is the same idea as Linux's sysrq-trigger: a way to make the
|
|
// kernel do something drastic on purpose. Right now the only command is 'c',
|
|
// crash — which exists because the crash screen is otherwise the one part of
|
|
// the system that can only be tested by having a bug. A screen you cannot look
|
|
// at until something goes wrong is a screen nobody has ever actually read.
|
|
// One sysrq command byte. Shared by the device path (procwrite, below) and
|
|
// the filesystem path (kernel/fs/procfs.c), so the two cannot drift.
|
|
static void
|
|
proc_sysrq_cmd(char cmd)
|
|
{
|
|
switch (cmd) {
|
|
case 'c':
|
|
panic("sysrq: crash requested from /proc/sysrq");
|
|
case 'u':
|
|
xhci_dump();
|
|
break;
|
|
case 's':
|
|
usb_storage_dump();
|
|
break;
|
|
case 'd':
|
|
display_selftest();
|
|
break;
|
|
case 'n':
|
|
netdev_selftest();
|
|
break;
|
|
case 't':
|
|
text_selftest();
|
|
break;
|
|
default:
|
|
cprintf("sysrq: unknown command '%c' "
|
|
"(known: c = crash, u = dump USB, s = read the USB disk, "
|
|
"d = display self-test)\n",
|
|
cmd);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// `user_src` says whether `src` is a user address that still needs copying in;
|
|
// the filesystem hands one over, the device path has already copied.
|
|
int
|
|
proc_write_sysrq(int user_src, uint64 src, uint n)
|
|
{
|
|
char c;
|
|
|
|
if (n < 1)
|
|
return -1;
|
|
if (user_src) {
|
|
if (copy_from_user(&c, src, 1) < 0)
|
|
return -EFAULT;
|
|
} else {
|
|
c = *(char *)src;
|
|
}
|
|
proc_sysrq_cmd(c);
|
|
return (int)n;
|
|
}
|
|
|
|
// The character-device path: /proc/sysrq reached through the old mknod'd node
|
|
// rather than through the /proc filesystem. `src` is already a kernel buffer
|
|
// here, which is why it passes user_src = 0.
|
|
int
|
|
procwrite(struct inode *ip, uint64 off, char *src, int n)
|
|
{
|
|
(void)off;
|
|
|
|
if (ip->minor != PROC_SYSRQ)
|
|
return -1;
|
|
return proc_write_sysrq(0, (uint64)src, (uint)n);
|
|
}
|