SchOS/include/defs.h
Nexoniarz 9e6d8dd4e3 sockets: a descriptor can cross between processes
sendmsg/recvmsg with SCM_RIGHTS. Item 1 of docs/plan/foundations.md and
mechanism 1 of docs/plan/hybrid.md, which are the same primitive seen from two
directions: Wayland's wl_shm_create_pool passes a file descriptor with no
integer-shaped alternative, and handing a driver process the device it is
allowed to touch is the same act. Both plans stopped here.

Linux's structures byte for byte -- msghdr, cmsghdr, the CMSG_ macros -- because
the programs that need this are not ours. libwayland and toybox both call
recvmsg with Linux's headers and would not notice a friendlier door.

The delivery rule is the whole design. A batch of descriptors is pinned to the
stream position of the first byte of the message that carried it, and that has
two halves: a reader arriving at that position is handed them, and a reader that
has not reached it is stopped SHORT of it. Leave out the second half and a
program asking for 4096 bytes swallows three messages and is handed one
message's descriptors, with nothing in the result saying which. socktest checks
exactly that: two one-byte messages, an eight-byte read, one byte back.

The kernel holds a reference to every descriptor in flight, so a sender may
close its own copy the moment sendmsg returns. Giving that reference back is the
part that leaks if it is not written down, and there are three ways a message
ends:

  - received with room            installed in the receiver's table
  - received without room, or by
    plain read(2)                 closed; MSG_CTRUNC for the first case
  - never received, socket closed closed by sockclose

The middle row looks like a choice and is not: holding kernel references for a
receiver that has proved it is not looking is a leak with extra steps.

No garbage collector. A socket descriptor sent over that same socket and never
received holds a reference to the socket that holds the reference to it, and
neither close reaches zero. Linux carries several hundred lines for this.
Nothing on either plan passes a socket over a socket -- Wayland passes memory,
the driver work passes devices -- so it is a bounded leak that is documented
rather than a silent one.

Fourteen checks in cmd/socktest, and every one moves data through the descriptor
rather than inspecting it. The descriptor passed is always a pipe end, because a
pipe announces its own lifetime: a read of a pipe whose writers are all gone
returns 0, which turns "was the kernel's reference released" -- otherwise
invisible from userspace -- into a value a test can compare.

Two bugs fell out, neither in the new code:

  - sockpair() never set the socket's domain, so every socketpair() socket
    claimed domain 0. Invisible while the data paths asked only whether a socket
    was AF_INET or AF_NETLINK, since zero answers "neither" correctly.
    SCM_RIGHTS asks "is this AF_UNIX", and zero is wrong for that one.

  - pipe_poll() reports POLLHUP but not POLLIN at end of file, so a non-blocking
    read of a pipe whose writers had all gone returned EAGAIN forever instead of
    0 -- a program that polls and reads spins waiting for an end that already
    happened. sockreadable() always had the equivalent condition; the pipe path
    did not. Found because the test helper for "is this descriptor closed" is a
    non-blocking read of a pipe.

projects/schcom/README.md said SCM_RIGHTS was done for a long time while nothing
in the kernel implemented it, and an entire GUI plan was drawn on that line.
It is true now, which is not the same as it having been right, and the README
says so.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-13 19:49:37 +02:00

659 lines
29 KiB
C

