Initial commit: Schunix as it stands before the SchOS rename

An x86-64 operating system built from xv6, now largely rewritten: SMP,
a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF
rasteriser, and a Linux-numbered syscall ABI.

No history before this point -- the tree was never under version control.
This commit exists so the rename that follows has something to undo to.
This commit is contained in:
Nexoniarz 2026-08-09 13:47:01 +02:00
commit d0f7acf9eb
275 changed files with 51417 additions and 0 deletions

810
kernel/core/console.c Normal file
View file

@ -0,0 +1,810 @@
#include <stdarg.h>
#include "types.h"
#include "defs.h"
#include "theme.h"
#include "irq.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "posix.h"
#include "x86.h"
void consputc(int);
// Set by panic() once the crash screen has been painted: after that, consputc
// spins instead of writing, so nothing else can scribble over the report.
int panicked = 0;
// When set, console output goes to the serial line only and the framebuffer
// is left alone.
//
// **Starts set, on purpose.** The display is the product; the serial line is
// the diagnostics. A machine that boots by printing "pci: 6 devices" and
// "vga: display controller 1234:1111, aperture fd000000" to the screen the
// user is looking at is showing them a developer's log, and that single fact
// is most of what makes an OS look like a hobby project.
//
// Nothing is lost: every one of those lines still goes out the UART, which is
// where tools/bootcheck.ps1 reads them from and where you want them when
// something goes wrong. console_takeover() clears this once userspace is up
// and the display genuinely belongs to the console. The F8 verbose boot never
// sets it, so "show me everything" still works.
int cons_serial_only = 1;
extern void vga_screen_reset(void);
// Text colour used when drawing to the framebuffer. Changed by SGR escapes
// (see sgr() below); reset to the default by ESC[0m and ESC c.
//
// This was initialised to 7, a leftover from when it was a VGA text-mode
// attribute byte. vga_putc takes a 32-bit RGB value, so 7 meant "very dark
// blue" -- it looked black against the cream background purely by accident.
static uint32 cons_fg = SCH_FG;
static struct {
struct spinlock lock;
int locking;
} cons;
static char digits[] = "0123456789abcdef";
static void
print_int(int64 xx, int base, int sign)
{
char buf[64];
int i;
uint64 x;
if(sign && (sign = (xx < 0)))
x = -xx;
else
x = xx;
i = 0;
do {
buf[i++] = digits[x % base];
} while((x /= base) != 0);
if(sign)
buf[i++] = '-';
while(--i >= 0)
consputc(buf[i]);
}
void
cprintf(char *fmt, ...)
{
va_list ap;
int i, c, locking;
char *s;
va_start(ap, fmt);
locking = cons.locking;
if (locking)
acquire(&cons.lock);
if (fmt == 0)
panic("null fmt");
for (i = 0; (c = fmt[i] & 0xff) != 0; i++) {
if (c != '%') {
consputc(c);
continue;
}
c = fmt[++i] & 0xff;
if (c == 0)
break;
switch(c) {
case 'd':
print_int(va_arg(ap, int), 10, 1);
break;
case 'u':
print_int(va_arg(ap, uint32), 10, 0);
break;
case 'l':
print_int(va_arg(ap, int64), 10, 1);
break;
case 'x':
print_int(va_arg(ap, uint32), 16, 0);
break;
case 'p':
print_int(va_arg(ap, uint64), 16, 0);
break;
case 'b':
print_int(va_arg(ap, uint32), 2, 0);
break;
case 's':
if ((s = va_arg(ap, char*)) == 0)
s = "(null)";
while (*s)
consputc(*(s++));
break;
case '%':
consputc('%');
break;
default:
consputc('%');
consputc(c);
break;
}
}
if (locking)
release(&cons.lock);
}
// Hand the console over to panic().
//
// Two things change. The lock stops being taken, so a panic can report itself
// from anywhere — including from inside a routine that already holds it. And
// output stops going to the framebuffer, because from here on the framebuffer
// belongs to the crash screen: without this the report would scroll straight
// over the panel that is trying to display it.
void
cons_panic_mode(void)
{
cons.locking = 0;
cons_serial_only = 1;
}
// Hand the display over to the text console.
//
// Called once, when userspace is far enough along that the screen should stop
// being a boot splash and start being a terminal. Until this runs the kernel
// has been talking to the serial line only (see cons_serial_only), so this is
// the moment the framebuffer changes owner -- which is why it clears first
// rather than scrolling the shell prompt in underneath the splash.
void
console_takeover(void)
{
if (!cons_serial_only)
return; // verbose boot: the console never left
cons_serial_only = 0;
vga_screen_reset();
}
// Foreground process group of the console, i.e. the controlling terminal.
// tcgetpgrp()/tcsetpgrp() read and write this through TIOCGPGRP/TIOCSPGRP in
// sys_fs.c. Initialised to 1 -- init's process group -- so the first shell,
// which inherits that group, already sees itself as the foreground job and
// does not immediately stop itself with SIGTTIN.
int tty_fg_pgrp = 1;
#define BACKSPACE 0x100
// ---------------------------------------------------------------------------
// Output: an ANSI escape parser, and UTF-8
//
// The console used to print every byte it was given, which meant every escape
// sequence any program emitted appeared on screen as literal text -- `clear`
// drew "^[[2J^[[H" and left the screen exactly as it found it. Colour, cursor
// addressing, and anything full-screen were all equally impossible.
//
// The serial line does NOT go through any of this. On the other end of it is
// a real terminal emulator that already understands ANSI far better than this
// ever will, so escapes are passed through to it verbatim and interpreted
// only for the framebuffer. Two consumers, two right answers.
// ---------------------------------------------------------------------------
// The sixteen ANSI colours as RGB, for a linear framebuffer.
//
// consoleinit() used to program these into the VGA DAC with set_vga_palette(),
// which does nothing at all in a 32bpp linear mode -- there is no palette to
// index. The colours had to live somewhere real, and this is it.
static const uint32 ansi_rgb[16] = {
0x000000, 0xAA0000, 0x00AA00, 0xAA5500, // black red green yellow(brown)
0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, // blue magenta cyan white
0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, // bright versions
0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF,
};
#define CONS_DEFAULT_FG SCH_FG // see include/theme.h
#define CONS_DEFAULT_BG SCH_BG
static uint32 cons_bg = CONS_DEFAULT_BG;
static int cons_bold = 0;
// Escape parser state.
enum { E_NORMAL, E_ESC, E_CSI };
static int esc_state = E_NORMAL;
static int esc_parm[8];
static int esc_nparm;
static int esc_private; // saw '?' after CSI
static int saved_col, saved_row;
// UTF-8 decoder state. A codepoint arrives as up to four bytes and has to be
// reassembled before it can be turned into a glyph.
static uint32 u8_cp;
static int u8_need;
// ---------------------------------------------------------------------------
// Unicode -> glyph
//
// The font's upper half is CP437, not Latin-1, so a codepoint cannot be used
// as an index. It looks like it can -- font.h's own comments say so, labelling
// each glyph with the Unicode character of the same number -- but the bitmaps
// disagree: index 0xE9 draws a capital theta, and rendering U+00E9 there turns
// "cafe" into "caf" followed by a theta. Every accented character came out as
// an unrelated symbol, consistently enough to look almost deliberate.
//
// So: a table. Entry i covers codepoint 0x80 + i, and 0 means "this font has
// no glyph for that". The C1 range (0x80-0x9F) has no printable characters at
// all, hence the leading run of zeros.
// ---------------------------------------------------------------------------
static const uchar latin1_to_cp437[128] = {
/* 0x80-0x9F: C1 controls, nothing to draw */
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
/* 0xA0 */ 0x20, 0xAD, 0x9B, 0x9C, 0, 0x9D, 0, 0x15,
/* 0xA8 */ 0, 0, 0xA6, 0xAE, 0xAA, 0x2D, 0, 0,
/* 0xB0 */ 0xF8, 0xF1, 0xFD, 0, 0, 0xE6, 0x14, 0xFA,
/* 0xB8 */ 0, 0, 0xA7, 0xAF, 0xAC, 0xAB, 0, 0xA8,
/* 0xC0 */ 0, 0, 0, 0, 0x8E, 0x8F, 0x92, 0x80,
/* 0xC8 */ 0, 0x90, 0, 0, 0, 0, 0, 0,
/* 0xD0 */ 0, 0xA5, 0, 0, 0, 0, 0x99, 0,
/* 0xD8 */ 0, 0, 0, 0, 0x9A, 0, 0, 0xE1,
/* 0xE0 */ 0x85, 0xA0, 0x83, 0, 0x84, 0x86, 0x91, 0x87,
/* 0xE8 */ 0x8A, 0x82, 0x88, 0x89, 0x8D, 0xA1, 0x8C, 0x8B,
/* 0xF0 */ 0, 0xA4, 0x95, 0xA2, 0x93, 0, 0x94, 0xF6,
/* 0xF8 */ 0, 0x97, 0xA3, 0x96, 0x81, 0, 0, 0x98,
};
// The handful of characters above U+00FF that CP437 does have. Box drawing
// and block elements matter because they are what every text-mode program
// draws frames and progress bars out of.
struct cp_pair { uint32 cp; uchar glyph; };
static const struct cp_pair cp437_extra[] = {
{ 0x2022, 0x07 }, /* bullet */
{ 0x2500, 0xC4 }, { 0x2502, 0xB3 }, /* light horizontal/vertical */
{ 0x250C, 0xDA }, { 0x2510, 0xBF },
{ 0x2514, 0xC0 }, { 0x2518, 0xD9 },
{ 0x251C, 0xC3 }, { 0x2524, 0xB4 },
{ 0x252C, 0xC2 }, { 0x2534, 0xC1 }, { 0x253C, 0xC5 },
{ 0x2550, 0xCD }, { 0x2551, 0xBA }, /* double */
{ 0x2554, 0xC9 }, { 0x2557, 0xBB },
{ 0x255A, 0xC8 }, { 0x255D, 0xBC },
{ 0x2580, 0xDF }, { 0x2584, 0xDC }, { 0x2588, 0xDB },
{ 0x258C, 0xDD }, { 0x2590, 0xDE },
{ 0x2591, 0xB0 }, { 0x2592, 0xB1 }, { 0x2593, 0xB2 },
{ 0x25A0, 0xFE }, /* black square */
{ 0x221A, 0xFB }, { 0x221E, 0xEC }, /* root, infinity */
{ 0x2260, 0, }, { 0x2264, 0xF3 }, { 0x2265, 0xF2 },
{ 0x2248, 0xF7 }, { 0x2261, 0xF0 },
};
// Returns the glyph to draw for a codepoint, or 0 if there is none.
static int
glyph_for(uint32 cp)
{
if (cp < 0x80)
return (int)cp; // ASCII is CP437's lower half
if (cp < 0x100)
return latin1_to_cp437[cp - 0x80];
for (unsigned i = 0; i < NELEM(cp437_extra); i++)
if (cp437_extra[i].cp == cp)
return cp437_extra[i].glyph;
return 0;
}
static void
sgr(void)
{
for (int i = 0; i < esc_nparm; i++) {
int p = esc_parm[i];
if (p == 0) {
cons_fg = CONS_DEFAULT_FG;
cons_bg = CONS_DEFAULT_BG;
cons_bold = 0;
} else if (p == 1) {
cons_bold = 1;
// Re-apply brightness to a colour already chosen.
for (int j = 0; j < 8; j++)
if (cons_fg == ansi_rgb[j]) { cons_fg = ansi_rgb[j + 8]; break; }
} else if (p == 22) {
cons_bold = 0;
} else if (p >= 30 && p <= 37) {
cons_fg = ansi_rgb[(p - 30) + (cons_bold ? 8 : 0)];
} else if (p == 39) {
cons_fg = CONS_DEFAULT_FG;
} else if (p >= 40 && p <= 47) {
cons_bg = ansi_rgb[p - 40];
} else if (p == 49) {
cons_bg = CONS_DEFAULT_BG;
} else if (p >= 90 && p <= 97) {
cons_fg = ansi_rgb[(p - 90) + 8];
} else if (p >= 100 && p <= 107) {
cons_bg = ansi_rgb[(p - 100) + 8];
}
}
vga_set_bg(cons_bg);
}
// One final byte of a CSI sequence, with esc_parm[] already filled in.
static void
csi_dispatch(int final)
{
int col, row;
int n = esc_nparm > 0 ? esc_parm[0] : 0;
int n1 = n ? n : 1; // most sequences default to 1
vga_cursor_get(&col, &row);
switch (final) {
case 'A': vga_cursor_set(col, row - n1); break;
case 'B': vga_cursor_set(col, row + n1); break;
case 'C': vga_cursor_set(col + n1, row); break;
case 'D': vga_cursor_set(col - n1, row); break;
case 'E': vga_cursor_set(0, row + n1); break;
case 'F': vga_cursor_set(0, row - n1); break;
case 'G': vga_cursor_set(n1 - 1, row); break;
case 'd': vga_cursor_set(col, n1 - 1); break;
case 'H': case 'f': {
int r = esc_nparm > 0 && esc_parm[0] ? esc_parm[0] : 1;
int c = esc_nparm > 1 && esc_parm[1] ? esc_parm[1] : 1;
vga_cursor_set(c - 1, r - 1); // ANSI counts from 1
break;
}
case 'J': vga_erase_display(n); break;
case 'K': vga_erase_line(n); break;
case 'm': sgr(); break;
case 's': saved_col = col; saved_row = row; break;
case 'u': vga_cursor_set(saved_col, saved_row); break;
// Cursor visibility (DECTCEM) and the other private modes. There is no
// hardware cursor being drawn, so there is nothing to show or hide --
// swallowing them silently is the correct behaviour, and far better than
// printing "?25l" into the middle of someone's output.
case 'h': case 'l': break;
default: break; // unknown: drop it, do not print it
}
}
// Feed one output byte to the framebuffer side. Returns nothing; the serial
// side has already had the raw byte.
static void
vga_feed(int c)
{
switch (esc_state) {
case E_ESC:
if (c == '[') {
esc_state = E_CSI;
esc_nparm = 0;
esc_private = 0;
for (int i = 0; i < 8; i++) esc_parm[i] = 0;
return;
}
esc_state = E_NORMAL;
if (c == 'c') { // RIS -- full reset
cons_fg = CONS_DEFAULT_FG;
cons_bg = CONS_DEFAULT_BG;
vga_screen_reset();
}
return; // other two-byte escapes: ignored
case E_CSI:
if (c == '?') { esc_private = 1; return; }
if (c >= '0' && c <= '9') {
if (esc_nparm == 0) esc_nparm = 1;
if (esc_nparm <= 8)
esc_parm[esc_nparm - 1] = esc_parm[esc_nparm - 1] * 10 + (c - '0');
return;
}
if (c == ';') {
if (esc_nparm < 8) esc_nparm++;
return;
}
esc_state = E_NORMAL;
if (!esc_private)
csi_dispatch(c);
return;
}
// E_NORMAL
if (c == '\x1b') { esc_state = E_ESC; return; }
// UTF-8 continuation. A codepoint the font cannot draw becomes '?' -- the
// one thing it must not become is a *different* character, which is exactly
// what indexing the font by codepoint did.
if (u8_need > 0) {
if ((c & 0xC0) != 0x80) { // malformed: abandon the sequence
u8_need = 0;
vga_putc('?', cons_fg);
return;
}
u8_cp = (u8_cp << 6) | (c & 0x3F);
if (--u8_need > 0)
return;
int g = glyph_for(u8_cp);
vga_putc(g ? g : '?', cons_fg);
return;
}
if (c >= 0xF0) { u8_cp = c & 0x07; u8_need = 3; return; }
else if (c >= 0xE0) { u8_cp = c & 0x0F; u8_need = 2; return; }
else if (c >= 0xC0) { u8_cp = c & 0x1F; u8_need = 1; return; }
else if (c >= 0x80) { vga_putc('?', cons_fg); return; } // stray continuation
vga_putc(c, cons_fg);
}
void
consputc(int c)
{
if (panicked) {
cli();
for(;;) hlt();
}
if (c == BACKSPACE) {
uartputc('\b'); uartputc(' '); uartputc('\b');
if (!cons_serial_only)
vga_putc(c, cons_fg);
return;
}
uartputc(c);
if (!cons_serial_only)
vga_feed(c & 0xff);
}
#define INPUT_BUF 256
struct {
struct spinlock lock;
char buf[INPUT_BUF];
uint r;
uint w;
uint e;
uint abort_read;
// Set by EOF (Ctrl-D) on an empty line. Canonical-mode end of input is not
// a character -- it is the *absence* of one -- so it cannot be signalled by
// putting something in the buffer. The old code pushed a literal ^D in and
// had consoleread() pick it back out, which meant a ^D typed mid-line was
// indistinguishable from one typed alone.
uint eof;
} input;
#define C(x) ((x)-'@')
// ---------------------------------------------------------------------------
// The line discipline
//
// TCGETS used to return a hardcoded set of flags and TCSETS threw its argument
// away, so the terminal was permanently in one mode: line-at-a-time, always
// echoing. Anything wanting a keypress at a time -- a pager, an editor, a
// shell doing its own line editing, which is most of what zsh is -- had no way
// to ask for one. This is the real thing: a struct programs can write, that
// changes what the kernel actually does.
// ---------------------------------------------------------------------------
static struct termios tio = {
.c_iflag = ICRNL,
.c_oflag = OPOST | ONLCR,
.c_cflag = CS8 | CREAD | CLOCAL,
.c_lflag = ISIG | ICANON | ECHO | ECHOE,
.c_cc = {
[VINTR] = C('C'), [VQUIT] = C('\\'), [VERASE] = '\x7f', [VKILL] = C('U'),
[VEOF] = C('D'), [VSUSP] = C('Z'), [VWERASE] = C('W'), [VMIN] = 1,
},
.c_ispeed = 15, .c_ospeed = 15,
};
void
cons_get_termios(struct termios *out)
{
acquire(&input.lock);
*out = tio;
release(&input.lock);
}
void
cons_set_termios(const struct termios *in)
{
acquire(&input.lock);
tio = *in;
// Switching out of canonical mode has to hand over whatever has been typed
// but not yet submitted, or a shell that turns off ICANON mid-line silently
// eats the characters already in the edit buffer.
if (!(tio.c_lflag & ICANON))
input.w = input.e;
release(&input.lock);
wakeup(&input.r);
}
static void
echo(int c)
{
if (c == '\n' || c == '\r') {
if ((tio.c_lflag & ECHO) || (tio.c_lflag & ECHONL))
consputc('\n');
return;
}
if (!(tio.c_lflag & ECHO))
return;
// Control characters echo as ^X, the way every other terminal shows them.
if (c < 32 && c != '\t') {
consputc('^');
consputc(c + '@');
} else {
consputc(c);
}
}
// Rub out the last character of the current line.
static void
erase_one(void)
{
if (input.e == input.w)
return;
input.e--;
if (tio.c_lflag & ECHOE)
consputc(BACKSPACE);
}
void
consoleintr(int (*getc)(void))
{
int c;
acquire(&input.lock);
while((c = getc()) >= 0){
if (c == 0)
continue;
// Input mapping, before anything looks at the character.
if (tio.c_iflag & ISTRIP) c &= 0x7f;
if (c == '\r') {
if (tio.c_iflag & IGNCR) continue;
if (tio.c_iflag & ICRNL) c = '\n';
} else if (c == '\n' && (tio.c_iflag & INLCR)) {
c = '\r';
}
// Signals. These are the characters that must not reach the reader.
//
// Ctrl-C used to do `myproc()->killed = 1` -- and `proc` in an interrupt
// handler is whatever happened to be running on this CPU when the key was
// pressed, which is almost never the program the user is looking at. It
// also set a kill flag rather than raising a signal, so nothing could
// catch it and the shell never learned the job had died. Both wrong;
// both fixed by sending a real signal to the foreground process group,
// which the kernel has been tracking in tty_fg_pgrp all along.
if (tio.c_lflag & ISIG) {
int sig = 0;
if (c == tio.c_cc[VINTR]) sig = SIGINT;
else if (c == tio.c_cc[VQUIT]) sig = SIGQUIT;
else if (c == tio.c_cc[VSUSP]) sig = SIGTSTP;
if (sig) {
echo(c);
input.e = input.w = input.r; // discard the partial line
input.abort_read = 1;
release(&input.lock);
sigsend_pgrp(tty_fg_pgrp, sig);
acquire(&input.lock);
wakeup(&input.r);
continue;
}
}
// Ctrl-P: dump the process table. Not POSIX, but it is the one debugging
// aid that works when everything else has stopped responding.
if (c == C('P')) {
procdump();
continue;
}
if (tio.c_lflag & ICANON) {
if (c == tio.c_cc[VERASE] || c == C('H')) {
erase_one();
continue;
}
if (c == tio.c_cc[VKILL]) {
while (input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] != '\n')
erase_one();
continue;
}
if (c == tio.c_cc[VWERASE]) {
while (input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] == ' ')
erase_one();
while (input.e != input.w &&
input.buf[(input.e-1) % INPUT_BUF] != ' ')
erase_one();
continue;
}
if (c == tio.c_cc[VEOF]) {
if (input.e == input.w)
input.eof = 1; // empty line: end of input
input.w = input.e; // otherwise just submit what is there
wakeup(&input.r);
continue;
}
}
if (input.e - input.r >= INPUT_BUF)
continue; // buffer full: drop it
// Everything else goes in, including bytes above 127 -- those are UTF-8
// continuation bytes, and the old `c >= 32 && c <= 126` test silently
// refused every non-ASCII character anyone tried to type.
input.buf[input.e++ % INPUT_BUF] = c;
echo(c);
// Canonical mode hands over a line at a time; raw mode hands over
// everything as it arrives.
if (!(tio.c_lflag & ICANON) ||
c == '\n' || input.e == input.r + INPUT_BUF) {
input.w = input.e;
wakeup(&input.r);
}
}
release(&input.lock);
}
int
consoleread(struct inode *ip, uint off, char *dst, int n)
{
uint target;
int c;
static uint32 prng_seed = 0x9A7B3C1D;
if (ip->minor == 2) {
return 0;
}
if (ip->minor == 4) {
for (int i = 0; i < n; i++) {
prng_seed ^= prng_seed << 13;
prng_seed ^= prng_seed >> 17;
prng_seed ^= prng_seed << 5;
dst[i] = prng_seed & 0xFF;
}
return n;
}
iunlock(ip);
target = n;
acquire(&input.lock);
while(n > 0){
while(input.r == input.w){
if (input.eof) {
input.eof = 0;
release(&input.lock);
ilock(ip);
return 0; // end of input
}
if (myproc()->killed || input.abort_read) {
input.abort_read = 0;
release(&input.lock);
ilock(ip);
return -1;
}
sleep(&input.r, &input.lock);
}
c = input.buf[input.r++ % INPUT_BUF];
*dst++ = c;
--n;
// Canonical mode returns a line at a time. Raw mode returns whatever has
// arrived: a program that asked for a keypress must not be made to wait
// for a newline that is never coming.
if (tio.c_lflag & ICANON) {
if (c == '\n')
break;
} else {
if (target - n >= (uint)(tio.c_cc[VMIN] ? tio.c_cc[VMIN] : 1))
break;
}
}
release(&input.lock);
ilock(ip);
return target - n;
}
// Nonzero if a completed input line is waiting — poll(2)'s POLLIN answer
// for the console tty. Mirrors the r/w cursors consoleread() blocks on.
int
console_input_ready(void)
{
int ready;
acquire(&input.lock);
ready = input.r != input.w;
release(&input.lock);
return ready;
}
int
consolewrite(struct inode *ip, uint off, char *buf, int n)
{
int i;
if (ip->minor == 2 || ip->minor == 3 || ip->minor == 4) {
return n;
}
iunlock(ip);
acquire(&cons.lock);
for(i = 0; i < n; i++)
consputc(buf[i] & 0xff);
release(&cons.lock);
ilock(ip);
return n;
}
void
disable_blink(void)
{
inb(0x3DA);
outb(0x3C0, 0x10 | 0x20);
uchar dummy = inb(0x3C1);
outb(0x3C0, dummy & ~0x08);
}
static void
set_vga_palette(uchar index, uchar r, uchar g, uchar b)
{
outb(0x3C6, 0xFF);
outb(0x3C8, index);
outb(0x3C9, r & 0x3F);
outb(0x3C9, g & 0x3F);
outb(0x3C9, b & 0x3F);
}
// The keyboard handler lives in kbd.c, but the console is what owns the
// keyboard as an input source, so this is where the line gets claimed.
static void
kbd_irq(void *arg)
{
(void)arg;
kbdintr();
}
void
consoleinit(void)
{
initlock(&cons.lock, "console");
initlock(&input.lock, "input");
memset(&input, 0, sizeof(input));
devsw[CONSOLE].write = consolewrite;
devsw[CONSOLE].read = consoleread;
cons.locking = 1;
disable_blink();
set_vga_palette(0, 0, 0, 0); // Black
set_vga_palette(1, 0, 0, 42); // Blue
set_vga_palette(2, 0, 42, 0); // Green
set_vga_palette(3, 0, 42, 42); // Cyan
set_vga_palette(4, 42, 0, 0); // Red
set_vga_palette(5, 42, 0, 42); // Magenta
set_vga_palette(6, 42, 21, 0); // Brown
set_vga_palette(7, 42, 42, 42); // Light Gray
set_vga_palette(8, 21, 21, 21); // Dark Gray
set_vga_palette(9, 21, 21, 63); // Light Blue
set_vga_palette(10, 21, 63, 21); // Light Green
set_vga_palette(11, 21, 63, 63); // Light Cyan
set_vga_palette(12, 63, 21, 21); // Light Red
set_vga_palette(13, 21, 21, 63); // Light Magenta
set_vga_palette(14, 63, 63, 21); // Yellow
set_vga_palette(15, 63, 63, 63); // White
irq_register(IRQ_KBD, kbd_irq, 0, "ps2-kbd", 0);
}

