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

160
kernel/dev/bio.c Normal file
View file

@ -0,0 +1,160 @@
// Buffer cache.
//
// The buffer cache is a linked list of buf structures holding
// cached copies of disk block contents. Caching disk blocks
// in memory reduces the number of disk reads and also provides
// a synchronization point for disk blocks used by multiple processes.
//
// Interface:
// * To get a buffer for a particular disk block, call bread.
// * After changing buffer data, call bwrite to write it to disk.
// * When done with the buffer, call brelse.
// * Do not use the buffer after calling brelse.
// * Only one process at a time can use a buffer,
// so do not keep them longer than necessary.
//
// The implementation uses two state flags internally:
// * B_VALID: the buffer data has been read from the disk.
// * B_DIRTY: the buffer data has been modified
// and needs to be written to disk.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"
struct {
struct spinlock lock;
struct buf buf[NBUF];
// Linked list of all buffers, through prev/next.
// head.next is most recently used.
struct buf head;
} bcache;
void
binit(void)
{
struct buf *b;
initlock(&bcache.lock, "bcache");
//PAGEBREAK!
// Create linked list of buffers
bcache.head.prev = &bcache.head;
bcache.head.next = &bcache.head;
for(b = bcache.buf; b < bcache.buf+NBUF; b++){
b->next = bcache.head.next;
b->prev = &bcache.head;
initsleeplock(&b->lock, "buffer");
bcache.head.next->prev = b;
bcache.head.next = b;
}
}
// Route a request to the driver that owns b->dev. Everything above this line
// is device-agnostic; everything below picks a backend. RAMDISK_DEV is served
// out of memory (kernel/dev/ramdisk.c), USBDISK_DEV by bulk transfers to a
// USB stick, everything else by the IDE driver.
static void
bdevrw(struct buf *b)
{
if(b->dev == RAMDISK_DEV){
ramdiskrw(b);
return;
}
if(b->dev == USBDISK_DEV){
usb_storage_rw(b);
return;
}
iderw(b);
}
// Look through buffer cache for block on device dev.
// If not found, allocate a buffer.
// In either case, return locked buffer.
static struct buf*
bget(uint dev, uint blockno)
{
struct buf *b;
acquire(&bcache.lock);
// Is the block already cached?
for(b = bcache.head.next; b != &bcache.head; b = b->next){
if(b->dev == dev && b->blockno == blockno){
b->refcnt++;
release(&bcache.lock);
acquiresleep(&b->lock);
return b;
}
}
// Not cached; recycle some unused buffer and clean buffer
// "clean" because B_DIRTY and not locked means log.c
// hasn't yet committed the changes to the buffer.
for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
if(b->refcnt == 0 && (b->flags & B_DIRTY) == 0) {
b->dev = dev;
b->blockno = blockno;
b->flags = 0;
b->refcnt = 1;
release(&bcache.lock);
acquiresleep(&b->lock);
return b;
}
}
panic("bget: no buffers");
}
// Return a locked buf with the contents of the indicated block.
struct buf*
bread(uint dev, uint blockno)
{
struct buf *b;
b = bget(dev, blockno);
if(!(b->flags & B_VALID)) {
bdevrw(b);
}
return b;
}
// Write b's contents to disk. Must be locked.
void
bwrite(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("bwrite");
b->flags |= B_DIRTY;
bdevrw(b);
}
// Release a locked buffer.
// Move to the head of the MRU list.
void
brelse(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("brelse");
releasesleep(&b->lock);
acquire(&bcache.lock);
b->refcnt--;
if (b->refcnt == 0) {
// no one is waiting for it.
b->next->prev = b->prev;
b->prev->next = b->next;
b->next = bcache.head.next;
b->prev = &bcache.head;
bcache.head.next->prev = b;
bcache.head.next = b;
}
release(&bcache.lock);
}
//PAGEBREAK!

107
kernel/dev/cdev.c Normal file
View file

@ -0,0 +1,107 @@
#include "types.h"
#include "defs.h"
#include "param.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "x86.h"
#include "errno.h"
extern int consoleread(struct inode*, uint, char*, int);
extern int consolewrite(struct inode*, uint, char*, int);
extern int mouseread(struct inode*, uint, char*, int);
static uint64 rseed = 0x2A99554433221100ULL;
int
nullwrite(struct inode *ip, uint off, char *dst, int n)
{
return n;
}
int
nullread(struct inode *ip, uint off, char *dst, int n)
{
return 0;
}
int
zeroread(struct inode *ip, uint off, char *dst, int n)
{
memset(dst, 0, n);
return n;
}
int
randomread(struct inode *ip, uint off, char *dst, int n)
{
for(int i = 0; i < n; i++){
rseed = rseed * 6364136223846793005ULL + 1442695040888963407ULL;
dst[i] = (rseed >> 32) & 0xFF;
}
return n;
}
// /dev/full — reads as an endless run of zero bytes, every write fails with
// ENOSPC. Programs use it to exercise their out-of-disk-space paths.
int
fullwrite(struct inode *ip, uint off, char *dst, int n)
{
return -ENOSPC;
}
int
mousewrite(struct inode *ip, uint off, char *dst, int n)
{
return n;
}
int
memread(struct inode *ip, uint off, char *dst, int n)
{
switch(ip->minor) {
case 1: return consoleread(ip, off, dst, n);
case 2: return nullread(ip, off, dst, n);
case 3: return consoleread(ip, off, dst, n);
case 5: return zeroread(ip, off, dst, n);
case 7: return zeroread(ip, off, dst, n); // /dev/full reads as zeros
case 8:
case 9: return randomread(ip, off, dst, n);
default: return -1;
}
}
int
memwrite(struct inode *ip, uint off, char *dst, int n)
{
switch(ip->minor) {
case 1: return consolewrite(ip, off, dst, n);
case 7: return fullwrite(ip, off, dst, n); // /dev/full: always ENOSPC
case 2:
case 5:
case 8:
case 9: return nullwrite(ip, off, dst, n);
default: return -1;
}
}
struct devsw devsw[] = {
[0] = { 0, 0 },
[1] = { memread, memwrite },
// /dev/fb0 (major 2) — the framebuffer, kernel/dev/fb.c. This used to be
// { nullread, nullwrite }, so writing pixels to it succeeded and did
// nothing.
// The close hook gives the text console its screen back when whatever was
// drawing on the framebuffer exits — see fbclose().
[2] = { fbread, fbwrite, 0, fbclose },
[3] = { mouseread, mousewrite },
[4] = { 0, 0 },
// /dev/tty (major 5) — alias for the console
[5] = { memread, memwrite },
// /proc status files (major 6) — see kernel/dev/procfs.c
[6] = { procread, procwrite },
// /dev/input/eventN (major 7) — raw evdev events, kernel/dev/input.c. The
// minor is the device index, so event0 is the keyboard and event1 the mouse.
[7] = { inputread, inputwrite, inputready, inputclose, inputopen },
};

308
kernel/dev/device.c Normal file
View file

@ -0,0 +1,308 @@
// The device model. See include/device.h for why it exists.
//
// A fixed table rather than a linked list: this is populated once during boot
// by whichever bus found each device, and read afterwards. Nothing is removed
// yet -- hot-unplug would need it, and when that arrives the in_use flag is
// already the hook for it.
//
// No lock. Every device_add() happens during single-threaded boot, before
// userspace exists, and every reader afterwards only walks. If a bus ever
// enumerates at runtime -- USB hotplug is the obvious one -- this needs a
// spinlock, and the reader in procfs needs to take it.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "device.h"
#include "display.h"
#include "theme.h"
#include "text.h"
static struct device devices[NDEVICE];
static int ndevices;
static const char *bus_names[DEVBUS_NBUS] = {
[DEVBUS_PLATFORM] = "platform",
[DEVBUS_PCI] = "pci",
[DEVBUS_USB] = "usb",
};
static const char *class_names[DEVCLASS_NCLASS] = {
[DEVCLASS_OTHER] = "other",
[DEVCLASS_DISPLAY] = "display",
[DEVCLASS_INPUT] = "input",
[DEVCLASS_STORAGE] = "storage",
[DEVCLASS_NET] = "net",
[DEVCLASS_BUS] = "bus",
};
const char *
dev_bus_name(enum dev_bus b)
{
if (b < 0 || b >= DEVBUS_NBUS || !bus_names[b])
return "?";
return bus_names[b];
}
const char *
dev_class_name(enum dev_class c)
{
if (c < 0 || c >= DEVCLASS_NCLASS || !class_names[c])
return "?";
return class_names[c];
}
// How many devices already share this base name, so "vga" becomes vga0, vga1.
static int
count_base(const char *base)
{
int n = 0;
for (int i = 0; i < ndevices; i++) {
const char *a = devices[i].name, *b = base;
while (*b && *a == *b) { a++; b++; }
// Matched the whole base and the rest is the index we appended.
if (*b == '\0' && *a >= '0' && *a <= '9')
n++;
}
return n;
}
struct device *
device_add(const char *base, enum dev_bus bus, enum dev_class class,
const char *driver)
{
if (ndevices >= NDEVICE)
return 0;
struct device *d = &devices[ndevices++];
d->bus = bus;
d->class = class;
d->driver = driver;
d->in_use = 1;
int idx = count_base(base);
int n = 0;
while (base[n] && n < DEV_NAMELEN - 3) {
d->name[n] = base[n];
n++;
}
// Single digit is enough for NDEVICE of 32 per base name, and a two-digit
// path that is never taken is a two-digit path that is never tested.
if (idx > 9)
idx = 9;
d->name[n++] = (char)('0' + idx);
d->name[n] = '\0';
return d;
}
void
device_set_desc(struct device *d, const char *s)
{
if (!d)
return;
int n = 0;
while (s[n] && n < (int)sizeof(d->desc) - 1) {
d->desc[n] = s[n];
n++;
}
d->desc[n] = '\0';
}
struct device *
device_at(int i)
{
if (i < 0 || i >= ndevices)
return 0;
return &devices[i];
}
int
device_count(void)
{
return ndevices;
}
struct device *
device_find_class(enum dev_class class)
{
for (int i = 0; i < ndevices; i++)
// Unclaimed devices are deliberately skipped. A display nobody drives is
// not a display you can draw on, and returning it would push the "is there
// a driver" check out to every caller.
if (devices[i].class == class && devices[i].driver)
return &devices[i];
return 0;
}
// --- the display class -----------------------------------------------------
//
// Thin wrappers over device_find_class() plus a null check on each op. They
// exist so that consumers are not each written around a vtable that may be
// absent -- a compositor asking to present on a machine with no display driver
// should be a no-op, not a null dereference in the middle of a frame.
struct device *
display_get(void)
{
return device_find_class(DEVCLASS_DISPLAY);
}
int
display_mode(struct display_mode *out)
{
struct device *d = display_get();
if (!d || !d->ops || !((struct display_ops *)d->ops)->mode)
return -1;
((struct display_ops *)d->ops)->mode(d, out);
return 0;
}
void *
display_acquire(uint32 *stride_out)
{
struct device *d = display_get();
if (!d || !d->ops || !((struct display_ops *)d->ops)->acquire)
return 0;
return ((struct display_ops *)d->ops)->acquire(d, stride_out);
}
void
display_present(int x, int y, int w, int h)
{
struct device *d = display_get();
if (!d || !d->ops || !((struct display_ops *)d->ops)->present)
return;
((struct display_ops *)d->ops)->present(d, x, y, w, h);
}
void
display_wait_vblank(void)
{
struct device *d = display_get();
if (!d || !d->ops || !((struct display_ops *)d->ops)->wait_vblank)
return;
((struct display_ops *)d->ops)->wait_vblank(d);
}
// Drive the whole display path once and report what happened: find the class,
// read the mode, acquire a back buffer, draw into RAM, present it.
//
// A self-test rather than a unit test because none of this can be checked
// without a display: the assertion is "did the right pixels reach the screen",
// and the only instrument for that is a screendump. Triggered with
// `echo d > /proc/sysrq` and photographed by tools/dispshot.ps1.
//
// The pattern is chosen to be wrong in visible ways. Four solid quadrants in
// theme colours catch a stride error (the bands would slant) and a bpp error
// (the colours would be wrong); the white frame one pixel inside the edge
// catches an off-by-one in the present rectangle; and the fact that it is
// drawn entirely into RAM before a single byte reaches the framebuffer is what
// double buffering means.
void
display_selftest(void)
{
struct display_mode m;
uint32 stride;
if (display_mode(&m) != 0) {
cprintf("display: no display bound\n");
return;
}
cprintf("display: %dx%d %dbpp stride %d\n", m.width, m.height, m.bpp, m.stride);
unsigned char *b = display_acquire(&stride);
if (!b) {
cprintf("display: no back buffer available\n");
return;
}
static const uint32 quad[4] = {
SCH_ACCENT, SCH_ACCENT_DIM, SCH_DIM, SCH_BG
};
for (int y = 0; y < m.height; y++) {
uint32 *row = (uint32 *)(b + (uint64)y * stride);
int top = y < m.height / 2;
for (int x = 0; x < m.width; x++) {
int left = x < m.width / 2;
row[x] = quad[(top ? 0 : 2) + (left ? 0 : 1)];
}
}
for (int x = 1; x < m.width - 1; x++) {
*(uint32 *)(b + 1 * (uint64)stride + x * 4) = SCH_FG;
*(uint32 *)(b + (uint64)(m.height - 2) * stride + x * 4) = SCH_FG;
}
for (int y = 1; y < m.height - 1; y++) {
*(uint32 *)(b + (uint64)y * stride + 1 * 4) = SCH_FG;
*(uint32 *)(b + (uint64)y * stride + (m.width - 2) * 4) = SCH_FG;
}
// Between frames, so the blit does not land halfway down a scan-out.
display_wait_vblank();
display_present(0, 0, m.width, m.height);
cprintf("display: presented a full frame\n");
}
// Draw a text specimen through the display class. `echo t > /proc/sysrq`.
//
// It exercises the three things a character-cell console cannot do, so a
// screenshot says whether the layer works rather than whether it compiled:
// glyphs on a common baseline at arbitrary x (not multiples of eight), text
// blended over an existing background rather than drawn into cleared cells, and
// codepoints above 255.
void
text_selftest(void)
{
struct display_mode m;
uint32 stride;
if (display_mode(&m) != 0) {
cprintf("text: no display\n");
return;
}
unsigned char *b = display_acquire(&stride);
if (!b) {
cprintf("text: no back buffer\n");
return;
}
struct surface s = { b, stride, m.width, m.height };
struct font *f = font_builtin();
// A background with structure in it, so blending is visible as blending
// rather than as drawing onto a flat colour.
for (int y = 0; y < m.height; y++) {
uint32 *row = (uint32 *)(b + (uint64)y * stride);
for (int x = 0; x < m.width; x++)
row[x] = ((x / 32 + y / 32) & 1) ? SCH_BG : SCH_ACCENT_DIM;
}
int base = f->ops->baseline(f);
int line = f->ops->height(f) + 6;
int y = 80;
text_draw(&s, f, 60, y + base, "Schunix text rendering", SCH_FG);
y += line * 2;
// Deliberately not on a multiple of eight. On the old console this string
// could only start at x = 56 or x = 64.
text_draw(&s, f, 61, y + base, "positioned at x=61, off the cell grid", SCH_DIM);
y += line;
text_draw(&s, f, 63, y + base, "and here at x=63", SCH_DIM);
y += line * 2;
// Above U+00FF, which a 256-entry table cannot index at all.
text_draw(&s, f, 60, y + base,
"utf-8: \xe2\x94\x8c\xe2\x94\x80\xe2\x94\x90 "
"\xe2\x96\x88\xe2\x96\x93\xe2\x96\x92\xe2\x96\x91 "
"\xc2\xb0 \xc2\xb1 \xe2\x86\x92 \xe2\x80\xa2", SCH_ACCENT);
y += line * 2;
// Measure, then centre on it -- the thing you cannot do without metrics.
const char *msg = "measured and centred";
int w = text_measure(f, msg);
text_draw(&s, f, (m.width - w) / 2, y + base, msg, SCH_FG);
display_present(0, 0, m.width, m.height);
cprintf("text: drew a specimen, \"%s\" measures %d px\n", msg, w);
}

202
kernel/dev/fb.c Normal file
View file

