include: one folder per subsystem, and includes that name it

Sixty-five headers sat side by side in include/, so `#include "irq.h"`
said nothing about where irq.h was or what it belonged to, and errno.h,
stat.h and fcntl.h at the top shadowed the system's for any host tool
built with -I./include. They are in folders now -- arch/ core/ fs/
dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and
every include names the folder, as Linux's do: "core/proc.h".

Done mechanically and checked the hard way: the kernel built from this
tree disassembles identically to the one built from the tree before
it. The full image boots, and userland is the same set of programs.
Paths in docs and comments follow; docs/guides/layout.md has a table
of what is where, and tools/fix-includes.py rewrites old-style
includes from the folders in the tree, for code written before this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
Nexoniarz 2026-10-23 15:05:12 +02:00
commit a1c965da1e
267 changed files with 1560 additions and 1440 deletions

33
include/dev/acpi.h Normal file
View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
// ACPI, in two layers.
//
// kernel/arch/x86_64/acpi.c reads the static tables at boot, before anything
// else can run: the MADT for the processors and the I/O APIC and its
// interrupt source overrides, the FADT for the PM timer.
//
// kernel/acpi/ is everything that needs AML, which is the firmware's own code
// and has to be interpreted: uACPI (ports/uacpi) runs it, and aml.c uses it
// for PCI interrupt routing, the power button, power-off and reset. Every
// function below that depends on it does nothing, and says so by returning
// -1, on a machine whose AML could not be loaded.
// The RSDP's physical address, or 0 if there is none.
uint64 acpi_rsdp_phys(void);
// The GSI an ISA IRQ is wired to, after the MADT's interrupt source
// overrides, and its trigger mode (an IRQ_* from irq.h) in *mode. `dflt` is
// the mode when the override says "conforms to the bus" or there is none:
// edge/high for an ISA device, level/low for the SCI.
int acpi_isa_gsi(int isa, int dflt, int *mode);
// Load and initialize the namespace. After kinit, before pci_init.
void acpi_aml_init(void);
// The worker thread that runs AML on behalf of interrupts. After sinit.
void acpi_thread_start(void);
// Route every PCI device's INTx through _PRT. From pci_init, before binding.
void acpi_pci_route(void);
int acpi_poweroff(void); // S5; returns only on failure
int acpi_reboot(void); // the FADT reset register; ditto

103
include/dev/audio.h Normal file
View file

@ -0,0 +1,103 @@
#pragma once
// The audio class, and the engine above it.
//
// A program that wants to make a sound should be able to open a device, write
// samples to it, and stop -- which is what UNIX audio has always been, and
// what this is. `/dev/dsp`, PCM in, `write()`, and a handful of ioctls to say
// what the samples are. No server, no session bus, no protocol.
//
// The shape is the display class's, for the same reason: a consumer asks for
// "the audio device" and gets whatever is bound, so adding a second driver is
// not an edit to every caller. Found with device_find_class(DEVCLASS_AUDIO).
//
// ---------------------------------------------------------------------------
//
// **What the engine is for, and what it is not yet.**
//
// AudioEngine owns everything above a driver that is not specific to a chip:
// what format the stream is in, how many bytes the hardware can take, and what
// a write() does when it cannot take them all. That is genuinely shared -- a
// second driver should not re-implement "block until there is room".
//
// It is deliberately *one stream*. Mixing several programs into one device is
// a real feature with a real design behind it, and building it before anything
// plays a single sound would be the mistake docs/plan/1.0.md warns about: work
// that exists and is connected to nothing. The second program that wants to
// play at the same time is what should cause it to be written.
struct device;
// Interleaved signed 16-bit little-endian, which is what every PC audio chip
// takes natively and what a WAV file already contains. Others exist and none
// of them is needed to make a sound; converting in the kernel to satisfy a
// caller that could have converted first is not an obvious trade.
#define AUDIO_FMT_S16LE 16
struct audio_format {
unsigned int rate; // frames per second -- 48000, 44100
unsigned int channels; // 1 mono, 2 stereo
unsigned int bits; // AUDIO_FMT_S16LE
};
struct audio_ops {
// Take this format, or report what the hardware will do instead by writing
// back into `f` and returning 0. Returning negative means it cannot play at
// all. A driver that only does 48 kHz stereo says so here rather than
// failing a write() later, which is the difference between a program that
// adapts and a program that gets silence.
int (*configure)(struct device *, struct audio_format *f);
// Bytes that can be queued right now without waiting. The engine uses this
// to decide whether a write() has to block, so a driver that returns a
// hardware-accurate number is a driver whose write() latency is honest.
int (*space)(struct device *);
// Queue PCM. Returns bytes accepted, which may be fewer than offered and
// may be zero; the engine handles the remainder. Never blocks.
int (*queue)(struct device *, const void *pcm, int bytes);
// Start the stream if it is not running. Called by the engine once there is
// something to play, rather than at open: a device left running with an
// empty ring plays whatever was in it, which on real hardware is a loop of
// the last buffer and sounds exactly as bad as it reads.
void (*start)(struct device *);
// Stop, and discard anything queued.
void (*stop)(struct device *);
// Bytes still to be played. Zero means the hardware has finished. The
// engine polls this for SNDCTL_DSP_SYNC rather than a driver growing its
// own wait loop.
int (*pending)(struct device *);
};
// --- what the kernel uses --------------------------------------------------
struct device *audio_get(void);
int audio_configure(struct audio_format *f); // 0, or negative
int audio_write(const void *pcm, int bytes); // bytes queued, or negative
void audio_drain(void);
void audio_stop(void);
// --- what userspace uses ---------------------------------------------------
//
// OSS numbering, which is the one every UNIX audio program already knows and
// the only reason to invent a different set would be to make ports harder.
// These four are the whole interface a tone generator or a WAV player needs.
#define SNDCTL_DSP_SPEED 0xC0045002 // int in/out: sample rate
#define SNDCTL_DSP_CHANNELS 0xC0045006 // int in/out: 1 or 2
#define SNDCTL_DSP_SETFMT 0xC0045005 // int in/out: AFMT_S16_LE
#define SNDCTL_DSP_SYNC 0x00005001 // wait for the queue to drain
#define SNDCTL_DSP_GETOSPACE 0x8010500C // struct audio_buf_info out
#define SNDCTL_DSP_RESET 0x00005000 // stop, discard
#define AFMT_S16_LE 0x00000010
struct audio_buf_info {
int fragments; // how many whole fragments are free
int fragstotal; // how many there are in total
int fragsize; // bytes in one
int bytes; // free bytes, which is the number that matters
};

90
include/dev/device.h Normal file
View file