191
kernel/core/dma.c Normal file
View file

@ -0,0 +1,191 @@
// Physically contiguous memory for devices. See include/dma.h.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
#include "dma.h"
#define DMA_GRAIN 64 // one cache line; also xHCI's ring alignment
#define NEXTENT 64 // free extents we can track
struct extent {
addr_t base; // kernel virtual address
uint64 len;
};
static struct {
struct spinlock lock;
struct extent free[NEXTENT];
int n; // free extents, kept sorted by base
addr_t lo, hi;
uint64 total;
} arena;
static uint64
roundup(uint64 v, uint64 to)
{
return (v + to - 1) & ~(to - 1);
}
void
dma_init(void *vstart, void *vend)
{
initlock(&arena.lock, "dma");
arena.lo = roundup((addr_t)vstart, PGSIZE);
arena.hi = (addr_t)vend & ~(addr_t)(PGSIZE - 1);
if(arena.hi <= arena.lo)
panic("dma_init: empty arena");
arena.free[0].base = arena.lo;
arena.free[0].len = arena.hi - arena.lo;
arena.n = 1;
arena.total = arena.free[0].len;
}
// Remove free[i], shifting the tail down.
static void
drop(int i)
{
int j;
for(j = i; j + 1 < arena.n; j++)
arena.free[j] = arena.free[j+1];
arena.n--;
}
// Insert a free extent at index i, shifting the tail up. Returns 0 if the
// table is full, in which case the memory is leaked rather than corrupting
// the list -- a leak is recoverable by rebooting, a corrupt free list hands
// two drivers the same ring.
static int
insert(int i, addr_t base, uint64 len)
{
int j;
if(arena.n >= NEXTENT)
return 0;
for(j = arena.n; j > i; j--)
arena.free[j] = arena.free[j-1];
arena.free[i].base = base;
arena.free[i].len = len;
arena.n++;
return 1;
}
void *
dma_alloc(uint64 size, uint64 align)
{
int i;
addr_t start;
if(size == 0)
return 0;
if(align < DMA_GRAIN)
align = DMA_GRAIN;
if(align & (align - 1))
panic("dma_alloc: alignment not a power of two");
size = roundup(size, DMA_GRAIN);
acquire(&arena.lock);
for(i = 0; i < arena.n; i++){
addr_t base = arena.free[i].base;
uint64 len = arena.free[i].len;
uint64 lead;
start = roundup(base, align);
lead = start - base;
if(lead + size > len)
continue;
// Three cases: the request consumes the extent, sits at its head, sits
// at its tail, or sits in the middle and splits it in two.
if(lead == 0 && size == len){
drop(i);
} else if(lead == 0){
arena.free[i].base += size;
arena.free[i].len -= size;
} else if(lead + size == len){
arena.free[i].len = lead;
} else {
uint64 tail_len = len - lead - size;
arena.free[i].len = lead;
if(!insert(i + 1, start + size, tail_len)){
// No room to record the tail. Give the whole extent to the caller
// rather than lose track of where the tail was.
arena.free[i].len = len;
continue;
}
}
release(&arena.lock);
memset((void*)start, 0, size);
return (void*)start;
}
release(&arena.lock);
cprintf("dma_alloc: out of DMA memory (wanted %d bytes, %d free)\n",
(int)size, (int)dma_free_bytes());
return 0;
}
void
dma_free(void *p, uint64 size)
{
addr_t base = (addr_t)p;
int i;
if(p == 0)
return;
size = roundup(size, DMA_GRAIN);
if(base < arena.lo || base + size > arena.hi)
panic("dma_free: not DMA memory");
acquire(&arena.lock);
// Find the first extent that starts after this block, so the list stays
// sorted and neighbours end up adjacent for coalescing.
for(i = 0; i < arena.n && arena.free[i].base < base; i++)
;
if(i > 0 && arena.free[i-1].base + arena.free[i-1].len == base){
arena.free[i-1].len += size; // merge with the block before
i--;
} else if(!insert(i, base, size)){
release(&arena.lock);
cprintf("dma_free: extent table full, leaking %d bytes\n", (int)size);
return;
}
if(i + 1 < arena.n &&
arena.free[i].base + arena.free[i].len == arena.free[i+1].base){
arena.free[i].len += arena.free[i+1].len; // and with the block after
drop(i + 1);
}
release(&arena.lock);
}
addr_t
dma_phys(const void *p)
{
return V2P((addr_t)p);
}
uint64
dma_free_bytes(void)
{
uint64 n = 0;
int i;
for(i = 0; i < arena.n; i++)
n += arena.free[i].len;
return n;
}
uint64
dma_total_bytes(void)
{
return arena.total;
}