@ -0,0 +1,202 @@
// /dev/fb0 -- the linear framebuffer as a file.
//
// Until now this node existed but was wired to major 2, whose devsw entry was
// { nullread, nullwrite }: reads returned end-of-file and writes were thrown
// away, silently. The only thing that actually worked was mmap, and only
// because sys_mmap special-cased the major number.
//
// Reads and writes here address the framebuffer by byte offset, so lseek
// positions the "cursor" and a whole frame can be written with one write().
// That is slower than mmap and always will be -- it is a copy -- but it is
// what makes the device inspectable from a shell, which is worth a great deal
// while there is no GUI yet to inspect it with.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "fb.h"
#include "uaccess.h"
#include "errno.h"
// Describe one colour channel. A channel that does not exist gets length 0,
// which is how callers are supposed to detect "no alpha" rather than by
// assuming from bits_per_pixel.
static void
chan(struct fb_bitfield *f, unsigned int offset, unsigned int length)
{
f->offset = offset;
f->length = length;
f->msb_right = 0;
}
// Pixel layout for the depths vga_draw_pixel knows how to write. Keeping the
// two in step matters: this is the kernel telling userspace how to build a
// pixel, and if it disagrees with how the kernel builds one, every colour
// drawn by a program is wrong in a way that looks like a hardware fault.
static void
pixel_format(struct fb_var_screeninfo *v, unsigned int bpp)
{
switch (bpp) {
case 32:
chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8);
chan(&v->transp, 24, 0); // the byte is there; nothing honours it
break;
case 24:
chan(&v->red, 16, 8); chan(&v->green, 8, 8); chan(&v->blue, 0, 8);
chan(&v->transp, 0, 0);
break;
case 16:
// RGB565, matching the packing vga_draw_pixel does for this depth.
chan(&v->red, 11, 5); chan(&v->green, 5, 6); chan(&v->blue, 0, 5);
chan(&v->transp, 0, 0);
break;
default:
chan(&v->red, 0, 0); chan(&v->green, 0, 0); chan(&v->blue, 0, 0);
chan(&v->transp, 0, 0);
break;
}
}
int
fbread(struct inode *ip, uint off, char *dst, int n)
{
volatile uchar *fb = vga_fb_ptr();
uint64 size = vga_fb_size();
if (!fb)
return -ENODEV;
if (n < 0)
return -EINVAL;
if ((uint64)off >= size)
return 0; // past the end of the screen: EOF
if ((uint64)off + (uint64)n > size)
n = (int)(size - (uint64)off);
memmove(dst, (void*)(fb + off), n);
return n;
}
// Set once something has drawn through this device, so that fbclose knows
// whether the screen still belongs to the text console. Reading the
// framebuffer, or asking it its geometry, does not count -- `fbinfo` with no
// arguments must not wipe its own output off the screen.
static int fb_dirty;
// Called from sys_mmap when /dev/fb0 is mapped. A mapping is a licence to
// draw, and the drawing happens with no further syscalls, so this is the last
// point at which the kernel hears about it at all.
void
fb_mapped(void)
{
fb_dirty = 1;
}
int
fbwrite(struct inode *ip, uint off, char *src, int n)
{
volatile uchar *fb = vga_fb_ptr();
uint64 size = vga_fb_size();
if (!fb)
return -ENODEV;
if (n < 0)
return -EINVAL;
if ((uint64)off >= size)
return -ENOSPC; // a screen cannot be extended
if ((uint64)off + (uint64)n > size)
n = (int)(size - (uint64)off);
memmove((void*)(fb + off), src, n);
fb_dirty = 1;
return n;
}
// The last descriptor on /dev/fb0 is going away.
//
// Whatever was drawing has finished, and the screen still holds its final
// frame -- so the shell prompt underneath is invisible and the machine looks
// hung. It is not: the process exited cleanly and the console is writing
// happily into a framebuffer nobody can read any more. There is no character
// backing store to repaint from (the console draws glyphs straight into the
// framebuffer and scrolls with a memmove), so the honest recovery is to clear
// and go home rather than to pretend the old text can be restored.
void
fbclose(struct inode *ip)
{
if (!fb_dirty)
return;
fb_dirty = 0;
// Said out loud, because a screen clearing on its own is otherwise
// indistinguishable from a bug -- and this is the only place in the system
// that wipes the display without being asked to.
cprintf("fb0: released by pid %d, console restored\n", myproc() ? myproc()->pid : -1);
vga_screen_reset();
}
// FBIO* handling, called from sys_ioctl once it has established that the fd
// refers to this device. `arg` is the raw user address; each request
// validates it for the size it is actually going to touch, rather than for
// the largest struct here -- fb_fix_screeninfo is half the size of
// fb_var_screeninfo, and a buffer sitting near the end of a mapping is a
// legitimate place to put the smaller one.
int
fb_ioctl(int request, addr_t arg)
{
int w, h;
if (!vga_fb_ptr())
return -ENODEV;
vga_dims(&w, &h);
switch (request) {
case FBIOGET_VSCREENINFO: {
struct fb_var_screeninfo *v =
(struct fb_var_screeninfo *)user_ptr(arg, sizeof(*v));
if (!v)
return -EFAULT;
memset(v, 0, sizeof(*v));
v->xres = v->xres_virtual = (unsigned int)w;
v->yres = v->yres_virtual = (unsigned int)h;
v->bits_per_pixel = vga_fb_bpp();
v->activate = FB_ACTIVATE_NOW;
pixel_format(v, v->bits_per_pixel);
// height/width (physical millimetres) and every timing field stay zero:
// VBE does not report them and a guess would be worse than an admission.
return 0;
}
case FBIOGET_FSCREENINFO: {
struct fb_fix_screeninfo *f =
(struct fb_fix_screeninfo *)user_ptr(arg, sizeof(*f));
if (!f)
return -EFAULT;
memset(f, 0, sizeof(*f));
safestrcpy(f->id, "schunixfb", sizeof(f->id));
f->smem_start = (unsigned long)vga_fb_phys();
f->smem_len = (unsigned int)vga_fb_size();
f->type = FB_TYPE_PACKED_PIXELS;
f->visual = FB_VISUAL_TRUECOLOR;
f->line_length = vga_fb_stride();
return 0;
}
// The mode came from the bootloader's VBE call and we have no way to
// change it. Saying so is more useful than accepting the request and
// leaving the caller to discover, by drawing, that nothing happened.
case FBIOPUT_VSCREENINFO:
return -EINVAL;
case FBIOBLANK:
return -EINVAL;
}
return -ENOTTY;
}

208
kernel/dev/ide.c Normal file
View file

@ -0,0 +1,208 @@
// Simple PIO-based (non-DMA) IDE driver code.
#include "types.h"
#include "defs.h"
#include "irq.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"
#define SECTOR_SIZE 512
#define IDE_BSY 0x80
#define IDE_DRDY 0x40
#define IDE_DF 0x20
#define IDE_ERR 0x01
#define IDE_CMD_READ 0x20
#define IDE_CMD_WRITE 0x30
#define IDE_CMD_RDMUL 0xc4
#define IDE_CMD_WRMUL 0xc5
// idequeue points to the buf now being read/written to the disk.
// idequeue->qnext points to the next buf to be processed.
// You must hold idelock while manipulating queue.
static struct spinlock idelock;
static struct buf *idequeue;
static int havedisk0;
static int havedisk1;
static void idestart(struct buf*);
// Wait for the drive to become ready, or give up.
//
// This used to be an unbounded `while`. On a machine with nothing attached to
// the primary channel the status register reads 0xFF forever, so the very
// first call from ideinit() hung the kernel -- no panic, no message, just a
// dead machine right after the VGA banner. That is not an exotic
// configuration: it is every CD boot, and most hardware newer than IDE.
//
// The bound is deliberately enormous. A real drive answers in microseconds;
// this only expires when there is nothing there at all.
static int
idewait(int checkerr)
{
int r = 0;
for(int i = 0; i < 20000000; i++){
r = inb(0x1f7);
if((r & (IDE_BSY|IDE_DRDY)) == IDE_DRDY){
if(checkerr && (r & (IDE_DF|IDE_ERR)) != 0)
return -1;
return 0;
}
}
return -1;
}
static void
ide_irq(void *arg)
{
(void)arg;
ideintr();
}
void
ideinit(void)
{
initlock(&idelock, "ide");
// ISA signalling: edge-triggered, active high. Everything at a fixed
// legacy port is wired this way; PCI devices are not.
irq_register(IRQ_IDE, ide_irq, 0, "ide", 0);
// A timeout here means there is no drive on this channel. Say so and leave
// havedisk0/1 clear: with a ramdisk root there is nothing else the system
// needs from IDE, and booting on is strictly better than stopping.
if(idewait(0) < 0){
cprintf("ide: no drive on the primary channel\n");
return;
}
havedisk0 = 1;
// Check if disk 1 is present
outb(0x1f6, 0xe0 | (1<<4));
for(int i=0; i<1000; i++){
if(inb(0x1f7) != 0){
havedisk1 = 1;
break;
}
}
// Switch back to disk 0.
outb(0x1f6, 0xe0 | (0<<4));
}
// Is there actually a drive there? Callers that probe -- rather than callers
// that know what they are looking for -- have to ask, because reading an
// absent drive is a bug and iderw() treats it as one.
int
ide_disk_present(int dev)
{
return dev == 0 ? havedisk0 : havedisk1;
}
// Start the request for b. Caller must hold idelock.
static void
idestart(struct buf *b)
{
if(b == 0)
panic("idestart");
if(b->blockno >= FSSIZE)
panic("incorrect blockno");
int sector_per_block = BSIZE/SECTOR_SIZE;
int sector = b->blockno * sector_per_block;
int read_cmd = (sector_per_block == 1) ? IDE_CMD_READ : IDE_CMD_RDMUL;
int write_cmd = (sector_per_block == 1) ? IDE_CMD_WRITE : IDE_CMD_WRMUL;
if (sector_per_block > 7) panic("idestart");
idewait(0);
outb(0x3f6, 0); // generate interrupt
outb(0x1f2, sector_per_block); // number of sectors
outb(0x1f3, sector & 0xff);
outb(0x1f4, (sector >> 8) & 0xff);
outb(0x1f5, (sector >> 16) & 0xff);
outb(0x1f6, 0xe0 | ((b->dev&1)<<4) | ((sector>>24)&0x0f));
if(b->flags & B_DIRTY){
outb(0x1f7, write_cmd);
outsl(0x1f0, b->data, BSIZE/4);
} else {
outb(0x1f7, read_cmd);
}
}
// Interrupt handler.
void
ideintr(void)
{
struct buf *b;
// First queued buffer is the active request.
acquire(&idelock);
if((b = idequeue) == 0){
release(&idelock);
// cprintf("spurious IDE interrupt\n");
return;
}
idequeue = b->qnext;
// Read data if needed.
if(!(b->flags & B_DIRTY) && idewait(1) >= 0)
insl(0x1f0, b->data, BSIZE/4);
// Wake process waiting for this buf.
b->flags |= B_VALID;
b->flags &= ~B_DIRTY;
wakeup(b);
// Start disk on next buf in queue.
if(idequeue != 0)
idestart(idequeue);
release(&idelock);
}
//PAGEBREAK!
// Sync buf with disk.
// If B_DIRTY is set, write buf to disk, clear B_DIRTY, set B_VALID.
// Else if B_VALID is not set, read buf from disk, set B_VALID.
void
iderw(struct buf *b)
{
struct buf **pp;
if(!holdingsleep(&b->lock))
panic("iderw: buf not locked");
if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
panic("iderw: nothing to do");
if(b->dev != 0 && !havedisk1)
panic("iderw: ide disk 1 not present");
if(b->dev == 0 && !havedisk0)
panic("iderw: ide disk 0 not present");
acquire(&idelock); //DOC:acquire-lock
// Append b to idequeue.
b->qnext = 0;
for(pp=&idequeue; *pp; pp=&(*pp)->qnext) //DOC:insert-queue
;
*pp = b;
// Start disk if necessary.
if(idequeue == b)
idestart(b);
// Wait for request to finish.
while((b->flags & (B_VALID|B_DIRTY)) != B_VALID){
sleep(b, &idelock);
}
release(&idelock);
}

394
kernel/dev/input.c Normal file
View file