@ -0,0 +1,90 @@
#pragma once
// The device model: one list of everything attached, whatever bus found it.
//
// Before this there were two registries in kernel/drivers/registry.c -- one
// array of PCI drivers, one of USB drivers -- and nothing that knew about both.
// That is fine for binding drivers to hardware, which is all it was for, and
// useless for the three things wanted next:
//
// *Enumeration.* "What is in this machine, and what claimed it" had no
// answer that covered a USB disk and a PCI display at once. A device manager
// is exactly that question, so it needs exactly this list.
//
// *Classes.* A compositor should ask for "the display" and not care whether
// that is the VBE framebuffer or virtio-gpu. Without a class, every consumer
// hardcodes a driver, and adding the second driver means editing the
// consumers -- which is how a framebuffer address ends up hardcoded in three
// places, as it once was here.
//
// *A boundary for drivers that are not compiled in.* Drivers the user
// installs (see docs/architecture/devices.md) have to attach somewhere that is not "an
// array in registry.c", because that array is fixed at link time.
//
// The shape is deliberately the one NeXTSTEP/macOS uses rather than Linux's:
// a device is an object with a class and an ops vtable, and consumers talk to
// the class. It is *not* a device tree -- there is no parent/child nesting
// here, only a flat list with a bus tag, because nothing yet needs to walk a
// hierarchy and a tree that nobody traverses is just a harder list.
#define DEV_NAMELEN 24
// Which bus found it. This is provenance, not capability -- what the device
// *does* is its class.
enum dev_bus {
DEVBUS_PLATFORM = 0, // found by looking, not by enumerating: PS/2, VBE
DEVBUS_PCI,
DEVBUS_USB,
DEVBUS_NBUS
};
// What it is. A consumer asks for a class and gets whatever is bound.
enum dev_class {
DEVCLASS_OTHER = 0,
DEVCLASS_DISPLAY,
DEVCLASS_INPUT,
DEVCLASS_STORAGE,
DEVCLASS_NET,
DEVCLASS_BUS, // a controller that finds more devices (xHCI, hubs)
DEVCLASS_AUDIO,
DEVCLASS_NCLASS
};
struct device {
char name[DEV_NAMELEN]; // "vga0", "xhci0" -- unique, human-readable
char desc[48]; // free text for the enumerator
enum dev_bus bus;
enum dev_class class;
// Where it came from, for the enumerator to print. Zero when not applicable.
uint16 vendor, product;
uint8 b, s, f; // PCI bus/slot/func
const char *driver; // whatever claimed it, or 0 for unclaimed
void *ops; // class-specific vtable; see display.h
void *priv; // the driver's own state
int in_use;
};
#define NDEVICE 128
// Register a device. Returns it, or 0 if the table is full. The name is made
// unique by appending an index, so a caller passes "vga" and gets "vga0".
struct device *device_add(const char *base, enum dev_bus bus,
enum dev_class class, const char *driver);
// Set the human-readable description. Bounded copy into the fixed field, so
// callers do not each need to know how long it is.
void device_set_desc(struct device *, const char *);
// Walk. Returns 0 past the end; index is dense over registered devices.
struct device *device_at(int i);
int device_count(void);
// First device of a class with a bound driver, or 0. This is how a consumer
// finds "the display" without naming a driver.
struct device *device_find_class(enum dev_class class);
const char *dev_bus_name(enum dev_bus);
const char *dev_class_name(enum dev_class);

99
include/dev/input.h Normal file
View file

@ -0,0 +1,99 @@
#pragma once
#include "core/types.h"
// Raw input events -- Linux's evdev interface.
//
// The console gives characters. That is the right thing for a shell and the
// wrong thing for everything else: a character stream has no key *releases*,
// no way to tell left shift from right, no notion of a key being held, and no
// mouse at all. A game holding W to walk forward, or a compositor deciding
// which window a click landed in, needs the events underneath the characters.
//
// The layout is Linux's, deliberately. It is what every program that reads input
// already speaks -- SDL, X, libinput, every game engine -- so a program that
// runs on Linux reads input here without a porting layer. It costs nothing to
// match: the struct is four fields.
//
// Nodes are /dev/input/event0 (keyboard) and /dev/input/event1 (mouse).
struct input_timeval {
int64 tv_sec;
int64 tv_usec;
};
struct input_event {
struct input_timeval time;
uint16 type;
uint16 code;
int32 value;
};
// Event types.
#define EV_SYN 0x00
#define EV_KEY 0x01
#define EV_REL 0x02
#define EV_ABS 0x03
#define EV_MSC 0x04
#define SYN_REPORT 0
// Relative axes.
// Absolute axes, for a pointing device that reports where it *is* rather than
// how far it moved -- a tablet, a touchscreen, or a VM's absolute pointer.
// Same numbering as Linux, because /dev/input/eventN claims to speak evdev.
#define ABS_X 0x00
#define ABS_Y 0x01
#define REL_X 0x00
#define REL_Y 0x01
#define REL_WHEEL 0x08
// Buttons live in the EV_KEY code space alongside keys, which is why the
// keyboard's codes stop well below 0x100.
#define BTN_LEFT 0x110
#define BTN_RIGHT 0x111
#define BTN_MIDDLE 0x112
// Key codes worth naming. The rest are the AT scancode-set-1 numbers, which
// Linux's keycodes were derived from and still match for everything below
// 0x54 -- that is not a coincidence and it is why the PS/2 path needs no
// translation table for unprefixed bytes.
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_ENTER 28
#define KEY_LEFTCTRL 29
#define KEY_LEFTSHIFT 42
#define KEY_RIGHTSHIFT 54
#define KEY_LEFTALT 56
#define KEY_SPACE 57
#define KEY_RIGHTCTRL 97
#define KEY_RIGHTALT 100
#define KEY_HOME 102
#define KEY_UP 103
#define KEY_PAGEUP 104
#define KEY_LEFT 105
#define KEY_RIGHT 106
#define KEY_END 107
#define KEY_DOWN 108
#define KEY_PAGEDOWN 109
#define KEY_INSERT 110
#define KEY_DELETE 111
#define KEY_LEFTMETA 125
#define KEY_RIGHTMETA 126
#define KEY_MAX 255
// EVIOCGRAB -- take/release exclusive access to a device.
//
// Linux's _IOW('E', 0x90, int). Spelled as the resulting constant rather than
// built from an _IOW macro because SchOS has no ioctl-encoding macros and
// inventing a set just for this would be a bigger change than the feature.
// A non-zero argument grabs, zero releases; the argument is passed by value.
#define EVIOCGRAB 0x40044590
// Which device an event came from. Both are minor numbers under the input
// major, and both are the index into the driver's array.
#define INPUT_KBD 0
#define INPUT_MOUSE 1
#define NINPUTDEV 2

115
include/dev/kbd.h Normal file
View file

@ -0,0 +1,115 @@
// PC keyboard interface constants
//
// The scancode tables themselves are no longer here. They moved to
// kernel/dev/keymap.c when there became more than one of them -- a header
// full of `static` arrays gives every file that includes it its own private
// copy, which was harmless while exactly one file did.
#ifndef KBD_H
#define KBD_H
#define KBSTATP 0x64 // kbd controller status port(I)
#define KBS_DIB 0x01 // kbd data in buffer
#define KBDATAP 0x60 // kbd data port(I)
#define NO 0
#define SHIFT (1<<0)
#define CTL (1<<1)
#define ALT (1<<2)
#define CAPSLOCK (1<<3)
#define NUMLOCK (1<<4)
#define SCROLLLOCK (1<<5)
#define E0ESC (1<<6)
// Right Alt, tracked apart from left Alt.
//
// They used to be the same bit, which is fine for typing -- neither is used to
// look up a character -- and wrong for chords. On a Polish layout AltGr is how
// you type a c e l n o s x z with their diacritics, and it is held constantly.
// Nothing the kernel binds may involve it.
#define RALT (1<<7)
// Special keycodes.
//
// **Above every Unicode codepoint, deliberately**, and they did not use to be:
// KBD_UP was 0xE2 and its neighbours filled 0xE0..0xE9. Those are exactly the
// UTF-8 lead bytes for three-byte characters, so typing any of U+0800..U+FFFF
// -- which is most punctuation, every arrow, every maths symbol, and the whole
// of CJK -- produced a keycode the console read as an arrow key and turned
// into `ESC [ A`. `∞`, `→`, `≤` and `'` all vanished from the input line while
// `ą` and `ę` came through, because those are two-byte sequences whose lead
// byte is 0xC4 and nothing claimed 0xC4.
//
// The chord codes below already knew this rule and said so; the navigation
// keys predate them and were never brought in line. The rule, stated once: a
// value that a key produces is either a Unicode codepoint or it is above
// 0x10FFFF, and nothing in between means anything.
#define KBD_SPECIAL 0x110000
#define KBD_HOME (KBD_SPECIAL + 0x00)
#define KBD_END (KBD_SPECIAL + 0x01)
#define KBD_UP (KBD_SPECIAL + 0x02)
#define KBD_DN (KBD_SPECIAL + 0x03)
#define KBD_LF (KBD_SPECIAL + 0x04)
#define KBD_RT (KBD_SPECIAL + 0x05)
#define KBD_PGUP (KBD_SPECIAL + 0x06)
#define KBD_PGDN (KBD_SPECIAL + 0x07)
#define KBD_INS (KBD_SPECIAL + 0x08)
#define KBD_DEL (KBD_SPECIAL + 0x09)
// Switch virtual terminal.
//
// *Which* chord is a runtime setting, not a constant here, and that is the
// interesting part. Three separate parties have a claim on every candidate:
//
// the HOST, which sees the keystroke first and may never pass it on
// Alt+F4, Alt+F2 window manager
// Ctrl+Alt+F1..F6 display manager, and it matches loosely --
// adding Shift does not get past it
// Ctrl+Alt+<digit> QEMU's own console tabs
//
// APPLICATIONS, which lose the key permanently if the kernel eats it
// Alt+<anything> Meta. zsh binds Meta-digit as digit-argument
// Ctrl+<letter> control characters
// Ctrl+Shift+<digit> types a character -- Ctrl+Shift+2 is ^@
// F1..F6 alone help, and full-screen programs
//
// the KEYBOARD LAYOUT
// AltGr+<letter> Polish diacritics, and on some setups AltGr
// arrives as Ctrl+Alt
//
// There is no combination that is free for everyone on every host, and the
// guest cannot find out what its host grabs -- the keystroke simply never
// arrives, which is indistinguishable from a chord nobody pressed. So the
// kernel ships a default and lets it be changed: VT_SETCHORD, and `chvt -c`
// in userspace. Setting it to no modifiers disables the chord entirely and
// leaves `chvt N` as the only way to switch, which is a legitimate choice on
// a machine whose host takes everything.
//
// The default is AltGr+F1..F6, and the "AltGr wants it" line above needs
// reading carefully: what a Polish layout wants is AltGr+*letter*. AltGr with
// a function key produces nothing on any layout, no window manager binds it,
// and no application asks for it. Right Alt being a separate modifier bit
// here is what makes the distinction expressible at all -- it used to share
// ALT, which is harmless for typing and useless for this.
#define KBD_VT_SWITCH (KBD_SPECIAL + 0x20)
// Previous / next terminal, on the same modifiers as the chord above. The
// minus and equals keys, which sit together and read as - and + -- a relative
// move for when you have two terminals and do not care which number they are.
#define KBD_VT_PREV (KBD_SPECIAL + 0x30)
#define KBD_VT_NEXT (KBD_SPECIAL + 0x31)
#define SCAN_MINUS 0x0C // - / _
#define SCAN_EQUALS 0x0D // = / +
#define SCAN_F1 0x3B // F1..F6 are scancodes 0x3B..0x40
#define SCAN_1 0x02 // 1..6 are scancodes 0x02..0x07
// C('A') == Control-A
#define C(x) (x - '@')
#endif /* KBD_H */