124
kernel/core/kalloc.c Normal file
View file

@ -0,0 +1,124 @@
// Physical memory allocator, intended to allocate
// memory for user processes, kernel stacks, page table pages,
// and pipe buffers. Allocates 4096-byte pages.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
void freerange(void *vstart, void *vend);
extern char end[]; // first address after kernel loaded from ELF file
struct run {
struct run *next;
};
struct {
struct spinlock lock;
int use_lock;
struct run *freelist;
} kmem;
// Catch kmem.freelist corruption at the point it's touched, rather than
// letting a bad pointer be silently returned by kalloc() and crash whatever
// code dereferences it minutes later on an unrelated call stack.
static void
check_run(struct run *r, char *where)
{
if(r == 0)
return;
if((addr_t)r % PGSIZE || (char*)r < end || V2P(r) >= PHYSTOP){
cprintf("kalloc: corrupt freelist entry %p at %s\n", r, where);
panic("kalloc: corrupt freelist");
}
}
void
kinit1(void *vstart, void *vend)
{
initlock(&kmem.lock, "kmem");
kmem.use_lock = 0;
kmem.freelist = 0; // empty
freerange(vstart, vend);
}
void
kinit2()
{
kmem.use_lock = 1;
}
void
freerange(void *vstart, void *vend)
{
char *p;
p = (char*)PGROUNDUP((addr_t)vstart);
for(; p + PGSIZE <= (char*)vend; p += PGSIZE)
kfree(p);
}
// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc(). (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(char *v)
{
struct run *r;
if((addr_t)v % PGSIZE || v < end || V2P(v) >= PHYSTOP)
panic("kfree");
// Fill with junk to catch dangling refs.
memset(v, 1, PGSIZE);
if(kmem.use_lock)
acquire(&kmem.lock);
check_run(kmem.freelist, "kfree entry");
r = (struct run*)v;
r->next = kmem.freelist;
kmem.freelist = r;
if(kmem.use_lock)
release(&kmem.lock);
}
// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
char*
kalloc(void)
{
struct run *r;
if(kmem.use_lock)
acquire(&kmem.lock);
check_run(kmem.freelist, "kalloc entry");
r = kmem.freelist;
if(r)
kmem.freelist = r->next;
if(kmem.use_lock)
release(&kmem.lock);
return (char*)r;
}
// Total free physical memory, in bytes. Walks the free list; only /proc
// readers call it, so the O(n) scan is not on any hot path.
uint64
kalloc_free_bytes(void)
{
struct run *r;
uint64 n = 0;
if(kmem.use_lock)
acquire(&kmem.lock);
for(r = kmem.freelist; r; r = r->next)
n += PGSIZE;
if(kmem.use_lock)
release(&kmem.lock);
return n;
}

