Re-organize the source tree as a monorepo mirroring the FHS
The source layout now mirrors the runtime filesystem hierarchy
(docs/danos-file-system-hierarchy-FSH.md): what lives under system/ in the
source is what a running danos represents under /system. Each service and
driver is a sub-project directory that is its own Zig module — cross-project
references go by module name, never by a path into another project's files.
Moves (all git mv, history preserved):
- src/ -> system/ (danos internals; the self-representation)
root.zig -> danos.zig (the kernel<->user contract module)
kernel/arch/ -> kernel/architecture/ (arch -> architecture)
device/ -> devices/ (what /system/devices reflects)
boot/ -> /boot (the loaders, top level)
- sbin/ -> split by role:
init, vfs -> system/services/<name>/<name>.zig
hpetd, busd -> system/drivers/<name>/<name>.zig
vfs-test -> system/services/vfs/vfs-test.zig (inside the vfs project)
- lib/ -> library/runtime/ (room for other libraries beside runtime)
The VFS wire protocol becomes its own module, system/services/vfs/protocol.zig
("vfs-protocol"): the vfs sub-project exposes its interface, and the runtime's
file layer imports it by name. First instance of the "protocol module" pattern
(docs/driver-model.md); usb/block will expose theirs the same way.
Also: fix a naming-standard violation in the protocol — Op -> Operation (and
req -> request, _pad -> _padding). Docs updated: /system/services added to the
FHS doc, a repository-layout section added to the docs index, and stale source
paths swept across comments and docs.
Runtime boot paths are unchanged (the bootloader still loads /sbin/init);
aligning the runtime filesystem to the FHS is a separate follow-up. Suite 35/35
plus host tests green.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
//! Serial console (16550-compatible UART) — the kernel's machine-readable output
|
||||
//! channel. Unlike the framebuffer console, serial text can be captured to a file
|
||||
//! by QEMU (`-serial file:...`), which is what the test harness asserts on.
|
||||
//!
|
||||
//! The UART defaults to the legacy PC COM1 at I/O port `0x3F8`, but a UEFI Class 3
|
||||
//! (legacy-free) machine may have no COM1 — or its debug UART somewhere else, and
|
||||
//! reachable via MMIO rather than port I/O. So the location is a runtime value:
|
||||
//! `reconfigure` repoints it once ACPI's SPCR table has been read. Early boot logs
|
||||
//! optimistically to COM1 (harmless if absent); the framebuffer console is the
|
||||
//! always-present log.
|
||||
|
||||
const paging = @import("paging.zig");
|
||||
|
||||
/// How the UART registers are reached: legacy I/O ports or memory-mapped.
|
||||
const Access = enum { port, mmio };
|
||||
|
||||
var access: Access = .port;
|
||||
var base: u64 = 0x3F8; // COM1
|
||||
|
||||
fn portOut(p: u16, value: u8) void {
|
||||
asm volatile ("outb %[value], %[p]"
|
||||
:
|
||||
: [value] "{al}" (value),
|
||||
[p] "{dx}" (p),
|
||||
);
|
||||
}
|
||||
|
||||
fn portIn(p: u16) u8 {
|
||||
return asm volatile ("inb %[p], %[value]"
|
||||
: [value] "={al}" (-> u8),
|
||||
: [p] "{dx}" (p),
|
||||
);
|
||||
}
|
||||
|
||||
/// Read UART register `off` through the active access method.
|
||||
fn register(off: u64) u8 {
|
||||
if (access == .mmio) return @as(*volatile u8, @ptrFromInt(base + off)).*;
|
||||
return portIn(@intCast(base + off));
|
||||
}
|
||||
|
||||
/// Write UART register `off` through the active access method.
|
||||
fn setRegister(off: u64, value: u8) void {
|
||||
if (access == .mmio) {
|
||||
@as(*volatile u8, @ptrFromInt(base + off)).* = value;
|
||||
} else {
|
||||
portOut(@intCast(base + off), value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure the UART: 38400 baud, 8N1, FIFO on. Safe to call before anything
|
||||
/// else; it has no dependencies, and is a harmless no-op if the port is absent.
|
||||
pub fn init() void {
|
||||
setRegister(1, 0x00); // disable interrupts
|
||||
setRegister(3, 0x80); // enable DLAB (set baud divisor)
|
||||
setRegister(0, 0x03); // divisor low: 38400 baud
|
||||
setRegister(1, 0x00); // divisor high
|
||||
setRegister(3, 0x03); // 8 bits, no parity, one stop bit; DLAB off
|
||||
setRegister(2, 0xC7); // enable + clear FIFO, 14-byte threshold
|
||||
setRegister(4, 0x0B); // RTS/DSR set
|
||||
}
|
||||
|
||||
/// Point the console at the UART ACPI's SPCR table names (MMIO or I/O port) and
|
||||
/// re-run the UART setup there. Called after discovery when an SPCR entry exists.
|
||||
pub fn reconfigure(is_mmio: bool, address: u64) void {
|
||||
access = if (is_mmio) .mmio else .port;
|
||||
// An MMIO UART is reached through the physmap; an I/O-port UART keeps its
|
||||
// port number unchanged.
|
||||
base = if (is_mmio) paging.mapMmio(address, 0x100, true) else address;
|
||||
init();
|
||||
}
|
||||
|
||||
fn writeByte(c: u8) void {
|
||||
// Wait for the transmit-holding register to empty — but bounded, so an absent
|
||||
// UART (whose line-status register reads back as 0x00) can't hang the kernel.
|
||||
var guard: u32 = 0;
|
||||
while (register(5) & 0x20 == 0 and guard < 100_000) : (guard += 1) {}
|
||||
setRegister(0, c);
|
||||
}
|
||||
|
||||
/// Write bytes, translating LF to CRLF so terminals and logs line up.
|
||||
pub fn write(bytes: []const u8) void {
|
||||
for (bytes) |c| {
|
||||
if (c == '\n') writeByte('\r');
|
||||
writeByte(c);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user