50
include/dev/keymap.h Normal file
View file

@ -0,0 +1,50 @@
// Keyboard layouts.
//
// A layout answers one question: given a scancode and the modifiers held,
// which Unicode codepoint was typed? Everything else about the keyboard --
// which port to read, when a key is a release, what the terminal-switch chord
// is -- belongs to kernel/dev/kbd.c and is the same whatever the layout.
//
// Codepoints, not bytes. A layout that can only produce bytes can produce
// `ó` (U+00F3, which fits in Latin-1) but not `ą` (U+0105, which does not),
// and half a Polish alphabet is not a Polish layout. kbd.c encodes the
// codepoint to UTF-8 on the way to the console.
//
// **No dead keys.** On a German layout the acute and circumflex keys are dead
// -- pressed, they wait for the next key and combine -- and here they simply
// produce the accent character on its own. That is wrong, it is visibly
// wrong, and it needs somewhere to hold "a dead key is pending", which is
// per-keyboard state this interface has no room for. Layouts that need it are
// not shipped rather than shipped broken; see keymap_layouts[] for which.
#ifndef KEYMAP_H
#define KEYMAP_H
#include "core/types.h"
// Which of a layout's four tables a set of modifiers selects. Plain, Shift,
// AltGr, and Shift+AltGr -- Ctrl is not one of them, because a control
// character is derived from the plain codepoint rather than looked up, which
// is what makes Ctrl+Z still be Ctrl+Z on a layout that moved the Z key.
#define KM_PLAIN 0
#define KM_SHIFT 1
#define KM_ALTGR 2
#define KM_SHIFTALTGR 3
#define KM_LEVELS 4
struct keylayout {
const char *name; // "us", "pl", "de" -- what you type
const char *desc; // what it is, for `loadkeys -l`
const uint32 *level[KM_LEVELS]; // 256 entries each; NULL falls back
};
// The active layout, and the catalogue.
const struct keylayout *keymap_active(void);
const struct keylayout *keymap_by_index(int i); // NULL past the end
int keymap_set(const char *name); // 0, or -1 if unknown
// Scancode plus modifier bits (SHIFT/CTL/ALT/RALT/CAPSLOCK from kbd.h) to a
// Unicode codepoint, or one of the KBD_* specials, or 0 for "no character".
uint32 keymap_lookup(uint scancode, uint mods);
#endif /* KEYMAP_H */

74
include/dev/netdev.h Normal file
View file

@ -0,0 +1,74 @@
#pragma once
// The network class: what the TCP/IP stack talks to instead of a driver.
//
// The stack should ask for "the network interfaces" and get whatever is bound
// -- today an e1000, tomorrow a virtio-net or an rtl8139 -- exactly as a
// compositor asks for "the display" and does not care which driver answers.
// Without this the first driver's name spreads: kernel/net/net.c called
// e1000_present(), the lwIP glue was named netif_e1000.c and called
// e1000_transmit(), and adding a second NIC would have meant editing the
// protocol stack to add it. That is the mistake include/dev/device.h was written
// to prevent, made once anyway because the net class did not exist yet --
// kernel/drivers/net/e1000/e1000.c even said so, with `.ops = 0, // no class
// vtable yet`.
//
// A driver publishes one of these and is then finished with the network stack
// forever: it never mentions lwIP, and the stack never mentions it.
//
// Found with device_find_class(DEVCLASS_NET) or, when there may be more than
// one, netdev_at(); see include/dev/device.h.
#include "dev/device.h"
struct netdev_ops {
// Queue one frame for transmission. Returns 0, or -1 if the ring is full --
// which is congestion, not failure, and the caller is expected to retry.
int (*transmit)(struct device *, const void *frame, int len);
// Take one received frame if there is one. Returns its length, or 0 when
// nothing is waiting. Never blocks: the caller decides how to wait.
int (*receive)(struct device *, void *out, int cap);
// The station address, six bytes. Every Ethernet interface has one and the
// stack needs it before it can send anything, so this is not optional.
const unsigned char *(*mac)(struct device *);
// Whether the link is up. Optional -- a driver that cannot tell leaves this
// null and is treated as connected, which is the assumption that was made
// before any driver could report it.
int (*link_up)(struct device *);
};
// The i-th bound network device, or 0 past the end. Dense over interfaces
// that have both a driver and a vtable, so the stack can simply count up.
struct device *netdev_at(int i);
int netdev_count(void);
// Wrappers with the null checks in one place, so no caller is written around
// a vtable that may be absent or partly filled in.
int netdev_transmit(struct device *, const void *frame, int len);
int netdev_receive(struct device *, void *out, int cap);
const unsigned char *netdev_mac(struct device *);
int netdev_link_up(struct device *);
// Tell the network stack a frame has arrived, from a driver's interrupt handler.
//
// The driver does not deliver the frame here and must not: the stack pulls with
// .receive, from a thread that can sleep, and an interrupt handler cannot. This
// only ends the thread's wait early, so the frame is taken now rather than at
// the next clock tick -- which is up to 10 ms away.
//
// Safe to call with interrupts off; see the note above netdev_rx_signal in
// kernel/net/net.c for why the wait is on the tick and not a channel of its own.
void netdev_rx_signal(void);
// Send an ARP request by hand and wait for the reply. `echo n > /proc/sysrq`.
//
// It proves the whole driver path at once -- descriptor formats, the DD
// handshake, tail bookkeeping, DMA addresses, the receive ring hand-back and
// the MAC filter -- because an ARP reply is addressed to this station and does
// not arrive if the filter is wrong. Written for the e1000 before there was a
// stack to test it with; it belongs to the class rather than to that driver,
// since it is the check every new NIC driver will want first.
void netdev_selftest(void);

182
include/dev/pci.h Normal file
View file