@ -0,0 +1,394 @@
// Raw input event devices -- /dev/input/event0 and /dev/input/event1.
//
// Everything that produces input feeds this: the PS/2 keyboard and mouse, and
// the USB HID driver. The console keeps its own path and is unaffected; these
// nodes are a *second* consumer of the same interrupts, so a program can read
// keys while the shell still gets characters.
//
// Two devices rather than one because they are two devices. A compositor
// grabs the pointer and leaves the keyboard alone, or the other way round, and
// merging them into a single stream makes that impossible to express.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "mmu.h"
#include "proc.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "input.h"
#include "errno.h"
#define NEVENT 256 // per device; ~10 seconds of frantic typing
struct input_dev {
struct spinlock lock;
struct input_event ev[NEVENT];
uint r, w; // free-running; the difference is the count
// Exclusive grab (EVIOCGRAB). While set, keys still arrive here but stop
// being handed to the console's line discipline.
//
// Without this a full-screen program does not really own the keyboard: it
// reads /dev/input/event0 *and* the shell behind it reads the same keystrokes
// off the tty. The observed failure was a full-screen program quietly typing
// its entire session into the shell underneath, so the first Enter after
// exiting ran whatever had accumulated. The grab is also exactly what a
// compositor needs, which is why it lives here rather than in any one client.
int grabbed;
// How many descriptors are open on this device.
//
// Events are only queued while this is non-zero, and the backlog is dropped
// on the first open. Both halves matter: without them the ring is a log of
// every key pressed since boot, and the next program to open the device is
// handed the lot as though the user had just typed it.
int opens;
};
static struct input_dev idev[NINPUTDEV];
static int input_up;
extern uint ticks;
void
inputinit(void)
{
int i;
for(i = 0; i < NINPUTDEV; i++)
initlock(&idev[i].lock, "input");
// Producers run from interrupt handlers and can fire before this point in
// boot. Until the locks exist, an event has nowhere safe to go.
__sync_synchronize();
input_up = 1;
}
// Append one event. Called from interrupt context, so it never sleeps and
// never blocks on a full ring.
void
input_report(int dev, int type, int code, int value)
{
struct input_dev *d;
struct input_event *e;
if(!input_up || dev < 0 || dev >= NINPUTDEV)
return;
d = &idev[dev];
// Nobody is listening, so there is nowhere for this to go. Linux does the
// same: an evdev device with no clients does not accumulate.
if(d->opens == 0)
return;
acquire(&d->lock);
// A full ring drops the *oldest* event, not the newest. Dropping the newest
// is the obvious choice and it is wrong: the event most likely to be lost is
// then a key release, and a lost release leaves the key held down forever
// as far as every reader is concerned. Losing the oldest costs history, not
// state.
if(d->w - d->r >= NEVENT)
d->r++;
e = &d->ev[d->w % NEVENT];
e->time.tv_sec = ticks / 100;
e->time.tv_usec = (ticks % 100) * 10000;
e->type = (uint16)type;
e->code = (uint16)code;
e->value = (int32)value;
d->w++;
release(&d->lock);
}
// End a report. A reader that acts on each event as it arrives will see a
// diagonal mouse move as two separate moves; the SYN is how it knows the two
// axes belong to the same physical motion.
void
input_sync(int dev)
{
if(!input_up || dev < 0 || dev >= NINPUTDEV)
return;
input_report(dev, EV_SYN, SYN_REPORT, 0);
wakeup(&idev[dev]);
}
int
input_ready(int dev)
{
int n;
if(!input_up || dev < 0 || dev >= NINPUTDEV)
return 0;
acquire(&idev[dev].lock);
n = idev[dev].w != idev[dev].r;
release(&idev[dev].lock);
return n;
}
// A descriptor has been opened on this device.
//
// The backlog is discarded, which is the whole point. Events queued before a
// program opened the device were meant for whoever had it *then* -- almost
// always the shell, which already consumed them as a command line. Replaying
// them into the new owner means a program is handed the keystrokes that
// launched it, as input, the instant it starts. That was a real crash and not a
// cosmetic one: the replayed characters were interpreted as commands, and one of
// them asked for a mode the program had not initialised yet.
//
// Linux gets this for free by giving every open its own client queue. One
// shared ring plus "start from now" is the same answer for a single reader,
// which is the case that exists.
void
inputopen(struct inode *ip)
{
int dev = ip->minor;
if(dev < 0 || dev >= NINPUTDEV)
return;
acquire(&idev[dev].lock);
idev[dev].opens++;
idev[dev].r = idev[dev].w; // start from now, not from boot
release(&idev[dev].lock);
}
// The devsw ready hook: would a read block?
int
inputready(struct inode *ip)
{
return input_ready(ip->minor);
}
// Is this device grabbed? Asked by the keyboard drivers before they hand a
// character to the console.
int
input_is_grabbed(int dev)
{
if(!input_up || dev < 0 || dev >= NINPUTDEV)
return 0;
return idev[dev].grabbed;
}
// EVIOCGRAB. Linux's semantics: one holder at a time, and it is not an error
// to grab what you already hold.
int
input_grab(int dev, int on)
{
if(!input_up || dev < 0 || dev >= NINPUTDEV)
return -ENODEV;
on = on ? 1 : 0;
if(idev[dev].grabbed != on){
idev[dev].grabbed = on;
// Said out loud. A grabbed keyboard is a console that has gone deaf on
// purpose, and that is indistinguishable from one that has gone deaf by
// accident unless the kernel says which it was.
cprintf("input: event%d %s by pid %d\n", dev,
on ? "grabbed" : "released", myproc() ? myproc()->pid : -1);
}
return 0;
}
// The last descriptor on this device is going away.
//
// Releasing the grab here is not tidiness, it is the difference between a
// crashed game and a machine you have to reboot: a program that grabs the
// keyboard and dies without ungrabbing would leave the console permanently
// deaf. The close hook makes the release unconditional.
void
inputclose(struct inode *ip)
{
int dev = ip->minor;
if(dev < 0 || dev >= NINPUTDEV)
return;
if(idev[dev].opens > 0)
idev[dev].opens--;
if(idev[dev].grabbed){
idev[dev].grabbed = 0;
cprintf("input: event%d grab released\n", dev);
}
}
// ioctl on /dev/input/eventN. Only EVIOCGRAB so far; the EVIOCG* query
// requests (name, id, capability bitmaps) can arrive when something needs
// them.
int
input_ioctl(struct inode *ip, int request, addr_t arg)
{
int on;
if(request == EVIOCGRAB){
// Linux passes the flag *by value* in the argument, not by pointer.
on = (int)arg;
return input_grab(ip->minor, on);
}
return -ENOTTY;
}
// A read returns whole events only. Handing back a partial struct would
// desynchronise the reader permanently -- every later event would be split
// across two reads and reassembled wrong.
int
inputread(struct inode *ip, uint off, char *dst, int n)
{
struct input_dev *d;
int got = 0;
int dev = ip->minor;
if(dev < 0 || dev >= NINPUTDEV)
return -ENODEV;
if(n < (int)sizeof(struct input_event))
return -EINVAL;
d = &idev[dev];
acquire(&d->lock);
while(d->r == d->w){
if(myproc()->killed){
release(&d->lock);
return -EINTR;
}
sleep(d, &d->lock);
}
while(d->r != d->w && got + (int)sizeof(struct input_event) <= n){
memmove(dst + got, &d->ev[d->r % NEVENT], sizeof(struct input_event));
d->r++;
got += sizeof(struct input_event);
}
release(&d->lock);
return got;
}
// evdev accepts writes to set keyboard LEDs. There are no LEDs to set here,
// and a program that writes one should not see an error for it.
int
inputwrite(struct inode *ip, uint off, char *src, int n)
{
return n;
}
// ---------------------------------------------------------------------------
// Producers
// ---------------------------------------------------------------------------
// AT scancode set 1, E0-prefixed. The unprefixed bytes need no table at all:
// Linux's keycodes were numbered from this set and still match it below 0x54.
// These are the ones that do not.
static const struct { uint8 sc; uint8 key; } e0map[] = {
{0x1C, 96}, // KP_ENTER
{0x1D, KEY_RIGHTCTRL},
{0x35, 98}, // KP_SLASH
{0x38, KEY_RIGHTALT},
{0x47, KEY_HOME},
{0x48, KEY_UP},
{0x49, KEY_PAGEUP},
{0x4B, KEY_LEFT},
{0x4D, KEY_RIGHT},
{0x4F, KEY_END},
{0x50, KEY_DOWN},
{0x51, KEY_PAGEDOWN},
{0x52, KEY_INSERT},
{0x53, KEY_DELETE},
{0x5B, KEY_LEFTMETA},
{0x5C, KEY_RIGHTMETA},
};
// One byte from the PS/2 port, before it has been turned into a character.
void
input_ps2_scancode(int raw, int e0)
{
int code = raw & 0x7F;
int down = (raw & 0x80) ? 0 : 1;
int key = 0;
unsigned i;
if(e0){
for(i = 0; i < sizeof(e0map)/sizeof(e0map[0]); i++)
if(e0map[i].sc == code){
key = e0map[i].key;
break;
}
if(key == 0)
return; // an E0 key nothing has a name for
} else {
if(code == 0 || code > 0x58)
return;
key = code;
}
input_report(INPUT_KBD, EV_KEY, key, down);
input_sync(INPUT_KBD);
}
// USB HID usage -> Linux keycode. The mapping is not derivable: HID numbers
// keys by position on a US layout starting at 'a', and the AT set numbers them
// by the order IBM wired the matrix. The table is the same one Linux carries
// for the same reason.
static const uint8 hid_keycode[] = {
0, 0, 0, 0, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38,
50, 49, 24, 25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 2, 3,
4, 5, 6, 7, 8, 9, 10, 11, 28, 1, 14, 15, 57, 12, 13, 26,
27, 43, 43, 39, 40, 41, 51, 52, 53, 58, 59, 60, 61, 62, 63, 64,
65, 66, 67, 68, 87, 88, 99, 70,119,110,102,104,111,107,109,106,
105,108,103, 69, 98, 55, 74, 78, 96, 79, 80, 81, 75, 76, 77, 71,
72, 73, 82, 83, 86,127,116,117,
};
// The eight modifier bits in a boot report, in bit order.
static const uint8 hid_modkey[8] = {
KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA,
KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA,
};
int
input_hid_keycode(int usage)
{
if(usage >= 0xE0 && usage <= 0xE7)
return hid_modkey[usage - 0xE0];
if(usage < 0 || usage >= (int)sizeof(hid_keycode))
return 0;
return hid_keycode[usage];
}
void
input_key(int usage, int down)
{
int key = input_hid_keycode(usage);
if(key == 0)
return;
input_report(INPUT_KBD, EV_KEY, key, down);
input_sync(INPUT_KBD);
}
// One mouse report: relative motion plus the current button state. `buttons`
// is a bitmask in the order both PS/2 and HID boot protocol use -- left,
// right, middle -- which is why neither caller has to reorder it.
void
input_mouse(int dx, int dy, int buttons)
{
static int last;
static const int btncode[3] = { BTN_LEFT, BTN_RIGHT, BTN_MIDDLE };
int i;
if(dx)
input_report(INPUT_MOUSE, EV_REL, REL_X, dx);
if(dy)
input_report(INPUT_MOUSE, EV_REL, REL_Y, dy);
// Only transitions. A button held down for a second is one press event, not
// a hundred -- a reader that has to filter repeats itself cannot tell a held
// button from a very fast double-click.
for(i = 0; i < 3; i++){
int now = (buttons >> i) & 1;
if(now != ((last >> i) & 1))
input_report(INPUT_MOUSE, EV_KEY, btncode[i], now);
}
last = buttons;
input_sync(INPUT_MOUSE);
}

214
kernel/dev/irq.c Normal file
View file

@ -0,0 +1,214 @@
// Hardware interrupt registration. See include/irq.h for why this exists.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "irq.h"
struct irq_action {
irq_handler_t fn;
void *arg;
const char *name;
uint64 count;
};
static struct {
struct irq_action act[NCHAIN];
int n; // handlers installed; only ever grows during boot
int enabled;
int level_low;
uint64 unhandled;
} line[NIRQ];
// Guards registration against itself. The *dispatch* path deliberately does
// not take it: an interrupt handler that blocked on a lock held by whoever
// was registering would deadlock the CPU it fired on. Instead registration
// publishes an entry by writing all of its fields, issuing a barrier, and
// only then incrementing n -- so a concurrent dispatcher either does not see
// the entry at all or sees it complete.
//
// Removal is the harder direction, because compacting the array *does* move
// entries a dispatcher may be walking. irq_unregister therefore masks the
// line first and only re-routes it once the chain has been rewritten: with
// the line masked no new interrupt can arrive, and any handler already
// running holds no reference past its own return.
static struct spinlock irqlock;
void
irq_init(void)
{
initlock(&irqlock, "irq");
}
// IRQ_ERROR and IRQ_SPURIOUS are not IOAPIC pins -- they are local APIC
// vectors that xv6 numbered as though they were, so T_IRQ0+19 and T_IRQ0+31
// are already spoken for. A PCI device routed to pin 19 would deliver on the
// same vector as the LAPIC error interrupt. Refuse it here rather than let it
// be debugged later.
static int
reserved(int irq)
{
return irq == IRQ_ERROR || irq == IRQ_SPURIOUS || irq == IRQ_TIMER;
}
int
irq_register(int irq, irq_handler_t fn, void *arg, const char *name,
int level_low)
{
struct irq_action *a;
if(irq < 0 || irq >= NIRQ || fn == 0)
return -1;
if(reserved(irq)){
cprintf("irq_register: %s wants irq %d, which is reserved\n", name, irq);
return -1;
}
acquire(&irqlock);
if(line[irq].n >= NCHAIN){
release(&irqlock);
cprintf("irq_register: irq %d chain full, %s not attached\n", irq, name);
return -1;
}
// Two devices sharing a line must agree on how it is signalled; they
// physically cannot be wired both ways.
if(line[irq].n > 0 && line[irq].level_low != level_low){
release(&irqlock);
cprintf("irq_register: irq %d shared with conflicting trigger mode "
"(%s)\n", irq, name);
return -1;
}
a = &line[irq].act[line[irq].n];
a->fn = fn;
a->arg = arg;
a->name = name;
a->count = 0;
__sync_synchronize(); // publish the entry before it is reachable
line[irq].n++;
if(!line[irq].enabled){
line[irq].level_low = level_low;
ioapicroute(irq, 0, level_low);
line[irq].enabled = 1;
}
release(&irqlock);
return 0;
}
void
irq_unregister(int irq, irq_handler_t fn, void *arg)
{
int i, j;
if(irq < 0 || irq >= NIRQ)
return;
acquire(&irqlock);
for(i = 0; i < line[irq].n; i++){
if(line[irq].act[i].fn == fn && line[irq].act[i].arg == arg){
// Mask before touching the chain, always -- not only when this is the
// last handler. Compacting the array moves entries that a dispatcher on
// another CPU may be walking, so the line has to be quiet for the
// duration, and then brought back only if anything is left on it.
ioapicmask(irq);
line[irq].enabled = 0;
for(j = i; j + 1 < line[irq].n; j++)
line[irq].act[j] = line[irq].act[j+1];
line[irq].n--;
__sync_synchronize();
if(line[irq].n > 0){
ioapicroute(irq, 0, line[irq].level_low);
line[irq].enabled = 1;
}
break;
}
}
release(&irqlock);
}
void
irq_dispatch(int irq)
{
int i, n;
if(irq < 0 || irq >= NIRQ)
return;
n = line[irq].n;
if(n == 0){
line[irq].unhandled++;
return;
}
// Every handler on a shared line is offered the interrupt; each one is
// responsible for recognising that its device is idle and returning.
for(i = 0; i < n; i++){
line[irq].act[i].count++;
line[irq].act[i].fn(line[irq].act[i].arg);
}
}
// ---------------------------------------------------------------------------
// /proc/interrupts
// ---------------------------------------------------------------------------
static int
emit(char *buf, int cap, int off, const char *s)
{
while(*s && off < cap)
buf[off++] = *s++;
return off;
}
static int
emit_dec(char *buf, int cap, int off, uint64 v, int width)
{
char tmp[24];
int n = 0, i;
if(v == 0)
tmp[n++] = '0';
while(v > 0){
tmp[n++] = '0' + (v % 10);
v /= 10;
}
for(i = n; i < width && off < cap; i++)
buf[off++] = ' ';
while(n > 0 && off < cap)
buf[off++] = tmp[--n];
return off;
}
int
irq_info(char *buf, int cap)
{
int irq, i, off = 0;
for(irq = 0; irq < NIRQ; irq++){
if(line[irq].n == 0 && line[irq].unhandled == 0)
continue;
off = emit_dec(buf, cap, off, irq, 4);
off = emit(buf, cap, off, ": ");
for(i = 0; i < line[irq].n; i++){
off = emit_dec(buf, cap, off, line[irq].act[i].count, 10);
off = emit(buf, cap, off, " ");
off = emit(buf, cap, off,
line[irq].act[i].name ? line[irq].act[i].name : "?");
off = emit(buf, cap, off, i + 1 < line[irq].n ? ", " : "");
}
if(line[irq].n == 0)
off = emit(buf, cap, off, "(unhandled)");
if(line[irq].unhandled)
off = emit(buf, cap, off, " [spurious]");
off = emit(buf, cap, off, line[irq].level_low ? " level\n" : " edge\n");
}
return off;
}

75
kernel/dev/kbd.c Normal file
View file

@ -0,0 +1,75 @@
#include "types.h"
#include "x86.h"
#include "defs.h"
#include "kbd.h"
#include "input.h"
int
kbdgetc(void)
{
static uint shift;
static uchar *charcode[4] = {
normalmap, shiftmap, ctlmap, ctlmap
};
uint st, data, c;
st = inb(KBSTATP);
if((st & KBS_DIB) == 0)
return -1;
if(st & 0x20)
return -1;
data = inb(KBDATAP);
if(data == 0xE0){
shift |= E0ESC;
return 0;
}
// Feed /dev/input/event0 before the byte is turned into a character. This
// has to happen here rather than in a second handler because the byte can
// only be read from the port once -- whoever reads it owes it to everyone
// else. The console path below is untouched: both consumers see every key.
input_ps2_scancode(data, (shift & E0ESC) != 0);
// While something holds an exclusive grab on the keyboard, the console gets
// nothing. The shift/caps state below is still tracked, so releasing the
// grab does not leave the line discipline believing a modifier is held.
if(input_is_grabbed(INPUT_KBD)){
if(data & 0x80){
data = (shift & E0ESC ? data : data & 0x7F);
shift &= ~(shiftcode[data] | E0ESC);
} else {
shift |= shiftcode[data];
shift ^= togglecode[data];
}
return 0;
}
if(data & 0x80){
data = (shift & E0ESC ? data : data & 0x7F);
shift &= ~(shiftcode[data] | E0ESC);
return 0;
} else if(shift & E0ESC){
data |= 0x80;
shift &= ~E0ESC;
}
shift |= shiftcode[data];
shift ^= togglecode[data];
c = charcode[shift & (CTL | SHIFT)][data];
if(shift & CAPSLOCK){
if('a' <= c && c <= 'z')
c += 'A' - 'a';
else if('A' <= c && c <= 'Z')
c += 'a' - 'A';
}
return c;
}
void
kbdintr(void)
{
consoleintr(kbdgetc);
}

214
kernel/dev/mouse.c Normal file
View file

@ -0,0 +1,214 @@
#include "types.h"
#include "defs.h"
#include "irq.h"
#include "x86.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#define KBD_S_STAT 0x64
#define KBD_S_DATA 0x60
#define KBD_C_WRITE 0xD4
#define MOUSE_ACK 0xFA
struct {
struct spinlock lock;
int x;
int y;
uint8 buttons;
} mouse_state;
static void
mouse_wait(int type)
{
int timeout = 100000;
if(type == 0){
while(timeout-- && !(inb(KBD_S_STAT) & 0x01));
} else {
while(timeout-- && (inb(KBD_S_STAT) & 0x02));
}
}
static void
mouse_write(uchar data)
{
mouse_wait(1);
outb(KBD_S_STAT, KBD_C_WRITE);
mouse_wait(1);
outb(KBD_S_DATA, data);
}
static uchar
mouse_read(void)
{
mouse_wait(0);
return inb(KBD_S_DATA);
}
static void
mouse_irq(void *arg)
{
(void)arg;
mouseintr();
}
// Move the pointer. Caller holds mouse_state.lock.
//
// Positive dy is *down*, matching both the screen and USB HID. The PS/2
// packet reports the opposite, which is why its caller negates first --
// there is one pointer and two protocols disagreeing about which way is up,
// and the conversion belongs at the edge rather than in here.
static void
mouse_move_locked(int dx, int dy)
{
mouse_state.x += dx;
mouse_state.y += dy;
if(mouse_state.x < 0) mouse_state.x = 0;
if(mouse_state.y < 0) mouse_state.y = 0;
if(mouse_state.x >= 800) mouse_state.x = 799;
if(mouse_state.y >= 600) mouse_state.y = 599;
}
// The same pointer, moved by something that is not the PS/2 port. USB HID
// uses this; anything else that produces motion events should too.
void
mouse_inject(int dx, int dy, int buttons)
{
acquire(&mouse_state.lock);
mouse_state.buttons = buttons;
mouse_move_locked(dx, dy);
release(&mouse_state.lock);
input_mouse(dx, dy, buttons);
}
void
mouseintr(void)
{
static uchar buf[3];
static int phase = 0;
uchar status;
status = inb(KBD_S_STAT);
if(!(status & 0x01) || !(status & 0x20)){
return;
}
uchar data = inb(KBD_S_DATA);
acquire(&mouse_state.lock);
if(phase == 0 && !(data & 0x08)){
release(&mouse_state.lock);
return;
}
buf[phase++] = data;
if(phase == 3){
phase = 0;
mouse_state.buttons = buf[0] & 0x07;
int dx = (buf[0] & 0x10) ? (int)buf[1] - 256 : buf[1];
int dy = (buf[0] & 0x20) ? (int)buf[2] - 256 : buf[2];
// The overflow bits mean the delta saturated and is meaningless; moving
// by it would throw the pointer across the screen.
if(!(buf[0] & 0x40) && !(buf[0] & 0x80))
mouse_move_locked(dx, -dy);
else
dx = dy = 0;
release(&mouse_state.lock);
// Outside the lock: input_report takes its own, and holding two at once
// is how a lock ordering rule gets invented by accident.
input_mouse(dx, -dy, buf[0] & 0x07);
return;
}
release(&mouse_state.lock);
}
int get_mouse_x(void) {
int x;
acquire(&mouse_state.lock);
x = mouse_state.x;
release(&mouse_state.lock);
return x;
}
int get_mouse_y(void) {
int y;
acquire(&mouse_state.lock);
y = mouse_state.y;
release(&mouse_state.lock);
return y;
}
uint8 get_mouse_buttons(void) {
uint8 b;
acquire(&mouse_state.lock);
b = mouse_state.buttons;
release(&mouse_state.lock);
return b;
}
int
mouseread(struct inode *ip, uint off, char *dst, int n)
{
struct mouse_data m;
int size = sizeof(struct mouse_data);
if(n < size) {
return -1;
}
m.x = get_mouse_x();
m.y = get_mouse_y();
m.buttons = get_mouse_buttons();
memmove(dst, &m, size);
return size;
}
void
mouseinit(void)
{
initlock(&mouse_state.lock, "mouse");
mouse_state.x = 400;
mouse_state.y = 300;
while(inb(KBD_S_STAT) & 0x01)
inb(KBD_S_DATA);
mouse_wait(1);
outb(KBD_S_STAT, 0xA8);
mouse_wait(1);
outb(KBD_S_STAT, 0x20);
mouse_wait(0);
uchar cb = inb(KBD_S_DATA);
cb |= 0x02;
cb |= 0x01;
cb &= ~0x20;
mouse_wait(1);
outb(KBD_S_STAT, 0x60);
mouse_wait(1);
outb(KBD_S_DATA, cb);
mouse_write(0xF6);
mouse_read();
mouse_write(0xF4);
mouse_read();
irq_register(IRQ_MOUSE, mouse_irq, 0, "ps2-mouse", 0);
}