231
kernel/core/main.c Normal file
View file

@ -0,0 +1,231 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
#include "irq.h"
#include "dma.h"
static void startothers(void);
static void mpmain(void) __attribute__((noreturn));
extern char end[]; // first address after kernel loaded from ELF file
// Set by stage2main.c (boot/stage2main.c, BOOT_VERBOSE_FLAG_ADDR) when the
// user picks "Boot Schunix (verbose)" from the F8 boot menu; read once here,
// before anything else, so subsystem init can log its own steps.
int boot_verbose;
// Must match BOOT_VERBOSE_FLAG_ADDR in boot/stage2main.c exactly: just past
// the VBE info block, not 0x7000 (startothers() below uses that address for
// the AP boot trampoline).
#define BOOT_VERBOSE_FLAG_ADDR (0x30000 + 0x100)
// Bootstrap processor starts running C code here.
// Allocate a real stack and switch to it, first
// doing some setup required for memory allocator to work.
int
kmain(uint32 fb_addr)
{
boot_verbose = *(uchar*)P2V(BOOT_VERBOSE_FLAG_ADDR);
uartearlyinit();
if (boot_verbose) cprintf("verbose boot: uartearlyinit done\n");
// The DMA arena is carved off the top of physical memory *before* kalloc is
// given the rest, because a device needs physically contiguous memory and
// kalloc's free list makes no such promise -- two pages from it are adjacent
// only by chance. Taking it first is the only way to be sure the region
// exists; asking for it later would mean asking kalloc for something it
// fundamentally cannot provide. See include/dma.h.
// Before anything touches PHYSTOP, which is now a variable read from the
// firmware's memory map rather than a constant asserting 224 MB exists.
e820_init();
kinit1(end, P2V(PHYSTOP - DMA_ARENA_BYTES));
if (boot_verbose) cprintf("verbose boot: kinit1 done\n");
dma_init(P2V(PHYSTOP - DMA_ARENA_BYTES), P2V(PHYSTOP));
if (boot_verbose) cprintf("verbose boot: dma_init done\n");
kvmalloc();
if (boot_verbose) cprintf("verbose boot: kvmalloc done\n");
// ACPI first, the legacy MP table only if it finds nothing. Modern firmware
// publishes a complete MADT and an MP table with one processor in it, so
// asking the older interface first means never seeing more than one CPU.
// See kernel/arch/x86_64/acpi.c.
if (!acpi_init())
mpinit();
if (boot_verbose) cprintf("verbose boot: cpu enumeration done\n");
lapicinit();
if (boot_verbose) cprintf("verbose boot: lapicinit done\n");
tvinit();
if (boot_verbose) cprintf("verbose boot: tvinit done\n");
seginit();
if (boot_verbose) cprintf("verbose boot: seginit done\n");
ioapicinit();
if (boot_verbose) cprintf("verbose boot: ioapicinit done\n");
irq_init(); // must precede any driver init that registers a handler
if (boot_verbose) cprintf("verbose boot: irq_init done\n");
// Before the keyboard and mouse, which start producing events the moment
// their handlers are registered.
inputinit();
if (boot_verbose) cprintf("verbose boot: inputinit done\n");
consoleinit();
if (boot_verbose) cprintf("verbose boot: consoleinit done\n");
uartinit();
if (boot_verbose) cprintf("verbose boot: uartinit done\n");
mouseinit();
if (boot_verbose) cprintf("verbose boot: mouseinit done\n");
vga_init(fb_addr);
if (boot_verbose) cprintf("verbose boot: vga_init done (fb=0x%x)\n", fb_addr);
// No splash drawing here on purpose. The loader owns the splash, logo and
// all, and vga_init() deliberately does not clear -- so it is still on the
// display right through to console_takeover(). Painting it again from here
// would land in the last few hundred milliseconds of boot and change nothing.
// After vga_init, because the display driver cross-checks the framebuffer
// address it finds on the bus against the one VBE reported -- and it can
// only do that once vga.c has read the VBE block.
// The devices that are simply known to be on a PC rather than enumerated by
// any bus. Registered before pci_init() so that /proc/devices lists them
// first, which puts the machine's fixed furniture above whatever happened to
// be plugged into it.
platform_register_devices();
pci_init();
if (boot_verbose) cprintf("verbose boot: pci_init done\n");
pinit();
if (boot_verbose) cprintf("verbose boot: pinit done\n");
binit();
if (boot_verbose) cprintf("verbose boot: binit done\n");
fileinit();
if (boot_verbose) cprintf("verbose boot: fileinit done\n");
sockinit();
if (boot_verbose) cprintf("verbose boot: sockinit done\n");
// After the timer is running, so boot_epoch can subtract the uptime so far,
// and before userspace, so the very first timestamp any program takes is
// already real. Reading the RTC is slow, which is exactly why it happens once
// here rather than on every gettimeofday().
time_init();
ideinit();
if (boot_verbose) cprintf("verbose boot: ideinit done\n");
ramdiskinit();
if (boot_verbose) cprintf("verbose boot: ramdiskinit done\n");
// Start the application processors.
//
// This was broken from the beginning of the project and is fixed as of
// 2026-08-08. Five distinct bugs, each hidden behind the one before it, and
// all of them invisible under QEMU because it publishes only one processor in
// the legacy MP table -- see kernel/arch/x86_64/acpi.c. VMware published the
// real count, which is why the triple fault only ever appeared there.
//
// 1. entry64high had no AP branch at all; the check was a comment saying it
// had never been written. Every AP fell into the boot processor's path,
// took the BSP's stack, and ran kmain() a second time.
// 2. seginit() called kalloc() for its per-CPU page. kalloc locks, locking
// reads `cpu`, and `cpu` is what seginit() was about to set up.
// 3. kernel_fs_base was one global for all CPUs, and trapasm.S read it on
// every entry from user mode, so both CPUs shared one per-CPU block.
// 4. `cpu` and `proc` were __thread. That means per *thread*, so the compiler
// cached the thread pointer across calls -- but %fs here is per *CPU*, and
// a kernel thread changes CPU at every sched(). See include/proc.h.
// 5. The new per-CPU slots collided with syscall_entry's scratch word at
// %fs:(8), which is the same memory as %gs:8.
//
int smp_start_aps = 1;
if (smp_start_aps) {
startothers();
if (boot_verbose) cprintf("verbose boot: startothers done\n");
} else if (ncpu > 1) {
cprintf("smp: %d cpus found, running on 1 (AP startup is broken)\n", ncpu);
}
kinit2();
if (boot_verbose) cprintf("verbose boot: kinit2 done\n");
sinit();
if (boot_verbose) cprintf("verbose boot: sinit done\n");
mpmain();
}
// Other CPUs jump here from entryother.S.
void
mpenter(void)
{
switchkvm();
seginit();
lapicinit();
mpmain();
}
// Enable SSE/FXSAVE so user binaries can use XMM registers.
static void
enable_sse(void)
{
uint64 cr0, cr4;
asm volatile("mov %%cr0, %0" : "=r"(cr0));
cr0 &= ~(1UL << 2); // clear CR0.EM (no FP emulation)
cr0 |= (1UL << 1); // set CR0.MP (monitor coprocessor)
asm volatile("mov %0, %%cr0" :: "r"(cr0));
asm volatile("mov %%cr4, %0" : "=r"(cr4));
cr4 |= (1UL << 9); // CR4.OSFXSR — OS handles FXSAVE/FXRSTOR
cr4 |= (1UL << 10); // CR4.OSXMMEXCPT — OS handles SSE exceptions
asm volatile("mov %0, %%cr4" :: "r"(cr4));
}
// Common CPU setup code.
static void
mpmain(void)
{
cprintf("cpu%d: starting\n", cpunum());
enable_sse(); // allow user XMM/SSE instructions
idtinit(); // load idt register
syscallinit(); // syscall set up
xchg(&mycpu()->started, 1); // tell startothers() we're up
scheduler(); // start running processes
}
void entry32mp(void);
// Start the non-boot (AP) processors.
static void
startothers(void)
{
extern uchar _binary_entryother_start[], _binary_entryother_size[];
uchar *code;
struct cpu *c;
char *stack;
// Write entry code to unused memory at 0x7000.
// The linker has placed the image of entryother.S in
// _binary_entryother_start.
code = P2V(0x7000);
memmove(code, _binary_entryother_start,
(addr_t)_binary_entryother_size);
for(c = cpus; c < cpus+ncpu; c++){
if(c == cpus+cpunum()) // We've started already.
continue;
// Tell entryother.S what stack to use, where to enter, and what
// pgdir to use. We cannot use kpgdir yet, because the AP processor
// is running in low memory, so we use entrypgdir for the APs too.
// The AP's per-CPU page, allocated here rather than by the AP itself.
// seginit() explains why: an AP cannot call kalloc() before %fs is set up,
// because kalloc locks and locking reads the per-CPU pointer.
if (c->local == 0) {
c->local = kalloc();
memset(c->local, 0, PGSIZE);
}
stack = kalloc();
*(uint32*)(code-4) = 0x8000; // enough stack to get us to entry64mp
*(uint32*)(code-8) = v2p(entry32mp);
*(uint64*)(code-16) = (uint64) (stack + KSTACKSIZE);
lapicstartap(c->apicid, V2P(code));
// wait for cpu to finish mpmain()
while(c->started == 0)
;
// Counted here, on the boot processor, after the AP has actually reported
// in -- not by the AP itself. This is serialised by the loop, so it needs
// no atomic, and it cannot overcount a processor that started and died.
ncpu_online++;
}
}

543
kernel/core/panic.c Normal file
View file

