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

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;
}