492
kernel/dev/pci.c Normal file
View file

@ -0,0 +1,492 @@
// PCI bus enumeration.
//
// Everything here is done through the two-port configuration mechanism at
// 0xCF8/0xCFC: write a (bus, slot, function, register) address to one port,
// read or write the value at the other. It is the oldest and least clever way
// to reach config space, and it is the one that works everywhere without
// first having to find an ACPI table to tell you where the memory-mapped
// version lives.
//
// The scan is recursive rather than a flat sweep of all 256 buses. A flat
// sweep does find everything on a PC, but it also probes 8192 slots that are
// not there, and it cannot tell you which bridge a device sits behind -- and
// that matters as soon as interrupt routing does.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "x86.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "pci.h"
#include "device.h"
static struct pci_dev devs[NPCI];
static int ndevs;
static void scan_bus(uint8 bus);
// ---------------------------------------------------------------------------
// Configuration space access
// ---------------------------------------------------------------------------
static uint32
cfg_addr(uint8 bus, uint8 slot, uint8 func, uint8 off)
{
return 0x80000000u
| ((uint32)bus << 16)
| ((uint32)slot << 11)
| ((uint32)func << 8)
| (off & 0xFC); // the low two bits are not part of the address
}
uint32
pci_read32(uint8 bus, uint8 slot, uint8 func, uint8 off)
{
outl(PCI_CONFIG_ADDR, cfg_addr(bus, slot, func, off));
return inl(PCI_CONFIG_DATA);
}
void
pci_write32(uint8 bus, uint8 slot, uint8 func, uint8 off, uint32 val)
{
outl(PCI_CONFIG_ADDR, cfg_addr(bus, slot, func, off));
outl(PCI_CONFIG_DATA, val);
}
// Config space is only addressable in dwords, so narrower reads are a shift
// and a mask of the containing dword rather than a narrower bus cycle.
uint16
pci_read16(uint8 bus, uint8 slot, uint8 func, uint8 off)
{
return (uint16)(pci_read32(bus, slot, func, off) >> ((off & 2) * 8));
}
uint8
pci_read8(uint8 bus, uint8 slot, uint8 func, uint8 off)
{
return (uint8)(pci_read32(bus, slot, func, off) >> ((off & 3) * 8));
}
void
pci_enable(struct pci_dev *d, uint16 bits)
{
uint32 cmd = pci_read32(d->bus, d->slot, d->func, PCI_COMMAND);
pci_write32(d->bus, d->slot, d->func, PCI_COMMAND, cmd | bits);
}
// ---------------------------------------------------------------------------
// Capabilities
//
// Everything a device can do beyond the 64 bytes of standard config header --
// MSI, MSI-X, power management, and the whole of virtio's configuration -- is
// described by a singly linked list of capability structures living in the
// device's config space. A driver that needs any of it has to walk that list.
// ---------------------------------------------------------------------------
// Returns the config-space offset of the first capability with this ID, or 0.
uint8
pci_find_cap(struct pci_dev *d, uint8 id)
{
uint8 off;
int guard;
if(!(pci_read16(d->bus, d->slot, d->func, PCI_STATUS) & PCI_STATUS_CAPLIST))
return 0;
off = pci_read8(d->bus, d->slot, d->func, PCI_CAP_PTR) & 0xFC;
// The list is built by the device, so it can be circular if the device is
// broken or if we misread it. 48 is more capabilities than the 192 usable
// bytes can hold; past that we are certainly looping.
for(guard = 0; off >= 0x40 && guard < 48; guard++){
if(pci_read8(d->bus, d->slot, d->func, off) == id)
return off;
off = pci_read8(d->bus, d->slot, d->func, off + 1) & 0xFC;
}
return 0;
}
// Point a device's MSI at a CPU and a vector.
//
// MSI is not a wire: it is a memory write the device performs to a magic
// address that the local APIC decodes as an interrupt. That makes it strictly
// better than INTx for anything modern -- no sharing, no level/edge question,
// and no chance of the interrupt arriving before the DMA it announces, since
// both are posted writes travelling the same path in order.
//
// Returns 0 on success, -1 if the device has no MSI capability.
int
pci_msi_enable(struct pci_dev *d, int vector, int cpu)
{
uint8 cap = pci_find_cap(d, PCI_CAP_MSI);
uint16 ctl;
if(cap == 0)
return -1;
ctl = pci_read16(d->bus, d->slot, d->func, cap + 2);
// 0xFEE00000 | (apic id << 12) is the LAPIC's message address; the data
// word is the vector, with delivery mode 000 (fixed) and edge trigger.
pci_write32(d->bus, d->slot, d->func, cap + 4,
0xFEE00000u | ((uint32)cpu << 12));
if(ctl & (1 << 7)){ // 64-bit address capable: data moved to +12
pci_write32(d->bus, d->slot, d->func, cap + 8, 0);
pci_write32(d->bus, d->slot, d->func, cap + 12, (uint32)vector);
} else {
pci_write32(d->bus, d->slot, d->func, cap + 8, (uint32)vector);
}
// Enable, and request exactly one vector (multiple-message-enable = 0).
// Message control shares its dword with the capability ID and next pointer,
// and config space is only writable a dword at a time, so read-modify-write
// the whole thing rather than clobbering the list this capability is on.
{
uint32 dw = pci_read32(d->bus, d->slot, d->func, cap);
ctl = (ctl & ~0x0070) | 1;
dw = (dw & 0x0000FFFF) | ((uint32)ctl << 16);
pci_write32(d->bus, d->slot, d->func, cap, dw);
}
// INTx must be masked or the device will signal both.
pci_write32(d->bus, d->slot, d->func, PCI_COMMAND,
pci_read32(d->bus, d->slot, d->func, PCI_COMMAND) | (1 << 10));
return 0;
}
// ---------------------------------------------------------------------------
// Base address registers
// ---------------------------------------------------------------------------
// A BAR does not report its own size. You find it by writing all ones and
// reading back: the device leaves the bits it does not decode as zero, so the
// size is one more than the inverse of what comes back. The original value
// has to be put back afterwards -- this is a live device whose decoder is
// briefly pointed at the top of the address space while we look.
static void
read_bars(struct pci_dev *d)
{
for (int i = 0; i < 6; i++) {
uint8 off = PCI_BAR0 + i * 4;
uint32 lo = pci_read32(d->bus, d->slot, d->func, off);
if (lo == 0)
continue;
pci_write32(d->bus, d->slot, d->func, off, 0xFFFFFFFF);
uint32 mask = pci_read32(d->bus, d->slot, d->func, off);
pci_write32(d->bus, d->slot, d->func, off, lo);
if (mask == 0)
continue;
if (lo & 1) { // I/O space
d->bar[i].is_io = 1;
d->bar[i].base = lo & ~0x3u;
d->bar[i].size = (~(mask & ~0x3u) + 1) & 0xFFFF;
continue;
}
d->bar[i].prefetch = (lo >> 3) & 1;
if (((lo >> 1) & 3) == 2) { // 64-bit BAR: eats the next slot
uint32 hi = pci_read32(d->bus, d->slot, d->func, off + 4);
pci_write32(d->bus, d->slot, d->func, off + 4, 0xFFFFFFFF);
uint32 hmask = pci_read32(d->bus, d->slot, d->func, off + 4);
pci_write32(d->bus, d->slot, d->func, off + 4, hi);
d->bar[i].is_64 = 1;
d->bar[i].base = ((uint64)hi << 32) | (lo & ~0xFu);
d->bar[i].size = ~(((uint64)hmask << 32) | (mask & ~0xFu)) + 1;
i++; // the upper half is not its own BAR
continue;
}
d->bar[i].base = lo & ~0xFu;
d->bar[i].size = (~(mask & ~0xFu) + 1) & 0xFFFFFFFF;
}
}
// ---------------------------------------------------------------------------
// Enumeration
// ---------------------------------------------------------------------------
static void
add_func(uint8 bus, uint8 slot, uint8 func)
{
uint16 vendor = pci_read16(bus, slot, func, PCI_VENDOR_ID);
if (vendor == 0xFFFF) // nothing plugged in
return;
if (ndevs >= NPCI) {
cprintf("pci: more than %d devices, ignoring the rest\n", NPCI);
return;
}
struct pci_dev *d = &devs[ndevs++];
d->bus = bus;
d->slot = slot;
d->func = func;
d->vendor = vendor;
d->device = pci_read16(bus, slot, func, PCI_DEVICE_ID);
d->revision = pci_read8(bus, slot, func, PCI_REVISION);
d->prog_if = pci_read8(bus, slot, func, PCI_PROG_IF);
d->subclass = pci_read8(bus, slot, func, PCI_SUBCLASS);
d->class = pci_read8(bus, slot, func, PCI_CLASS);
d->header_type = pci_read8(bus, slot, func, PCI_HEADER_TYPE);
d->irq = pci_read8(bus, slot, func, PCI_INTERRUPT_LINE);
// Only header type 0 has six BARs. A bridge (type 1) has two and a very
// different layout, and reading it as though it had six would hand out
// window registers as if they were device apertures.
if ((d->header_type & 0x7F) == 0)
read_bars(d);
// Walk down through a PCI-to-PCI bridge to whatever is behind it.
if ((d->header_type & 0x7F) == 1) {
uint8 secondary = pci_read8(bus, slot, func, PCI_SECONDARY_BUS);
if (secondary != 0 && secondary != bus)
scan_bus(secondary);
}
}
static void
scan_bus(uint8 bus)
{
for (uint8 slot = 0; slot < 32; slot++) {
if (pci_read16(bus, slot, 0, PCI_VENDOR_ID) == 0xFFFF)
continue;
add_func(bus, slot, 0);
// Bit 7 of the header type says the device has more than one function.
// Probing all eight unconditionally is harmless on real hardware but
// produces phantom entries on some emulated devices, which answer every
// function with a copy of function 0.
if (pci_read8(bus, slot, 0, PCI_HEADER_TYPE) & 0x80)
for (uint8 func = 1; func < 8; func++)
add_func(bus, slot, func);
}
}
// ---------------------------------------------------------------------------
// Lookup
// ---------------------------------------------------------------------------
int
pci_count(void)
{
return ndevs;
}
struct pci_dev *
pci_get(int i)
{
return (i >= 0 && i < ndevs) ? &devs[i] : 0;
}
struct pci_dev *
pci_find_class(uint8 class, uint8 subclass)
{
for (int i = 0; i < ndevs; i++)
if (devs[i].class == class && devs[i].subclass == subclass)
return &devs[i];
return 0;
}
struct pci_dev *
pci_find_id(uint16 vendor, uint16 device)
{
for (int i = 0; i < ndevs; i++)
if (devs[i].vendor == vendor && devs[i].device == device)
return &devs[i];
return 0;
}
// ---------------------------------------------------------------------------
// Driver binding
// ---------------------------------------------------------------------------
static int
matches(const struct pci_match *m, struct pci_dev *d)
{
if (m->vendor != PCI_ANY)
return m->vendor == d->vendor &&
(m->device == PCI_ANY || m->device == d->device);
// A class match, for drivers that handle a kind of device rather than a
// particular model.
return m->class == d->class &&
(m->subclass == 0xFF || m->subclass == d->subclass);
}
// Offer every unclaimed device to a driver. Called by pci_init() for each
// entry in the registry; see kernel/drivers/registry.c.
// Defined further down with the /proc/pci formatting it was written for.
static const char *class_name(uint8 class, uint8 subclass);
// Translate a PCI class code into the bus-agnostic one the device model uses.
//
// The two vocabularies are deliberately separate. PCI's class byte describes
// how the card presents itself on that bus; DEVCLASS describes what the rest of
// the kernel can *do* with it, and a USB disk has to land in the same bucket as
// a SATA one despite sharing no numbering with it at all.
enum dev_class
pci_class_of(const struct pci_dev *d)
{
switch (d->class) {
case PCI_CLASS_DISPLAY: return DEVCLASS_DISPLAY;
case PCI_CLASS_STORAGE: return DEVCLASS_STORAGE;
case PCI_CLASS_NETWORK: return DEVCLASS_NET;
case PCI_CLASS_SERIAL_BUS: return DEVCLASS_BUS;
default: return DEVCLASS_OTHER;
}
}
// Human-readable description, the same text /proc/pci prints for the device.
void
pci_describe(const struct pci_dev *d, char *out, int cap)
{
const char *s = class_name(d->class, d->subclass);
int n = 0;
while (s[n] && n < cap - 1) {
out[n] = s[n];
n++;
}
out[n] = '\0';
}
void
pci_bind(const struct pci_driver *drv)
{
for (int i = 0; i < ndevs; i++) {
struct pci_dev *d = &devs[i];
if (d->driver)
continue;
for (const struct pci_match *m = drv->match; m->vendor || m->class; m++) {
if (!matches(m, d))
continue;
if (drv->probe(d) == 0) {
d->driver = drv->name;
// Publish it in the bus-agnostic device list. Done here rather than in
// each driver's probe() so that a driver cannot forget to, and so the
// list is populated in bind order -- which is the order a device
// manager should show.
struct device *dev = device_add(drv->devname ? drv->devname : drv->name,
DEVBUS_PCI, pci_class_of(d), drv->name);
if (dev) {
dev->ops = (void *)drv->ops;
dev->vendor = d->vendor;
dev->product = d->device;
dev->b = d->bus; dev->s = d->slot; dev->f = d->func;
pci_describe(d, dev->desc, sizeof(dev->desc));
}
}
break;
}
}
}
// ---------------------------------------------------------------------------
// Fixed-width lowercase hex, the way lspci prints addresses.
static int
put_hex(char *out, uint64 v, int digits)
{
static const char d[] = "0123456789abcdef";
for (int i = digits - 1; i >= 0; i--)
*out++ = d[(v >> (i * 4)) & 0xF];
return digits;
}
static int
put_str(char *out, const char *s)
{
int n = 0;
while (s[n]) { out[n] = s[n]; n++; }
return n;
}
static const char *
class_name(uint8 class, uint8 subclass)
{
switch (class) {
case PCI_CLASS_STORAGE:
return subclass == 0x01 ? "IDE controller"
: subclass == 0x06 ? "SATA controller"
: subclass == 0x08 ? "NVMe controller"
: "storage controller";
case PCI_CLASS_NETWORK: return "network controller";
case PCI_CLASS_DISPLAY: return "display controller";
case PCI_CLASS_MULTIMEDIA: return "multimedia device";
case PCI_CLASS_BRIDGE:
return subclass == 0x00 ? "host bridge"
: subclass == 0x01 ? "ISA bridge"
: subclass == 0x04 ? "PCI bridge"
: "bridge";
case PCI_CLASS_SERIAL_BUS:
return subclass == 0x03 ? "USB controller" : "serial bus controller";
}
return "device";
}
// Fills a buffer for /proc/pci. Returns bytes written.
//
// There is no snprintf in the kernel, so this appends by hand in the same
// style as kernel/dev/procfs.c. The output is deliberately lspci-shaped --
// "bus:slot.func vendor:device description" -- because that is the format
// anyone reading it will already know how to scan.
int
pci_info(char *buf, int cap)
{
int len = 0;
for (int i = 0; i < ndevs; i++) {
struct pci_dev *d = &devs[i];
if (len > cap - 96) // room for one more line
break;
len += put_hex(buf + len, d->bus, 2);
buf[len++] = ':';
len += put_hex(buf + len, d->slot, 2);
buf[len++] = '.';
len += put_hex(buf + len, d->func, 1);
buf[len++] = ' ';
len += put_hex(buf + len, d->vendor, 4);
buf[len++] = ':';
len += put_hex(buf + len, d->device, 4);
buf[len++] = ' ';
len += put_str(buf + len, class_name(d->class, d->subclass));
len += put_str(buf + len, " [");
len += put_str(buf + len, d->driver ? d->driver : "-");
buf[len++] = ']';
buf[len++] = '\n';
}
return len;
}
void
pci_init(void)
{
ndevs = 0;
// If there is no host bridge at 0:0.0 there is no PCI, and every config
// read will come back 0xFFFFFFFF. Saying so once beats 8192 silent probes.
if (pci_read16(0, 0, 0, PCI_VENDOR_ID) == 0xFFFF) {
cprintf("pci: no host bridge; bus not present\n");
return;
}
scan_bus(0);
cprintf("pci: %d device%s\n", ndevs, ndevs == 1 ? "" : "s");
pci_bind_all();
}