@ -0,0 +1,182 @@
#pragma once
// PCI bus enumeration and driver binding.
//
// Until this existed, every driver in SchOS found its hardware by knowing
// where it was: IDE at 0x1F0 because IDE has always been at 0x1F0, the
// framebuffer wherever the BIOS's VBE call said. That works for exactly the
// devices that predate PCI and for nothing else. A NIC, an AHCI controller,
// a virtio device or a real GPU has no fixed address at all -- you find it by
// asking the bus what is plugged into it, which is what this does.
// ---------------------------------------------------------------------------
// Config space
// ---------------------------------------------------------------------------
#define PCI_CONFIG_ADDR 0xCF8
#define PCI_CONFIG_DATA 0xCFC
#define PCI_VENDOR_ID 0x00
#define PCI_DEVICE_ID 0x02
#define PCI_COMMAND 0x04
#define PCI_STATUS 0x06
#define PCI_REVISION 0x08
#define PCI_PROG_IF 0x09
#define PCI_SUBCLASS 0x0A
#define PCI_CLASS 0x0B
#define PCI_HEADER_TYPE 0x0E
#define PCI_BAR0 0x10
#define PCI_SECONDARY_BUS 0x19
#define PCI_INTERRUPT_LINE 0x3C
#define PCI_INTERRUPT_PIN 0x3D
// PCI_COMMAND bits a driver usually wants set before touching its device.
#define PCI_CMD_IO 0x0001
#define PCI_CMD_MEMORY 0x0002
#define PCI_CMD_BUS_MASTER 0x0004
// Base classes worth naming. The full list is long and mostly irrelevant here.
#define PCI_CLASS_STORAGE 0x01
#define PCI_CLASS_NETWORK 0x02
#define PCI_CLASS_DISPLAY 0x03
#define PCI_CLASS_MULTIMEDIA 0x04
#define PCI_CLASS_BRIDGE 0x06
#define PCI_CLASS_SERIAL_BUS 0x0C
#define PCI_STATUS_CAPLIST 0x0010 // device has a capability list
#define PCI_CAP_PTR 0x34 // offset of the first capability
#define PCI_CAP_MSI 0x05
#define PCI_CAP_MSIX 0x11
#define PCI_CAP_VENDOR 0x09 // virtio's configuration lives here
#define PCI_CAP_PCIE 0x10
#define PCI_ANY 0xFFFF // wildcard in a match table
// Devices we will remember. A laptop has 30 to 60 functions, a desktop with
// a couple of cards and Thunderbolt more than 64, and every one past the limit
// was ignored -- including whatever disk or NIC came last in bus order.
#define NPCI 256
struct pci_bar {
uint64 base; // already masked; 0 means "no BAR here"
uint64 size; // determined by write-ones-and-read-back
int is_io; // I/O port range rather than memory
int is_64; // consumed the following BAR slot too
int prefetch;
};
struct pci_dev {
uint8 bus, slot, func;
uint16 vendor, device;
uint8 class, subclass, prog_if, revision;
uint8 irq; // interrupt line, 0xFF when unrouted
// How that line is signalled, an IRQ_* mode from irq.h. PCI's level/low
// unless ACPI's routing says otherwise -- see kernel/acpi/aml.c.
uint8 irq_mode;
uint8 header_type;
struct pci_bar bar[6];
const char *driver; // name of whatever claimed it, or 0
// The driver itself, so it can be asked to release the device. The name
// alone cannot be called.
const struct pci_driver *drv;
// How pci_irq_attach() connected the interrupt: PCI_IRQ_INTX, _MSI or
// _MSIX, and on which line. See kernel/dev/pci.c.
uint8 irq_kind;
int irq_line;
};
#define PCI_IRQ_NONE 0
#define PCI_IRQ_INTX 1
#define PCI_IRQ_MSI 2
#define PCI_IRQ_MSIX 3
// pci_irq_attach() flags. MSI is always tried first; MSI-X only when the
// driver says it can take it, because some parts change how they report
// interrupt causes once MSI-X is on (the 82574's IVAR, for one).
#define PCI_IRQF_MSIX 0x1
// One row of a driver's match table. Terminate the table with a row whose
// vendor is 0.
struct pci_match {
uint16 vendor, device; // PCI_ANY to ignore
uint8 class, subclass; // used only when vendor is PCI_ANY
};
// Declared here rather than defs.h because both take a struct pci_dev, which
// only this header defines. See kernel/dev/device.c for the model they feed.
// device.h is included rather than forward-declaring `enum dev_class`, which
// is a compiler extension and warns.
#include "dev/device.h"
enum dev_class pci_class_of(const struct pci_dev *);
// Connect a device's interrupt to a handler: an MSI vector if the device has
// one, MSI-X if allowed and it has that instead, and its INTx line otherwise.
// Returns the line, or -1 if there is no way to interrupt at all -- which is
// not fatal; a driver with a poll path should say so and carry on.
#include "core/irq.h"
int pci_irq_attach(struct pci_dev *, irq_handler_t, void *arg,
const char *name, int flags);
void pci_irq_detach(struct pci_dev *, irq_handler_t, void *arg);
const char *pci_irq_kind(const struct pci_dev *);
void pci_describe(const struct pci_dev *, char *out, int cap);
struct pci_driver {
const char *name;
// Base for the device's name in /proc/devices, if the driver's own name
// makes a bad one. `e1000` plus an index reads as "e10000"; a NIC wants to
// be `eth0`. Optional -- null means use `name`.
const char *devname;
const struct pci_match *match;
// Class-specific vtable, published on the device when this driver claims
// it -- struct display_ops for a display, and so on. Optional; a driver
// with nothing to offer a consumer leaves it null.
//
// It lives on the driver rather than being set inside probe() because
// pci_bind() registers the device *after* probe returns, so a driver has
// nothing to attach it to at the point it would want to.
const void *ops;
// Return 0 to claim the device. A driver that returns non-zero leaves it
// unclaimed for anything else that matches.
int (*probe)(struct pci_dev *);
// Called right after the device is published, for a driver that supports
// more than one instance and needs to say which of its own is which.
// Optional -- a single-instance driver has nothing to attach.
//
// It exists because probe() runs *before* device_add(), so a driver has
// nowhere to put its per-instance pointer at the point it would want to.
// Without this the second card of the same model is either refused or
// silently shares the first one's registers.
void (*attached)(struct pci_dev *, struct device *);
// Give the device up, so a userspace driver can take it.
//
// docs/plan/hybrid.md is built on the kernel *handing over* a device
// rather than a process wrestling it away: two drivers writing one set of
// registers is not a subtle failure, it is hardware that appears to have
// started lying. So the transfer is the incumbent's decision, and this is
// where it is made.
//
// A driver that implements this must leave the device in a state the next
// one can start from, and must stop touching it. A driver that does not
// implement it keeps its device: refusing is always safe, and for anything
// on the path to a shell it is also correct.
int (*release)(struct pci_dev *);
// Put one last frame on a device this driver was told to let go of.
//
// Called from panic(), and only from there: interrupts are off, no other
// CPU is coming back, and the process that was driving the device will not
// run again. The framebuffer is one the userspace driver registered with
// PCIIOC_PANICFB and the kernel has already painted the report into; all
// that is left is to make the device show it.
//
// The contract is what panic context allows: **no allocation, no locks, no
// sleeping.** Whatever memory this needs has to already exist, because the
// allocator may be exactly what was being held when the machine died. A
// driver that cannot meet that leaves this null, and the screen it handed
// over stays frozen -- which is what happens today and is not a
// regression.
//
// Returns 0 if something was put on screen.
int (*panic_present)(struct pci_dev *, uint64 fb_paddr, int w, int h);
};

185
include/dev/pciuser.h Normal file
View file