#pragma once
struct buf;
struct context;
struct file;
struct inode;
struct pipe;
struct proc;
struct rtcdate;
struct spinlock;
struct sleeplock;
struct stat;
struct superblock;
struct trapframe;
struct dirent;
struct device;
// entryother.S / syscall.S
void syscall_entry(void);
void ignore_sysret(void);
// bio.c
void binit(void);
struct buf* bread(uint, uint64);
void brelse(struct buf*);
void bwrite(struct buf*);
// Modify without writing. The buffer stays dirty in the cache until an
// eviction or a bflush() takes it out; the caller is promising to ask for the
// ordering it needs explicitly rather than relying on write order.
void bdirty(struct buf*);
// Write out every dirty buffer for `dev`, then make the device commit them.
// Returns 0, or -1 if the device could not confirm durability.
int bflush(uint dev);
// console.c
struct termios;
void consoleinit(void);
// The terminal's line-discipline settings. TCGETS/TCSETS in sys_fs.c are the
// only callers; the console honours ICANON, ECHO, ECHOE, ECHONL, ISIG,
// ICRNL, INLCR, IGNCR, ISTRIP and VMIN.
void cons_get_termios(int minor, struct termios *);
void cons_set_termios(int minor, const struct termios *);
void cons_flush_input(int minor);
int cons_fg_pgrp(int minor);
void cons_set_fg_pgrp(int minor, int pg);
int console_vt_hotkey(int key);
extern uint vt_chord_mods; // kernel/dev/kbd.c -- the switch chord
extern uint vt_chord_base;
struct vt *vt_for_minor(int minor);
extern uint lapic_timer_hz; // measured, kernel/arch/x86_64/lapic.c
extern uint64 tsc_hz;
extern uint64 boot_tsc;
void monotonic(uint64 *sec, uint64 *usec);
int proc_write_schinit(int user_src, uint64 src, uint n);
int proc_wedge(const char *name);
void pciuser_init(void);
int pciuser_ioctl(uint64 request, uint64 argp);
int pciuser_read(struct inode*, uint64, char*, int);
int pciuser_write(struct inode*, uint64, char*, int);
int pciuser_mmio_ok(uint64 phys, uint64 len);
void pciuser_release_all(struct proc*);
int proc_reincarnate(int status);
void proc_kill_init(void);
extern uint init_generation;
void cprintf(char*, ...);
// A log line: the serial port only, never the display.
//
// console_takeover() hands the framebuffer to userspace on the first exec, and
// after that the screen belongs to whoever is using the machine. A kernel
// message arriving then is not information they asked for -- it lands in the
// middle of their shell, or their editor, or a compositor's output. The DHCP
// lease is what made this necessary: it turns up seconds after the prompt does,
// so the first thing anyone sees after booting is the kernel talking over them.
//
// The same rule the boot path already follows, and the reason cons_serial_only
// starts set: the display is the product, the serial line is the diagnostics.
// klog is that rule for messages that cannot be timed to happen before
// userspace starts.
//
// A macro rather than a function because cprintf's varargs cannot be forwarded
// -- there is no vcprintf, and adding one to duplicate the formatter would cost
// more than it buys. cprintf holds cons.lock for the whole message, so the flag
// cannot be seen half-changed by another CPU printing at the same time.
//
// Nothing is lost: serial is where bootcheck reads from and where you want
// these when something is wrong, and anything a *user* should see belongs in a
// file they can read -- /proc/netif, for the example above.
extern int cons_serial_only;
#define klog(...) do { \
int _klog_was = cons_serial_only; \
cons_serial_only = 1; \
cprintf(__VA_ARGS__); \
cons_serial_only = _klog_was; \
} while (0)
void consoleintr(int(*)(void));
void panic(char*) __attribute__((noreturn));
// exec.c
int exec(char*, char**, char**);
// file.c
struct file* filealloc(void);
int fdalloc(struct file*);
void fileclose(struct file*);
struct file* filedup(struct file*);
void fileinit(void);
int fileread(struct file*, char*, int n);
int file_poll(struct file*, int events);
int filestat(struct file*, struct stat*);
int filewrite(struct file*, char*, int n);
// fs.c
void fsinit(int);
int dirlink(struct inode*, char*, uint64);
struct inode* dirlookup(struct inode*, char*, uint*);
// Remove the entry `name` at the cookie dirlookup returned in *poff.
int dirunlink(struct inode*, char*, uint);
// 1 if the directory holds no real entries, 0 if it does, -1 on error.
int dirempty(struct inode*);
// Fill `out` with getdents64 records starting at *poff, which is an opaque
// position the filesystem advances. Returns bytes written, 0 at end of
// directory. The inode must be locked.
int getdents(struct inode*, char *out, int cap, uint64 *poff);
// Mount table: a substitution applied during path resolution. Nothing on disk
// records a mount, so unmounting is forgetting.
void mountinit(void);
// 1 if the filesystem on this device refuses writes -- a mounted snapshot.
// Ask before mutating, not after: the create path panics on a failed dirlink.
int fs_readonly(uint dev);
int mount_add(uint dev, uint64 inum, uint fsdev, uint64 root,
const char *path, const char *source, const char *type);
// Walk the mount table for /proc/mounts. `path` and `source` need MAXPATH
// bytes, `type` 16. Returns 0 for a live entry, -1 for an empty slot.
int mount_get(int i, char *path, char *source, char *type,
uint *fsdev);
int mount_del_by_root(uint fsdev, uint64 root);
struct inode* ialloc(uint, short);
struct inode* iget(uint, uint64);
struct inode* idup(struct inode*);
void iinit(void);
void ilock(struct inode*);
void iput(struct inode*);
void iunlock(struct inode*);
void iunlockput(struct inode*);
void iupdate(struct inode*);
int namecmp(const char*, const char*);
struct inode* namei(char*);
struct inode* nameiparent(char*, char*);
struct inode* nameifrom(struct inode*, char*);
struct inode* nameiparentfrom(struct inode*, char*, char*);
int readi(struct inode*, int, uint64, uint64, uint);
void stati(struct inode*, struct stat*);
int writei(struct inode*, int, uint64, uint64, uint);
void itrunc(struct inode*);
void itrunc_to(struct inode*, uint64);
unsigned int BKDRHash(char * str);
uint tree_expand(struct inode *ip, uint off);
void tree_search(struct inode* dp, char* name, uint* poff, struct dirent* de,uint add);
void build_dir_tree(struct inode* dp,uint64 pinum);
struct inode* divesymlink(struct inode *);
void readsb(int dev, struct superblock *sb);
// pci.c -- bus enumeration. Drivers that need to *find* their hardware use
// these; drivers for things that have lived at a fixed address since 1984
// (the PIC, the PIT, the legacy IDE ports) do not.
struct pci_dev;
struct pci_driver;
void pci_init(void);
uint32 pci_read32(uint8, uint8, uint8, uint8);
uint16 pci_read16(uint8, uint8, uint8, uint8);
uint8 pci_read8(uint8, uint8, uint8, uint8);
void pci_write32(uint8, uint8, uint8, uint8, uint32);
void pci_enable(struct pci_dev *, uint16 bits);
uint8 pci_find_cap(struct pci_dev *, uint8 id);
int pci_msi_enable(struct pci_dev *, int vector, int cpu);
int pci_count(void);
struct pci_dev *pci_get(int);
struct pci_dev *pci_find_class(uint8 class, uint8 subclass);
struct pci_dev *pci_find_id(uint16 vendor, uint16 device);
void pci_bind(const struct pci_driver *);
int pci_info(char *buf, int cap);
// drivers/registry.c -- the one list of every driver in the kernel.
void pci_bind_all(void);
// xhci.c — `echo u > /proc/sysrq`
void xhci_dump(void);
// ide.c
void ideinit(void);
void ideintr(void);
void iderw(struct buf*);
// For probes, not for reads: iderw() panics on an absent drive, so anything
// that is guessing whether a disk exists has to ask here first.
int ide_disk_present(int dev);
// ATA FLUSH CACHE: block until the drive has committed its write cache.
int ideflush(int dev);
// fs/schfs/kmount.c -- SchFS.
//
// Objects are named by 64-bit object id, not by a path: resolution is the
// caller's business, so these compose with a VFS rather than assuming one.
// Every schfs_k* that changes anything is one transaction and is durable by the
// time it returns.
struct schfs_inode;
// Mounting. Slot 0 is the root filesystem, mounted by proc.c before pid 1 has a
// working directory; schfs_kmount_dev takes any other SchFS disk and returns
// the filesystem device number it landed on, or a negative errno.
void schfs_kmount(uint bdev);
int schfs_kmount_dev(uint bdev);
int schfs_kumount(uint dev);
int schfs_kmounted(void);
// Read side. `dev` is a filesystem device number -- a live volume or a mounted
// snapshot -- not the block device underneath it.
uint64 schfs_klookup(uint dev, const char *path);
long schfs_kread_dev(uint dev, uint64 ino, uint64 off, void *buf,
unsigned n);
int schfs_kstat_dev(uint dev, uint64 ino, struct schfs_inode *out);
uint64 schfs_klookup_at_dev(uint dev, uint64 dir, const char *name,
unsigned len);
int schfs_kreaddir_dev(uint dev, uint64 dir,
int (*fn)(uint64, unsigned short, const char *,
unsigned, void *),
void *arg);
// Resume a directory walk at a tree key. The callback gets each entry's hash,
// which is what getdents hands back to userland as its position.
int schfs_kreaddir_from_dev(uint dev, uint64 dir, uint64 start_hash,
int (*fn)(uint64, unsigned short, const char *,
unsigned, uint64, void *),
void *arg);
uint64 schfs_kroot(uint dev);
void schfs_lockinfo(int slot, int *locked, int *owner_pid, int *depth);
int schfs_kinfo(char *buf, int cap);
// Write side. Each is one transaction and is durable by the time it returns,
// unless the caller has a bracket open -- see schfs_ktx_open.
int schfs_kcreate(uint dev, uint64 dir, const char *name,
unsigned namelen, unsigned short type, uint mode,
unsigned short major, unsigned short minor, uint64 *out);
long schfs_kwrite(uint dev, uint64 ino, uint64 off, const void *buf,
unsigned n);
int schfs_ktruncate(uint dev, uint64 ino, uint64 size);
int schfs_kunlink(uint dev, uint64 dir, const char *name,
unsigned namelen);
int schfs_klink(uint dev, uint64 dir, const char *name,
unsigned namelen, uint64 ino);
int schfs_krename(uint dev, uint64 olddir, const char *oldname,
unsigned oldlen, uint64 newdir, const char *newname,
unsigned newlen);
int schfs_ksetattr(uint dev, uint64 ino,
const struct schfs_inode *in);
int schfs_ksync(uint dev);
int schfs_ksync_all(void);
// Share src's data blocks with dst without copying. Both stay writable; the
// first write to either copies only the run it touches. Behind FICLONE.
int schfs_kreflink(uint dev, uint64 src, uint64 dst);
// Attributes, and the index over them. See kernel/fs/schfs/attr.c.
int schfs_kattr_set(uint dev, uint64 ino, const char *name,
unsigned namelen, const void *val, unsigned vallen,
unsigned short type);
int schfs_kattr_get(uint dev, uint64 ino, const char *name,
unsigned namelen, void *out, unsigned cap,
unsigned *vallen);
int schfs_kattr_del(uint dev, uint64 ino, const char *name,
unsigned namelen);
int schfs_kindex(uint dev, const char *name, unsigned namelen);
int schfs_kquery(uint dev, const char *name, unsigned namelen,
const void *val, unsigned vallen, uint64 *out, int cap);
// Bracket a run of changes into one transaction group. Every schfs_k* call made
// between open and close joins it and commits once, at the close; the bracket
// nests, and holds the volume lock for its whole extent. Costly to leave open —
// nothing else touches that volume meanwhile — so bracket a bounded piece of
// work such as one write(2), not a whole syscall's worth of unrelated steps.
// schfs_ktx_close returns the commit's status, or -1 if the caller had no
// bracket open.
void schfs_ktx_open(uint dev);
int schfs_ktx_close(uint dev);
// Volume counts for statfs. 0 if `dev` is a mounted SchFS volume (live or
// snapshot) and the counts are filled in, -1 if it is not.
int schfs_kstatfs(uint dev, uint64 *total, uint64 *freeblocks,
uint64 *objects);
// Pieces the VFS layer (fs/schfs/vfs.c) needs, where a directory entry and the
// object it names are separate operations. The VFS keeps the link count.
int schfs_kdirempty(uint dev, uint64 dir);
int schfs_kalloc_object(uint dev, unsigned short type, uint mode,
uint64 *out);
int schfs_kdiradd(uint dev, uint64 dir, const char *name,
unsigned len, uint64 ino, unsigned short type);
int schfs_kdirdel(uint dev, uint64 dir, const char *name,
unsigned len);
int schfs_kfree(uint dev, uint64 ino);
// A snapshot mounted read-only: the same block device with an older tree root.
// The read path takes a device number so the VFS can address either view; there
// is no write path, because a snapshot that could be written to would not be
// one. See kernel/fs/schfs/kmount.c.
// Returns the device number of the slot the snapshot landed in, or negative.
int schfs_ksnap_mount(uint dev, const char *name, unsigned namelen);
void schfs_ksnap_unmount(uint dev);
uint64 schfs_ksnap_root(uint dev);
// ramdisk.c
uint uptime(void);
// procfs.c
int procread(struct inode*, uint64, char*, int);
int procwrite(struct inode*, uint64, char*, int);
// Shared with the /proc filesystem (kernel/fs/procfs.c): one owns the
// namespace, the other the content.
int proc_gen_global(int id, char *buf, int cap);
int proc_write_sysrq(int user_src, uint64 src, uint n);
void procfs_mount(void);
void devfs_mount(void);
// input.c -- raw evdev event devices
void inputinit(void);
int inputread(struct inode*, uint64, char*, int);
int inputwrite(struct inode*, uint64, char*, int);
int inputready(struct inode*);
void inputclose(struct inode*);
void inputopen(struct inode*);
int input_ioctl(struct inode*, int request, addr_t arg);
int input_grab(int dev, int on);
int input_is_grabbed(int dev);
void input_report(int dev, int type, int code, int value);
// An absolute pointing device (usb-tablet). Coordinates are already in screen
// pixels; see input_mouse_abs in kernel/dev/input.c for why this is separate
// from the relative path.
void input_mouse_abs(int x, int y, int buttons);
void mouse_set_abs(int x, int y, int buttons);
void input_sync(int dev);
int input_ready(int dev);
void input_ps2_scancode(int raw, int e0);
void input_key(int hid_usage, int down);
int input_hid_keycode(int hid_usage);
void input_mouse(int dx, int dy, int buttons);
void ramdiskinit(void);
void ramdiskrw(struct buf*);
int ramdisk_present(void);
// usb_storage.c
void usb_storage_rw(struct buf*);
int usb_storage_present(void);
// SCSI SYNCHRONIZE CACHE: the same barrier as ideflush, over bulk transport.
int usb_storage_flush(void);
void usb_storage_dump(void); // `echo s > /proc/sysrq`
// ioapic.c
void ioapicenable(int irq, int cpu);
void ioapicroute(int irq, int cpu, int level_low);
void ioapicmask(int irq);
extern uchar ioapicid;
void ioapicinit(void);
// dma.c — see include/dma.h for the full contract
void dma_init(void*, void*);
void *dma_alloc(uint64 size, uint64 align);
void dma_free(void*, uint64 size);
addr_t dma_phys(const void*);
uint64 dma_free_bytes(void);
uint64 dma_total_bytes(void);
// kalloc.c
char* kalloc(void);
uint64 kalloc_free_bytes(void);
void kfree(char*);
void kinit1(void*, void*);
void kinit2(void);
// kbd.c
void kbdintr(void);
// mouse.c
void mouseinit(void);
void mouseintr(void);
void mouse_inject(int dx, int dy, int buttons);
int get_mouse_x(void);
int get_mouse_y(void);
uint8 get_mouse_buttons(void);
// lapic.c
void cmostime(struct rtcdate *r);
// time.c -- wall-clock time, from the RTC read once at boot plus uptime.
void time_init(void);
void wallclock(uint64 *sec, uint64 *usec);
extern uint64 boot_epoch;
int cpunum(void);
extern volatile uint* lapic;
void lapiceoi(void);
void lapicinit(void);
void lapicstartap(uchar, uint);
void microdelay(int);
// log.c
void initlog(int dev);
void log_write(struct buf*);
void begin_op();
void end_op();
// mp.c
extern int ismp;
void mpinit(void);
// pipe.c
int pipealloc(struct file**, struct file**);
void pipeclose(struct pipe*, int);
int piperead(struct pipe*, char*, int);
int pipewrite(struct pipe*, char*, int);
// net/unix.c
struct socket;
void sockinit(void);
struct socket* socknew(int domain, int type, int proto);
void sockclose(struct socket*);
void sockdup(struct socket*);
int sockread(struct socket*, char*, int);
int sockwrite(struct socket*, char*, int);
// The same two, carrying descriptors. See kernel/net/unix.c.
int sockread_fds(struct socket*, char*, int, struct file**, int*);
int sockwrite_fds(struct socket*, char*, int, struct file**, int);
int sockbind(struct socket*, const char *path);
int socklisten(struct socket*, int backlog);
struct socket* sockaccept(struct socket*);
int sockconnect(struct socket*, const char *path);
int sockreadable(struct socket*);
int sock_hup(struct socket*);
int sock_shutdown(struct socket*, int how);
int sockpair(struct socket **a, struct socket **b);
const char* sockname(struct socket*);
// AF_INET addressing. Separate from sockbind/sockconnect because the families
// do not name the same kind of thing; `addr` and `port` are network order.
int sockrecvfrom(struct socket*, char *buf, int n,
uint *addr, ushort *port);
int socksendto(struct socket*, char *buf, int n,
uint addr, ushort port);
int sockbind_in(struct socket*, uint addr, ushort port);
int sockconnect_in(struct socket*, uint addr, ushort port);
int sock_domain(struct socket*);
// Socket options. The syscall layer does the user-memory marshalling and these
// do the work, because struct socket's fields live in kernel/net/unix.c and the
// syscall layer deliberately cannot see them.
int sockopt_set(struct socket*, int level, int name, int val);
int sockopt_get(struct socket*, int level, int name, int *out);
// net/inet.c -- AF_INET on lwIP's raw API. Called only from net/unix.c, which
// is where the per-domain dispatch lives.
void inet_init(void);
void* inet_new(int type, int proto);
int inet_bind(void *h, uint addr, ushort port);
int inet_listen(void *h, int backlog);
int inet_setopt(void *h, int level, int name, int val);
void* inet_accept(void *h);
int inet_connect(void *h, uint addr, ushort port);
int inet_read(void *h, char *buf, int n);
int inet_write(void *h, char *buf, int n);
int inet_recvfrom(void *h, char *buf, int n,
uint *addr, ushort *port);
int inet_sendto(void *h, char *buf, int n,
uint addr, ushort port);
int inet_readable(void *h);
int inet_hup(void *h);
void inet_close(void *h);
// proc.c
void exit(int status);
// Start a kernel thread: a process that runs `fn` in kernel mode forever. It
// has no files, no cwd and no parent, and must never return. Returns the pid.
int kthread_create(void (*fn)(void), const char *name);
int fork(void);
int growproc(int64);
int kill(int);
int sigsend_pgrp(int pgid, int sig);
int proc_pgid(int pid);
int proc_setpgid(int pid, int pgid);
int proc_setsid(void);
int sigsend(int pid, int sig);
int proc_exists(int);
int proc_num(void);
struct procinfo;
int proc_snapshot(int idx, struct procinfo *out);
int proc_snapshot_pid(int pid, struct procinfo *out);
void pinit(void);
void procdump(void);
void scheduler(void) __attribute__((noreturn));
void sched(void);
void sleep(void*, struct spinlock*);
int wait(void);
int waitpid(int, int*, int);
void wakeup(void*);
void yield(void);
void sinit(void);
void kstack_check_canary(const char *where);
// swtch.S
void swtch(struct context**, struct context*);
// spinlock.c
void acquire(struct spinlock*);
void getcallerpcs(void*, addr_t*);
void getstackpcs(addr_t*, addr_t*);
int holding(struct spinlock*);
void initlock(struct spinlock*, char*);
void release(struct spinlock*);
void pushcli(void);
void popcli(void);
// sleeplock.c
void acquiresleep(struct sleeplock*);
void releasesleep(struct sleeplock*);
int holdingsleep(struct sleeplock*);
void initsleeplock(struct sleeplock*, char*);
// string.c
int memcmp(const void*, const void*, uint64);
void* memmove(void*, const void*, uint64);
void* memset(void*, int, uint64);
char* safestrcpy(char*, const char*, int);
int strlen(const char*);
int strnlen(const char*, int);
int strcmp(const char*, const char*);
int strncmp(const char*, const char*, uint64);
char* strncpy(char*, const char*, int);
// syscall.c
void syscall(struct trapframe *);
void syscallinit(void);
void deliver_signals(struct trapframe *);
int argint(int, int*);
int argptr(int, char**, int);
int argstr(int, char**);
int argaddr(int, addr_t*);
int fetchaddr(addr_t, addr_t*);
int fetchstr(addr_t, char**);
int fetchint(addr_t, int*);
// trap.c
void idtinit(void);
extern uint ticks;
void tvinit(void);
extern struct spinlock tickslock;
// uart.c
void uartearlyinit(void);
void uartinit(void);
void uartintr(void);
void uartputc(int);
// Drawing, the text console and the framebuffer geometry are NOT declared
// here: they live in include/gfx.h, and the display drivers that back them in
// include/display.h. Declaring vga_fill_rect() here is what let the crash
// screen draw on one named driver.
// fb.c -- /dev/fb0, major 2.
void fbclose(struct inode*);
// Called from sys_mmap: a mapping of /dev/fb0 is a licence to draw with no
// further syscalls, so it is the last the kernel hears about it.
void fb_mapped(void);
int fbread(struct inode*, uint64, char*, int);
int fbwrite(struct inode*, uint64, char*, int);
int fb_ioctl(int request, addr_t arg);
// dev/disk.c -- /dev/hdN, raw seekable access to a whole disk. The minor is the
// IDE device number. Writes bypass every filesystem above them.
int diskread(struct inode*, uint64, char*, int);
int diskwrite(struct inode*, uint64, char*, int);
// Network *drivers* are not declared here at all. They are reached through the
// netdev class in include/netdev.h -- a driver publishes a vtable and nothing
// above it ever learns its name. Declaring e1000_transmit() here was what let
// the protocol stack call it directly.
// net/net.c -- the TCP/IP stack (lwIP). netinit starts the network thread;
// net_is_up reports whether an address has been obtained.
void netinit(void);
int net_is_up(void);
int net_is_ready(void);
void net_wake_counts(uint64 *rx, uint64 *tick);
int net_info(char *buf, int cap);
// net/lwip/ -- the port layer and the Ethernet interfaces, called only from
// net.c. One netif per bound network device; the index is that interface's.
void lwip_sys_init(void);
void lwip_sys_lock(void);
void lwip_sys_unlock(void);
int net_eth_attach(struct device *);
int net_eth_poll(void);
int net_eth_count(void);
void net_eth_counts(int i, uint64 *rx, uint64 *tx);
struct netif *net_eth_netif(int i);
uint netif_addr4(struct netif *);
uint netif_mask4(struct netif *);
uint ntohl_u32(uint);
uint htonl_u32(uint);
// rtnetlink: turn one request into a whole dump. See kernel/net/netlink.c.
int netlink_request(const char *req, uint reqlen,
char *out, uint outcap);
struct device *net_eth_device(int i);
// registry.c
// Platform devices, and the boot framebuffer. Takes the framebuffer address
// the loader handed over, because bringing that up is the first thing it does.
void platform_register_devices(uint64 fb_addr);
// drivers/gpu/vga -- the VBE framebuffer the loader programmed. Called only
// from registry.c; everything else draws through include/gfx.h.
void vga_fb_init(uint64 phys_addr);
uint64 vga_fb_phys(void);
void vga_fb_restore(void);
uint64 vga_fb_size(void);
void e820_init(void);
int acpi_init(void);
// console.c / panic.c
void cons_panic_mode(void);
extern int cons_serial_only;
void console_takeover(void);
// vm.c
void seginit(void);
void kvmalloc(void);
// Map device registers. Returns a kernel address for `size` bytes of physical
// MMIO at `pa`, or 0 if the window is exhausted. Use this rather than P2V for
// anything that came out of a BAR: firmware may place a 64-bit BAR far above
// the 4 GB the direct map covers.
void* ioremap(uint64 pa, uint64 size);
pml4e_t* setupkvm(void);
char* uva2ka(pml4e_t*, char*);
addr_t allocuvm(pml4e_t*, uint64, uint64);
addr_t deallocuvm(pml4e_t*, uint64, uint64);
void freevm(pml4e_t*);
void inituvm(pml4e_t*, char*, uint64);
int loaduvm(pml4e_t*, char*, struct inode*, uint64, uint64);
pml4e_t* copyuvm(pml4e_t*, uint64);
void switchuvm(struct proc*);
void switchkvm(void);
int pipe_poll(struct pipe*, int readable, int writable);
int console_input_ready(int minor);
int copyin(pml4e_t*, addr_t, void*, uint64);
int copyout(pml4e_t*, addr_t, void*, uint64);
void vm_unmap_range(pml4e_t*, uint64, uint64);
// Unmap without freeing: for pages this process does not own — device memory
// and shared segments. vm_unmap_range() would hand them to kfree.
void vm_unmap_norelease(pml4e_t*, uint64, uint64);
// Shared memory (sys_mem.c). shm_ref/shm_detach maintain the attachment
// count that decides when a segment's pages go back.
void shm_ref(int id);
void shm_detach(int id);
void proc_release_shared(struct proc *);
int copy_vma_range(pml4e_t *dstpgdir, pml4e_t *srcpgdir, uint64 va, uint64 length, int is_fb);
void clearpteu(pml4e_t *pgdir, char *uva);
#define NELEM(x) (sizeof(x)/sizeof((x)[0]))