431
kernel/dev/procfs.c Normal file
View file

@ -0,0 +1,431 @@
// /proc — synthesised kernel status files.
//
// Each file is a character device node under major PROCFS_MAJOR, distinguished
// by its minor number, created by init at boot. Reading one renders the whole
// text into a scratch buffer and returns the slice starting at the caller's
// file offset, so a normal read-until-zero loop terminates.
//
// SCOPE: these are the *global* files. Per-process directories (/proc/<pid>/…)
// are not here, because they need something this kernel does not have yet:
// synthetic directories, i.e. a directory whose entries are generated at
// lookup and getdents time rather than read from disk. That is a real VFS
// feature touching iget/ilock/dirlookup/getdents, and it is the natural
// companion to the SchFS work rather than a bolt-on to the current fs.c.
// The namespace (which files exist, including one directory per process) is
// kernel/fs/procfs.c; this file only generates the *content* of the global
// files. `ps` and `top` work through the former.
#include "types.h"
#include "defs.h"
#include "irq.h"
#include "x86.h"
#include "device.h"
#include "display.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "proc.h"
#include "uaccess.h"
#include "errno.h"
#define PROC_BUFSZ 2048
// --- tiny formatting helpers (no snprintf in the kernel) -------------------
struct sbuf {
char *p;
int len;
int cap;
};
static void
sb_putc(struct sbuf *sb, char c)
{
if (sb->len < sb->cap - 1)
sb->p[sb->len++] = c;
}
static void
sb_puts(struct sbuf *sb, const char *s)
{
while (*s)
sb_putc(sb, *s++);
}
// A string in a fixed-width column, space-padded. cprintf has no width
// specifier and neither does this buffer, so tables build their own columns.
// Over-long values are allowed to run past their column rather than be
// truncated: a misaligned row is easier to read than a mangled name.
static void
sb_pad(struct sbuf *sb, const char *s, int width)
{
int n = 0;
while (s[n]) {
sb_putc(sb, s[n]);
n++;
}
while (n++ < width)
sb_putc(sb, ' ');
}
// Four lowercase hex digits, zero-padded. PCI and USB identifiers are always
// written this way, and a lookup table keyed on "8086:100e" does not match
// "8086:100E" or "8086:10e".
static void
sb_puthex4(struct sbuf *sb, uint32 v)
{
static const char hex[] = "0123456789abcdef";
for (int i = 3; i >= 0; i--)
sb_putc(sb, hex[(v >> (i * 4)) & 0xF]);
}
static void
sb_putu(struct sbuf *sb, uint64 v)
{
char tmp[24];
int i = 0;
if (v == 0) { sb_putc(sb, '0'); return; }
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (v % 10); v /= 10; }
while (i--) sb_putc(sb, tmp[i]);
}
// "<label><value> <unit>\n" — the shape almost every /proc line takes.
static void
sb_kv(struct sbuf *sb, const char *label, uint64 v, const char *unit)
{
sb_puts(sb, label);
sb_putu(sb, v);
if (unit && *unit) { sb_putc(sb, ' '); sb_puts(sb, unit); }
sb_putc(sb, '\n');
}
// --- the files -------------------------------------------------------------
// PHYSTOP is the top of usable physical memory; kalloc hands out everything
// above the kernel image. Reporting the total honestly and leaving "free" as a
// share of it is better than the invented constants sysinfo() used to return.
#define TOTAL_KB (PHYSTOP / 1024)
static void
gen_meminfo(struct sbuf *sb)
{
uint64 total = TOTAL_KB;
uint64 freek = kalloc_free_bytes() / 1024;
sb_kv(sb, "MemTotal: ", total, "kB");
sb_kv(sb, "MemFree: ", freek, "kB");
sb_kv(sb, "MemAvailable: ", freek, "kB");
sb_kv(sb, "Buffers: ", 0, "kB");
sb_kv(sb, "Cached: ", 0, "kB");
sb_kv(sb, "SwapTotal: ", 0, "kB");
sb_kv(sb, "SwapFree: ", 0, "kB");
}
static void
gen_uptime(struct sbuf *sb)
{
uint t = uptime(); // timer ticks, 100 Hz
sb_putu(sb, t / 100);
sb_putc(sb, '.');
sb_putu(sb, (t % 100) / 10);
sb_putu(sb, (t % 100) % 10);
sb_puts(sb, " 0.00\n"); // idle time: not tracked
}
static void
gen_cpuinfo(struct sbuf *sb)
{
// Online CPUs, not present ones -- the same rule Linux follows, and the
// reason matters: something reading this to size a thread pool would
// otherwise be told a VM with four processors has four to schedule on, when
// three of them are enumerated and idle. "cpu cores" stays the present count,
// which is what that field means.
// Real values, from CPUID. These used to read "Schunix" and "Schunix virtual
// CPU" -- a thing to print when you have not asked the processor, and
// actively unhelpful, because identifying the machine is the one question
// /proc/cpuinfo exists to answer.
//
// Leaf 0 returns the vendor string in EBX:EDX:ECX, in that order, which is
// not the order anyone expects. Leaves 0x80000002..4 hold the 48-character
// brand string. Leaf 0x80000000 reports the highest extended leaf, and asking
// for one the CPU does not implement returns the highest supported leaf's
// value rather than an error -- so checking first is not optional.
char vendor[13], brand[49];
uint32 a, b, c, d;
cpuid_leaf(0, &a, &b, &c, &d);
*(uint32 *)&vendor[0] = b;
*(uint32 *)&vendor[4] = d;
*(uint32 *)&vendor[8] = c;
vendor[12] = '\0';
brand[0] = '\0';
cpuid_leaf(0x80000000, &a, 0, 0, 0);
if (a >= 0x80000004) {
for (int leaf = 0; leaf < 3; leaf++) {
cpuid_leaf(0x80000002 + leaf, &a, &b, &c, &d);
*(uint32 *)&brand[leaf * 16 + 0] = a;
*(uint32 *)&brand[leaf * 16 + 4] = b;
*(uint32 *)&brand[leaf * 16 + 8] = c;
*(uint32 *)&brand[leaf * 16 + 12] = d;
}
brand[48] = '\0';
}
// Intel pads the brand string with leading spaces, which read as a broken
// field rather than a name.
const char *model = brand;
while (*model == ' ')
model++;
if (*model == '\0')
model = "unknown";
for (int i = 0; i < ncpu_online; i++) {
sb_kv(sb, "processor\t: ", i, 0);
sb_puts(sb, "vendor_id\t: ");
sb_puts(sb, vendor);
sb_putc(sb, '\n');
sb_puts(sb, "model name\t: ");
sb_puts(sb, model);
sb_putc(sb, '\n');
sb_kv(sb, "cpu cores\t: ", ncpu, 0);
sb_putc(sb, '\n');
}
if (ncpu_online < ncpu) {
sb_kv(sb, "cpus present\t: ", ncpu, 0);
sb_puts(sb, "note\t\t: additional processors are enumerated but not "
"started; see docs/roadmap.md\n");
}
}
static void
gen_version(struct sbuf *sb)
{
sb_puts(sb, "Schunix version 0.1.0 (schunix) #1 SMP x86_64\n");
}
static void
gen_stat(struct sbuf *sb)
{
uint t = uptime();
sb_puts(sb, "cpu ");
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
// Online, for the same reason as cpuinfo: top and friends count the cpuN
// lines here to decide how many cores to draw.
for (int i = 0; i < ncpu_online; i++) {
sb_puts(sb, "cpu");
sb_putu(sb, i);
sb_putc(sb, ' ');
sb_putu(sb, t); sb_puts(sb, " 0 0 0 0 0 0 0 0 0\n");
}
sb_kv(sb, "processes ", (uint64)proc_num(), 0);
sb_kv(sb, "procs_running ", 1, 0);
sb_kv(sb, "btime ", 0, 0);
}
// Everything attached, whatever bus found it. The one place that answers "what
// is in this machine" across PCI, USB and platform devices at once -- which is
// the question a device manager is, so this is where one would read from.
//
// Columns rather than key/value: this is a table of like things, and a reader
// scanning for an unclaimed device wants the driver column to line up.
static void
gen_devices(struct sbuf *sb)
{
sb_puts(sb, "name bus class driver id device\n");
for (int i = 0; i < device_count(); i++) {
struct device *d = device_at(i);
if (!d)
break;
// 14 for the name and driver columns: "virtio-gpu0" is eleven characters
// and overran a ten-wide column, which sb_pad allows on purpose but which
// made the whole table unreadable.
sb_pad(sb, d->name, 14);
sb_pad(sb, dev_bus_name(d->bus), 10);
sb_pad(sb, dev_class_name(d->class), 10);
// An unclaimed device is the interesting row, so it gets a visible marker
// rather than an empty column.
sb_pad(sb, d->driver ? d->driver : "(none)", 14);
// vendor:product, the way lspci and lsusb print it. The kernel has no
// business resolving these to names -- that is what /usr/share/usb.ids is
// for, and it belongs to a userspace tool. What the kernel owes is the
// number, in the form a lookup expects.
if (d->vendor || d->product) {
sb_puthex4(sb, d->vendor);
sb_putc(sb, ':');
sb_puthex4(sb, d->product);
sb_putc(sb, ' ');
} else {
sb_puts(sb, " ");
}
sb_puts(sb, d->desc[0] ? d->desc : "-");
sb_putc(sb, '\n');
}
}
static void
gen_loadavg(struct sbuf *sb)
{
// No load tracking; report zeros but a truthful process count.
sb_puts(sb, "0.00 0.00 0.00 1/");
sb_putu(sb, (uint64)proc_num());
sb_puts(sb, " 1\n");
}
// df and mountpoint parse this. One root filesystem, no mount table yet.
static void
gen_mounts(struct sbuf *sb)
{
// Root must be last: df keeps the last mount point that prefixes the path
// it was asked about, so listing / first made `df /` report /proc instead.
sb_puts(sb, "devtmpfs /dev devtmpfs rw 0 0\n");
sb_puts(sb, "proc /proc proc rw 0 0\n");
sb_puts(sb, "/dev/root / schfs rw 0 0\n");
}
static void
gen_filesystems(struct sbuf *sb)
{
sb_puts(sb, "\tschfs\n");
sb_puts(sb, "nodev\tproc\n");
sb_puts(sb, "nodev\tdevtmpfs\n");
}
// --- dispatch --------------------------------------------------------------
#define PROC_MEMINFO 1
#define PROC_UPTIME 2
#define PROC_CPUINFO 3
#define PROC_VERSION 4
#define PROC_STAT 5
#define PROC_LOADAVG 6
#define PROC_MOUNTS 7
#define PROC_FILESYSTEMS 8
#define PROC_SCHFS 9
#define PROC_SYSRQ 10
#define PROC_PCI 11
#define PROC_INTERRUPTS 12
#define PROC_DEVICES 13
// Render global file `id` (a PROC_* constant) into `buf`. Returns its length.
//
// Split out of procread() so the /proc *filesystem* (kernel/fs/procfs.c) can
// use the same generators: that file owns the namespace, this one owns the
// content, and they have no other business with each other.
int
proc_gen_global(int id, char *buf, int cap)
{
struct sbuf sb = { buf, 0, cap };
switch (id) {
case PROC_MEMINFO: gen_meminfo(&sb); break;
case PROC_UPTIME: gen_uptime(&sb); break;
case PROC_CPUINFO: gen_cpuinfo(&sb); break;
case PROC_VERSION: gen_version(&sb); break;
case PROC_STAT: gen_stat(&sb); break;
case PROC_LOADAVG: gen_loadavg(&sb); break;
case PROC_MOUNTS: gen_mounts(&sb); break;
case PROC_FILESYSTEMS: gen_filesystems(&sb); break;
// SchFS renders straight into the buffer rather than through sbuf, because
// it lives in kernel/fs/schfs and must not depend on this file's helpers.
case PROC_SCHFS: sb.len = schfs_kinfo(buf, PROC_BUFSZ); break;
// Same arrangement as SchFS above: pci.c renders its own lines so that it
// does not depend on this file's helpers.
case PROC_PCI: sb.len = pci_info(buf, PROC_BUFSZ); break;
// Likewise irq.c: it owns the table, so it renders it.
case PROC_INTERRUPTS: sb.len = irq_info(buf, PROC_BUFSZ); break;
case PROC_DEVICES: gen_devices(&sb); break;
default: return 0;
}
return sb.len;
}
int
procread(struct inode *ip, uint off, char *dst, int n)
{
char buf[PROC_BUFSZ];
int len = proc_gen_global(ip->minor, buf, PROC_BUFSZ);
if (len <= 0 || (int)off >= len)
return 0; // EOF
int avail = len - (int)off;
if (n > avail)
n = avail;
memmove(dst, buf + off, n);
return n;
}
// /proc is read-only, with one deliberate exception.
//
// /proc/sysrq is the same idea as Linux's sysrq-trigger: a way to make the
// kernel do something drastic on purpose. Right now the only command is 'c',
// crash — which exists because the crash screen is otherwise the one part of
// the system that can only be tested by having a bug. A screen you cannot look
// at until something goes wrong is a screen nobody has ever actually read.
// One sysrq command byte. Shared by the device path (procwrite, below) and
// the filesystem path (kernel/fs/procfs.c), so the two cannot drift.
static void
proc_sysrq_cmd(char cmd)
{
switch (cmd) {
case 'c':
panic("sysrq: crash requested from /proc/sysrq");
case 'u':
xhci_dump();
break;
case 's':
usb_storage_dump();
break;
case 'd':
display_selftest();
break;
case 'n':
e1000_selftest();
break;
case 't':
text_selftest();
break;
default:
cprintf("sysrq: unknown command '%c' "
"(known: c = crash, u = dump USB, s = read the USB disk, "
"d = display self-test)\n",
cmd);
break;
}
}
// `user_src` says whether `src` is a user address that still needs copying in;
// the filesystem hands one over, the device path has already copied.
int
proc_write_sysrq(int user_src, uint64 src, uint n)
{
char c;
if (n < 1)
return -1;
if (user_src) {
if (copy_from_user(&c, src, 1) < 0)
return -EFAULT;
} else {
c = *(char *)src;
}
proc_sysrq_cmd(c);
return (int)n;
}
// The character-device path: /proc/sysrq reached through the old mknod'd node
// rather than through the /proc filesystem. `src` is already a kernel buffer
// here, which is why it passes user_src = 0.
int
procwrite(struct inode *ip, uint off, char *src, int n)
{
(void)off;
if (ip->minor != PROC_SYSRQ)
return -1;
return proc_write_sysrq(0, (uint64)src, (uint)n);
}

92
kernel/dev/ramdisk.c Normal file
View file