@ -0,0 +1,543 @@
// SPDX-License-Identifier: Apache-2.0
//
// PSOGM — the Schunix crash screen.
//
// The name is a nod to the Amiga's "Guru Meditation", but nothing else about
// this is a nod to 1985. Three things it is built around:
//
// *It does not clear the screen.* Whatever was being displayed stays there,
// dimmed and pushed back, with the report floating over it. A crash screen that
// wipes the display tells you the machine is gone; one that leaves your work
// visible underneath tells you it stopped, which is both truer and much less
// alarming.
//
// *It is not frightening.* No red, no capitals, no "FATAL". A calm sentence, a
// face that is obviously a cartoon, and then the facts. Whoever is reading this
// is already having a bad time.
//
// *It says considerably more than it needs to.* The screen is the only artefact
// of a kernel panic — there is no core dump and no log to go back to — so
// everything cheap to collect is collected: which CPU, which process, how far
// into the boot, where the kernel is loaded, and a call chain. Anything left
// out here is gone.
//
// Everything is also written to the serial console, in the same order. That is
// what tools/bootcheck.ps1 reads, and it is what you get when the crash happens
// before there is a framebuffer at all.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "x86.h"
#include "proc.h"
#include "spinlock.h"
// Purple, because the name says so, and white on it because nothing else is
// legible at every brightness a monitor might be set to. The palette lives in
// theme.h with everything else; this file used to keep its own copy under the
// same names but with different values, which meant theme.h described a crash
// screen nobody had ever seen.
#include "theme.h"
#include "qr.h"
extern uint ticks;
extern int panicked;
/* ------------------------------------------------------------------ */
/* Small formatting helpers */
/* */
/* cprintf goes to the console, which during a panic is also the screen */
/* being painted over -- so the panel builds its strings itself and */
/* places them with vga_text_at. */
/* ------------------------------------------------------------------ */
static char *
put_str(char *p, char *end, const char *s)
{
while (*s && p < end - 1)
*p++ = *s++;
*p = '\0';
return p;
}
static char *
put_dec(char *p, char *end, uint64 v)
{
char tmp[24];
int i = 0;
if (v == 0)
tmp[i++] = '0';
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
while (i-- && p < end - 1)
*p++ = tmp[i];
*p = '\0';
return p;
}
static char *
put_hex(char *p, char *end, uint64 v, int digits)
{
const char *d = "0123456789abcdef";
if (p < end - 2) { *p++ = '0'; *p++ = 'x'; }
for (int i = digits - 1; i >= 0 && p < end - 1; i--)
*p++ = d[(v >> (i * 4)) & 0xF];
*p = '\0';
return p;
}
/* ------------------------------------------------------------------ */
/* The face */
/* */
/* ":(" — two dots and a parenthesis, drawn large. It is a sad face */
/* only if you tilt your head, which is exactly why it works: it is */
/* legible as a mood without being a picture of anything, and it does */
/* not pretend the machine has feelings about what just happened. */
/* */
/* Drawn from primitives rather than stored as a bitmap, so it scales */
/* with the display and costs no space in a kernel image that stage2 */
/* loads one sector at a time by PIO. */
/* ------------------------------------------------------------------ */
static int
isqrt(int v)
{
if (v <= 0)
return 0;
// Newton's method, iterated only while the estimate is still *decreasing*.
//
// The obvious "stop when it stops changing" version does not terminate: on a
// non-square it settles into a two-cycle rather than a fixed point, and
// isqrt(3) alternates between 1 and 2 forever. In a panic handler that is a
// hang with the screen half drawn and no way to find out why.
int x = v;
int y = (x + 1) / 2;
while (y < x) {
x = y;
y = (x + v / x) / 2;
}
return x;
}
// The "(" of ":(" — the left half of a squashed circle, so the tips point right
// and the middle bulges left, the way the glyph does.
static void
draw_paren(int cx, int cy, int h, int thick, uint32 color)
{
int r = h / 2;
for (int dy = -r; dy <= r; dy++) {
int dx = isqrt(r * r - dy * dy);
// Narrow it: a true half-circle reads as a bracket, not a parenthesis.
int x = cx - (dx * 45) / 100;
vga_fill_circle(x, cy + dy, thick / 2, color);
}
}
static void
draw_sad_face(int x, int y, int h, uint32 color)
{
int dot = h / 12;
int gap = h / 4;
// The colon.
vga_fill_circle(x + dot, y + h / 2 - gap, dot, color);
vga_fill_circle(x + dot, y + h / 2 + gap, dot, color);
// The parenthesis, a little to the right of it.
draw_paren(x + dot * 2 + h / 2, y + h / 2, h, dot * 2, color);
}
/* ------------------------------------------------------------------ */
/* The report */
/* ------------------------------------------------------------------ */
struct field {
const char *label;
char value[72];
};
#define NFIELD 10
static struct field fields[NFIELD];
static int nfields;
static struct field *
field(const char *label)
{
if (nfields >= NFIELD)
return &fields[NFIELD - 1];
struct field *f = &fields[nfields++];
f->label = label;
f->value[0] = '\0';
return f;
}
// Collect everything worth knowing while the machine is still in the state
// that produced the fault. Interrupts are already off and no lock is taken:
// a panic that deadlocks trying to report itself tells you nothing at all.
// A short, shoutable name for the failure, derived from the panic message
// rather than declared at every call site.
//
// Windows stop codes are genuinely useful: they are the thing you search for,
// and they stay the same across builds while the prose around them changes.
// Taking the message up to its first colon (or its first few words) gets that
// for free -- "sysrq: crash requested" becomes SYSRQ, "short filewrite" becomes
// SHORT_FILEWRITE -- with no risk of a call site forgetting to supply one.
static void
stop_code(char *out, int cap, const char *reason)
{
int n = 0;
for (int i = 0; reason[i] && n < cap - 1; i++) {
char c = reason[i];
if (c == ':')
break;
if (c == ' ' || c == '\t') {
if (n == 0 || out[n-1] == '_')
continue;
out[n++] = '_';
continue;
}
if (c >= 'a' && c <= 'z') c = c - 'a' + 'A';
if ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')
out[n++] = c;
}
while (n > 0 && out[n-1] == '_')
n--;
if (n == 0)
{ out[n++] = 'P'; out[n++] = 'A'; out[n++] = 'N'; out[n++] = 'I'; out[n++] = 'C'; }
out[n] = '\0';
}
static void
gather(char *reason, addr_t *pcs, int npcs)
{
char *e;
struct field *f;
nfields = 0;
f = field("Stop code");
stop_code(f->value, sizeof(f->value), reason);
f = field("Reason");
put_str(f->value, f->value + sizeof(f->value), reason);
f = field("CPU");
e = f->value + sizeof(f->value);
put_dec(f->value, e, mycpu() ? (uint64)mycpu()->id : 0);
if (!mycpu())
put_str(f->value, e, "unknown (very early boot)");
f = field("Process");
e = f->value + sizeof(f->value);
if (myproc()) {
char *p = put_str(f->value, e, myproc()->name);
p = put_str(p, e, " pid ");
put_dec(p, e, (uint64)myproc()->pid);
} else {
put_str(f->value, e, "none -- in the kernel, not on anybody's behalf");
}
f = field("Uptime");
e = f->value + sizeof(f->value);
{
char *p = put_dec(f->value, e, (uint64)ticks);
p = put_str(p, e, " ticks (about ");
p = put_dec(p, e, (uint64)ticks / 100);
put_str(p, e, " s)");
}
// Every address below is a link-time address. Printing where the kernel
// actually sits is what lets addr2line resolve them afterwards, and it is
// the single most useful line here when the trace is all you have.
f = field("Kernel base");
e = f->value + sizeof(f->value);
put_hex(f->value, e, (uint64)KERNBASE, 16);
f = field("Fault PC");
e = f->value + sizeof(f->value);
if (npcs > 0 && pcs[0])
put_hex(f->value, e, (uint64)pcs[0], 16);
else
put_str(f->value, e, "unavailable");
}
/* ------------------------------------------------------------------ */
/* The QR code */
/* */
/* A panic report is only worth what can be carried away from it, and */
/* at this point there is no network, no log to write to, and often no */
/* second machine on the desk. Photographing the screen works but */
/* turns a call chain into something that has to be retyped by hand, */
/* with sixteen hex digits per frame and no way to tell 8 from B. */
/* */
/* A phone camera is a working out-of-band channel that needs no */
/* driver on our side at all. See kernel/core/qr.c. */
/* ------------------------------------------------------------------ */
// Deliberately not a URL. This encodes the report itself, so it goes wherever
// the person holding the phone decides -- and reaches nothing on its own.
//
// Compact because the budget is 134 bytes for the largest symbol this encoder
// builds, and a crash with a deep call chain is exactly when the budget is
// tightest. Two decisions buy most of the room: frames are stored as offsets
// from the kernel base rather than absolute addresses, which halves 16 hex
// digits to 8, and the fields are single letters. The base is included, so
// nothing is lost -- addr2line wants the link-time address and can be given it.
//
// Fields are appended in priority order and the loop simply stops when the
// buffer is full, so a long reason string costs call-chain depth rather than
// producing a report that will not encode.
static int
build_qr_payload(char *buf, int cap, char *reason, addr_t *pcs, int npcs)
{
char *p = buf, *end = buf + cap;
// Magic and format version, so whatever reads this later can tell what it is
// looking at and can change the layout without ambiguity.
p = put_str(p, end, "SXPSOGM1\n");
// The reason, not the stop code: the stop code is derived from the reason by
// stop_code() and carries strictly less, so encoding both would spend bytes
// on something the reader can recompute.
//
// Held back from the end of the buffer so that a long message costs some of
// the call chain but never all of it. A report with a sentence and no
// addresses is much harder to act on than one with addresses and a truncated
// sentence.
{
char *reason_end = end - 34; // three frames and their separators
if (reason_end > p)
p = put_str(p, reason_end, reason);
}
p = put_str(p, end, "\nc");
p = put_dec(p, end, mycpu() ? (uint64)mycpu()->id : 0);
p = put_str(p, end, " p");
p = put_dec(p, end, myproc() ? (uint64)myproc()->pid : 0);
p = put_str(p, end, " t");
p = put_dec(p, end, (uint64)ticks);
if (myproc()) {
p = put_str(p, end, " ");
p = put_str(p, end, myproc()->name);
}
p = put_str(p, end, "\nb");
p = put_hex(p, end, (uint64)KERNBASE, 16);
p = put_str(p, end, "\n");
for (int i = 0; i < npcs && pcs[i]; i++) {
// Nine bytes: eight hex digits and the separator. Stopping here rather than
// letting put_hex truncate matters -- half an address in a crash report is
// worse than one fewer address, because it looks like a real one.
if (end - p < 10)
break;
if (i)
p = put_str(p, end, " ");
// put_hex prefixes "0x", which is 14 wasted bytes across seven frames.
{
const char *d = "0123456789abcdef";
uint64 off = (uint64)pcs[i] - (uint64)KERNBASE;
for (int k = 7; k >= 0; k--)
*p++ = d[(off >> (k * 4)) & 0xF];
*p = '\0';
}
}
return (int)(p - buf);
}
// One static symbol rather than a local: struct qr is about 3.4 KB and the
// panic path runs on whatever stack the fault left behind, which may not have
// that much room. It is only ever touched here, after everything else has
// stopped.
static struct qr panic_qr;
// Returns the width used, so the text column knows what it may not overlap.
static int
draw_qr_panel(int sw, int sh, int ch, char *reason, addr_t *pcs, int npcs)
{
char payload[QR_MAX_BYTES];
int n = build_qr_payload(payload, sizeof(payload), reason, pcs, npcs);
if (n <= 0 || qr_encode(&panic_qr, (unsigned char *)payload, n) != 0)
return 0;
// Four modules of quiet zone, which the standard requires and which decoders
// genuinely need -- without it many will not find the symbol at all.
const int quiet = 4;
int cells = panic_qr.size + 2 * quiet;
// Scale to about a quarter of the display, then clamp. The lower bound is
// what a phone can still resolve from a photograph of a monitor; the upper
// bound stops the thing dominating a screen whose actual job is the text.
int scale = (sw / 4) / cells;
if (scale < 3) scale = 3;
if (scale > 8) scale = 8;
int card = cells * scale;
int x0 = sw - card - sw / 24;
int y0 = sh / 8;
vga_fill_rect(x0, y0, card, card, PSOGM_CARD);
for (int y = 0; y < panic_qr.size; y++)
for (int x = 0; x < panic_qr.size; x++)
if (panic_qr.mod[y][x])
vga_fill_rect(x0 + (x + quiet) * scale, y0 + (y + quiet) * scale,
scale, scale, PSOGM_INK);
vga_text_at(x0, y0 + card + ch, "Scan for a text copy", PSOGM_DIM);
vga_text_at(x0, y0 + card + ch * 2 + 2, "of this report.", PSOGM_DIM);
return card + sw / 24;
}
// The frame pointers, one per line, as one string per row.
static void
trace_line(char *buf, int cap, int i, addr_t pc)
{
char *e = buf + cap;
char *p = put_str(buf, e, " #");
p = put_dec(p, e, (uint64)i);
p = put_str(p, e, i < 10 ? " " : " ");
put_hex(p, e, (uint64)pc, 16);
}
/* ------------------------------------------------------------------ */
static void
paint(char *reason, addr_t *pcs, int npcs)
{
int sw, sh;
vga_dims(&sw, &sh);
int ch = vga_char_h();
int cw = vga_char_w();
// The whole display, one colour. An earlier version dimmed the previous
// screen and floated a panel over it, which looked good and was wrong: the
// thing is called a Purple Screen, and half a screen of somebody's abandoned
// work behind the report is a distraction at the worst possible moment.
vga_clear(PSOGM_BG);
// Glyphs are drawn with their background filled, so the console's cream has
// to become the background or every line of text arrives in its own box.
vga_set_bg(PSOGM_BG);
// The symbol goes first, because it decides how much width the text has. It
// is drawn before anything can fail: if the encoder refuses the payload the
// screen simply has no card and the report is unchanged.
draw_qr_panel(sw, sh, ch, reason, pcs, npcs);
int left = sw / 8;
if (left > 110) left = 110;
int y = sh / 8;
int face_h = 96;
draw_sad_face(left, y, face_h, PSOGM_FG);
y += face_h + ch * 2;
// Left-aligned, not centred. Centred text reads as a poster; this is a
// report, and a report is read down the left edge.
vga_text_at(left, y, "Schunix ran into a problem and needs to restart.",
PSOGM_FG);
y += ch + ch / 2;
vga_text_at(left, y, "Nothing on your disks was touched by this.",
PSOGM_DIM);
// A rule in the boot splash's accent, the same width as the one under the
// wordmark there, tying this screen to the one the machine started with.
//
// Equal space above and below on purpose: at any less it sits close enough to
// the line above to read as an underline of that sentence rather than as a
// divider before the facts.
y += ch * 2;
vga_fill_rect(left, y, 120, 2, SCH_ACCENT);
y += ch;
// Facts: quiet labels down the left, bright values in a second column.
int vx = left + 14 * cw;
for (int i = 0; i < nfields; i++) {
vga_text_at(left, y, fields[i].label, PSOGM_DIM);
vga_text_at(vx, y, fields[i].value, PSOGM_FG);
y += ch + 2;
}
y += ch;
vga_text_at(left, y, "Call chain", PSOGM_DIM);
y += ch + 2;
for (int i = 0; i < npcs && pcs[i]; i++) {
char line[48];
trace_line(line, sizeof(line), i, pcs[i]);
vga_text_at(vx - 3 * cw, y, line, PSOGM_FG);
y += ch;
if (y > sh - ch * 5)
break;
}
// Two closing lines, bottom left: what to do with the numbers above, and
// what this screen is called, so it can be searched for by name. Shortened
// from the full command line, which at 62 characters ran under the QR card.
vga_text_at(left, sh - ch * 3,
"Resolve: addr2line -e kernel <address>", PSOGM_DIM);
vga_text_at(left, sh - ch * 3 + ch + 4,
"PSOGM -- Purple Screen of Guru Meditation", PSOGM_DIM);
}
// Same report, down the serial line. This is the copy that survives: it is what
// bootcheck reads, and the only one that exists when the framebuffer does not.
static void
report_serial(char *reason, addr_t *pcs, int npcs)
{
cprintf("\n");
cprintf(" :(\n\n");
cprintf(" Schunix ran into a problem and needs to restart.\n");
cprintf(" PSOGM -- Purple Screen of Guru Meditation\n\n");
for (int i = 0; i < nfields; i++) {
// Pad the label out so the values line up, without a printf width
// specifier -- cprintf has no such thing.
int n = 0;
while (fields[i].label[n]) n++;
cprintf(" %s", fields[i].label);
for (int pad = n; pad < 14; pad++)
cprintf(" ");
cprintf("%s\n", fields[i].value);
}
cprintf("\n Call chain\n");
for (int i = 0; i < npcs && pcs[i]; i++)
cprintf(" #%d %p\n", i, pcs[i]);
cprintf("\n Resolve with: addr2line -e build/kernel/kernel <address>\n");
cprintf(" Reason: %s\n", reason);
cprintf(" System halted.\n");
}
__attribute__((noreturn))
void
panic(char *s)
{
addr_t pcs[10];
cli();
// Stop every other path from writing to the console. consputc checks this
// and spins, which is what keeps another CPU from painting over the report
// while it is being drawn.
cons_panic_mode();
for (int i = 0; i < 10; i++)
pcs[i] = 0;
getcallerpcs(&s, pcs);
gather(s, pcs, 10);
report_serial(s, pcs, 10);
if (vga_ready())
paint(s, pcs, 10);
panicked = 1;
for (;;)
hlt();
}