@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// /dev/pci -- device access from userspace.
//
// docs/plan/hybrid.md, mechanisms 2 and 4: MMIO and DMA to a process. This is
// the interface a driver that does not live in the kernel talks to, and it is
// deliberately about *PCI devices*, not about any particular driver model.
//
// That generality is the point, and it is worth being explicit about why.
// The first users of this will be SchOS's own drivers, evicted one at a time
// -- virtio-gpu, then xHCI -- and the interface has to be judged by them.
// Something that hosts foreign drivers later is then a consumer of a general
// mechanism rather than a hole shaped like one, which is both the honest
// design and the only one that stays honest under examination.
//
// What a driver needs from a kernel, and all it needs:
//
// - to find its device, and read what the bus says about it INFO
// - to be given it, exclusively CLAIM
// - to reach its registers mmap(BAR)
// - memory it can hand to the device DMA
// - to be told when the device wants attention IRQ + read()
//
// There is no IOMMU. A process holding a DMA buffer can point the device at
// any physical address, so this isolates a driver against its own *bugs* and
// not against its own *intent*. docs/plan/hybrid.md says so at length and it
// remains true here: a crashed driver that restarts instead of taking the
// machine down is the whole benefit, and it is not a security boundary.
#ifndef PCIUSER_H
#define PCIUSER_H
#define PCIUSER_MAJOR 9
#define PCIUSER_MAXBAR 6
struct pciuser_bar {
unsigned long long base; // physical address, 0 when the BAR is unused
unsigned long long size;
unsigned int flags; // PCIUSER_BAR_*
unsigned int pad;
};
#define PCIUSER_BAR_MMIO 0x01 // memory space; mappable
#define PCIUSER_BAR_IO 0x02 // I/O port space; not mappable
#define PCIUSER_BAR_PREFETCH 0x04
// One device, as the bus describes it. `index` is the only field the caller
// fills in; everything else comes back.
struct pciuser_info {
unsigned int index; // in: 0 .. PCIIOC_COUNT-1
unsigned char bus, slot, func;
unsigned char header_type;
unsigned short vendor, device;
unsigned char class, subclass, prog_if, revision;
unsigned char irq; // interrupt line, 0xFF when unrouted
unsigned char claimed; // 1 when a userspace process holds it
unsigned short pad;
// The name of whatever owns it: an in-kernel driver, "userspace" when a
// process has claimed it, or empty when nothing has.
char driver[16];
struct pciuser_bar bar[PCIUSER_MAXBAR];
};
// A DMA buffer. The kernel allocates it from the DMA arena, maps it into the
// caller, and reports the physical address the *device* must be given -- which
// is the entire reason a driver cannot do this for itself with mmap.
// A dword of PCI configuration space. A virtio device says where its registers
// live in vendor capabilities, which are here and nowhere else -- so without
// this a driver cannot find its own device.
struct pciuser_cfg {
unsigned int index; // in
unsigned int offset; // in: dword-aligned, 0..0xFC
unsigned int value; // out
unsigned int pad;
};
struct pciuser_dma {
unsigned long long size; // in: bytes, rounded up to a page
unsigned long long uaddr; // out: where it landed in this process
unsigned long long paddr; // out: what to program into the device
};
#define PCIIOC_COUNT 0x9000 // out: int, how many devices the bus has
#define PCIIOC_INFO 0x9001 // struct pciuser_info
#define PCIIOC_CLAIM 0x9002 // in: int index -- take it from the kernel
#define PCIIOC_RELEASE 0x9003 // in: int index
#define PCIIOC_DMA 0x9004 // struct pciuser_dma
#define PCIIOC_CFG 0x9006 // struct pciuser_cfg -- read config space
#define PCIIOC_PANICFB 0x9007 // struct pciuser_panicfb -- see below
#define PCIIOC_IRQ 0x9005 // in: int index -- deliver this device's
// interrupts to this process
// Interrupts arrive by *reading* this device, which is how Linux's uio does it
// and for the same reason: a descriptor composes with poll, and a driver that
// stops reading falls behind rather than losing events.
//
// ioctl(fd, PCIIOC_IRQ, &index);
// for (;;) {
// unsigned long long n;
// read(fd, &n, sizeof n); // blocks until the device interrupts
// ... service the device, and ACK IT AT THE DEVICE ...
// write(fd, &n, sizeof n); // re-arm
// }
//
// **The write is not optional.** A PCI interrupt line is level-triggered: it
// stays asserted until the driver clears the cause *at the device*, and the
// kernel cannot do that because clearing it is device-specific -- it is a
// different register on every chip. So the kernel masks the line when the
// interrupt arrives and leaves it masked until the write says the device has
// been dealt with.
//
// Without that, EOI is signalled on a line that is still asserted and the
// interrupt re-fires immediately, for ever. That is not hypothetical: it
// happened in this kernel, at 72,000 interrupts a second, and what it looked
// like from userspace was a clock running seven times slow.
// See docs/history/kernel.md section 13.
//
// One device per process. A driver drives one thing; if you need two, run two.
// --- the crash screen, on a display the kernel no longer drives ------------
//
// A userspace display driver leaves a hole the kernel cannot see around: when
// the machine panics, the screen this process is scanning out is frozen at
// whatever it last put there. The machine is dead and its second monitor is
// still showing a shell prompt.
//
// The kernel cannot fix that by itself. It let go of the device deliberately
// (PCIIOC_CLAIM), and the process that took it will not run again -- panic
// means no scheduling, so there is nobody left to ask.
//
// So a userspace display driver tells the kernel, in advance, where its
// scanout lives:
//
// struct pciuser_panicfb pfb;
// pfb.index = index; // the device, already claimed
// pfb.paddr = dma.paddr; // the framebuffer, from PCIIOC_DMA
// pfb.width = 1280;
// pfb.height = 800;
// pfb.stride = 1280 * 4; // bytes per row; XRGB8888 only
// ioctl(fd, PCIIOC_PANICFB, &pfb);
//
// and on a panic the kernel paints the crash report into that memory and asks
// the driver it handed the device to -- its *kernel* driver, the one that was
// told to let go -- to put it on screen one last time. That driver resets the
// device and brings up a minimal queue from static memory: no allocation, no
// locks, nothing that can fail differently depending on what the machine was
// doing when it died.
//
// Registering is a promise about memory, so it is checked like one: the range
// must lie inside a block this process got from PCIIOC_DMA on this device.
// Without that check it would be a way to nominate any physical address in the
// machine to be overwritten at panic, which is a worse bug than the one it
// fixes.
//
// A zero paddr unregisters. The registration goes away with the claim and with
// the process, so a driver that exits leaves nothing behind.
struct pciuser_panicfb {
unsigned int index; // in: the claimed device
unsigned long long paddr; // in: physical address of the scanout, 0 to
// unregister
unsigned int width; // in: pixels
unsigned int height;
unsigned int stride; // in: bytes per row
};
// Registers are reached with mmap() on this device, where the *offset is the
// physical address of the BAR*:
//
// ioctl(fd, PCIIOC_INFO, &info);
// ioctl(fd, PCIIOC_CLAIM, &info.index);
// regs = mmap(0, info.bar[0].size, PROT_READ|PROT_WRITE, MAP_SHARED,
// fd, info.bar[0].base);
//
// The kernel refuses an offset that is not inside a BAR of a device this
// process has claimed, which is what stops this being /dev/mem with extra
// steps.
#endif // PCIUSER_H

302
include/dev/usb.h Normal file
View file