@ -0,0 +1,92 @@
// Memory-backed block device.
//
// Serves an entire SCHFS image out of RAM, so the system can boot and run a
// shell with the physical disk untouched. That is what makes destructive work
// on the real disk survivable: reformatting, running mkfs, or bringing up a new
// on-disk format cannot take away the root you are running from. It is also the
// first piece of the installer -- an install environment is exactly "a root in
// RAM plus a disk you are allowed to destroy".
//
// The image is linked into the kernel as a binary blob (see the ramdisk_obj
// rule in the Makefile) and is served in place: the blob lives in writable
// kernel memory, so writes to the ramdisk simply modify it. They are lost on
// reboot, which is the expected behaviour for this kind of root.
//
// Enabled by building with RAMROOT=1. Without it this file still compiles, the
// device reports zero size, and the kernel boots from the IDE disk as before.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "memlayout.h"
#include "mmu.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "buf.h"
#ifdef RAMDISK_ROOT
// Supplied by `ld -r -b binary` over build/fs-ram.img, renamed by objcopy.
extern uchar _binary_ramdisk_start[];
extern uchar _binary_ramdisk_size[];
#endif
static uchar *rd_base;
static uint64 rd_size; // bytes
static uint rd_blocks; // rd_size / BSIZE
void
ramdiskinit(void)
{
#ifdef RAMDISK_ROOT
rd_base = _binary_ramdisk_start;
rd_size = (uint64)_binary_ramdisk_size;
rd_blocks = (uint)(rd_size / BSIZE);
if(rd_blocks == 0)
panic("ramdiskinit: embedded image is empty");
// A truncated image is worth catching here rather than as a confusing
// "invalid file system" panic later: the superblock lives in block 1.
if(rd_blocks < 2)
panic("ramdiskinit: embedded image smaller than a superblock");
cprintf("ramdisk: %u blocks (%u KB) at %p\n",
rd_blocks, (uint)(rd_size / 1024), rd_base);
#else
rd_base = 0;
rd_size = 0;
rd_blocks = 0;
#endif
}
int
ramdisk_present(void)
{
return rd_blocks != 0;
}
// Sync buf with the in-memory image. Mirrors iderw()'s contract: on a read,
// fill b->data and set B_VALID; on a write, copy out and clear B_DIRTY.
void
ramdiskrw(struct buf *b)
{
if(!holdingsleep(&b->lock))
panic("ramdiskrw: buf not locked");
if((b->flags & (B_VALID|B_DIRTY)) == B_VALID)
panic("ramdiskrw: nothing to do");
if(rd_base == 0)
panic("ramdiskrw: no ramdisk");
if(b->blockno >= rd_blocks)
panic("ramdiskrw: block out of range");
uchar *p = rd_base + (uint64)b->blockno * BSIZE;
if(b->flags & B_DIRTY){
memmove(p, b->data, BSIZE);
b->flags &= ~B_DIRTY;
} else {
memmove(b->data, p, BSIZE);
}
b->flags |= B_VALID;
}

242
kernel/dev/text.c Normal file
View file

@ -0,0 +1,242 @@
// Text rendering. See include/text.h for what this replaces and why.
#include "types.h"
#include "defs.h"
#include "param.h"
#include "text.h"
// Declared, not included. include/font.h *defines* font_data rather than
// declaring it, so including it here as well as in vga.c gives two definitions
// of the same array and the link fails. The header is generated data; leaving
// it alone and referring to the symbol is less fragile than editing it.
extern unsigned char font_data[256][16];
// --- UTF-8 -----------------------------------------------------------------
uint32
utf8_next(const char **p)
{
const unsigned char *s = (const unsigned char *)*p;
uint32 c = s[0];
if (c < 0x80) {
*p += 1;
return c;
}
int extra;
uint32 cp;
if ((c & 0xE0) == 0xC0) { extra = 1; cp = c & 0x1F; }
else if ((c & 0xF0) == 0xE0) { extra = 2; cp = c & 0x0F; }
else if ((c & 0xF8) == 0xF0) { extra = 3; cp = c & 0x07; }
else {
// A continuation byte or 0xF8..0xFF where a lead byte should be. Consume
// exactly one so a malformed string cannot stall the loop -- a decoder that
// does not advance on bad input is a hang, not a rendering artefact.
*p += 1;
return 0xFFFD;
}
for (int i = 1; i <= extra; i++) {
if ((s[i] & 0xC0) != 0x80) { // truncated sequence
*p += 1;
return 0xFFFD;
}
cp = (cp << 6) | (s[i] & 0x3F);
}
*p += extra + 1;
// Surrogates are not characters, and an overlong encoding is the classic way
// to smuggle a byte past a filter that checks the decoded form.
if (cp >= 0xD800 && cp <= 0xDFFF)
return 0xFFFD;
if ((extra == 1 && cp < 0x80) || (extra == 2 && cp < 0x800) ||
(extra == 3 && cp < 0x10000))
return 0xFFFD;
return cp;
}
// --- the built-in bitmap font as a provider --------------------------------
//
// font_data is 256 CP437 glyphs of 8x16, one bit per pixel. Expanded here into
// 8-bit coverage so that every caller works in one representation and an
// outline rasteriser can be added without touching any of them.
//
// The expansion buffer is static and reused per glyph. That is fine because
// glyphs are consumed immediately by the blitter and nothing holds one; it
// would not be fine the moment a caller kept a struct glyph across calls, which
// is why the header says nothing about the lifetime being longer than that.
#define BUILTIN_W 8
#define BUILTIN_H 16
static unsigned char builtin_cov[BUILTIN_W * BUILTIN_H];
// Unicode to CP437, for the range where they differ and it matters.
//
// The built-in font is CP437, not Latin-1 -- a fact that has bitten this
// codebase before. Rather than pretend otherwise, map the handful of
// codepoints worth having and fall back for the rest. This table exists to be
// *deleted* when a real font arrives; it is a bridge, not a design.
static uint32
to_cp437(uint32 cp)
{
if (cp < 0x80)
return cp;
switch (cp) {
case 0x00A0: return 0x20; // no-break space
case 0x00B0: return 0xF8; // degree
case 0x00B1: return 0xF1; // plus-minus
case 0x00B7: return 0xFA; // middle dot
case 0x2022: return 0x07; // bullet
case 0x2190: return 0x1B; // arrows
case 0x2191: return 0x18;
case 0x2192: return 0x1A;
case 0x2193: return 0x19;
case 0x2500: return 0xC4; // box drawing, the part worth keeping
case 0x2502: return 0xB3;
case 0x250C: return 0xDA;
case 0x2510: return 0xBF;
case 0x2514: return 0xC0;
case 0x2518: return 0xD9;
case 0x251C: return 0xC3;
case 0x2524: return 0xB4;
case 0x252C: return 0xC2;
case 0x2534: return 0xC1;
case 0x253C: return 0xC5;
case 0x2588: return 0xDB; // full block
case 0x2591: return 0xB0; // shades
case 0x2592: return 0xB1;
case 0x2593: return 0xB2;
default: return 0; // "no glyph"
}
}
static int
builtin_glyph(struct font *f, uint32 cp, struct glyph *out)
{
uint32 idx = to_cp437(cp);
if (idx == 0 && cp != 0)
return -1; // caller substitutes
for (int y = 0; y < BUILTIN_H; y++) {
unsigned char bits = font_data[idx & 0xFF][y];
for (int x = 0; x < BUILTIN_W; x++)
builtin_cov[y * BUILTIN_W + x] = (bits & (0x80 >> x)) ? 0xFF : 0x00;
}
out->cov = builtin_cov;
out->w = BUILTIN_W;
out->h = BUILTIN_H;
out->bearing_x = 0;
// The whole cell sits above the baseline, less two rows of descender space.
// The value matters only for consistency with whatever font comes next.
out->bearing_y = BUILTIN_H - 3;
out->advance = BUILTIN_W;
return 0;
}
static int builtin_height(struct font *f) { return BUILTIN_H; }
static int builtin_baseline(struct font *f) { return BUILTIN_H - 3; }
static const struct font_ops builtin_ops = {
.glyph = builtin_glyph,
.height = builtin_height,
.baseline = builtin_baseline,
};
static struct font builtin = { &builtin_ops, 0 };
struct font *
font_builtin(void)
{
return &builtin;
}
// --- blitting --------------------------------------------------------------
// Blend `color` over the pixel at (x, y) by coverage a.
//
// Reads the destination, which a bitmap font never needs -- but an
// anti-aliased edge is defined as a blend with what is already there, and a
// renderer that cannot do it is a renderer that cannot draw a real typeface.
static void
blend_px(struct surface *s, int x, int y, uint32 color, unsigned a)
{
if (x < 0 || y < 0 || x >= s->width || y >= s->height)
return;
uint32 *px = (uint32 *)((unsigned char *)s->pixels + (uint64)y * s->stride) + x;
if (a >= 255) {
*px = color;
return;
}
uint32 dst = *px;
uint32 r = (((color >> 16) & 0xFF) * a + ((dst >> 16) & 0xFF) * (255 - a)) / 255;
uint32 g = (((color >> 8) & 0xFF) * a + ((dst >> 8) & 0xFF) * (255 - a)) / 255;
uint32 b = (((color ) & 0xFF) * a + ((dst ) & 0xFF) * (255 - a)) / 255;
*px = (r << 16) | (g << 8) | b;
}
int
text_draw_glyph(struct surface *s, struct font *f, int x, int y,
uint32 cp, uint32 color)
{
struct glyph g;
if (f->ops->glyph(f, cp, &g) != 0) {
// Substitute rather than skip. A missing glyph that draws nothing makes
// text silently shorter and the fault impossible to see; a visible box
// says exactly what happened and where.
if (f->ops->glyph(f, 0x25A1, &g) != 0 && // white square
f->ops->glyph(f, '?', &g) != 0)
return 0;
}
int ox = x + g.bearing_x;
int oy = y - g.bearing_y; // bearing is measured up from the baseline
for (int gy = 0; gy < g.h; gy++)
for (int gx = 0; gx < g.w; gx++) {
unsigned a = g.cov[gy * g.w + gx];
if (a)
blend_px(s, ox + gx, oy + gy, color, a);
}
return g.advance;
}
int
text_draw(struct surface *s, struct font *f, int x, int y,
const char *utf8, uint32 color)
{
int pen = x;
const char *p = utf8;
while (*p) {
uint32 cp = utf8_next(&p);
if (cp == '\n')
break; // layout is the caller's business, not ours
pen += text_draw_glyph(s, f, pen, y, cp, color);
}
return pen - x;
}
int
text_measure(struct font *f, const char *utf8)
{
int w = 0;
const char *p = utf8;
struct glyph g;
while (*p) {
uint32 cp = utf8_next(&p);
if (cp == '\n')
break;
if (f->ops->glyph(f, cp, &g) == 0)
w += g.advance;
else if (f->ops->glyph(f, '?', &g) == 0)
w += g.advance;
}
return w;
}

606
kernel/dev/ttf.c Normal file
View file