456
kernel/core/qr.c Normal file
View file

@ -0,0 +1,456 @@
// QR encoder. See include/qr.h for why it exists and why it is this small.
//
// Freestanding on purpose: no allocation, no floating point, no libc beyond
// memset/memmove. It has to run in panic context, where the heap may be exactly
// what is broken. Every buffer here is either on the caller's struct or a
// bounded local.
//
// The structure follows ISO/IEC 18004 in the order the standard describes it,
// which is also the only order in which any of it can be checked:
//
// 1. encode the payload into data codewords
// 2. append Reed-Solomon error correction, per block
// 3. interleave the blocks
// 4. draw the function patterns (finders, timing, alignment, format area)
// 5. lay the codeword bits into the remaining modules in a zigzag
// 6. try all 8 masks, score each, keep the best
//
// Verified by scanning the output with a phone, which is the only test that
// matters and is also literally the feature.
#ifdef QR_HOST
#include <string.h>
#else
#include "types.h"
#include "defs.h"
#endif
#include "qr.h"
// ---------------------------------------------------------------------------
// Version table
// ---------------------------------------------------------------------------
//
// Per version, at error correction level L: total codewords, error correction
// codewords per block, and number of blocks. These are from the standard's
// tables and cannot be derived, so they are the one part of this file that has
// to be right by transcription rather than by reasoning.
//
// A useful check on them: total - ecc*blocks must be divisible by blocks for
// every row below, because levels 1..6 at L happen to have equally sized
// blocks. qr_encode() asserts exactly that at runtime, which is what turns a
// mistyped number here into a visible failure instead of an unscannable symbol.
static const struct {
short total; // total codewords in the symbol
unsigned char ecc; // EC codewords per block
unsigned char blocks; // number of RS blocks
} vinfo[QR_MAX_VERSION + 1] = {
{ 0, 0, 0 }, // version 0 does not exist; keeps indexing natural
{ 26, 7, 1 },
{ 44, 10, 1 },
{ 70, 15, 1 },
{ 100, 20, 1 },
{ 134, 26, 1 },
{ 172, 18, 2 },
};
int
qr_capacity(int version)
{
if (version < 1 || version > QR_MAX_VERSION)
return 0;
// Data codewords, less the two bytes of framing: a 4-bit mode indicator, an
// 8-bit character count (versions 1-9), and a 4-bit terminator come to
// exactly two codewords' worth of overhead.
int data = vinfo[version].total - vinfo[version].ecc * vinfo[version].blocks;
return data - 2;
}
// ---------------------------------------------------------------------------
// GF(256) and Reed-Solomon
// ---------------------------------------------------------------------------
// Multiply in GF(256) modulo x^8 + x^4 + x^3 + x^2 + 1 (0x11D).
//
// Done by shift-and-add rather than log/exp tables. Two 256-byte tables plus
// their initialisation is more code than this loop, and this needs no init step
// at all -- which matters when the caller is a panic handler that cannot assume
// any particular earlier function ran.
static unsigned char
gmul(unsigned char x, unsigned char y)
{
unsigned char z = 0;
for (int i = 7; i >= 0; i--) {
z = (unsigned char)((z << 1) ^ ((z >> 7) * 0x1D));
z ^= (unsigned char)(((y >> i) & 1) * x);
}
return z;
}
// Coefficients of the degree-`degree` generator polynomial, highest power
// first, with the leading 1 left implicit.
static void
rs_divisor(int degree, unsigned char *result)
{
memset(result, 0, degree);
result[degree - 1] = 1; // start at the monomial x^0
// Multiply by (x - r) for r = 1, a, a^2, ... Each pass shifts the polynomial
// up one degree and folds in the new root.
unsigned char root = 1;
for (int i = 0; i < degree; i++) {
for (int j = 0; j < degree; j++) {
result[j] = gmul(result[j], root);
if (j + 1 < degree)
result[j] ^= result[j + 1];
}
root = gmul(root, 0x02);
}
}
// The remainder of data[] divided by the generator: the EC codewords.
static void
rs_remainder(const unsigned char *data, int len,
const unsigned char *gen, int degree, unsigned char *result)
{
memset(result, 0, degree);
for (int i = 0; i < len; i++) {
unsigned char factor = data[i] ^ result[0];
memmove(&result[0], &result[1], degree - 1);
result[degree - 1] = 0;
for (int j = 0; j < degree; j++)
result[j] ^= gmul(gen[j], factor);
}
}
// ---------------------------------------------------------------------------
// Module access
// ---------------------------------------------------------------------------
static void
set_fn(struct qr *q, int x, int y, int dark)
{
if (x < 0 || y < 0 || x >= q->size || y >= q->size)
return;
q->mod[y][x] = (unsigned char)(dark != 0);
q->fn[y][x] = 1;
}
static int
absi(int v)
{
return v < 0 ? -v : v;
}
static int
cheb(int dx, int dy)
{
int ax = absi(dx), ay = absi(dy);
return ax > ay ? ax : ay;
}
// ---------------------------------------------------------------------------
// Function patterns
// ---------------------------------------------------------------------------
// A finder plus its separator, as one 9x9 stamp centred on (cx, cy).
//
// Writing it by Chebyshev distance rather than as a literal bitmap is not
// cleverness for its own sake: the concentric rings *are* the definition -- dark
// 3x3 core, light ring, dark ring, light separator -- and the edge stamps run
// off the symbol, which set_fn() clips.
static void
draw_finder(struct qr *q, int cx, int cy)
{
for (int dy = -4; dy <= 4; dy++)
for (int dx = -4; dx <= 4; dx++) {
int d = cheb(dx, dy);
set_fn(q, cx + dx, cy + dy, d != 2 && d != 4);
}
}
// The 5x5 alignment pattern: dark 3x3 outline with a light ring inside.
static void
draw_alignment(struct qr *q, int cx, int cy)
{
for (int dy = -2; dy <= 2; dy++)
for (int dx = -2; dx <= 2; dx++)
set_fn(q, cx + dx, cy + dy, cheb(dx, dy) != 1);
}
// The 15-bit format information, twice, plus the always-dark module.
//
// Called once with the real mask at the end, and once early with mask 0 purely
// to *reserve* these modules -- the zigzag below skips anything marked as a
// function pattern, so the reservation has to exist before data is placed.
static void
draw_format(struct qr *q, int mask)
{
// 0b01 is error correction level L. Five data bits, then a BCH(15,5) check
// computed with generator 0x537, then XOR 0x5412 so an all-zero format never
// produces an all-zero pattern.
int data = (1 << 3) | mask;
int rem = data;
for (int i = 0; i < 10; i++)
rem = (rem << 1) ^ ((rem >> 9) * 0x537);
int bits = ((data << 10) | rem) ^ 0x5412;
int n = q->size;
// First copy: down the left of the top-right finder, then left along the top
// of the bottom-left one. The 6/7 and 7/8 jogs skip the timing pattern.
for (int i = 0; i <= 5; i++)
set_fn(q, 8, i, (bits >> i) & 1);
set_fn(q, 8, 7, (bits >> 6) & 1);
set_fn(q, 8, 8, (bits >> 7) & 1);
set_fn(q, 7, 8, (bits >> 8) & 1);
for (int i = 9; i < 15; i++)
set_fn(q, 14 - i, 8, (bits >> i) & 1);
// Second copy, so the format survives damage to either corner.
for (int i = 0; i < 8; i++)
set_fn(q, n - 1 - i, 8, (bits >> i) & 1);
for (int i = 8; i < 15; i++)
set_fn(q, 8, n - 15 + i, (bits >> i) & 1);
set_fn(q, 8, n - 8, 1); // the dark module, always set, always here
}
static void
draw_function_patterns(struct qr *q)
{
int n = q->size;
// Timing patterns first: the finders overwrite their ends, which is correct.
for (int i = 0; i < n; i++) {
set_fn(q, 6, i, i % 2 == 0);
set_fn(q, i, 6, i % 2 == 0);
}
draw_finder(q, 3, 3);
draw_finder(q, n - 4, 3);
draw_finder(q, 3, n - 4);
// For versions 2-6 the alignment centres are {6, n-7}, and three of those
// four combinations collide with a finder. Only the bottom-right survives,
// which is why there is no position table here. Version 7 would need one.
if (q->size > 21)
draw_alignment(q, n - 7, n - 7);
draw_format(q, 0); // reserve; overwritten once a mask is chosen
}
// ---------------------------------------------------------------------------
// Masking
// ---------------------------------------------------------------------------
static int
mask_bit(int mask, int x, int y)
{
switch (mask) {
case 0: return (x + y) % 2 == 0;
case 1: return y % 2 == 0;
case 2: return x % 3 == 0;
case 3: return (x + y) % 3 == 0;
case 4: return (x / 3 + y / 2) % 2 == 0;
case 5: return x * y % 2 + x * y % 3 == 0;
case 6: return (x * y % 2 + x * y % 3) % 2 == 0;
case 7: return ((x + y) % 2 + x * y % 3) % 2 == 0;
}
return 0;
}
static void
apply_mask(struct qr *q, int mask)
{
for (int y = 0; y < q->size; y++)
for (int x = 0; x < q->size; x++)
if (!q->fn[y][x])
q->mod[y][x] ^= (unsigned char)mask_bit(mask, x, y);
}
// The standard's four penalty rules. Lower is better.
//
// A decoder will read any legal mask, so this is not correctness -- it is scan
// reliability, which is the whole point when the reader is a handheld camera
// pointed at a screen. Rule 3 in particular exists to stop the data accidentally
// containing something that looks like a finder pattern.
static int
penalty(const struct qr *q)
{
int n = q->size, score = 0, dark = 0;
for (int y = 0; y < n; y++) {
for (int x = 0; x < n; x++)
dark += q->mod[y][x];
// Rule 1, both directions: runs of five or more identical modules.
for (int dir = 0; dir < 2; dir++) {
int run = 1;
int prev = dir ? q->mod[0][y] : q->mod[y][0];
for (int i = 1; i < n; i++) {
int cur = dir ? q->mod[i][y] : q->mod[y][i];
if (cur == prev) {
run++;
} else {
if (run >= 5) score += 3 + (run - 5);
run = 1;
prev = cur;
}
}
if (run >= 5) score += 3 + (run - 5);
}
}
// Rule 2: every 2x2 block of one colour.
for (int y = 0; y + 1 < n; y++)
for (int x = 0; x + 1 < n; x++) {
int c = q->mod[y][x];
if (c == q->mod[y][x + 1] && c == q->mod[y + 1][x] && c == q->mod[y + 1][x + 1])
score += 3;
}
// Rule 3: the finder-lookalike 1011101 with four light modules on either
// side, in either direction.
for (int dir = 0; dir < 2; dir++)
for (int a = 0; a < n; a++) {
unsigned bits = 0;
for (int b = 0; b < n; b++) {
int m = dir ? q->mod[b][a] : q->mod[a][b];
bits = ((bits << 1) | (unsigned)m) & 0x7FF;
if (b >= 10 && (bits == 0x05D || bits == 0x5D0))
score += 40;
}
}
// Rule 4: deviation from an even split of dark and light, in 5% steps.
int total = n * n;
int k = (absi(dark * 20 - total * 10) + total - 1) / total;
score += k * 10;
return score;
}
// ---------------------------------------------------------------------------
// Encode
// ---------------------------------------------------------------------------
static void
put_bits(unsigned char *buf, int *pos, unsigned val, int n)
{
for (int i = n - 1; i >= 0; i--) {
buf[*pos >> 3] |= (unsigned char)(((val >> i) & 1) << (7 - (*pos & 7)));
(*pos)++;
}
}
int
qr_encode(struct qr *q, const unsigned char *data, int len)
{
int v;
if (len < 0)
return -1;
for (v = 1; v <= QR_MAX_VERSION; v++)
if (len <= qr_capacity(v))
break;
if (v > QR_MAX_VERSION)
return -1;
int total = vinfo[v].total;
int ecclen = vinfo[v].ecc;
int blocks = vinfo[v].blocks;
int datacw = total - ecclen * blocks;
// The table claims equal-sized blocks for every version this file supports.
// If that is ever untrue the interleave below is silently wrong, so check it
// rather than trust it.
if (datacw % blocks != 0)
return -1;
int blocklen = datacw / blocks;
// --- 1. payload -> data codewords ---------------------------------------
unsigned char cw[172]; // >= max total codewords
memset(cw, 0, sizeof(cw));
int pos = 0;
put_bits(cw, &pos, 0x4, 4); // byte mode
put_bits(cw, &pos, (unsigned)len, 8);
for (int i = 0; i < len; i++)
put_bits(cw, &pos, data[i], 8);
// Terminator, up to four zero bits, then round up to a whole codeword.
int spare = datacw * 8 - pos;
put_bits(cw, &pos, 0, spare < 4 ? spare : 4);
if (pos & 7)
put_bits(cw, &pos, 0, 8 - (pos & 7));
// Fill the rest with the standard alternating pad bytes. They are specified
// values, not arbitrary filler: a decoder uses the character count to know
// where the data ends, but the pad pattern keeps the symbol from having large
// uniform regions that mask selection would then have to fight.
for (int i = pos / 8; i < datacw; i++)
cw[i] = (i - pos / 8) % 2 == 0 ? 0xEC : 0x11;
// --- 2 and 3. error correction, then interleave -------------------------
unsigned char gen[30];
unsigned char ec[4][30];
rs_divisor(ecclen, gen);
for (int b = 0; b < blocks; b++)
rs_remainder(&cw[b * blocklen], blocklen, gen, ecclen, ec[b]);
unsigned char out[172];
int o = 0;
for (int i = 0; i < blocklen; i++)
for (int b = 0; b < blocks; b++)
out[o++] = cw[b * blocklen + i];
for (int i = 0; i < ecclen; i++)
for (int b = 0; b < blocks; b++)
out[o++] = ec[b][i];
// --- 4. function patterns ------------------------------------------------
memset(q, 0, sizeof(*q));
q->size = 17 + 4 * v;
draw_function_patterns(q);
// --- 5. zigzag placement -------------------------------------------------
//
// Two-module-wide columns walked from the right edge leftwards, alternating
// upward and downward. Column 6 is skipped entirely because it is the vertical
// timing pattern -- without that step every subsequent column lands one place
// out and the symbol is quietly unreadable.
int bit = 0;
int nbits = o * 8;
for (int right = q->size - 1; right >= 1; right -= 2) {
if (right == 6)
right = 5;
for (int vert = 0; vert < q->size; vert++) {
for (int j = 0; j < 2; j++) {
int x = right - j;
int upward = ((right + 1) & 2) == 0;
int y = upward ? q->size - 1 - vert : vert;
if (!q->fn[y][x] && bit < nbits) {
q->mod[y][x] = (unsigned char)((out[bit >> 3] >> (7 - (bit & 7))) & 1);
bit++;
}
}
}
}
// --- 6. pick a mask ------------------------------------------------------
int best = 0, bestscore = 0;
for (int m = 0; m < 8; m++) {
apply_mask(q, m);
draw_format(q, m);
int s = penalty(q);
if (m == 0 || s < bestscore) {
bestscore = s;
best = m;
}
apply_mask(q, m); // XOR again to undo; masking is involutive
}
apply_mask(q, best);
draw_format(q, best);
return 0;
}