@ -0,0 +1,302 @@
#pragma once
#include "dev/device.h"
// USB, the parts that are the same whatever host controller you have.
//
// Everything here comes from the USB 2.0 specification chapter 9, which every
// USB device implements regardless of speed, class or vendor. That is what
// makes enumeration possible at all: before you know anything about a device
// you can still ask it what it is, because the question is standardised.
// bmRequestType
#define USB_DIR_OUT 0x00
#define USB_DIR_IN 0x80
#define USB_TYPE_STANDARD 0x00
#define USB_TYPE_CLASS 0x20
#define USB_RECIP_DEVICE 0x00
#define USB_RECIP_INTERFACE 0x01
#define USB_RECIP_ENDPOINT 0x02
#define USB_RECIP_OTHER 0x03 // a hub's downstream port
// bRequest, standard requests
#define USB_REQ_GET_STATUS 0
#define USB_REQ_CLEAR_FEATURE 1
#define USB_REQ_SET_FEATURE 3
#define USB_REQ_SET_ADDRESS 5
#define USB_REQ_GET_DESCRIPTOR 6
#define USB_REQ_SET_DESCRIPTOR 7
#define USB_REQ_GET_CONFIG 8
#define USB_REQ_SET_CONFIG 9
#define USB_REQ_GET_INTERFACE 10
#define USB_REQ_SET_INTERFACE 11
// Descriptor types (the high byte of wValue in a GET_DESCRIPTOR)
#define USB_DT_DEVICE 1
#define USB_DT_CONFIG 2
#define USB_DT_STRING 3
#define USB_DT_INTERFACE 4
#define USB_DT_ENDPOINT 5
// Class codes we care about
#define USB_CLASS_HID 0x03
#define USB_CLASS_MASS_STORAGE 0x08
#define USB_CLASS_HUB 0x09
// HID subclass/protocol. Boot protocol is the one a BIOS uses, and the reason
// a USB keyboard works before any operating system has loaded: the report
// layout is fixed, so it can be read without parsing a report descriptor.
#define USB_HID_SUBCLASS_BOOT 0x01
#define USB_HID_PROTO_KEYBOARD 0x01
#define USB_HID_PROTO_MOUSE 0x02
// Not a USB value: bInterfaceProtocol has no code for "absolute pointer".
// A HID device that claims neither boot protocol is treated as a tablet, which
// is what a VM offers so its cursor and the guest's can agree. Kept out of the
// 0x00-0x02 range the specification defines so it cannot collide.
#define USB_HID_PROTO_TABLET 0x80
// Bus speeds.
//
// These are xHCI's numbering, used as the common currency rather than
// translated at the boundary. That is a deliberate choice: there is one host
// controller, the values have to reach a slot context eventually, and a
// second enum whose only job is to be converted into this one would be
// ceremony. If a second controller ever appears, this is the thing to
// revisit -- but a class driver still has no business including xhci.h.
#define USB_SPEED_FULL 1 // 12 Mb/s
#define USB_SPEED_LOW 2 // 1.5 Mb/s
#define USB_SPEED_HIGH 3 // 480 Mb/s
#define USB_SPEED_SUPER 4 // 5 Gb/s
// An eight-byte SETUP packet, the header of every control transfer.
struct usb_setup {
uint8 bmRequestType;
uint8 bRequest;
uint16 wValue;
uint16 wIndex;
uint16 wLength;
} __attribute__((packed));
struct usb_device_desc {
uint8 bLength;
uint8 bDescriptorType;
uint16 bcdUSB;
uint8 bDeviceClass;
uint8 bDeviceSubClass;
uint8 bDeviceProtocol;
uint8 bMaxPacketSize0;
uint16 idVendor;
uint16 idProduct;
uint16 bcdDevice;
uint8 iManufacturer;
uint8 iProduct;
uint8 iSerialNumber;
uint8 bNumConfigurations;
} __attribute__((packed));
struct usb_config_desc {
uint8 bLength;
uint8 bDescriptorType;
uint16 wTotalLength;
uint8 bNumInterfaces;
uint8 bConfigurationValue;
uint8 iConfiguration;
uint8 bmAttributes;
uint8 bMaxPower;
} __attribute__((packed));
struct usb_interface_desc {
uint8 bLength;
uint8 bDescriptorType;
uint8 bInterfaceNumber;
uint8 bAlternateSetting;
uint8 bNumEndpoints;
uint8 bInterfaceClass;
uint8 bInterfaceSubClass;
uint8 bInterfaceProtocol;
uint8 iInterface;
} __attribute__((packed));
struct usb_endpoint_desc {
uint8 bLength;
uint8 bDescriptorType;
uint8 bEndpointAddress; // bit 7: 1 = IN
uint8 bmAttributes; // bits 1:0: 0 ctrl, 1 iso, 2 bulk, 3 interrupt
uint16 wMaxPacketSize;
uint8 bInterval;
} __attribute__((packed));
#define USB_EP_XFERTYPE(a) ((a) & 0x03)
#define USB_EP_BULK 2
#define USB_EP_INTERRUPT 3
#define USB_EP_IS_IN(a) (((a) & 0x80) != 0)
#define USB_EP_NUM(a) ((a) & 0x0F)
// Mass storage: SCSI transparent command set over bulk-only transport.
#define USB_MSC_SUBCLASS_SCSI 0x06
#define USB_MSC_PROTO_BOT 0x50
// ---------------------------------------------------------------------------
// Hubs
//
// A hub is a USB device like any other, and everything it does is done by
// sending it ordinary control requests. There is no separate bus protocol:
// "reset port 3" is a SET_FEATURE addressed to the hub.
// ---------------------------------------------------------------------------
#define USB_DT_HUB 0x29
// Port features, the wValue of SET_FEATURE / CLEAR_FEATURE on a port.
#define PORT_FEAT_ENABLE 1
#define PORT_FEAT_RESET 4
#define PORT_FEAT_POWER 8
#define PORT_FEAT_C_CONNECTION 16
#define PORT_FEAT_C_ENABLE 17
#define PORT_FEAT_C_RESET 20
// wPortStatus, the first half of a port's GET_STATUS reply.
#define PORTSTAT_CONNECTION (1 << 0)
#define PORTSTAT_ENABLE (1 << 1)
#define PORTSTAT_RESET (1 << 4)
#define PORTSTAT_POWER (1 << 8)
#define PORTSTAT_LOWSPEED (1 << 9)
#define PORTSTAT_HIGHSPEED (1 << 10)
struct usb_hub_desc {
uint8 bDescLength;
uint8 bDescriptorType;
uint8 bNbrPorts;
uint16 wHubCharacteristics;
uint8 bPwrOn2PwrGood; // in 2 ms units
uint8 bHubContrCurrent;
} __attribute__((packed));
// Bits 1:0 of wHubCharacteristics: 00 ganged power, 01 per-port.
#define HUB_CHAR_TT_MASK 0x0060
// HID class requests, on the interface rather than the device.
#define USB_HID_SET_IDLE 0x0A
#define USB_HID_SET_PROTOCOL 0x0B
#define USB_HID_PROTOCOL_BOOT 0
#define USB_HID_PROTOCOL_REPORT 1
#define USB_DT_HID 0x21
#define USB_DT_REPORT 0x22
// ---------------------------------------------------------------------------
// The interface between the USB core and a host controller
//
// One host controller exists (xHCI) and one probably always will, but the
// core has no business knowing about TRBs and slot IDs, and the controller
// has no business knowing what a HID report is. This is where the line goes.
// ---------------------------------------------------------------------------
struct usb_dev;
struct usb_hc_ops {
// A control transfer on endpoint 0. `buf`, if any, must be DMA memory.
int (*control)(struct usb_dev *, struct usb_setup *, void *buf, int len);
// Make an endpoint usable. Returns 0, or -1 if the controller refused.
int (*open_ep)(struct usb_dev *, const struct usb_endpoint_desc *);
// Queue one IN transfer. Completion arrives via dev->complete.
int (*queue_in)(struct usb_dev *, int ep_addr, void *buf, int len);
// One bulk transfer, synchronously. Sleeps, so process context only.
// Returns bytes transferred, or -1.
int (*bulk)(struct usb_dev *, int ep_addr, void *buf, int len);
// Bring up a device found on a downstream port of `hub`. The controller
// works out the route string from the hub's own position.
void (*attach)(struct usb_dev *hub, int hub_port, int speed);
// Tell the controller this device is a hub with `nports` ports. It has to
// know: a hub is scheduled differently from anything else, and devices
// behind it are addressed relative to it.
int (*set_hub)(struct usb_dev *, int nports, int mtt);
// Whatever is on a hub's downstream port has gone: tear it down, and
// everything behind it if it was a hub too.
void (*detach)(struct usb_dev *hub, int hub_port);
};
#define USB_MAXCONF 512 // bytes of configuration descriptor we keep
struct usb_dev {
const struct usb_hc_ops *ops;
void *hc; // the controller's own handle for this device
int slot;
int speed;
int port; // ROOT hub port this whole branch hangs off
// Where the device sits in the tree.
//
// A USB device has no address of its own that describes its position -- the
// controller finds it by being told the path. `route` is xHCI's route
// string: five nibbles, one per tier, each holding the downstream port
// number taken at that tier. Tier 0 is a device on a root port, and its
// route is 0.
uint32 route;
int tier;
// Transaction translator, for a full or low speed device behind a
// high-speed hub. The controller has to know which hub is doing the
// speed conversion on its behalf, because it is the hub, not the device,
// that it actually schedules against.
int tt_slot, tt_port;
int in_use;
// A device with several interfaces is one usb_dev per interface, so that
// each can have its own driver -- the keyboard and the mouse on one
// wireless receiver are interfaces 0 and 1. `base` is the device's own
// usb_dev (the one the controller addressed) for every interface after the
// first, and 0 for the first, which is the device itself.
struct usb_dev *base;
struct usb_device_desc desc;
uchar conf[USB_MAXCONF]; // the configuration descriptor and everything
int conflen; // that follows it, exactly as the device sent
// The first interface, which is all a boot-protocol or bulk-only device
// needs, and the endpoints belonging to it.
struct usb_interface_desc iface;
struct usb_endpoint_desc intr_in;
struct usb_endpoint_desc bulk_in;
struct usb_endpoint_desc bulk_out;
int has_intr_in, has_bulk_in, has_bulk_out;
// Filled in by whichever class driver claimed the device.
const char *driver;
const struct usb_driver *drv; // the same, for calling disconnect
void *drvdata;
void (*complete)(struct usb_dev *, void *buf, int len);
};
// One row per class driver. Matching is on the *interface* descriptor,
// because bDeviceClass is very often 0 ("look at the interfaces").
struct usb_driver {
const char *name;
uint8 class, subclass, protocol; // 0xFF matches anything
// What the rest of the kernel should consider this device to be, once this
// driver has claimed it. Distinct from `class` above, which is the USB
// interface class the descriptor advertises: the driver knows what it made
// of the device, the descriptor only says what the device claims to be.
enum dev_class devclass;
int (*probe)(struct usb_dev *);
// The device has been unplugged. Stop using it; the controller frees its
// endpoints after this returns. May be 0 for a driver with nothing to undo.
void (*disconnect)(struct usb_dev *);
};
// Called by a host controller once a device is addressed and its device
// descriptor has been read. The core reads the configuration, selects it, and
// offers the device to the class drivers.
void usb_attach(struct usb_dev *dev);
// The device, and every interface of it, is gone. Calls each bound driver's
// disconnect. From the controller, before it frees the slot.
void usb_detach(struct usb_dev *dev);
// Re-check every hub's downstream ports, for devices plugged into or pulled
// out of one. From the hot-plug thread; see kernel/drivers/usb/hub/.
void usb_hub_poll(void);
// The hot-plug thread. After sinit(); see kernel/drivers/bus/xhci/xhci.c.
void usb_hotplug_start(void);
// Key repeat for USB keyboards, which do not repeat by themselves. Called
// every tick from the hot-plug thread; see kernel/drivers/usb/hid/.
void usb_hid_tick(void);

