Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
219 lines
7.4 KiB
C
219 lines
7.4 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/spinlock.h"
|
|
#include "core/sleeplock.h"
|
|
#include "fs/fs.h"
|
|
#include "fs/file.h"
|
|
#include "arch/x86.h"
|
|
#include "uapi/errno.h"
|
|
|
|
extern int consoleread(struct inode*, uint64, char*, int);
|
|
extern int consolewrite(struct inode*, uint64, char*, int);
|
|
extern int mouseread(struct inode*, uint64, char*, int);
|
|
|
|
// --- /dev/random and /dev/urandom -------------------------------------------
|
|
//
|
|
// This was a linear congruential generator seeded with a constant, which means
|
|
// **every boot produced the same bytes in the same order**. Not weak: the same.
|
|
// A program asking for a key, a nonce, a temporary filename or a hash seed got
|
|
// one it shared with every other SchOS machine that had ever run.
|
|
//
|
|
// What is here now is still not a CSPRNG and does not pretend to be. It is an
|
|
// LCG that is *seeded* from something real and re-stirred from the cycle
|
|
// counter on every read, which fixes the one property that made the old one
|
|
// dangerous rather than merely poor. See "What it does not do" in
|
|
// docs/architecture/devices.md.
|
|
static uint64 rseed;
|
|
static int rseed_ready;
|
|
static void rseed_init(void);
|
|
|
|
// RDRAND, if this CPU has it. CPUID leaf 1, ECX bit 30.
|
|
//
|
|
// Checked rather than assumed: the instruction faults with #UD on a CPU that
|
|
// does not implement it, and "the random device is a kernel panic" is a worse
|
|
// failure than a weak seed.
|
|
static int
|
|
rdrand64(uint64 *out)
|
|
{
|
|
uint32 a, b, c, d;
|
|
unsigned char ok = 0;
|
|
uint64 v = 0;
|
|
|
|
cpuid_leaf(1, &a, &b, &c, &d);
|
|
if (!(c & (1u << 30)))
|
|
return 0;
|
|
|
|
// Ten tries, per Intel's own guidance: the instruction is allowed to fail
|
|
// when its entropy pool is momentarily empty, and it says so in CF.
|
|
for (int i = 0; i < 10; i++) {
|
|
asm volatile("rdrand %0; setc %1" : "=r"(v), "=qm"(ok) :: "cc");
|
|
if (ok) { *out = v; return 1; }
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Seeded at boot rather than on the first read.
|
|
//
|
|
// Lazy would work, but it puts the one line that says where the entropy came
|
|
// from in the middle of whatever the machine was doing hours later, and it
|
|
// makes the first reader pay for the CPUID. Called from main(); the lazy path
|
|
// stays as a belt-and-braces for anything that reads before init runs.
|
|
void
|
|
random_init(void)
|
|
{
|
|
if (!rseed_ready)
|
|
rseed_init();
|
|
}
|
|
|
|
static void
|
|
rseed_init(void)
|
|
{
|
|
uint64 v;
|
|
|
|
rseed_ready = 1;
|
|
if (rdrand64(&v)) {
|
|
rseed = v;
|
|
kinfo("random: seeded from RDRAND\n");
|
|
return;
|
|
}
|
|
|
|
// No RDRAND. The cycle counter is the only thing here that is different on
|
|
// every boot and not written down anywhere -- it is not entropy in any
|
|
// serious sense, but it is not a constant either, which was the whole
|
|
// problem. Mixed with the wall clock so that two machines starting at the
|
|
// same instant do not agree.
|
|
rseed = rdtsc();
|
|
rseed ^= boot_epoch * 0x9E3779B97F4A7C15ULL;
|
|
rseed ^= (uint64)(uintptr_t)&rseed;
|
|
if (rseed == 0)
|
|
rseed = 0x9E3779B97F4A7C15ULL;
|
|
kinfo("random: no RDRAND; seeded from the cycle counter and the clock\n");
|
|
}
|
|
|
|
int
|
|
nullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
nullread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
zeroread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
memset(dst, 0, n);
|
|
return n;
|
|
}
|
|
|
|
int
|
|
randomread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
if(!rseed_ready)
|
|
rseed_init();
|
|
|
|
for(int i = 0; i < n; i++){
|
|
// Re-stirred from the cycle counter every byte, so two reads a moment
|
|
// apart do not continue one sequence -- and so that a process cannot
|
|
// predict the next caller's bytes from its own.
|
|
rseed ^= rdtsc();
|
|
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
|
|
dst[i] = (rseed >> 33) & 0xFF;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// /dev/full — reads as an endless run of zero bytes, every write fails with
|
|
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
|
|
int
|
|
fullwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return -ENOSPC;
|
|
}
|
|
|
|
int
|
|
mousewrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
return n;
|
|
}
|
|
|
|
int
|
|
memread(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consoleread(ip, off, dst, n);
|
|
case 2: return nullread(ip, off, dst, n);
|
|
case 3: return consoleread(ip, off, dst, n);
|
|
/* 10 = /dev/tty, 11..16 = /dev/tty1..tty6. consoleread resolves the
|
|
minor to a terminal; see vt_for_minor(). */
|
|
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
|
return consoleread(ip, off, dst, n);
|
|
case 5: return zeroread(ip, off, dst, n);
|
|
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
|
|
case 8:
|
|
case 9: return randomread(ip, off, dst, n);
|
|
// Not "operation not permitted", which is what -1 means once it reaches
|
|
// userspace. There is no device on this minor.
|
|
default: return -ENODEV;
|
|
}
|
|
}
|
|
|
|
int
|
|
memwrite(struct inode *ip, uint64 off, char *dst, int n)
|
|
{
|
|
switch(ip->minor) {
|
|
case 1: return consolewrite(ip, off, dst, n);
|
|
case 10: case 11: case 12: case 13: case 14: case 15: case 16:
|
|
return consolewrite(ip, off, dst, n);
|
|
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
|
|
case 3: // /dev/keyboard: discarded
|
|
case 2:
|
|
case 5:
|
|
case 8:
|
|
case 9: return nullwrite(ip, off, dst, n);
|
|
default: return -ENODEV;
|
|
}
|
|
}
|
|
|
|
struct devsw devsw[] = {
|
|
[0] = { 0, 0 },
|
|
[1] = { memread, memwrite },
|
|
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
|
|
// { nullread, nullwrite }, so writing pixels to it succeeded and did
|
|
// nothing.
|
|
// The close hook gives the text console its screen back when whatever was
|
|
// drawing on the framebuffer exits — see fbclose().
|
|
[2] = { fbread, fbwrite, 0, fbclose },
|
|
[3] = { mouseread, mousewrite },
|
|
[4] = { 0, 0 },
|
|
// /dev/tty (major 5) — alias for the console
|
|
[5] = { memread, memwrite },
|
|
// /proc status files (major 6) — see kernel/dev/procfs.c
|
|
[6] = { procread, procwrite },
|
|
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
|
|
// minor is the device index, so event0 is the keyboard and event1 the mouse.
|
|
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
|
|
// /dev/hdN (major 8) — raw disks, kernel/dev/disk.c. The minor is the IDE
|
|
// device number. No `ready` hook: a disk is always ready, which is what
|
|
// fileread assumes when one is absent.
|
|
[8] = { diskread, diskwrite },
|
|
// /dev/pci (major 9) -- PCI devices reachable from userspace, for drivers
|
|
// that do not live in the kernel. Registers arrive through mmap and
|
|
// everything else through ioctl; see include/dev/pciuser.h.
|
|
[9] = { pciuser_read, pciuser_write },
|
|
// /dev/dbg (major 10) -- ask the kernel a question. Write a line, read the
|
|
// answer; see include/core/dbg.h and docs/architecture/debugging.md.
|
|
[10] = { dbg_read, dbg_write },
|
|
// /dev/netdev (major 11) -- a process, being a network interface. The
|
|
// reverse of major 9: that hands a device to a process, this lets a process
|
|
// be one. See include/net/netuser.h.
|
|
[11] = { netuser_read, netuser_write, netuser_ready },
|
|
// /dev/dsp (major 12) -- UNIX audio, kernel/dev/audio.c. Write PCM to it.
|
|
// The close hook plays out what is still queued: a program that writes a
|
|
// sound and exits expects to hear the end of it.
|
|
[12] = { dspread, dspwrite, 0, dspclose },
|
|
};
|