55
kernel/core/sleeplock.c Normal file
View file

@ -0,0 +1,55 @@
// Sleeping locks
#include "types.h"
#include "defs.h"
#include "param.h"
#include "x86.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
#include "sleeplock.h"
void
initsleeplock(struct sleeplock *lk, char *name)
{
initlock(&lk->lk, "sleep lock");
lk->name = name;
lk->locked = 0;
lk->pid = 0;
}
void
acquiresleep(struct sleeplock *lk)
{
acquire(&lk->lk);
while (lk->locked) {
sleep(lk, &lk->lk);
}
lk->locked = 1;
if (myproc()) {
lk->pid = myproc()->pid;
} else {
lk->pid = 0;
}
release(&lk->lk);
}
void
releasesleep(struct sleeplock *lk)
{
acquire(&lk->lk);
lk->locked = 0;
lk->pid = 0;
wakeup(lk);
release(&lk->lk);
}
int
holdingsleep(struct sleeplock *lk)
{
acquire(&lk->lk);
int r = lk->locked;
release(&lk->lk);
return r;
}

115
kernel/core/spinlock.c Normal file
View file

@ -0,0 +1,115 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "x86.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
void
initlock(struct spinlock *lk, char *name)
{
lk->name = name;
lk->locked = 0;
lk->cpu = 0;
}
void
acquire(struct spinlock *lk)
{
pushcli();
if(holding(lk))
panic("acquire");
{
uint64 spins = 0;
while(xchg(&lk->locked, 1) != 0){
if(++spins == 200000000ULL){
cprintf("acquire: stuck on \"%s\" held by cpu=%p pcs0=%p pcs1=%p pcs2=%p, acquirer cpu=%p\n",
lk->name, lk->cpu, lk->pcs[0], lk->pcs[1], lk->pcs[2], mycpu());
panic("acquire: deadlock");
}
}
}
__sync_synchronize();
lk->cpu = mycpu();
getcallerpcs(&lk, lk->pcs);
}
void
release(struct spinlock *lk)
{
if(!holding(lk))
panic("release");
lk->pcs[0] = 0;
lk->cpu = 0;
__sync_synchronize();
asm volatile("movq $0, %0" : "+m" (lk->locked) : );
popcli();
}
void
getcallerpcs(void *v, addr_t pcs[])
{
addr_t *rbp;
asm volatile("mov %%rbp, %0" : "=r" (rbp));
getstackpcs(rbp, pcs);
}
void
getstackpcs(addr_t *rbp, addr_t pcs[])
{
int i;
for(i = 0; i < 10; i++){
if(rbp == 0 || rbp < (addr_t*)KERNBASE || rbp == (addr_t*)0xffffffff)
break;
pcs[i] = rbp[1];
rbp = (addr_t*)rbp[0];
}
for(; i < 10; i++)
pcs[i] = 0;
}
int
holding(struct spinlock *lock)
{
return lock->locked && lock->cpu == mycpu();
}
void
pushcli(void)
{
int eflags;
eflags = readeflags();
cli();
if(mycpu() == 0){
extern uint64 kernel_fs_base;
cprintf("pushcli: cpu==NULL cs=0x%p kernel_fs_base=0x%p actual_fs_base=0x%p\n",
readcs(), kernel_fs_base, rdmsr(0xC0000100));
panic("pushcli: cpu is NULL");
}
if(mycpu()->ncli == 0)
mycpu()->intena = eflags & FL_IF;
mycpu()->ncli += 1;
}
void
popcli(void)
{
if(readeflags()&FL_IF)
panic("popcli - interruptible");
if(--mycpu()->ncli < 0)
panic("popcli");
if(mycpu()->ncli == 0 && mycpu()->intena)
sti();
}