202
include/dev/virtio.h Normal file
View file

@ -0,0 +1,202 @@
#pragma once
// virtio 1.0 over PCI — the transport, and virtio-gpu's 2D protocol.
//
// It lived next to the driver while exactly one driver read it, with a note
// saying it should move when a second reader appeared. One has: cmd/vgpud is
// the same device driven from userspace (docs/plan/hybrid.md's first
// eviction), and it needs the same register offsets and the same commands.
//
// Still one file rather than transport-and-protocol split in two. Both readers
// want both halves, so a seam here would be drawn for the sake of having one.
// virtio-net or virtio-blk arriving is when that changes.
//
// Written from the OASIS specification (virtio 1.1, chapters 2, 4.1 and 5.7),
// which is a public document with no licence on what you write from it. Same
// reasoning as kernel/drivers/xhci.c.
//
// Why virtio-gpu at all, when there is already a framebuffer: the VBE
// framebuffer is a flat block of memory the loader happened to be told about.
// It has no mode setting, no notion of a frame being complete, and no way to
// ask the host to present anything -- so a compositor drawing into it is
// racing the scanout with no way to synchronise. virtio-gpu has resources,
// explicit transfer and an explicit flush, which is the model every real GPU
// driver has. It is the smallest device that teaches the right shape.
// --- PCI transport ---------------------------------------------------------
#define VIRTIO_VENDOR 0x1AF4
// Modern (1.0) device IDs are 0x1040 + the virtio device type. GPU is type 16.
#define VIRTIO_DEV_GPU 0x1050
// Vendor-specific PCI capability, which is how a virtio device says where in
// its BARs each configuration structure lives.
#define PCI_CAP_ID_VNDR 0x09
#define VIRTIO_PCI_CAP_COMMON_CFG 1
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2
#define VIRTIO_PCI_CAP_ISR_CFG 3
#define VIRTIO_PCI_CAP_DEVICE_CFG 4
// Offsets within the common configuration structure. Written out rather than
// declared as a struct: the fields are little-endian by specification and of
// mixed width, and a struct invites the compiler to pad or a caller to forget
// that these are MMIO and must be accessed at exactly the stated width.
#define VCOMMON_DFEAT_SEL 0x00 // le32
#define VCOMMON_DFEAT 0x04 // le32
#define VCOMMON_GFEAT_SEL 0x08 // le32
#define VCOMMON_GFEAT 0x0C // le32
#define VCOMMON_MSIX_CFG 0x10 // le16
#define VCOMMON_NUM_QUEUES 0x12 // le16
#define VCOMMON_STATUS 0x14 // u8
#define VCOMMON_CFG_GEN 0x15 // u8
#define VCOMMON_Q_SELECT 0x16 // le16
#define VCOMMON_Q_SIZE 0x18 // le16
#define VCOMMON_Q_MSIX 0x1A // le16
#define VCOMMON_Q_ENABLE 0x1C // le16
#define VCOMMON_Q_NOTIFY_OFF 0x1E // le16
#define VCOMMON_Q_DESC 0x20 // le64
#define VCOMMON_Q_DRIVER 0x28 // le64
#define VCOMMON_Q_DEVICE 0x30 // le64
// device_status bits, set in this order during bring-up.
#define VSTATUS_ACK 1
#define VSTATUS_DRIVER 2
#define VSTATUS_DRIVER_OK 4
#define VSTATUS_FEATURES_OK 8
#define VSTATUS_FAILED 0x80
#define VIRTIO_F_VERSION_1 32 // feature bit: this is a 1.0 device
// --- split virtqueue -------------------------------------------------------
#define VRING_DESC_F_NEXT 1
// Set in vring_avail.flags to tell the device not to interrupt on completion.
//
// A driver that polls the used ring wants this, and wants it before the queue
// is enabled. Without it the device raises its interrupt line on every
// completed command -- and if nothing is registered to acknowledge it, a
// level-triggered PCI line stays asserted and re-fires for ever. That is the
// failure in docs/history/kernel.md section 13, and cmd/vgpud reproduced it
// exactly on its first working run.
#define VRING_AVAIL_F_NO_INTERRUPT 1
#define VRING_DESC_F_WRITE 2 // device writes it; i.e. this is our buffer
struct vring_desc {
uint64 addr;
uint32 len;
uint16 flags;
uint16 next;
} __attribute__((packed));
struct vring_avail {
uint16 flags;
uint16 idx;
uint16 ring[];
} __attribute__((packed));
struct vring_used_elem {
uint32 id;
uint32 len;
} __attribute__((packed));
struct vring_used {
uint16 flags;
uint16 idx;
struct vring_used_elem ring[];
} __attribute__((packed));
// --- virtio-gpu, 2D --------------------------------------------------------
#define VIRTIO_GPU_CMD_GET_DISPLAY_INFO 0x0100
#define VIRTIO_GPU_CMD_RESOURCE_CREATE_2D 0x0101
#define VIRTIO_GPU_CMD_RESOURCE_UNREF 0x0102
#define VIRTIO_GPU_CMD_SET_SCANOUT 0x0103
#define VIRTIO_GPU_CMD_RESOURCE_FLUSH 0x0104
#define VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D 0x0105
#define VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING 0x0106
#define VIRTIO_GPU_RESP_OK_NODATA 0x1100
#define VIRTIO_GPU_RESP_OK_DISPLAY_INFO 0x1101
#define VIRTIO_GPU_MAX_SCANOUTS 16
// B8G8R8X8, which is what a 32-bit little-endian 0x00RRGGBB word looks like in
// memory -- the same layout vga.c already draws into, so a framebuffer can be
// handed over without a conversion pass.
#define VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM 2
struct virtio_gpu_ctrl_hdr {
uint32 type;
uint32 flags;
uint64 fence_id;
uint32 ctx_id;
uint32 padding;
} __attribute__((packed));
struct virtio_gpu_rect {
uint32 x, y, width, height;
} __attribute__((packed));
struct virtio_gpu_display_one {
struct virtio_gpu_rect r;
uint32 enabled;
uint32 flags;
} __attribute__((packed));
struct virtio_gpu_resp_display_info {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_display_one pmodes[VIRTIO_GPU_MAX_SCANOUTS];
} __attribute__((packed));
struct virtio_gpu_resource_create_2d {
struct virtio_gpu_ctrl_hdr hdr;
uint32 resource_id;
uint32 format;
uint32 width;
uint32 height;
} __attribute__((packed));
// Releasing a resource, which a modeset has to do before it can create one of
// a different size. The backing pages are detached with it; they belong to the
// driver and are not freed by this.
struct virtio_gpu_resource_unref {
struct virtio_gpu_ctrl_hdr hdr;
uint32 resource_id;
uint32 padding;
} __attribute__((packed));
struct virtio_gpu_mem_entry {
uint64 addr;
uint32 length;
uint32 padding;
} __attribute__((packed));
struct virtio_gpu_resource_attach_backing {
struct virtio_gpu_ctrl_hdr hdr;
uint32 resource_id;
uint32 nr_entries;
} __attribute__((packed));
struct virtio_gpu_set_scanout {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint32 scanout_id;
uint32 resource_id;
} __attribute__((packed));
struct virtio_gpu_transfer_to_host_2d {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint64 offset;
uint32 resource_id;
uint32 padding;
} __attribute__((packed));
struct virtio_gpu_resource_flush {
struct virtio_gpu_ctrl_hdr hdr;
struct virtio_gpu_rect r;
uint32 resource_id;
uint32 padding;
} __attribute__((packed));

