M11–M12: IRQ-as-IPC and bus drivers; expand names tree-wide
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.
This commit is contained in:
-32
@@ -1,32 +0,0 @@
|
||||
//! User-space device access: enumerate the kernel's device table, claim a
|
||||
//! device, and map its MMIO. A driver uses these to find and take ownership of
|
||||
//! its hardware; the claim is the capability the kernel checks before mapping.
|
||||
|
||||
const danos = @import("danos");
|
||||
const sc = @import("syscall.zig");
|
||||
|
||||
pub const DeviceDesc = danos.DeviceDesc;
|
||||
pub const ResDesc = danos.ResDesc;
|
||||
pub const DeviceClass = danos.DeviceClass;
|
||||
pub const ResourceKind = danos.ResourceKind;
|
||||
|
||||
inline fn failed(r: usize) bool {
|
||||
return r > ~@as(usize, 0) - 4095;
|
||||
}
|
||||
|
||||
/// Copy up to `buf.len` device descriptors into `buf`; returns the total count.
|
||||
pub fn enumerate(buf: []DeviceDesc) usize {
|
||||
return sc.syscall2(.dev_enumerate, @intFromPtr(buf.ptr), buf.len);
|
||||
}
|
||||
|
||||
/// Take exclusive ownership of device `id`. Returns false if taken or invalid.
|
||||
pub fn claim(id: u64) bool {
|
||||
return !failed(sc.syscall1(.dev_claim, id));
|
||||
}
|
||||
|
||||
/// Map resource `res_idx` (which must be an MMIO window) of claimed device
|
||||
/// `dev_id` into this address space; returns the register base virtual address.
|
||||
pub fn mmioMap(dev_id: u64, res_idx: u64) ?usize {
|
||||
const r = sc.syscall2(.mmio_map, dev_id, res_idx);
|
||||
return if (failed(r)) null else r;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
//! User-space device access: enumerate the kernel's device table, claim a device,
|
||||
//! map its MMIO, and bind its interrupt. A driver uses these to find and take
|
||||
//! ownership of its hardware; the claim is the capability the kernel checks before
|
||||
//! mapping registers or routing an IRQ.
|
||||
|
||||
const danos = @import("danos");
|
||||
const sc = @import("system-call.zig");
|
||||
|
||||
pub const DeviceDescriptor = danos.DeviceDescriptor;
|
||||
pub const ResourceDescriptor = danos.ResourceDescriptor;
|
||||
pub const DeviceClass = danos.DeviceClass;
|
||||
pub const ResourceKind = danos.ResourceKind;
|
||||
|
||||
inline fn failed(r: usize) bool {
|
||||
return r > ~@as(usize, 0) - 4095;
|
||||
}
|
||||
|
||||
/// Copy up to `buffer.len` device descriptors into `buffer`; returns the total count.
|
||||
pub fn enumerate(buffer: []DeviceDescriptor) usize {
|
||||
return sc.systemCall2(.device_enumerate, @intFromPtr(buffer.ptr), buffer.len);
|
||||
}
|
||||
|
||||
/// Take exclusive ownership of device `id`. Returns false if taken or invalid.
|
||||
pub fn claim(id: u64) bool {
|
||||
return !failed(sc.systemCall1(.device_claim, id));
|
||||
}
|
||||
|
||||
/// Map resource `resource_index` (which must be an MMIO window) of claimed device
|
||||
/// `device_id` into this address space; returns the register base virtual address.
|
||||
pub fn mmioMap(device_id: u64, resource_index: u64) ?usize {
|
||||
const r = sc.systemCall2(.mmio_map, device_id, resource_index);
|
||||
return if (failed(r)) null else r;
|
||||
}
|
||||
|
||||
/// `DeviceDescriptor.parent` for a device with no parent.
|
||||
pub const no_parent = danos.no_parent;
|
||||
|
||||
/// Publish `descriptor` as a child of `parent_id`, which this process must have claimed.
|
||||
/// Returns the new device id. The child is left unclaimed, so whichever driver owns
|
||||
/// that class of device can `claim` it — that is how a bus hands off a device.
|
||||
///
|
||||
/// Every resource in `descriptor` must be **contained** in a parent resource of the same
|
||||
/// kind: a sub-window of the parent's MMIO, or one of its IRQs. The kernel refuses
|
||||
/// anything else, because a device descriptor is a licence to map physical memory and
|
||||
/// a bus driver may only subdivide what it already owns. `descriptor.id` and `descriptor.parent`
|
||||
/// are ignored. A device with no resources at all is fine — a USB device is reached
|
||||
/// through its controller, not by MMIO.
|
||||
pub fn register(parent_id: u64, descriptor: *const DeviceDescriptor) ?u64 {
|
||||
const r = sc.systemCall2(.device_register, parent_id, @intFromPtr(descriptor));
|
||||
return if (failed(r)) null else r;
|
||||
}
|
||||
|
||||
/// Bind resource `resource_index` (which must be an IRQ) of claimed device `device_id` to
|
||||
/// `endpoint`. From then on the interrupt arrives as an asynchronous notification:
|
||||
/// `ipc.replyWait` on that endpoint returns with the high bit set in `badge` and the
|
||||
/// low bits carrying the GSI. The kernel masks the line before waking you.
|
||||
pub fn irqBind(device_id: u64, resource_index: u64, endpoint: usize) bool {
|
||||
return !failed(sc.systemCall3(.irq_bind, device_id, resource_index, endpoint));
|
||||
}
|
||||
|
||||
/// Re-arm a bound IRQ. Call this **after** quieting the device (clearing whatever
|
||||
/// status register holds its line asserted) — the kernel left the line masked
|
||||
/// precisely because it could not do that for you. Skip it and the interrupt never
|
||||
/// fires again; call it before the device is quiet and a level-triggered line storms.
|
||||
pub fn irqAck(device_id: u64, resource_index: u64) bool {
|
||||
return !failed(sc.systemCall2(.irq_ack, device_id, resource_index));
|
||||
}
|
||||
+27
-27
@@ -5,16 +5,16 @@
|
||||
//! The algorithm is a straight port of the kernel's first-fit free list
|
||||
//! (src/kernel/heap.zig): an address-ordered singly linked list of free blocks,
|
||||
//! split on allocation and coalesced with neighbours on free. The only thing
|
||||
//! that changes on this side of the syscall boundary is where memory comes from
|
||||
//! that changes on this side of the system_call boundary is where memory comes from
|
||||
//! — `grow` asks the kernel for pages via `mmap` instead of mapping frames
|
||||
//! itself, and the kernel picks the base address.
|
||||
//!
|
||||
//! Single-threaded and 16-byte max alignment, exactly like the kernel heap; a
|
||||
//! Single-threaded and 16-byte maximum alignment, exactly like the kernel heap; a
|
||||
//! lock and larger alignments come when user programs gain threads.
|
||||
|
||||
const std = @import("std");
|
||||
const danos = @import("danos");
|
||||
const sys = @import("sys.zig");
|
||||
const system = @import("system.zig");
|
||||
|
||||
const page_size = danos.page_size;
|
||||
|
||||
@@ -26,8 +26,8 @@ const Block = extern struct {
|
||||
};
|
||||
|
||||
const header_size = @sizeOf(Block); // 16
|
||||
const min_block = header_size + 16; // smallest block worth splitting off
|
||||
/// Grow granularity: one `mmap` per 64 KiB amortises the syscall.
|
||||
const minimum_block = header_size + 16; // smallest block worth splitting off
|
||||
/// Grow granularity: one `mmap` per 64 KiB amortises the system_call.
|
||||
const chunk = 64 * 1024;
|
||||
|
||||
var free_list: ?*Block = null;
|
||||
@@ -44,10 +44,10 @@ fn payloadOf(block: *Block) [*]u8 {
|
||||
/// `mmap` is an independent grant, cross-grant coalescing happens only when the
|
||||
/// kernel returns adjacent bases (its arena is a bump allocator, so consecutive
|
||||
/// grants usually are adjacent). Returns false if the kernel is out of memory.
|
||||
fn grow(min_bytes: usize) bool {
|
||||
const bytes = alignUp(@max(min_bytes, chunk), page_size);
|
||||
const ret = sys.mmap(bytes, sys.PROT_READ | sys.PROT_WRITE);
|
||||
if (sys.mmapFailed(ret)) return false;
|
||||
fn grow(minimum_bytes: usize) bool {
|
||||
const bytes = alignUp(@max(minimum_bytes, chunk), page_size);
|
||||
const ret = system.mmap(bytes, system.PROT_READ | system.PROT_WRITE);
|
||||
if (system.mmapFailed(ret)) return false;
|
||||
|
||||
const block: *Block = @ptrFromInt(ret);
|
||||
block.size = bytes;
|
||||
@@ -58,25 +58,25 @@ fn grow(min_bytes: usize) bool {
|
||||
/// Insert a block into the address-ordered free list, coalescing with the
|
||||
/// physically adjacent free blocks on either side.
|
||||
fn insertFree(block: *Block) void {
|
||||
var prev: ?*Block = null;
|
||||
var cur = free_list;
|
||||
while (cur) |c| : (cur = c.next) {
|
||||
var previous: ?*Block = null;
|
||||
var current = free_list;
|
||||
while (current) |c| : (current = c.next) {
|
||||
if (@intFromPtr(c) > @intFromPtr(block)) break;
|
||||
prev = c;
|
||||
previous = c;
|
||||
}
|
||||
|
||||
block.next = cur;
|
||||
if (prev) |p| p.next = block else free_list = block;
|
||||
block.next = current;
|
||||
if (previous) |p| p.next = block else free_list = block;
|
||||
|
||||
// Merge forward into `cur` if they're contiguous.
|
||||
if (cur) |c| {
|
||||
// Merge forward into `current` if they're contiguous.
|
||||
if (current) |c| {
|
||||
if (@intFromPtr(block) + block.size == @intFromPtr(c)) {
|
||||
block.size += c.size;
|
||||
block.next = c.next;
|
||||
}
|
||||
}
|
||||
// Merge `prev` forward into `block` if they're contiguous.
|
||||
if (prev) |p| {
|
||||
// Merge `previous` forward into `block` if they're contiguous.
|
||||
if (previous) |p| {
|
||||
if (@intFromPtr(p) + p.size == @intFromPtr(block)) {
|
||||
p.size += block.size;
|
||||
p.next = block.next;
|
||||
@@ -90,24 +90,24 @@ fn rawAlloc(len: usize) ?[*]u8 {
|
||||
|
||||
var attempts: u32 = 0;
|
||||
while (attempts < 2) : (attempts += 1) {
|
||||
var prev: ?*Block = null;
|
||||
var cur = free_list;
|
||||
while (cur) |block| : ({
|
||||
prev = block;
|
||||
cur = block.next;
|
||||
var previous: ?*Block = null;
|
||||
var current = free_list;
|
||||
while (current) |block| : ({
|
||||
previous = block;
|
||||
current = block.next;
|
||||
}) {
|
||||
if (block.size < need) continue;
|
||||
|
||||
if (block.size >= need + min_block) {
|
||||
if (block.size >= need + minimum_block) {
|
||||
// Split: carve `need` off the front, leave the rest free.
|
||||
const rest: *Block = @ptrFromInt(@intFromPtr(block) + need);
|
||||
rest.size = block.size - need;
|
||||
rest.next = block.next;
|
||||
if (prev) |p| p.next = rest else free_list = rest;
|
||||
if (previous) |p| p.next = rest else free_list = rest;
|
||||
block.size = need;
|
||||
} else {
|
||||
// Take the whole block.
|
||||
if (prev) |p| p.next = block.next else free_list = block.next;
|
||||
if (previous) |p| p.next = block.next else free_list = block.next;
|
||||
}
|
||||
return payloadOf(block);
|
||||
}
|
||||
|
||||
+30
-14
@@ -4,7 +4,7 @@
|
||||
//! added with the first server binary.
|
||||
|
||||
const danos = @import("danos");
|
||||
const sc = @import("syscall.zig");
|
||||
const sc = @import("system-call.zig");
|
||||
|
||||
/// A small-int handle into the calling process's handle table.
|
||||
pub const Handle = usize;
|
||||
@@ -18,61 +18,77 @@ pub const Message = extern struct {
|
||||
c: u64 = 0,
|
||||
};
|
||||
|
||||
/// Whether a syscall return value is a wrapped -errno (lands in the top page).
|
||||
/// Whether a system_call return value is a wrapped -errno (lands in the top page).
|
||||
inline fn failed(r: usize) bool {
|
||||
return r > ~@as(usize, 0) - 4095;
|
||||
}
|
||||
|
||||
/// Create a new endpoint owned by this process; returns its handle.
|
||||
pub fn createEndpoint() ?Handle {
|
||||
const r = sc.syscall0(.create_endpoint);
|
||||
const r = sc.systemCall0(.create_endpoint);
|
||||
return if (failed(r)) null else r;
|
||||
}
|
||||
|
||||
/// Publish endpoint `h` under a well-known service id so other processes find it.
|
||||
pub fn register(id: danos.ServiceId, h: Handle) bool {
|
||||
return !failed(sc.syscall2(.ipc_register, @intFromEnum(id), h));
|
||||
return !failed(sc.systemCall2(.ipc_register, @intFromEnum(id), h));
|
||||
}
|
||||
|
||||
/// Find the endpoint published under `id`, installing a handle to it in this
|
||||
/// process.
|
||||
pub fn lookup(id: danos.ServiceId) ?Handle {
|
||||
const r = sc.syscall1(.ipc_lookup, @intFromEnum(id));
|
||||
const r = sc.systemCall1(.ipc_lookup, @intFromEnum(id));
|
||||
return if (failed(r)) null else r;
|
||||
}
|
||||
|
||||
pub const CallError = error{Failed};
|
||||
|
||||
/// Send `msg` to endpoint `h` and block until the server replies into `reply`.
|
||||
/// Send `message` to endpoint `h` and block until the server replies into `reply`.
|
||||
/// Returns the reply length.
|
||||
pub fn call(h: Handle, msg: []const u8, reply: []u8) CallError!usize {
|
||||
const r = sc.syscall5(.ipc_call, h, @intFromPtr(msg.ptr), msg.len, @intFromPtr(reply.ptr), reply.len);
|
||||
pub fn call(h: Handle, message: []const u8, reply: []u8) CallError!usize {
|
||||
const r = sc.systemCall5(.ipc_call, h, @intFromPtr(message.ptr), message.len, @intFromPtr(reply.ptr), reply.len);
|
||||
return if (failed(r)) error.Failed else r;
|
||||
}
|
||||
|
||||
/// Set in `Received.badge` when what arrived is an asynchronous notification — a
|
||||
/// bound device interrupt — rather than a client's message. The low bits carry the
|
||||
/// GSI. See `isNotification`.
|
||||
pub const notify_badge_bit: u64 = danos.notify_badge_bit;
|
||||
|
||||
/// The result of a `replyWait`: the request length and the sender's badge (a
|
||||
/// task id, or an IRQ notification if the high bit is set).
|
||||
pub const Received = struct {
|
||||
len: usize,
|
||||
badge: u64,
|
||||
|
||||
/// True if this wake-up was a device interrupt, not a client request. A driver's
|
||||
/// event loop branches on this; there is no reply owed on the notification path.
|
||||
pub fn isNotification(self: Received) bool {
|
||||
return self.badge & notify_badge_bit != 0;
|
||||
}
|
||||
|
||||
/// The interrupt source (a GSI), meaningful only when `isNotification`.
|
||||
pub fn source(self: Received) u64 {
|
||||
return self.badge & ~notify_badge_bit;
|
||||
}
|
||||
};
|
||||
|
||||
/// Server side of IPC_ReplyWait: deliver `reply` to the client last received (if
|
||||
/// any), then block until the next request arrives in `recv`. Returns its length
|
||||
/// and the sender badge. This syscall returns two values — the length in rax and
|
||||
/// any), then block until the next request arrives in `receive`. Returns its length
|
||||
/// and the sender badge. This system_call returns two values — the length in rax and
|
||||
/// the badge in rdx — so it needs a hand-written stub: rdx is a read-write
|
||||
/// operand (input = reply length, arg #3; output = badge).
|
||||
pub fn replyWait(h: Handle, reply: []const u8, recv: []u8) Received {
|
||||
pub fn replyWait(h: Handle, reply: []const u8, receive: []u8) Received {
|
||||
var rax: usize = undefined;
|
||||
var rdx: usize = reply.len; // in: reply_len (arg #3 -> rdx); out: badge
|
||||
asm volatile ("syscall"
|
||||
: [rax] "={rax}" (rax),
|
||||
[rdx] "+{rdx}" (rdx),
|
||||
: [n] "{rax}" (@intFromEnum(danos.Syscall.ipc_reply_wait)),
|
||||
: [n] "{rax}" (@intFromEnum(danos.SystemCall.ipc_reply_wait)),
|
||||
[a0] "{rdi}" (h),
|
||||
[a1] "{rsi}" (@intFromPtr(reply.ptr)),
|
||||
[a3] "{r10}" (@intFromPtr(recv.ptr)),
|
||||
[a4] "{r8}" (recv.len),
|
||||
[a3] "{r10}" (@intFromPtr(receive.ptr)),
|
||||
[a4] "{r8}" (receive.len),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
return .{ .len = rax, .badge = rdx };
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
//! danos user-space runtime library — a nascent libc. Every user binary (init,
|
||||
//! and later the VFS server + device drivers) imports this as `@import("rt")`:
|
||||
//! syscall wrappers, the C-convention heap, IPC helpers, and the process start
|
||||
//! and later the VFS server + device drivers) imports this as `@import("runtime")`:
|
||||
//! system_call wrappers, the C-convention heap, IPC helpers, and the process start
|
||||
//! shim. It is compiled into each binary (inheriting its `.large` code model and
|
||||
//! freestanding target), so all user programs share one implementation.
|
||||
//!
|
||||
//! A user binary needs three lines:
|
||||
//! const rt = @import("rt");
|
||||
//! pub const panic = rt.panic;
|
||||
//! comptime { _ = &rt.start._start; } // pull the entry shim in
|
||||
//! const runtime = @import("runtime");
|
||||
//! pub const panic = runtime.panic;
|
||||
//! comptime { _ = &runtime.start._start; } // pull the entry shim in
|
||||
//! and a `pub fn main() void`.
|
||||
|
||||
pub const sys = @import("sys.zig");
|
||||
pub const system = @import("system.zig");
|
||||
pub const heap = @import("heap.zig");
|
||||
pub const ipc = @import("ipc.zig");
|
||||
pub const start = @import("start.zig");
|
||||
/// The VFS wire protocol (shared with the VFS server).
|
||||
pub const vfsproto = @import("vfs_proto.zig");
|
||||
pub const vfs_protocol = @import("vfs-protocol.zig");
|
||||
/// POSIX-style file API: open/read/write/lseek/stat/close.
|
||||
pub const unistd = @import("unistd.zig");
|
||||
/// C stdio: fopen/fread/fwrite/fseek/ftell/fclose over unistd.
|
||||
pub const stdio = @import("stdio.zig");
|
||||
/// Device access for drivers: enumerate/claim/mmioMap.
|
||||
pub const dev = @import("dev.zig");
|
||||
pub const device = @import("device.zig");
|
||||
|
||||
/// Re-exported so a user binary can `pub const panic = rt.panic;`.
|
||||
/// Re-exported so a user binary can `pub const panic = runtime.panic;`.
|
||||
pub const panic = start.panic;
|
||||
|
||||
/// The heap as a `std.mem.Allocator`, for Zig `std` containers in user code.
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
//! The user-space process entry shim. Every user binary roots `_start` here (via
|
||||
//! `entry = _start` in build.zig) and forces this file to be analysed with
|
||||
//! `comptime { _ = &rt.start._start; }`, so the whole runtime is linked in.
|
||||
//! `comptime { _ = &runtime.start._start; }`, so the whole runtime is linked in.
|
||||
|
||||
const std = @import("std");
|
||||
const sys = @import("sys.zig");
|
||||
const system = @import("system.zig");
|
||||
|
||||
/// The kernel enters at `_start` with rsp 16-aligned, but a SysV function expects
|
||||
/// The kernel enters at `_start` with rsp 16-aligned, but a SystemV function expects
|
||||
/// rsp ≡ 8 (mod 16) on entry (as if reached by `call`). The `call` below pushes
|
||||
/// the 8-byte return address, satisfying the ABI before any Zig frame runs; the
|
||||
/// `ud2` is a safety net if `rt_start` ever returns.
|
||||
@@ -21,12 +21,12 @@ pub export fn _start() callconv(.naked) noreturn {
|
||||
export fn rt_start() callconv(.c) noreturn {
|
||||
const root = @import("root"); // the user binary's root source file
|
||||
root.main();
|
||||
sys.exit(0);
|
||||
system.exit(0);
|
||||
}
|
||||
|
||||
/// No runtime to unwind into — report a panic as a nonzero exit code.
|
||||
pub const panic = std.debug.FullPanic(struct {
|
||||
fn panic(_: []const u8, _: ?usize) noreturn {
|
||||
sys.exit(127);
|
||||
system.exit(127);
|
||||
}
|
||||
}.panic);
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@ const unistd = @import("unistd.zig");
|
||||
const heap = @import("heap.zig");
|
||||
|
||||
pub const SEEK_SET = unistd.SEEK_SET;
|
||||
pub const SEEK_CUR = unistd.SEEK_CUR;
|
||||
pub const SEEK_CURRENT = unistd.SEEK_CURRENT;
|
||||
pub const SEEK_END = unistd.SEEK_END;
|
||||
|
||||
/// A C `FILE`: an fd plus sticky end-of-file / error flags. Allocated on the
|
||||
@@ -47,10 +47,10 @@ pub fn fclose(f: *FILE) c_int {
|
||||
}
|
||||
|
||||
/// Read `size*nmemb` bytes; returns the number of whole items read.
|
||||
pub fn fread(buf: []u8, size: usize, nmemb: usize, f: *FILE) usize {
|
||||
pub fn fread(buffer: []u8, size: usize, nmemb: usize, f: *FILE) usize {
|
||||
const total = size * nmemb;
|
||||
if (total == 0) return 0;
|
||||
const n = unistd.read(f.fd, buf[0..@min(buf.len, total)]);
|
||||
const n = unistd.read(f.fd, buffer[0..@min(buffer.len, total)]);
|
||||
if (n <= 0) {
|
||||
f.eof = 1;
|
||||
return 0;
|
||||
@@ -76,7 +76,7 @@ pub fn fseek(f: *FILE, off: i64, whence: u32) c_int {
|
||||
}
|
||||
|
||||
pub fn ftell(f: *FILE) i64 {
|
||||
return unistd.lseek(f.fd, 0, unistd.SEEK_CUR);
|
||||
return unistd.lseek(f.fd, 0, unistd.SEEK_CURRENT);
|
||||
}
|
||||
|
||||
pub fn rewind(f: *FILE) void {
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
//! Raw `syscall` instruction wrappers for user space — one per arity.
|
||||
//! Raw `system_call` instruction wrappers for user space — one per arity.
|
||||
//!
|
||||
//! ABI: number in rax, arguments in rdi, rsi, rdx, r10, r8, r9, result in rax.
|
||||
//! The `syscall` instruction itself clobbers rcx (it holds the return rip) and
|
||||
//! The `system_call` instruction itself clobbers rcx (it holds the return rip) and
|
||||
//! r11 (the saved rflags); the kernel entry stub preserves everything else.
|
||||
//! Note argument #3 goes in **r10, not rcx** — rcx is unavailable across the
|
||||
//! instruction, so the kernel reads the 4th argument from r10.
|
||||
|
||||
const danos = @import("danos");
|
||||
const Syscall = danos.Syscall;
|
||||
const SystemCall = danos.SystemCall;
|
||||
|
||||
pub inline fn syscall0(n: Syscall) usize {
|
||||
pub inline fn systemCall0(n: SystemCall) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
}
|
||||
|
||||
pub inline fn syscall1(n: Syscall, a0: usize) usize {
|
||||
pub inline fn systemCall1(n: SystemCall, a0: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
}
|
||||
|
||||
pub inline fn syscall2(n: Syscall, a0: usize, a1: usize) usize {
|
||||
pub inline fn systemCall2(n: SystemCall, a0: usize, a1: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
}
|
||||
|
||||
pub inline fn syscall3(n: Syscall, a0: usize, a1: usize, a2: usize) usize {
|
||||
pub inline fn systemCall3(n: SystemCall, a0: usize, a1: usize, a2: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), [a2] "{rdx}" (a2),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
}
|
||||
|
||||
pub inline fn syscall4(n: Syscall, a0: usize, a1: usize, a2: usize, a3: usize) usize {
|
||||
pub inline fn systemCall4(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), [a2] "{rdx}" (a2), [a3] "{r10}" (a3),
|
||||
: .{ .rcx = true, .r11 = true, .memory = true });
|
||||
}
|
||||
|
||||
pub inline fn syscall5(n: Syscall, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) usize {
|
||||
pub inline fn systemCall5(n: SystemCall, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) usize {
|
||||
return asm volatile ("syscall"
|
||||
: [ret] "={rax}" (-> usize),
|
||||
: [n] "{rax}" (@intFromEnum(n)), [a0] "{rdi}" (a0), [a1] "{rsi}" (a1), [a2] "{rdx}" (a2), [a3] "{r10}" (a3), [a4] "{r8}" (a4),
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Typed syscall surface for user space — thin wrappers over the raw `syscall`
|
||||
//! stubs, one per kernel call. Numbers come from `danos.Syscall`, the single
|
||||
//! Typed system_call surface for user space — thin wrappers over the raw `system_call`
|
||||
//! stubs, one per kernel call. Numbers come from `danos.SystemCall`, the single
|
||||
//! source of truth shared with the kernel dispatcher.
|
||||
|
||||
const danos = @import("danos");
|
||||
const sc = @import("syscall.zig");
|
||||
const sc = @import("system-call.zig");
|
||||
|
||||
/// `mmap` protection flags (matching the usual C bit values). Grants are always
|
||||
/// readable+writable today; the kernel does not yet honour finer prot.
|
||||
@@ -13,23 +13,23 @@ pub const PROT_EXEC: usize = danos.prot_exec;
|
||||
|
||||
/// Give up the rest of this quantum.
|
||||
pub fn yield() void {
|
||||
_ = sc.syscall0(.yield);
|
||||
_ = sc.systemCall0(.yield);
|
||||
}
|
||||
|
||||
/// Write raw bytes to the kernel log (a bring-up diagnostic; real output goes
|
||||
/// through the console/VFS later). Returns the byte count, or a wrapped -1.
|
||||
pub fn write(msg: []const u8) usize {
|
||||
return sc.syscall2(.debug_write, @intFromPtr(msg.ptr), msg.len);
|
||||
pub fn write(message: []const u8) usize {
|
||||
return sc.systemCall2(.debug_write, @intFromPtr(message.ptr), message.len);
|
||||
}
|
||||
|
||||
/// Block the caller for `ms` milliseconds.
|
||||
pub fn sleep(ms: usize) void {
|
||||
_ = sc.syscall1(.sleep, ms);
|
||||
_ = sc.systemCall1(.sleep, ms);
|
||||
}
|
||||
|
||||
/// End the process. Never returns.
|
||||
pub fn exit(code: usize) noreturn {
|
||||
_ = sc.syscall1(.exit, code);
|
||||
_ = sc.systemCall1(.exit, code);
|
||||
unreachable; // the kernel never returns from exit
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ pub fn exit(code: usize) noreturn {
|
||||
/// memory and return the base virtual address. On failure returns a value in the
|
||||
/// top page (see `mmapFailed`). The user heap grows through this call.
|
||||
pub fn mmap(len: usize, prot: usize) usize {
|
||||
return sc.syscall2(.mmap, len, prot);
|
||||
return sc.systemCall2(.mmap, len, prot);
|
||||
}
|
||||
|
||||
/// Release a range previously handed out by `mmap`.
|
||||
pub fn munmap(base: usize, len: usize) usize {
|
||||
return sc.syscall2(.munmap, base, len);
|
||||
return sc.systemCall2(.munmap, base, len);
|
||||
}
|
||||
|
||||
/// Whether an `mmap` return value is an error (the kernel returns a wrapped
|
||||
+38
-38
@@ -4,13 +4,13 @@
|
||||
//! kernel knows nothing of files or fds — the fd table lives here, per process.
|
||||
|
||||
const std = @import("std");
|
||||
const proto = @import("vfs_proto.zig");
|
||||
const protocol = @import("vfs-protocol.zig");
|
||||
const ipc = @import("ipc.zig");
|
||||
const danos = @import("danos");
|
||||
|
||||
pub const O_CREAT = proto.O_CREAT;
|
||||
pub const O_CREAT = protocol.O_CREAT;
|
||||
pub const SEEK_SET: u32 = 0;
|
||||
pub const SEEK_CUR: u32 = 1;
|
||||
pub const SEEK_CURRENT: u32 = 1;
|
||||
pub const SEEK_END: u32 = 2;
|
||||
|
||||
// Resolve (and cache) the VFS server endpoint, looked up by well-known id.
|
||||
@@ -24,9 +24,9 @@ fn vfs() ?usize {
|
||||
return vfs_handle;
|
||||
}
|
||||
|
||||
const max_fds = 32;
|
||||
const maximum_fds = 32;
|
||||
const Fd = struct { used: bool = false, node: u64 = 0, offset: u64 = 0 };
|
||||
var fds = [_]Fd{.{}} ** max_fds;
|
||||
var fds = [_]Fd{.{}} ** maximum_fds;
|
||||
|
||||
fn allocFd() ?usize {
|
||||
for (&fds, 0..) |*f, i| {
|
||||
@@ -38,30 +38,30 @@ fn allocFd() ?usize {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Result = struct { reply: proto.Reply, payload: []u8 };
|
||||
const Result = struct { reply: protocol.Reply, payload: []u8 };
|
||||
|
||||
/// One request/reply round trip: [Request header][send payload] -> VFS ->
|
||||
/// [Reply header][recv payload]. The recv payload is written into `out`.
|
||||
fn transact(req: proto.Request, send: []const u8, out: []u8) ?Result {
|
||||
/// [Reply header][receive payload]. The receive payload is written into `out`.
|
||||
fn transact(req: protocol.Request, send: []const u8, out: []u8) ?Result {
|
||||
const h = vfs() orelse return null;
|
||||
var msg: [proto.msg_max]u8 = undefined;
|
||||
@memcpy(msg[0..proto.req_size], std.mem.asBytes(&req));
|
||||
const slen = @min(send.len, proto.max_payload);
|
||||
@memcpy(msg[proto.req_size..][0..slen], send[0..slen]);
|
||||
var message: [protocol.message_maximum]u8 = undefined;
|
||||
@memcpy(message[0..protocol.req_size], std.mem.asBytes(&req));
|
||||
const slen = @min(send.len, protocol.maximum_payload);
|
||||
@memcpy(message[protocol.req_size..][0..slen], send[0..slen]);
|
||||
|
||||
var rbuf: [proto.msg_max]u8 = undefined;
|
||||
const n = ipc.call(h, msg[0 .. proto.req_size + slen], &rbuf) catch return null;
|
||||
if (n < proto.reply_size) return null;
|
||||
const reply = std.mem.bytesToValue(proto.Reply, rbuf[0..proto.reply_size]);
|
||||
const rpl = @min(n - proto.reply_size, out.len);
|
||||
@memcpy(out[0..rpl], rbuf[proto.reply_size..][0..rpl]);
|
||||
var rbuf: [protocol.message_maximum]u8 = undefined;
|
||||
const n = ipc.call(h, message[0 .. protocol.req_size + slen], &rbuf) catch return null;
|
||||
if (n < protocol.reply_size) return null;
|
||||
const reply = std.mem.bytesToValue(protocol.Reply, rbuf[0..protocol.reply_size]);
|
||||
const rpl = @min(n - protocol.reply_size, out.len);
|
||||
@memcpy(out[0..rpl], rbuf[protocol.reply_size..][0..rpl]);
|
||||
return .{ .reply = reply, .payload = out[0..rpl] };
|
||||
}
|
||||
|
||||
/// Open (or create, with O_CREAT) `path`; returns an fd or -1.
|
||||
pub fn open(path: []const u8, flags: u32) i32 {
|
||||
const fd = allocFd() orelse return -1;
|
||||
const req = proto.Request{ .op = .open, .node = 0, .offset = 0, .len = @intCast(path.len), .flags = flags };
|
||||
const req = protocol.Request{ .op = .open, .node = 0, .offset = 0, .len = @intCast(path.len), .flags = flags };
|
||||
const r = transact(req, path, &.{}) orelse {
|
||||
fds[fd].used = false;
|
||||
return -1;
|
||||
@@ -75,17 +75,17 @@ pub fn open(path: []const u8, flags: u32) i32 {
|
||||
}
|
||||
|
||||
fn fdPtr(fd: i32) ?*Fd {
|
||||
if (fd < 0 or fd >= max_fds) return null;
|
||||
if (fd < 0 or fd >= maximum_fds) return null;
|
||||
const f = &fds[@intCast(fd)];
|
||||
return if (f.used) f else null;
|
||||
}
|
||||
|
||||
/// Read up to `buf.len` bytes at the current offset; returns the count or -1.
|
||||
pub fn read(fd: i32, buf: []u8) isize {
|
||||
/// Read up to `buffer.len` bytes at the current offset; returns the count or -1.
|
||||
pub fn read(fd: i32, buffer: []u8) isize {
|
||||
const f = fdPtr(fd) orelse return -1;
|
||||
const want: u32 = @intCast(@min(buf.len, proto.max_payload));
|
||||
const req = proto.Request{ .op = .read, .node = f.node, .offset = f.offset, .len = want, .flags = 0 };
|
||||
const r = transact(req, &.{}, buf) orelse return -1;
|
||||
const want: u32 = @intCast(@min(buffer.len, protocol.maximum_payload));
|
||||
const req = protocol.Request{ .op = .read, .node = f.node, .offset = f.offset, .len = want, .flags = 0 };
|
||||
const r = transact(req, &.{}, buffer) orelse return -1;
|
||||
if (r.reply.status != 0) return -1;
|
||||
f.offset += r.reply.len;
|
||||
return @intCast(r.reply.len);
|
||||
@@ -94,8 +94,8 @@ pub fn read(fd: i32, buf: []u8) isize {
|
||||
/// Write `data` at the current offset; returns the count or -1.
|
||||
pub fn write(fd: i32, data: []const u8) isize {
|
||||
const f = fdPtr(fd) orelse return -1;
|
||||
const want: u32 = @intCast(@min(data.len, proto.max_payload));
|
||||
const req = proto.Request{ .op = .write, .node = f.node, .offset = f.offset, .len = want, .flags = 0 };
|
||||
const want: u32 = @intCast(@min(data.len, protocol.maximum_payload));
|
||||
const req = protocol.Request{ .op = .write, .node = f.node, .offset = f.offset, .len = want, .flags = 0 };
|
||||
const r = transact(req, data[0..want], &.{}) orelse return -1;
|
||||
if (r.reply.status != 0) return -1;
|
||||
f.offset += r.reply.len;
|
||||
@@ -108,13 +108,13 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 {
|
||||
const f = fdPtr(fd) orelse return -1;
|
||||
const base: i64 = switch (whence) {
|
||||
SEEK_SET => 0,
|
||||
SEEK_CUR => @intCast(f.offset),
|
||||
SEEK_CURRENT => @intCast(f.offset),
|
||||
SEEK_END => blk: {
|
||||
const req = proto.Request{ .op = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
var sbuf: [@sizeOf(proto.Stat)]u8 = undefined;
|
||||
const req = protocol.Request{ .op = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined;
|
||||
const r = transact(req, &.{}, &sbuf) orelse return -1;
|
||||
if (r.reply.status != 0 or r.payload.len < @sizeOf(proto.Stat)) return -1;
|
||||
const st = std.mem.bytesToValue(proto.Stat, sbuf[0..@sizeOf(proto.Stat)]);
|
||||
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1;
|
||||
const st = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]);
|
||||
break :blk @intCast(st.size);
|
||||
},
|
||||
else => return -1,
|
||||
@@ -126,24 +126,24 @@ pub fn lseek(fd: i32, off: i64, whence: u32) i64 {
|
||||
}
|
||||
|
||||
/// Stat `path`. Returns 0 or -1.
|
||||
pub fn stat(path: []const u8, out: *proto.Stat) i32 {
|
||||
pub fn stat(path: []const u8, out: *protocol.Stat) i32 {
|
||||
// Open, stat by node, close — simple and enough for now.
|
||||
const fd = open(path, 0);
|
||||
if (fd < 0) return -1;
|
||||
defer close(fd);
|
||||
const f = fdPtr(fd).?;
|
||||
const req = proto.Request{ .op = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
var sbuf: [@sizeOf(proto.Stat)]u8 = undefined;
|
||||
const req = protocol.Request{ .op = .stat, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
var sbuf: [@sizeOf(protocol.Stat)]u8 = undefined;
|
||||
const r = transact(req, &.{}, &sbuf) orelse return -1;
|
||||
if (r.reply.status != 0 or r.payload.len < @sizeOf(proto.Stat)) return -1;
|
||||
out.* = std.mem.bytesToValue(proto.Stat, sbuf[0..@sizeOf(proto.Stat)]);
|
||||
if (r.reply.status != 0 or r.payload.len < @sizeOf(protocol.Stat)) return -1;
|
||||
out.* = std.mem.bytesToValue(protocol.Stat, sbuf[0..@sizeOf(protocol.Stat)]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Close an fd (best effort — tells the VFS to release the open file).
|
||||
pub fn close(fd: i32) void {
|
||||
const f = fdPtr(fd) orelse return;
|
||||
const req = proto.Request{ .op = .close, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
const req = protocol.Request{ .op = .close, .node = f.node, .offset = 0, .len = 0, .flags = 0 };
|
||||
_ = transact(req, &.{}, &.{});
|
||||
f.used = false;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! The VFS wire protocol — the message format spoken between a client (via the
|
||||
//! `rt` file API) and the user-space VFS server over IPC. A request is a fixed
|
||||
//! `runtime` file API) and the user-space VFS server over IPC. A request is a fixed
|
||||
//! `Request` header followed by an inline payload (a path, or write bytes); a
|
||||
//! reply is a fixed `Reply` header followed by an inline payload (read bytes, or
|
||||
//! a Stat). Everything fits in one IPC message (<= ipc MSG_MAX = 256 bytes).
|
||||
//! a Stat). Everything fits in one IPC message (<= ipc MESSAGE_MAXIMUM = 256 bytes).
|
||||
//!
|
||||
//! This is user-space only — the kernel knows nothing of files or paths; it only
|
||||
//! moves the bytes. Shared by lib/unistd.zig (client) and sbin/vfs.zig (server).
|
||||
@@ -43,11 +43,11 @@ pub const Stat = extern struct {
|
||||
_pad: u32 = 0,
|
||||
};
|
||||
|
||||
pub const msg_max: usize = 256;
|
||||
pub const message_maximum: usize = 256;
|
||||
pub const req_size: usize = @sizeOf(Request);
|
||||
pub const reply_size: usize = @sizeOf(Reply);
|
||||
/// Largest inline payload that still fits one IPC message alongside a header.
|
||||
pub const max_payload: usize = msg_max - req_size;
|
||||
pub const maximum_payload: usize = message_maximum - req_size;
|
||||
|
||||
/// Open flags.
|
||||
pub const O_CREAT: u32 = 1;
|
||||
Reference in New Issue
Block a user