113
kernel/core/string.c Normal file
View file

@ -0,0 +1,113 @@
#include "types.h"
#include "x86.h"
void*
memset(void *dst, int c, uint64 n)
{
uint64 c64;
if ((addr_t)dst % 8 == 0 && n % 8 == 0) {
c &= 0xFF;
c64 = (uint64)c;
c64 = (c64 << 56) | (c64 << 48) | (c64 << 40) | (c64 << 32) |
(c64 << 24) | (c64 << 16) | (c64 << 8) | c64;
stosq(dst, c64, n / 8);
} else {
stosb(dst, c, n);
}
return dst;
}
int
memcmp(const void *v1, const void *v2, uint n)
{
const uchar *s1, *s2;
s1 = v1;
s2 = v2;
while(n-- > 0){
if(*s1 != *s2)
return *s1 - *s2;
s1++, s2++;
}
return 0;
}
void*
memmove(void *dst, const void *src, uint n)
{
const char *s;
char *d;
s = src;
d = dst;
if(s < d && s + n > d){
s += n;
d += n;
while(n-- > 0)
*--d = *--s;
} else
while(n-- > 0)
*d++ = *s++;
return dst;
}
void*
memcpy(void *dst, const void *src, uint n)
{
return memmove(dst, src, n);
}
int
strncmp(const char *p, const char *q, uint n)
{
while(n > 0 && *p && *p == *q)
n--, p++, q++;
if(n == 0)
return 0;
return (uchar)*p - (uchar)*q;
}
char*
strncpy(char *s, const char *t, int n)
{
char *os = s;
while(n-- > 0 && (*s++ = *t++) != 0)
;
while(n-- > 0)
*s++ = 0;
return os;
}
char*
safestrcpy(char *s, const char *t, int n)
{
char *os = s;
if(n <= 0)
return os;
while(--n > 0 && (*s++ = *t++) != 0)
;
*s = 0;
return os;
}
int
strlen(const char *s)
{
int n;
for(n = 0; s[n]; n++)
;
return n;
}
int
strnlen(const char *s, int max)
{
int n;
for(n = 0; n < max && s[n]; n++)
;
return n;
}

116
kernel/core/time.c Normal file
View file

@ -0,0 +1,116 @@
// Wall-clock time.
//
// The kernel has always known how long it had been running -- `ticks`, bumped
// 100 times a second by the timer interrupt -- and has never known what time it
// was. sys_gettimeofday() returned `ticks / 100` as though it were seconds
// since the epoch, so every Schunix machine believed it was the 1st of January
// 1970 and that the date advanced at the rate of its own uptime. `date` printed
// a number that barely moved, which is what it looks like when a clock is
// counting from the wrong place rather than not counting at all.
//
// cmostime() in kernel/arch/x86_64/lapic.c has been able to read the hardware
// clock the whole time. Nothing called it.
//
// The arrangement here is the usual one: read the RTC once at boot, convert it
// to a Unix timestamp, and add uptime to it thereafter. The RTC is slow to read
// (it is polled over two I/O ports and has to be sampled twice to catch it
// mid-update) so reading it on every gettimeofday would be absurd; and `ticks`
// is a perfectly good interval clock once it has something to count from.
#include "types.h"
#include "defs.h"
#include "date.h"
#include "param.h"
#include "spinlock.h"
extern uint ticks;
// Seconds between the Unix epoch and the moment this machine booted. Zero until
// time_init() runs, which makes the behaviour before that point exactly what it
// used to be -- uptime presented as absolute time -- rather than something new
// and differently wrong.
uint64 boot_epoch;
// Days from 1970-01-01 to the given civil date, proleptic Gregorian.
//
// Howard Hinnant's days_from_civil. It is worth using rather than a loop over
// years because it is branchless, exact for any year, and has no table to get
// wrong; the alternative -- accumulating 365 or 366 days per year since 1970 --
// gets leap centuries wrong unless you remember that 1900 was not a leap year
// and 2000 was, which is precisely the sort of thing that is discovered on a
// February morning years later.
//
// The trick is shifting the year so that March is month 1, which puts the leap
// day at the *end* of the year and makes the leap rule depend only on the year
// number rather than on where in the year you are.
static int64
days_from_civil(int64 y, unsigned m, unsigned d)
{
y -= m <= 2;
const int64 era = (y >= 0 ? y : y - 399) / 400;
const unsigned yoe = (unsigned)(y - era * 400); // [0, 399]
const unsigned doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096]
return era * 146097 + (int64)doe - 719468;
}
// Read the hardware clock and work out when we booted.
//
// The RTC is read in UTC. That is not universally true on a PC -- a machine
// that has had Windows installed on it usually keeps the RTC in local time --
// but there is nowhere to store a timezone yet and no way to ask, and UTC is
// the honest default for a system with no configuration. When there is a
// settings store, this is where the offset goes.
void
time_init(void)
{
struct rtcdate r;
cmostime(&r);
// cmostime() returns a two-digit year on hardware that only stores one.
// 2000-2099 is the right guess now and stays right for a lifetime; a machine
// reporting 70 means the battery is flat, and the epoch is as good an answer
// as any for that.
int64 year = r.year;
if (year < 100)
year += 2000;
// A wildly implausible reading means the CMOS is not telling us anything.
// Leaving boot_epoch at zero keeps the old behaviour, which is wrong but
// predictable, rather than putting the machine somewhere in the Bronze Age.
if (r.month < 1 || r.month > 12 || r.day < 1 || r.day > 31 ||
r.hour > 23 || r.minute > 59 || r.second > 60 || year < 1970) {
cprintf("time: CMOS clock is not plausible, staying at the epoch\n");
return;
}
int64 days = days_from_civil(year, r.month, r.day);
uint64 now = (uint64)(days * 86400) + r.hour * 3600 + r.minute * 60 + r.second;
// Subtract the uptime so far, so boot_epoch is the moment of boot rather than
// the moment this function ran. It is only a fraction of a second, but it
// keeps boot_epoch meaning exactly what its name says.
uint64 up = ticks / 100;
boot_epoch = now > up ? now - up : now;
cprintf("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);
}
// Seconds and microseconds since the epoch. One place, so gettimeofday,
// clock_gettime and anything added later cannot disagree about what time it is.
void
wallclock(uint64 *sec, uint64 *usec)
{
uint xticks;
acquire(&tickslock);
xticks = ticks;
release(&tickslock);
if (sec)
*sec = boot_epoch + xticks / 100;
if (usec)
*usec = (uint64)(xticks % 100) * 10000;
}

79
kernel/core/uaccess.c Normal file
View file

@ -0,0 +1,79 @@
// User-memory access helpers — see include/uaccess.h for the rules.
//
// Everything here validates against the *current* process's page table
// (myproc()->pgdir), which is also the live CR3 while a syscall runs, so a
// range that passes access_ok() can be dereferenced directly by kernel
// code for the remainder of the syscall (single-threaded processes can't
// unmap it out from under us mid-call).
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "errno.h"
#include "uaccess.h"
int
access_ok(addr_t uaddr, uint64 size)
{
addr_t a, end;
if(size == 0)
return 1;
end = uaddr + size;
if(end < uaddr || end > KERNBASE)
return 0;
for(a = PGROUNDDOWN(uaddr); a < end; a += PGSIZE)
if(uva2ka(myproc()->pgdir, (char*)a) == 0)
return 0;
return 1;
}
void*
user_ptr(addr_t uaddr, uint64 size)
{
if(uaddr == 0 || !access_ok(uaddr, size))
return 0;
return (void*)uaddr;
}
int
copy_from_user(void *dst, addr_t uaddr, uint64 n)
{
if(uaddr + n < uaddr || uaddr + n > KERNBASE)
return -EFAULT;
if(copyin(myproc()->pgdir, uaddr, dst, n) < 0)
return -EFAULT;
return 0;
}
int
copy_to_user(addr_t uaddr, const void *src, uint64 n)
{
if(uaddr + n < uaddr || uaddr + n > KERNBASE)
return -EFAULT;
if(copyout(myproc()->pgdir, uaddr, (void*)src, n) < 0)
return -EFAULT;
return 0;
}
long
strlen_user(addr_t uaddr)
{
addr_t a = uaddr;
while(a - uaddr < USER_STR_MAX){
// Scan through the per-page kernel alias so an unmapped page is a
// clean failure instead of a fault.
char *ka = uva2ka(myproc()->pgdir, (char*)a);
if(ka == 0)
return -EFAULT;
addr_t pgend = PGROUNDDOWN(a) + PGSIZE;
for(; a < pgend; a++, ka++)
if(*ka == 0)
return a - uaddr;
}
return -EFAULT;
}