Time is a kernel concern in danos: the kernel owns the scheduling timer and already exposes monotonic time via the clock/sleep/timer_bind syscalls, so a userspace time service would be a redundant, slower path. This adds the generic runtime.time module over those syscalls, retires the two demonstration drivers, reorganizes the milestone docs, and makes the monotonic clock correct on Intel, AMD, and inside any VM. runtime.time (library/runtime/time.zig) - Instant/Duration interface: now, sleep, spin, after, monotonicNanos, available - a thin layer over system.clock/sleep/timerOnce; unit-tested arithmetic Remove the demo drivers hpet and bus (a teaching example belongs in the docs, not shipped in the tree) - system/drivers/ now holds only real drivers: pci-bus, ps2-bus, usb-xhci-bus - device-manager end-to-end test repointed to pci-bus (asserts on kernel state: the process table and the device tree, not a racy serial marker) - device_register containment moved to a new in-kernel `containment` test - the driver-model worked example moved inline into docs/drivers.md Reorganize milestone docs into topic docs - m17-m18 / m19-m20 / m21 plans dissolved into process-lifecycle, device-manager, discovery, and acpi docs; new docs/power.md and docs/timers.md; ~20 citations repointed; plan docs deleted TSC reliability (apic.zig, smp.zig, cpu.zig, kernel.zig) - check the invariant-TSC bit (CPUID 0x80000007 EDX[8]) on Intel and AMD - cross-core "warp" check at SMP bring-up, pairwise BSP<->AP as each core comes up - fall back to the HPET clocksource when the TSC is not invariant (a bare VM) or not synchronized (a warp), switched continuously so time never jumps - boot log reports the outcome; new tsc-sync test exercises the TSC + warp path Verified: zig build; zig build test; 60/60 QEMU cases (incl. new containment and tsc-sync).
195 lines
9.8 KiB
Markdown
195 lines
9.8 KiB
Markdown
# 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). `devices_broker`, not `devices_broker`. `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.
|
|
|
|
1. **Foreign ABI names are spelled exactly as the ABI spells them — but only inside
|
|
the layer that *is* that ABI.** 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`, `O_CREAT`. We don't get to rename `fwrite` to `fileWrite` — it wouldn't be
|
|
`fwrite` any more.
|
|
|
|
**This exception is scoped to one place: `library/posix/`.** A file under
|
|
`library/posix/` *is* the foreign ABI, so it keeps the ABI's spellings — that is the
|
|
whole rule for that directory. **Everywhere else, Zig/danos naming applies with no
|
|
POSIX exception**, so there is nothing to get wrong: if you're not in
|
|
`library/posix/`, expand it. A concept POSIX also has gets a danos name outside that
|
|
layer — the VFS wire protocol carries a `FileStatus`, not a `Stat`, and a `create`
|
|
flag, not `O_CREAT`; `library/posix/` is what maps `stat`→`status` and
|
|
`O_CREAT`→`create` at the boundary. (The `syscall` *wrappers* elsewhere are not an
|
|
exception to this — they wrap the private danos ABI, so they use danos names.)
|
|
|
|
2. **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 bare `len`/`ptr` fields to mirror them, so a reader carries one
|
|
mental model. (Compounds still expand: a field is `message_len`, not
|
|
`message_length` — `len` is kept, `msg` is not.)
|
|
- The builtins (`@min`, `@max`, `@memcpy`) and `allocator.alloc` / `.create` are
|
|
Zig's spelling.
|
|
|
|
The rule governs the names *we* coin.
|
|
|
|
3. **Single-letter variables in a trivial local scope.** `for (items) |item, i|` may
|
|
keep `i`; a coordinate may be `x`, `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.
|
|
|
|
That's all — no Unix-abbreviation exception. The source directories are full words
|
|
(`system`, `library`, not `src`/`lib`), and there is no daemon `d` suffix: a driver
|
|
lives in `system/drivers/` and a service in `system/services/`, so the *location*
|
|
already says what it is. Encoding the role in the name too (`busd`, `vfsd`) is
|
|
redundant — the program is just `ps2-bus`, `vfs`. Don't put in a name what its directory
|
|
already tells you.
|
|
|
|
## 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 `config` module (compile-time tunables — `maximum_cpus`, `timer_hz`) would
|
|
collide with `cfg` (a `PlatformConfiguration` value) at `configuration`. The module
|
|
became **`parameters`**, which is what it holds.
|
|
- The kernel `device.zig` module would collide with `dev` (a device value) at
|
|
`device`. The module alias became **`device_model`**, which is what it is — the
|
|
device data model (`Device`, `DeviceTree`, `ResourceKind`).
|
|
- The `Namespace` module alias (`ns`/`nsp` across the AML files) collides with a
|
|
`Namespace` **instance**. Resolved by dropping the module alias entirely — the two
|
|
types it provided are imported directly (`const Node = @import("namespace.zig").Node;`)
|
|
— which frees `namespace` for 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`, `devices_broker`,
|
|
`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`, `devices-broker.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.)
|
|
|
|
**A sub-project's entry point repeats its directory's name** — `init/init.zig`,
|
|
`runtime/runtime.zig`, `ps2-bus/ps2-bus.zig` — and the sub-project is addressed by the
|
|
*directory* (`system/services/init`, `library/runtime`), with the repeated leaf
|
|
resolving away. See the repository-layout section of [README.md](README.md).
|
|
|
|
## Named values, not magic numbers
|
|
|
|
The naming rule has a twin: **a value with meaning gets a name, too.** The same
|
|
principle drives both — a reader should never have to leave the code to understand it.
|
|
An abbreviated *name* forces a reader to guess; a bare *number* forces them worse, out
|
|
to a spec or a header or a comment three files away, to learn what the value even *is*.
|
|
If `0x0C` is the PCI serial-bus class, the code says `BaseClass.serial_bus`, not `0x0C`;
|
|
if `0x04` is the ACPI IRQ resource descriptor, it says `SmallResourceType.irq`, not
|
|
`0x04`. The number is an implementation detail of the name — recorded once, where the
|
|
name is defined, and never spelled again at a use site.
|
|
|
|
**Prefer an `enum`** when the values form a set (device classes, AML opcodes, resource
|
|
descriptor types, states): the type then also says *which* set a value belongs to, and
|
|
the compiler rejects a value from the wrong one. A lone `pub const` with a descriptive
|
|
name suffices for a one-off (`const large_descriptor_bit = 0x80`). Reach for the enum
|
|
the moment code elsewhere compares against, packs, or produces the value — a packed PCI
|
|
class triple is written from named parts (`.serial_bus`, `.usb`, `.xhci`), never as
|
|
`0x0C_03_30` under a comment that decodes the bytes.
|
|
|
|
The exceptions are the numbers that carry no hidden meaning: `0` and `1` as plain zero
|
|
and one, an index step, a field width, a bit shift. `x + 1`, `buffer[0]`, and `<< 8`
|
|
need no christening — there is nothing to look up. The test is exactly the naming test:
|
|
*would a reader have to look this up to know what it means?* If yes, name it. This is
|
|
what `opcodes.zig`'s `*_opcode` constants, `acpi-ids`'s `HardwareId`, and `pci-class`'s
|
|
class enums already are — reference data defined once and named everywhere it is used.
|
|
|
|
## 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.
|
|
|
|
## Zen of Zig
|
|
|
|
* Communicate intent precisely.
|
|
* Edge cases matter.
|
|
* Favor reading code over writing code.
|
|
* Only one obvious way to do things.
|
|
* Runtime crashes are better than bugs.
|
|
* Compile errors are better than runtime crashes.
|
|
* Incremental improvements.
|
|
* Avoid local maximums.
|
|
* Reduce the amount one must remember.
|
|
* Focus on code rather than style.
|
|
* Resource allocation may fail; resource deallocation must succeed.
|
|
* Memory is a resource.
|
|
* Together we serve the users.
|