@ -0,0 +1,606 @@
// TrueType outlines. See include/ttf.h for scope.
//
// Written from Apple's TrueType Reference Manual and the OpenType
// specification. Nothing here is derived from FreeType or stb_truetype.
#ifdef TTF_HOST
#include <string.h>
#include <stdlib.h>
typedef unsigned char uint8;
typedef unsigned short uint16;
typedef unsigned int uint32;
typedef signed short int16;
#else
#include "types.h"
#include "defs.h"
#include "param.h"
#endif
#include "text.h"
#include "ttf.h"
// Every multi-byte field in an sfnt file is big-endian, whatever the machine
// is. Read them byte by byte rather than casting: the file is not guaranteed
// to be aligned, and on x86 a cast would silently byte-swap everything.
static uint16 rd16(const uint8 *p) { return (uint16)((p[0] << 8) | p[1]); }
static int16 rs16(const uint8 *p) { return (int16)rd16(p); }
static uint32 rd32(const uint8 *p)
{
return ((uint32)p[0] << 24) | ((uint32)p[1] << 16) |
((uint32)p[2] << 8) | p[3];
}
static uint32
find_table(const uint8 *d, uint32 len, const char *tag, uint32 *out_len)
{
if (len < 12)
return 0;
int n = rd16(d + 4);
for (int i = 0; i < n; i++) {
const uint8 *e = d + 12 + 16 * i;
if ((uint32)(12 + 16 * i + 16) > len)
return 0;
if (e[0] == (uint8)tag[0] && e[1] == (uint8)tag[1] &&
e[2] == (uint8)tag[2] && e[3] == (uint8)tag[3]) {
uint32 off = rd32(e + 8), sz = rd32(e + 12);
if (off >= len)
return 0;
if (out_len)
*out_len = sz;
return off;
}
}
return 0;
}
int
ttf_open(struct ttf *f, const void *data, uint32 len, int px)
{
const uint8 *d = data;
memset(f, 0, sizeof(*f));
f->data = d;
f->len = len;
f->px = px;
// 0x00010000 is TrueType; 'true' is Apple's older tag. 'OTTO' is CFF
// outlines, which this cannot read -- rejecting it explicitly beats failing
// later with an empty glyph and no explanation.
if (len < 12)
return -1;
uint32 ver = rd32(d);
if (ver == 0x4F54544FU) { // 'OTTO'
return -1;
}
if (ver != 0x00010000U && ver != 0x74727565U)
return -1;
uint32 head = find_table(d, len, "head", 0);
uint32 maxp = find_table(d, len, "maxp", 0);
uint32 hhea = find_table(d, len, "hhea", 0);
f->hmtx = find_table(d, len, "hmtx", 0);
f->glyf = find_table(d, len, "glyf", &f->glyf_len);
f->loca = find_table(d, len, "loca", &f->loca_len);
if (!head || !maxp || !hhea || !f->hmtx || !f->glyf || !f->loca)
return -1;
f->units_per_em = rd16(d + head + 18);
f->long_loca = rs16(d + head + 50);
f->num_glyphs = rd16(d + maxp + 4);
f->ascent = rs16(d + hhea + 4);
f->descent = rs16(d + hhea + 6);
f->num_hmetrics = rd16(d + hhea + 34);
if (f->units_per_em == 0)
return -1;
// cmap: prefer a Windows BMP subtable (platform 3, encoding 1), fall back to
// symbol (3,0). Format 4 only -- it covers the basic multilingual plane,
// which is every codepoint this system will draw for a long time.
uint32 cmap = find_table(d, len, "cmap", 0);
if (cmap) {
int n = rd16(d + cmap + 2);
for (int i = 0; i < n; i++) {
const uint8 *rec = d + cmap + 4 + 8 * i;
int plat = rd16(rec), enc = rd16(rec + 2);
uint32 sub = cmap + rd32(rec + 4);
if (sub + 4 > len)
continue;
if (rd16(d + sub) != 4)
continue;
if (plat == 3 && (enc == 1 || enc == 0)) {
f->cmap4 = sub;
if (enc == 1)
break; // exact match; keep looking otherwise
}
}
}
return f->cmap4 ? 0 : -1;
}
// Codepoint to glyph index, cmap format 4.
//
// The format is four parallel arrays of segments plus an idDelta/idRangeOffset
// pair whose second form is the awkward one: idRangeOffset is a byte offset
// *from its own address* into glyphIdArray, which is why the arithmetic below
// looks like it does rather than being an index.
static int
glyph_index(struct ttf *f, uint32 cp)
{
if (cp > 0xFFFF || !f->cmap4)
return 0;
const uint8 *t = f->data + f->cmap4;
int segx2 = rd16(t + 6);
const uint8 *ends = t + 14;
const uint8 *starts = ends + segx2 + 2;
const uint8 *deltas = starts + segx2;
const uint8 *ranges = deltas + segx2;
for (int i = 0; i < segx2; i += 2) {
if (rd16(ends + i) < cp)
continue;
if (rd16(starts + i) > cp)
return 0; // in a gap between segments
int ro = rd16(ranges + i);
if (ro == 0)
return (int)((cp + rd16(deltas + i)) & 0xFFFF);
const uint8 *p = ranges + i + ro + (cp - rd16(starts + i)) * 2;
if (p + 1 >= f->data + f->len)
return 0;
int g = rd16(p);
return g ? (int)((g + rd16(deltas + i)) & 0xFFFF) : 0;
}
return 0;
}
static int
advance_for(struct ttf *f, int gid)
{
// hmtx holds numberOfHMetrics full entries, then only left side bearings --
// monospaced tails are stored once. Clamping is the whole trick.
int i = gid < f->num_hmetrics ? gid : f->num_hmetrics - 1;
if (i < 0)
return 0;
return rd16(f->data + f->hmtx + 4 * i);
}
// --- outline extraction ----------------------------------------------------
#define MAXPTS 512
#define MAXCONT 32
struct outline {
int x[MAXPTS], y[MAXPTS]; // font units
uint8 on[MAXPTS]; // on-curve flag
int ends[MAXCONT];
int npts, ncont;
};
static int
load_outline(struct ttf *f, int gid, struct outline *o)
{
uint32 g0, g1;
if (gid < 0 || gid >= f->num_glyphs)
return -1;
if (f->long_loca) {
g0 = rd32(f->data + f->loca + 4 * gid);
g1 = rd32(f->data + f->loca + 4 * gid + 4);
} else {
g0 = (uint32)rd16(f->data + f->loca + 2 * gid) * 2;
g1 = (uint32)rd16(f->data + f->loca + 2 * gid + 2) * 2;
}
o->npts = o->ncont = 0;
if (g0 >= g1)
return 0; // empty glyph, e.g. space -- not an error
const uint8 *g = f->data + f->glyf + g0;
int nc = rs16(g);
// A negative contour count means a composite: the glyph is other glyphs
// placed at offsets. This is not an exotic case to skip -- `i` is built from
// a dotless i plus the dot, so skipping composites loses a letter from
// "Schunix", which is exactly how the gap was found.
//
// Only the translated case is handled: components with a scale or a 2x2
// matrix are rare outside of synthesised obliques, and applying a transform
// wrongly is worse than not applying one.
if (nc < 0) {
const uint8 *p = g + 10;
int flags;
do {
flags = rd16(p);
int cgid = rd16(p + 2);
p += 4;
int dx, dy;
if (flags & 0x0001) { // ARG_1_AND_2_ARE_WORDS
dx = rs16(p); dy = rs16(p + 2); p += 4;
} else {
dx = (signed char)p[0]; dy = (signed char)p[1]; p += 2;
}
if (!(flags & 0x0002)) // ARGS_ARE_XY_VALUES
dx = dy = 0; // point matching: not supported
if (flags & 0x0008) p += 2; // WE_HAVE_A_SCALE
else if (flags & 0x0040) p += 4; // X_AND_Y_SCALE
else if (flags & 0x0080) p += 8; // TWO_BY_TWO
// Recurse, then merge the component's contours into ours with the offset
// applied. Depth is bounded by the recursion into load_outline, which
// rejects anything that would overflow the point arrays.
struct outline sub;
if (load_outline(f, cgid, &sub) != 0)
continue;
if (o->npts + sub.npts > MAXPTS || o->ncont + sub.ncont > MAXCONT)
break;
for (int i = 0; i < sub.npts; i++) {
o->x[o->npts + i] = sub.x[i] + dx;
o->y[o->npts + i] = sub.y[i] + dy;
o->on[o->npts + i] = sub.on[i];
}
for (int i = 0; i < sub.ncont; i++)
o->ends[o->ncont + i] = sub.ends[i] + o->npts;
o->npts += sub.npts;
o->ncont += sub.ncont;
} while (flags & 0x0020); // MORE_COMPONENTS
return 0;
}
if (nc > MAXCONT)
return -1;
o->ncont = nc;
const uint8 *p = g + 10;
for (int i = 0; i < nc; i++) {
o->ends[i] = rd16(p);
p += 2;
}
int npts = nc ? o->ends[nc - 1] + 1 : 0;
if (npts > MAXPTS)
return -1;
o->npts = npts;
p += 2 + rd16(p); // skip the hinting instructions
// Flags are run-length encoded: bit 3 means "the next byte is a repeat
// count". Missing that turns every glyph with a repeated flag into garbage.
uint8 flags[MAXPTS];
for (int i = 0; i < npts; ) {
uint8 fl = *p++;
flags[i++] = fl;
if (fl & 8) {
int rep = *p++;
while (rep-- > 0 && i < npts)
flags[i++] = fl;
}
}
// Coordinates are deltas, and each axis is stored in one of three ways
// depending on two flag bits: 8-bit with a separate sign bit, or 16-bit
// signed, or "same as the previous point" which occupies no bytes at all.
int v = 0;
for (int i = 0; i < npts; i++) {
if (flags[i] & 2) {
int dv = *p++;
v += (flags[i] & 16) ? dv : -dv;
} else if (!(flags[i] & 16)) {
v += rs16(p);
p += 2;
}
o->x[i] = v;
o->on[i] = (flags[i] & 1) != 0;
}
v = 0;
for (int i = 0; i < npts; i++) {
if (flags[i] & 4) {
int dv = *p++;
v += (flags[i] & 32) ? dv : -dv;
} else if (!(flags[i] & 32)) {
v += rs16(p);
p += 2;
}
o->y[i] = v;
}
return 0;
}
// --- rasterising -----------------------------------------------------------
//
// Scanline fill with the non-zero winding rule, four sub-scanlines per output
// row. Coverage is accumulated in fixed point per pixel, so a horizontal span
// ending part-way through a pixel contributes a fraction rather than being
// rounded -- which is where anti-aliasing actually comes from. Vertical
// smoothing comes from the four samples.
#define SUBS 4
struct edge { int x0, y0, x1, y1; }; // in 1/64 pixel
#define MAXEDGE 1024
struct raster {
struct edge e[MAXEDGE];
int n;
};
static void
add_edge(struct raster *r, int x0, int y0, int x1, int y1)
{
if (y0 == y1 || r->n >= MAXEDGE)
return; // horizontal edges contribute no crossings
r->e[r->n].x0 = x0; r->e[r->n].y0 = y0;
r->e[r->n].x1 = x1; r->e[r->n].y1 = y1;
r->n++;
}
// Flatten a quadratic bézier into line segments.
//
// Fixed subdivision rather than adaptive. At text sizes the control polygon is
// a few pixels across, so eight segments is already below the point where the
// difference is visible, and an adaptive version would spend more time deciding
// than drawing.
static void
quad_to(struct raster *r, int x0, int y0, int cx, int cy, int x1, int y1)
{
const int N = 8;
int px = x0, py = y0;
for (int i = 1; i <= N; i++) {
int t = (i * 256) / N, u = 256 - t;
// (u*u*p0 + 2*u*t*c + t*t*p1) / 65536
int nx = (u * u * x0 + 2 * u * t * cx + t * t * x1) >> 16;
int ny = (u * u * y0 + 2 * u * t * cy + t * t * y1) >> 16;
add_edge(r, px, py, nx, ny);
px = nx; py = ny;
}
}
int
ttf_render(struct ttf *f, uint32 cp, struct glyph *out, unsigned char *cov,
int covcap, int *ox, int *oy)
{
struct outline o;
struct raster r;
int gid = glyph_index(f, cp);
if (gid == 0 && cp != 0)
return -1;
if (load_outline(f, gid, &o) != 0)
return -1;
// Font units to 1/64 pixel.
int upem = f->units_per_em;
#define SX(v) (((v) * f->px * 64) / upem)
int minx = 0, miny = 0, maxx = 0, maxy = 0;
if (o.npts) {
minx = maxx = SX(o.x[0]);
miny = maxy = SX(o.y[0]);
for (int i = 1; i < o.npts; i++) {
int X = SX(o.x[i]), Y = SX(o.y[i]);
if (X < minx) minx = X;
if (X > maxx) maxx = X;
if (Y < miny) miny = Y;
if (Y > maxy) maxy = Y;
}
}
int x0 = minx >> 6, y0 = miny >> 6;
int w = ((maxx + 63) >> 6) - x0 + 1;
int h = ((maxy + 63) >> 6) - y0 + 1;
if (w < 1) w = 1;
if (h < 1) h = 1;
if (w * h > covcap)
return -1;
memset(cov, 0, (uint32)(w * h));
// Build the edge list, contour by contour. Off-curve points imply an
// on-curve point midway between consecutive off-curve neighbours -- TrueType
// omits those, and a renderer that does not synthesise them draws corners
// where the font has curves.
r.n = 0;
int start = 0;
for (int c = 0; c < o.ncont; c++) {
int end = o.ends[c];
int n = end - start + 1;
if (n < 2) { start = end + 1; continue; }
int px = 0, py = 0, have = 0, fx = 0, fy = 0;
int cx = 0, cy = 0, havec = 0;
for (int k = 0; k <= n; k++) {
int i = start + (k % n);
int X = SX(o.x[i]) - (x0 << 6), Y = SX(o.y[i]) - (y0 << 6);
if (o.on[i]) {
if (!have) { fx = px = X; fy = py = Y; have = 1; }
else if (havec) { quad_to(&r, px, py, cx, cy, X, Y); px = X; py = Y; havec = 0; }
else { add_edge(&r, px, py, X, Y); px = X; py = Y; }
} else {
if (havec) {
int mx = (cx + X) / 2, my = (cy + Y) / 2; // implied on-curve point
if (!have) { fx = px = mx; fy = py = my; have = 1; }
else { quad_to(&r, px, py, cx, cy, mx, my); px = mx; py = my; }
}
cx = X; cy = Y; havec = 1;
}
}
if (have) {
if (havec) quad_to(&r, px, py, cx, cy, fx, fy);
else add_edge(&r, px, py, fx, fy);
}
start = end + 1;
}
// Fill. Y grows upward in font space and downward in the bitmap, so the
// sample line is mirrored as it is computed.
for (int row = 0; row < h; row++) {
for (int s = 0; s < SUBS; s++) {
int sy = ((h - 1 - row) << 6) + (s * 64) / SUBS + 32 / SUBS;
int xs[64], nx = 0, dir[64];
for (int i = 0; i < r.n && nx < 64; i++) {
struct edge *e = &r.e[i];
int ya = e->y0, yb = e->y1, d = 1;
if (ya > yb) { int t = ya; ya = yb; yb = t; d = -1; }
if (sy < ya || sy >= yb)
continue;
int t = ((sy - e->y0) * 1024) / (e->y1 - e->y0);
xs[nx] = e->x0 + ((e->x1 - e->x0) * t) / 1024;
dir[nx] = d;
nx++;
}
// Insertion sort: nx is small and this keeps the direction paired.
for (int i = 1; i < nx; i++) {
int vx = xs[i], vd = dir[i], j = i - 1;
while (j >= 0 && xs[j] > vx) { xs[j+1] = xs[j]; dir[j+1] = dir[j]; j--; }
xs[j+1] = vx; dir[j+1] = vd;
}
int wind = 0;
for (int i = 0; i + 1 < nx; i++) {
wind += dir[i];
if (wind == 0)
continue; // non-zero winding
int xa = xs[i], xb = xs[i + 1];
for (int col = xa >> 6; col <= (xb >> 6) && col < w; col++) {
if (col < 0)
continue;
// Fraction of this pixel the span covers, in 1/64ths.
int l = xa > (col << 6) ? xa : (col << 6);
int rr = xb < ((col + 1) << 6) ? xb : ((col + 1) << 6);
if (rr <= l)
continue;
int add = ((rr - l) * 255) / (64 * SUBS);
int v = cov[row * w + col] + add;
cov[row * w + col] = (unsigned char)(v > 255 ? 255 : v);
}
}
}
}
out->cov = cov;
out->w = w;
out->h = h;
out->bearing_x = x0;
out->bearing_y = (maxy + 63) >> 6;
out->advance = (advance_for(f, gid) * f->px + upem / 2) / upem;
if (ox) *ox = x0;
if (oy) *oy = y0;
#undef SX
return 0;
}
// --- font provider ---------------------------------------------------------
// --- glyph cache -----------------------------------------------------------
//
// Rasterising is not cheap -- it walks the outline, flattens every curve and
// fills four sub-scanlines per row -- and a terminal draws the same hundred
// glyphs over and over. Without a cache every character costs that again, and
// worse: the single scratch buffer meant a glyph was only valid until the next
// call, so a caller could not hold two at once.
//
// Direct-mapped over printable ASCII, which is the case that matters and needs
// no eviction policy, no hashing and no allocation. Anything outside that range
// still renders, just uncached, through the scratch buffer.
//
// The pool is filled linearly and never freed. A cache that can only fill up is
// the right shape here because the contents are pure functions of (codepoint,
// size) -- nothing ever becomes stale -- and the only event that invalidates it
// is a size change, which drops the whole thing.
#define CACHE_LO 32 // space
#define CACHE_HI 127 // exclusive
#define CACHE_N (CACHE_HI - CACHE_LO)
#define CACHE_POOL (48 * 1024)
struct gcache {
struct glyph g;
uint32 off; // into pool, valid only when `have`
int have;
};
static struct gcache cache[CACHE_N];
static unsigned char cache_pool[CACHE_POOL];
static uint32 cache_used;
static int cache_px; // size the cache was built at
// Scratch for anything the cache cannot hold. Same contract as before -- valid
// until the next call -- and now only used off the common path.
static unsigned char ttf_cov[128 * 128];
static int
ttf_glyph_op(struct font *fo, uint32 cp, struct glyph *out)
{
struct ttf *f = fo->priv;
// A size change makes every cached bitmap wrong. Dropping the lot is correct
// and costs nothing: it happens when a font is resized, not per glyph.
if (cache_px != f->px) {
for (int i = 0; i < CACHE_N; i++)
cache[i].have = 0;
cache_used = 0;
cache_px = f->px;
}
if (cp < CACHE_LO || cp >= CACHE_HI)
return ttf_render(f, cp, out, ttf_cov, (int)sizeof(ttf_cov), 0, 0);
struct gcache *c = &cache[cp - CACHE_LO];
if (c->have) {
*out = c->g;
out->cov = cache_pool + c->off; // rebind: the pool may have moved in
return 0; // a future revision, the offset has not
}
struct glyph g;
if (ttf_render(f, cp, &g, ttf_cov, (int)sizeof(ttf_cov), 0, 0) != 0)
return -1;
uint32 need = (uint32)(g.w * g.h);
if (cache_used + need <= CACHE_POOL) {
memmove(cache_pool + cache_used, g.cov, need);
c->off = cache_used;
c->g = g;
c->g.cov = cache_pool + cache_used; // not the scratch buffer
c->have = 1;
cache_used += need;
*out = c->g;
return 0;
}
// Pool exhausted. Still correct, just uncached -- and the glyph is in the
// scratch buffer, which is exactly the old contract.
*out = g;
return 0;
}
static int ttf_height_op(struct font *fo)
{
struct ttf *f = fo->priv;
return ((f->ascent - f->descent) * f->px + f->units_per_em / 2) / f->units_per_em;
}
static int ttf_baseline_op(struct font *fo)
{
struct ttf *f = fo->priv;
return (f->ascent * f->px + f->units_per_em / 2) / f->units_per_em;
}
static const struct font_ops ttf_ops = {
.glyph = ttf_glyph_op,
.height = ttf_height_op,
.baseline = ttf_baseline_op,
};
static struct font ttf_font_obj;
struct font *
ttf_font(struct ttf *f)
{
ttf_font_obj.ops = &ttf_ops;
ttf_font_obj.priv = f;
return &ttf_font_obj;
}

95
kernel/dev/uart.c Normal file
View file

@ -0,0 +1,95 @@
// Intel 8250 serial port (UART).
#include "types.h"
#include "defs.h"
#include "irq.h"
#include "param.h"
#include "traps.h"
#include "spinlock.h"
#include "sleeplock.h"
#include "fs.h"
#include "file.h"
#include "mmu.h"
#include "proc.h"
#include "x86.h"
#define COM1 0x3f8
static int uart; // is there a uart?
void
uartearlyinit(void)
{
char *p;
// Turn off the FIFO
outb(COM1+2, 0);
// 9600 baud, 8 data bits, 1 stop bit, parity off.
outb(COM1+3, 0x80); // Unlock divisor
outb(COM1+0, 115200/9600);
outb(COM1+1, 0);
outb(COM1+3, 0x03); // Lock divisor, 8 data bits.
outb(COM1+4, 0);
outb(COM1+1, 0x01); // Enable receive interrupts.
// If status is 0xFF, no serial port.
if(inb(COM1+5) == 0xFF)
return;
uart = 1;
// Announce that we're here.
for(p="Schunix UART...\n"; *p; p++)
uartputc(*p);
}
// irq.c hands every handler an opaque argument, because a driver with two
// controllers needs to know which one interrupted. There is only one COM1.
static void
uart_irq(void *arg)
{
(void)arg;
uartintr();
}
void
uartinit(void)
{
if(!uart)
return;
// Acknowledge pre-existing interrupt conditions;
// enable interrupts.
inb(COM1+2);
inb(COM1+0);
irq_register(IRQ_COM1, uart_irq, 0, "serial", 0);
}
void
uartputc(int c)
{
int i;
if(!uart)
return;
for(i = 0; i < 128 && !(inb(COM1+5) & 0x20); i++)
microdelay(10);
outb(COM1+0, c);
}
static int
uartgetc(void)
{
if(!uart)
return -1;
if(!(inb(COM1+5) & 0x01))
return -1;
return inb(COM1+0);
}
void
uartintr(void)
{
consoleintr(uartgetc);
}

555
kernel/dev/vga.c Normal file
View file