164
include/dev/vt.h Normal file
View file

@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0-or-later
//
// Virtual terminals.
//
// Until this existed there was one console. `/dev/tty` was an alias for
// `/dev/console` with the same major and minor, `tty_fg_pgrp` was a single
// global int, and TIOCSCTTY was `return 0`. One screen, one foreground
// process group, one line discipline, no way to have a second shell.
//
// The thing that makes switching possible is a backing store. The console
// drew straight into the framebuffer and scrolled by moving pixels, which
// works exactly as long as nothing else ever wants the screen: there was
// nowhere to put terminal 2 while terminal 1 was visible, and nothing to
// repaint terminal 1 from when it came back. So each VT owns a grid of cells
// and the framebuffer shows whichever grid is active.
//
// A VT that is not active is not a special case. Writes to it land in its
// grid, its cursor moves, it scrolls; the only thing it does not do is draw.
// That is the whole switching mechanism: vt_switch repaints, and everything
// else is written as though it were always on screen.
#ifndef VT_H
#define VT_H
#include "core/types.h"
#include "core/param.h"
#include "uapi/posix.h"
// Six, and Linux's numbering: /dev/tty1 through /dev/tty6, reached with
// Alt+1..Alt+6. A seventh is deliberately not bound -- that is where a
// display server goes, and Schcom will want it.
#define NVT 6
// A cell.
//
// `ch` is the glyph the font draws, not a codepoint: the console maps UTF-8
// to CP437 before it gets here, because that is what include/gfx/font.h contains.
// Storing the codepoint would mean re-running that mapping on every repaint
// and getting a different answer for anything the font cannot draw.
//
// The colours are palette indices rather than RGB. Sixteen ANSI colours plus
// the two theme defaults fit in a byte each and make a cell four bytes; the
// same cell holding two 32-bit colours would be twelve, and the grids are
// static.
struct vtcell {
// A Unicode codepoint, not a byte.
//
// This was a uint8, which is to say the grid could hold 256 distinct glyphs
// and every terminal was a CP437 terminal. A codepoint outside that was not
// stored wrongly, it was *converted* -- `glyph_for()` mapped what it could
// and turned the rest into `?`. Ą, Ę, Ś, Ω, Œ, Ə, ∞ and every other
// character anybody actually types were question marks before they reached
// the font, so no font could have fixed it.
//
// Four bytes a cell instead of one. A 1920x1080 screen at 9x18 is 12,720
// cells, so the grid costs 100 KB instead of 50 KB per terminal.
uint32 ch;
uint8 fg;
uint8 bg;
uint8 attr; // reserved: bold/underline/reverse, which do not change
uint8 pad; // the glyph and so can be added without touching a grid
};
#define VT_FG_DEFAULT 16 // SCH_FG from include/gfx/theme.h
#define VT_BG_DEFAULT 17 // SCH_BG
#define VT_NCOLOUR 18
// Cell attributes that do not change which glyph is drawn, only how.
#define VT_ATTR_REVERSE 0x01
// The largest console a grid can hold, which is 1920x1200 with the 8x16 font.
// A framebuffer wider or taller than this still works -- vt_init clamps the
// grid and the rest of the screen stays background -- but it is worth raising
// rather than living with, because the clamp is invisible until someone
// notices the bottom of their screen never has text on it.
#define VT_MAXCOLS 240
#define VT_MAXROWS 75
#define VT_INPUT_BUF 256
#define VT_MAXPARM 8
// Escape parser states. E_OSC and E_OSC_ESC exist for hyperlinks: a terminal
// that does not follow OSC 8 must still consume it, or the URL is printed as
// text. See the comment in kernel/core/console.c that this came from.
enum { VE_NORMAL, VE_ESC, VE_CSI, VE_OSC, VE_OSC_ESC };
struct vt {
int index; // 0-based; /dev/tty<index+1>
int cols, rows;
struct vtcell *cells; // cols*rows, row-major, into vt_storage
int cx, cy; // cursor, in cells
// Deferred wrap, the rule every real terminal follows and none of them
// document well: a glyph printed in the last column does NOT move the
// cursor to the next line. It sets this instead, and the wrap happens when
// the *next* glyph arrives. Anything that moves the cursor deliberately --
// CR, LF, backspace, an escape sequence -- clears it.
int wrap_pending;
int saved_cx, saved_cy; // DECSC/DECRC
uint8 fg, bg; // current SGR colours
int bold;
int attr; // VT_ATTR_* applied to new cells
// Escape parser.
int esc_state;
int esc_parm[VT_MAXPARM];
int esc_nparm;
int esc_private;
// UTF-8 decoder.
uint32 u8_cp;
int u8_need;
// Line discipline. One per terminal, because ICANON and ECHO are properties
// of a terminal and not of the machine.
struct termios tio;
struct {
char buf[VT_INPUT_BUF];
uint r, w, e;
uint abort_read;
uint eof;
} in;
// Job control. This was one global for the whole system; a terminal each
// is the entire point of having more than one.
int fg_pgrp;
int sid; // session that owns this terminal, 0 = none
int opened; // times it has been opened, for /dev/tty
// VT_AUTO or VT_PROCESS. In VT_PROCESS a program has claimed switching and
// the kernel stops acting on the chord -- see vt_kernel_switches().
int mode;
};
// The active terminal, and the one the framebuffer is showing.
extern int vt_active;
void vt_init(void);
void vt_resize(void); // a scanout arrived; re-measure
struct vt *vt_get(int i); // 0-based, clamped, never null
struct vt *vt_current(void);
int vt_switch(int i); // 0-based; repaints. -1 if out of range
int vt_cycle(int dir); // -1 previous, +1 next, in-use only
// Output, per terminal. Drawing happens only if `v` is the active one.
void vt_feed(struct vt *v, int c); // one output byte, through the parser
void vt_putc(struct vt *v, int glyph); // one glyph, already mapped
void vt_cursor_set(struct vt *v, int col, int row);
void vt_cursor_get(struct vt *v, int *col, int *row);
void vt_erase_display(struct vt *v, int mode);
void vt_erase_line(struct vt *v, int mode);
void vt_clear(struct vt *v);
void vt_scroll(struct vt *v);
void vt_repaint(struct vt *v); // grid -> framebuffer, if active
uint32 vt_rgb(uint8 idx); // palette index -> RGB
int vt_index(struct vt *v); // 0-based number of a terminal
int vt_kernel_switches(void); // 0 if a program owns switching
#endif // VT_H