Two driver-model milestones plus a tree-wide naming pass. Suite 35/35 (QEMU) + host tests green. M11 — IRQ-as-IPC. A ring-3 driver now sleeps until its device interrupts it. New src/kernel/irq.zig: per-GSI endpoint bindings, comptime per-vector trampolines, dispatch = mask GSI -> LAPIC EOI -> notifyLocked, all under one lock region. irq_bind/irq_ack syscalls, gated by the device claim like mmio_map. interruptDispatch no longer EOIs — each handler owns its EOI, because a level line must be masked before it is acknowledged (irq_ack is the unmask). Bindings are keyed on the owning task and released on exit (a shared endpoint's siblings survive). hpetd rewritten interrupt-driven. Tests: hpet (rewritten, reads back the I/O APIC routing) and irqfree. M12 — bus drivers. DeviceDesc gains a parent, making the device table a tree. dev_register (device_register) lets a process publish children below a device it claimed; the kernel enforces resource containment (a child's resources must nest in its parent's), so a descriptor can't fabricate a window over kernel RAM. Descriptor copied in via copyFromUser (physmap walk — an unmapped user pointer fails the call instead of faulting the kernel). Per-parent child cap bounds table exhaustion. sbin/busd.zig is a worked bus driver. Test: bus. Naming — per docs/coding-standards.md: non-acronym abbreviations spelled out (message, descriptor, device_service, scheduler, runtime, physical, interpreter, ...); acronyms kept (IPC, MMIO, DMA, HCD, ...); files are kebab-case (ipc-synchronous.zig, device-service.zig, vfs-protocol.zig, ...). Exceptions: POSIX/C ABI names and Zig idioms (init/len/ptr) kept. Module collisions resolved by specific naming (config -> parameters, device.zig alias -> device_model). AML op/Op disambiguated: op = opcode, Op = operation; per-opcode parse handlers renamed opX -> parseX. New driver docs: drivers.md, driver-model.md (bus/class/HCD shapes + the proposed M13–M16 ABI), coding-standards.md.
6.5 KiB
Coding standards
Conventions for danos source. The overriding one, from which most of the rest follows:
Names are spelled out in full. An identifier is not abbreviated unless the abbreviation is an acronym.
interruptDispatch, not intDisp. message_len, not message_len (msg expands, len
is a Zig idiom — see the exceptions). device_service, not device_service. scheduler, not
sched. The cost of a longer name is paid once, at the keyboard; the cost of a
cryptic one is paid every time the code is read, by everyone who reads it. In a
microkernel whose whole argument is that a human can hold each piece in their head,
that trade is not close.
The rule, precisely
Acronyms and initialisms stay. They are the full name — expanding them would make
the code worse, not better. IPC, MMIO, DMA, IRQ, TSS, GDT, IDT, APIC,
GSI, HPET, ACPI, PCI, EOI, BAR, ECAM, MSI, CPU, ELF, ABI, UEFI,
MMU, TLB, ISR, ISA, GAS, HAL, PMM, VMM, VFS, HID, HCD, SMP,
AML, MADT, MCFG, FADT, RSDP, XSDT, RSDT, GOP, EDID, TSC, PIT,
RTC, LAPIC, SIPI. In code they carry whatever case the surrounding convention
demands: Hal the type, hal the variable, mapMmio the function.
Everything else is spelled out. If it's a word with letters removed, restore them:
| Abbreviation | Full |
|---|---|
proto |
protocol |
msg |
message |
desc |
descriptor |
res |
resource |
recv |
receive |
buf |
buffer |
cur |
current |
src / dst |
source / destination |
idx |
index |
addr |
address |
reg |
register |
prev |
previous |
cfg / config |
configuration |
arch |
architecture |
sched |
scheduler |
dev |
device |
sys / syscall |
system / system_call |
info |
information |
dt |
device_tree |
ep |
endpoint |
rt |
runtime |
func |
function |
phys / virt |
physical / virtual |
wq |
wait_queue |
This list is illustrative, not exhaustive. The rule is the rule; when you meet a new abbreviation, expand it.
Exceptions
Three, and only three.
-
Foreign ABI names are spelled exactly as the ABI spells them. A function that is the C or POSIX interface keeps its name:
fopen,fwrite,fread,malloc,calloc,realloc,free,memcpy,mmap,munmap,open,read,write,close,lseek,stat,errno. We don't get to renamefwritetofileWrite— it wouldn't befwriteany more. This also covers the syscall wrappers that exist to match those names. It does not license inventing new abbreviated names in that style. -
Zig idioms are spelled the way Zig spells them. Three names are the language's, not ours, and are left alone:
init/deinit— the constructor convention (std.ArrayList.init), not a shortening of "initialize".len/ptr— the slice field names (slice.len,slice.ptr). Our own structs use barelen/ptrfields to mirror them, so a reader carries one mental model. (Compounds still expand: a field ismessage_len, notmessage_length—lenis kept,msgis not.)- The builtins (
@min,@max,@memcpy) andallocator.alloc/.createare Zig's spelling.
The rule governs the names we coin.
-
Single-letter variables in a trivial local scope.
for (items) |item, i|may keepi; a coordinate may bex,y. The moment the scope is big enough that the letter's meaning isn't obvious on sight, give it a real name. When in doubt, name it. -
Established Unix filesystem and program conventions. Top-level directories keep their conventional names —
src,lib,sbin,bin,docs— as do daemon programs by theirdsuffix (hpetd,busd, followingsshd/httpd). These are names a Unix reader already knows; expanding them fights the convention rather than serving it.
A note on collisions
Two identifiers can legitimately expand to the same word. When they do, keep both meaningful by renaming one to its specific identity rather than the generic expansion. Two cases resolved this way:
- The
configmodule (compile-time tunables —maximum_cpus,timer_hz) would collide withcfg(aPlatformConfigurationvalue) atconfiguration. The module becameparameters, which is what it holds. - The kernel
device.zigmodule would collide withdev(a device value) atdevice. The module alias becamedevice_model, which is what it is — the device data model (Device,DeviceTree,ResourceKind). - The
Namespacemodule alias (ns/nspacross the AML files) collides with aNamespaceinstance. Resolved by dropping the module alias entirely — the two types it provided are imported directly (const Node = @import("namespace.zig").Node;) — which freesnamespacefor the instance.
A related case is one abbreviation with two meanings. In the AML code, op means
opcode (opcodes.zig, the *_opcode constants) but Op in BinaryOperation /
LogicOperation means operation — distinguished by case. The per-opcode parser
handlers, formerly opName/opField, are parseName/parseField: they parse the
opcode's structure, which says what they do without overloading "op".
Case and file names
Within those spelling rules, follow Zig's own conventions:
- Types —
PascalCase:DeviceDescriptor,Endpoint,WaitQueue. - Functions —
camelCase:mapUserDeviceInto,notifyFromIsr. - Variables, fields, constants —
snake_case:message_length,device_service,notify_badge_bit.
File names are kebab-case. A file named for a multi-word thing hyphenates it:
device-tree.zig, ipc-synchronous.zig, vfs-protocol.zig, device-service.zig. A
single word or acronym needs no hyphen: scheduler.zig, paging.zig, apic.zig,
idt.zig. (The module alias a file is imported under still follows the code
conventions above — snake_case — because it's an identifier, not a filename.)
Why acronyms are the line
Because an acronym has no letters to restore. MMIO doesn't become "memory mapped
input output" in code — that expansion is what the acronym is for. But msg is just
message with three letters stolen, and stealing them buys nothing a reader wants. The
test for "is this an abbreviation I must expand" is simply: is there a longer word this
is a clipped form of? If yes, write the word. If it's an initialism standing in for a
phrase, leave it.