@ -0,0 +1,555 @@
#include "types.h"
#include "x86.h"
#include "memlayout.h"
#include "defs.h"
#include "theme.h"
#include "font.h"
#include "logo.h"
#include "device.h"
#include "display.h"
#include "dma.h"
#define BACKSPACE 0x100
#define CHAR_HEIGHT 16
#define CHAR_WIDTH 8
#define COLOR_BG SCH_BG
volatile uchar *vga_mem = 0;
// Physical base of the linear framebuffer, as reported by VBE. Every other
// part of the kernel that needs to map the framebuffer -- fork's VMA copy,
// mmap of /dev/fb0 -- asks for this rather than assuming a value. Three
// places used to hardcode 0xfd000000, which is only where QEMU's stdvga
// happens to put it.
static uint64 vga_phys = 0;
static uint32 vga_stride = 0;
static uint32 vga_bpp = 0;
static uint32 vga_width = 800;
static uint32 vga_height = 600;
static int cursor_x = 0;
static int cursor_y = 0;
// Current fill color for glyph backgrounds, backspace erase, and scroll —
// normally COLOR_BG, but panic() switches this to paint the whole console
// a solid color (e.g. PSOGM's purple) instead of leaving cream-colored boxes
// around each character.
static uint32 vga_bg = COLOR_BG;
void
vga_set_bg(uint32 color)
{
vga_bg = color;
}
// Written by boot/stage2.S's VBE call and read by boot/stage2main.c under
// the same name (VBE_INFO_ADDR) — not 0x9000, since stage2's own code now
// runs up to nearly 0x9000 once the boot-menu/progress-bar logic is
// included, which would otherwise clobber a mode info block placed there.
#define VBE_INFO_ADDR 0x30000
void
vga_init(uint64 phys_addr) {
if (phys_addr == 0) {
phys_addr = *(uint32*)(P2V(VBE_INFO_ADDR) + 40);
}
vga_mem = (volatile uchar*)P2V(phys_addr);
vga_phys = phys_addr;
vga_stride = *(ushort*)(P2V(VBE_INFO_ADDR) + 16);
vga_width = *(ushort*)(P2V(VBE_INFO_ADDR) + 18);
vga_height = *(ushort*)(P2V(VBE_INFO_ADDR) + 20);
vga_bpp = *(uchar*)(P2V(VBE_INFO_ADDR) + 25);
if (vga_width == 0) vga_width = 800;
if (vga_height == 0) vga_height = 600;
if (vga_stride == 0) vga_stride = vga_width * (vga_bpp / 8);
cprintf("VGA: %dx%d, %d bpp, stride %d, LFB: 0x%x\n", vga_width, vga_height, vga_bpp, vga_stride, (uint32)phys_addr);
// Deliberately does NOT clear the screen.
//
// The bootloader has already drawn the splash, and wiping it here is what
// produced the old two-act boot: a purple splash for an instant, then an
// abrupt cream screen filling with kernel log lines. The splash now stays up
// for the whole of boot, and console_takeover() clears it exactly once --
// when userspace starts and the display changes owner.
}
void
vga_draw_pixel(int x, int y, uint32 color)
{
if (!vga_mem || x < 0 || x >= vga_width || y < 0 || y >= vga_height)
return;
uint32 offset = (y * vga_stride) + (x * (vga_bpp / 8));
if (vga_bpp == 32) {
*(volatile uint32*)(vga_mem + offset) = color;
} else if (vga_bpp == 24) {
vga_mem[offset] = color & 0xFF;
vga_mem[offset + 1] = (color >> 8) & 0xFF;
vga_mem[offset + 2] = (color >> 16) & 0xFF;
} else if (vga_bpp == 16) {
uint16 c16 = (((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F);
*(volatile uint16*)(vga_mem + offset) = c16;
}
}
void
vga_clear(uint32 color)
{
for (int y = 0; y < vga_height; y++) {
for (int x = 0; x < vga_width; x++) {
vga_draw_pixel(x, y, color);
}
}
}
void
vga_draw_char(int x, int y, uchar c, uint32 color)
{
// No `c > 127` rejection any more: font_data holds 256 Latin-1 glyphs, and
// a uchar cannot index past them.
if (!vga_mem) return;
for (int row = 0; row < CHAR_HEIGHT; row++) {
uchar row_data = font_data[c][row];
for (int col = 0; col < CHAR_WIDTH; col++) {
if (row_data & (0x80 >> col)) {
vga_draw_pixel(x + col, y + row, color);
} else {
vga_draw_pixel(x + col, y + row, vga_bg);
}
}
}
}
void
vga_puts(int x, int y, char *s, uint32 color)
{
while (*s) {
vga_putc((uchar)*s, color);
s++;
}
}
void
vga_putc(int c, uint32 color)
{
if (!vga_mem) return;
if (c == BACKSPACE || c == 0x08 || c == '\b' || c == 127) {
if (cursor_x >= CHAR_WIDTH) {
cursor_x -= CHAR_WIDTH;
for (int i = 0; i < CHAR_HEIGHT; i++) {
for (int j = 0; j < CHAR_WIDTH; j++) {
vga_draw_pixel(cursor_x + j, cursor_y + i, vga_bg);
}
}
}
return;
}
if (c == '\n') {
cursor_x = 0;
cursor_y += CHAR_HEIGHT;
} else if (c == '\r') {
cursor_x = 0;
} else if (c == '\t') {
// Advance to the next multiple of eight columns, erasing as it goes so a
// tab overwrites rather than skipping over stale characters.
//
// There was no tab case at all, so '\t' fell through to vga_draw_char and
// was drawn as glyph 9 -- which in CP437 is a hollow circle. Pressing Tab
// printed a circle. Same for every other control character: the font has
// a picture for each of them, and none of those pictures is what the
// character means.
int col = cursor_x / CHAR_WIDTH;
int stop = (col + 8) & ~7;
while (col < stop && cursor_x < (int)vga_width) {
for (int i = 0; i < CHAR_HEIGHT; i++)
for (int j = 0; j < CHAR_WIDTH; j++)
vga_draw_pixel(cursor_x + j, cursor_y + i, vga_bg);
cursor_x += CHAR_WIDTH;
col++;
}
} else if (c < 32) {
return; // any other control character: not a glyph
} else {
vga_draw_char(cursor_x, cursor_y, (uchar)c, color);
cursor_x += CHAR_WIDTH;
}
if (cursor_x >= vga_width) {
cursor_x = 0;
cursor_y += CHAR_HEIGHT;
}
if (cursor_y > (vga_height - CHAR_HEIGHT)) {
uint32 line_size = vga_stride * CHAR_HEIGHT;
uint32 total_size = vga_stride * vga_height;
uint32 copy_size = total_size - line_size;
memmove((void*)vga_mem, (void*)(vga_mem + line_size), copy_size);
for (int y = vga_height - CHAR_HEIGHT; y < vga_height; y++) {
for (int x = 0; x < vga_width; x++) {
vga_draw_pixel(x, y, vga_bg);
}
}
cursor_y = vga_height - CHAR_HEIGHT;
}
}
void
vga_screen_reset(void)
{
vga_bg = COLOR_BG;
vga_clear(COLOR_BG);
cursor_x = 0;
cursor_y = 0;
}
/* ------------------------------------------------------------------ */
/* Drawing primitives for the crash screen */
/* */
/* The crash screen lays itself out rather than printing, which needs */
/* two things the console never did: filled shapes, and text at an */
/* absolute position that leaves the cursor alone. */
/* ------------------------------------------------------------------ */
int
vga_ready(void)
{
return vga_mem != 0;
}
void
vga_dims(int *w, int *h)
{
if (w) *w = (int)vga_width;
if (h) *h = (int)vga_height;
}
int vga_char_w(void) { return CHAR_WIDTH; }
int vga_char_h(void) { return CHAR_HEIGHT; }
/* ------------------------------------------------------------------ */
/* Framebuffer geometry */
/* */
/* vga.c is the only place that reads the VBE mode info, so it is the */
/* only place that knows where the framebuffer actually is. Everything */
/* else asks. See kernel/dev/fb.c for the device these back. */
/* ------------------------------------------------------------------ */
uint64 vga_fb_phys(void) { return vga_phys; }
// Bytes of framebuffer, measured in scanlines rather than as
// width * height * bpp/8 -- the two differ whenever the card pads its rows,
// and it is the stride that says how far apart two rows really are.
uint64 vga_fb_size(void)
{
if (!vga_mem)
return 0;
return (uint64)vga_stride * (uint64)vga_height;
}
uint32 vga_fb_stride(void) { return vga_stride; }
uint32 vga_fb_bpp(void) { return vga_bpp; }
volatile uchar *vga_fb_ptr(void) { return vga_mem; }
/* ------------------------------------------------------------------ */
/* Cursor addressing and erasing */
/* */
/* What an ANSI escape parser needs and vga_putc alone cannot give it: */
/* the ability to put the cursor somewhere specific and to erase a */
/* region without writing spaces over it. See kernel/core/console.c. */
/* ------------------------------------------------------------------ */
int vga_cols(void) { return vga_width ? (int)(vga_width / CHAR_WIDTH) : 0; }
int vga_rows(void) { return vga_height ? (int)(vga_height / CHAR_HEIGHT) : 0; }
void
vga_cursor_get(int *col, int *row)
{
if (col) *col = cursor_x / CHAR_WIDTH;
if (row) *row = cursor_y / CHAR_HEIGHT;
}
// Clamped rather than validated: an escape sequence is untrusted input that
// arrives from any program that can write to the terminal, and "CUP to row
// 9999" should put the cursor on the last row, not off the end of the
// framebuffer.
void
vga_cursor_set(int col, int row)
{
int maxc = vga_cols(), maxr = vga_rows();
if (maxc <= 0 || maxr <= 0)
return;
if (col < 0) col = 0;
if (row < 0) row = 0;
if (col >= maxc) col = maxc - 1;
if (row >= maxr) row = maxr - 1;
cursor_x = col * CHAR_WIDTH;
cursor_y = row * CHAR_HEIGHT;
}
// Fill a run of character cells with the background colour.
static void
erase_cells(int col, int row, int count)
{
int maxc = vga_cols();
while (count-- > 0) {
int x = col * CHAR_WIDTH, y = row * CHAR_HEIGHT;
for (int i = 0; i < CHAR_HEIGHT; i++)
for (int j = 0; j < CHAR_WIDTH; j++)
vga_draw_pixel(x + j, y + i, vga_bg);
if (++col >= maxc) { col = 0; row++; }
}
}
// ED -- erase in display. 0: cursor to end, 1: start to cursor, 2: all.
void
vga_erase_display(int mode)
{
int col, row, maxc = vga_cols(), maxr = vga_rows();
vga_cursor_get(&col, &row);
switch (mode) {
case 0: erase_cells(col, row, (maxr - row) * maxc - col); break;
case 1: erase_cells(0, 0, row * maxc + col + 1); break;
default: vga_clear(vga_bg); break;
}
}
// EL -- erase in line. Same three modes, bounded to the current row.
void
vga_erase_line(int mode)
{
int col, row, maxc = vga_cols();
vga_cursor_get(&col, &row);
switch (mode) {
case 0: erase_cells(col, row, maxc - col); break;
case 1: erase_cells(0, row, col + 1); break;
default: erase_cells(0, row, maxc); break;
}
}
// Row pointer computed once per row rather than per pixel. The crash screen
// paints a solid card behind the QR symbol, and at 8 pixels per module that is
// tens of thousands of writes going through vga_draw_pixel's bounds check and
// multiply.
void
vga_fill_rect(int x0, int y0, int w, int h, uint32 color)
{
if (!vga_mem)
return;
if (x0 < 0) { w += x0; x0 = 0; }
if (y0 < 0) { h += y0; y0 = 0; }
if (x0 + w > (int)vga_width) w = (int)vga_width - x0;
if (y0 + h > (int)vga_height) h = (int)vga_height - y0;
if (w <= 0 || h <= 0)
return;
// Only the 32bpp case gets the fast path; anything else is rare enough that
// correctness through vga_draw_pixel is the better trade.
if (vga_bpp != 32) {
for (int y = y0; y < y0 + h; y++)
for (int x = x0; x < x0 + w; x++)
vga_draw_pixel(x, y, color);
return;
}
for (int y = y0; y < y0 + h; y++) {
volatile uint32 *row = (volatile uint32 *)(vga_mem + y * vga_stride) + x0;
for (int x = 0; x < w; x++)
row[x] = color;
}
}
void
vga_fill_circle(int cx, int cy, int r, uint32 color)
{
for (int dy = -r; dy <= r; dy++)
for (int dx = -r; dx <= r; dx++)
if (dx * dx + dy * dy <= r * r)
vga_draw_pixel(cx + dx, cy + dy, color);
}
// Text at an absolute position, leaving the console cursor alone. The crash
// screen lays itself out, so vga_putc's cursor tracking and scrolling are
// exactly what it does not want.
void
vga_text_at(int x, int y, const char *s, uint32 color)
{
for (int i = 0; s[i]; i++)
vga_draw_char(x + i * CHAR_WIDTH, y, (uchar)s[i], color);
}
// --- Logo ------------------------------------------------------------------
//
// The boot splash's logo is drawn by the *loader*, not here -- see splash() in
// boot/stage2main.c. It has to be, because the kernel does not reach vga_init()
// until roughly the last 300ms of a 2.8s boot: drawing it here put the logo on
// screen for one frame before console_takeover() wiped it, which is worse than
// not having one.
//
// What lives here is the same drawing code against the same mask, for the
// things that want the mark *after* boot -- the crash screen, and eventually
// whatever schinit shows. Keeping one copy of the compositing logic is why this
// is not simply deleted.
// Blend `fg` over `bg` by coverage a (0..255). Per-channel, integer only: this
// runs before any floating point is safe to touch.
static uint32
blend(uint32 bg, uint32 fg, uint a)
{
uint32 r = (((fg >> 16) & 0xFF) * a + ((bg >> 16) & 0xFF) * (255 - a)) / 255;
uint32 g = (((fg >> 8) & 0xFF) * a + ((bg >> 8) & 0xFF) * (255 - a)) / 255;
uint32 b = (((fg ) & 0xFF) * a + ((bg ) & 0xFF) * (255 - a)) / 255;
return (r << 16) | (g << 8) | b;
}
void
vga_draw_logo(int x, int y, uint32 color, uint32 bg)
{
if (!vga_mem)
return;
for (int dy = 0; dy < LOGO_H; dy++) {
for (int dx = 0; dx < LOGO_W; dx++) {
uint a = LOGO_INK(dy * LOGO_W + dx);
// Skip the background rather than painting it. The mask is a square but
// the mark is a circle, so most of it is empty -- and drawing that empty
// area in bg would stamp a visible box over whatever is underneath.
if (a == 0)
continue;
vga_draw_pixel(x + dx, y + dy, a == 255 ? color : blend(bg, color, a));
}
}
}
// Centred, for callers that own the whole screen.
void
vga_logo_centered(int cy, uint32 color, uint32 bg)
{
if (!vga_mem || vga_width < LOGO_W)
return;
vga_draw_logo(((int)vga_width - LOGO_W) / 2, cy - LOGO_H / 2, color, bg);
}
// --- display class implementation ------------------------------------------
//
// See include/display.h. This is the VBE linear framebuffer presenting itself
// as a generic display, so a compositor can be written against the class and
// keep working when virtio-gpu arrives.
static uchar *back; // shadow buffer in RAM, or 0 if never acquired
static uint64 back_bytes;
static void
vga_disp_mode(struct device *d, struct display_mode *m)
{
m->width = (int)vga_width;
m->height = (int)vga_height;
m->bpp = (int)vga_bpp;
m->stride = vga_stride;
}
static void *
vga_disp_acquire(struct device *d, uint32 *stride_out)
{
if (stride_out)
*stride_out = vga_stride;
if (back)
return back;
if (!vga_mem || vga_height == 0 || vga_stride == 0)
return 0;
back_bytes = (uint64)vga_stride * vga_height;
// From the DMA arena, which is the wrong pool and is used here because it is
// the only *physically contiguous* allocator in the kernel -- kalloc hands
// out single pages whose neighbours are adjacent only by chance, and a
// framebuffer wants one flat span.
//
// At 800x600x32 this is 1.9 MB of a 4 MB arena meant for device rings, which
// is tolerable only because nothing else large lives there yet. The real fix
// is a contiguous page allocator, or reserving this at boot from the memory
// map -- which is work that belongs with the E820 change in docs/roadmap.md
// §3b, since that is what stops the kernel pretending 224 MB exists.
//
// Allocated on first use rather than at boot on purpose: a machine that never
// starts a compositor should not pay two megabytes for one.
back = dma_alloc(back_bytes, 4096);
if (!back) {
cprintf("display: no room for a %d KB back buffer; drawing will tear\n",
(int)(back_bytes / 1024));
return 0;
}
memset(back, 0, back_bytes);
cprintf("display: %d KB back buffer, double buffered\n",
(int)(back_bytes / 1024));
return back;
}
// Blit a rectangle from the shadow buffer to the framebuffer.
//
// Row at a time rather than one memmove for the whole rectangle: only a
// full-width rectangle is contiguous in either buffer, and a compositor
// updating one window is the case worth being right about.
static void
vga_disp_present(struct device *d, int x, int y, int w, int h)
{
if (!back || !vga_mem)
return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > (int)vga_width) w = (int)vga_width - x;
if (y + h > (int)vga_height) h = (int)vga_height - y;
if (w <= 0 || h <= 0)
return;
int bytes = w * (int)(vga_bpp / 8);
uint32 off = (uint32)x * (vga_bpp / 8);
for (int row = y; row < y + h; row++)
memmove((void *)(vga_mem + row * vga_stride + off),
back + row * vga_stride + off, bytes);
}
// Wait for vertical retrace, by polling the VGA input status register.
//
// Bit 3 of 0x3DA is set during vertical retrace. This is the original 1987 VGA
// interface and it still works on everything that emulates a VGA, QEMU's
// stdvga included -- VBE's own protected-mode interface for this is optional
// and frequently absent, so the old port is the portable answer.
//
// Two loops, not one: waiting only for the flag to *be* set would return
// immediately if a retrace is already in progress, having just missed the
// start of it. Waiting for the current one to end and the next to begin is
// what actually lands the blit between frames.
//
// The counters are a deadlock guard. On hardware that never reports retrace
// this returns after a bounded spin instead of hanging the caller forever,
// which matters because this can be called with the console locked.
static void
vga_disp_wait_vblank(struct device *d)
{
int guard;
for (guard = 0; guard < 1000000; guard++)
if (!(inb(0x3DA) & 0x08))
break;
for (guard = 0; guard < 1000000; guard++)
if (inb(0x3DA) & 0x08)
break;
}
const struct display_ops vga_display_ops = {
.mode = vga_disp_mode,
.acquire = vga_disp_acquire,
.present = vga_disp_present,
.wait_vblank = vga_disp_wait_vblank,
};