From 1d1234c963feac0ae525b57bd9e030ca3be5b5ac Mon Sep 17 00:00:00 2001 From: Daniel Samson <12231216+daniel-samson@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:46:51 +0100 Subject: [PATCH] reorg: move the USB client to library/device/usb (drop runtime.usb) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The USB class-driver transfer client was library/runtime/usb.zig, re-exported as runtime.usb — which compiled the USB client and usb-transfer-protocol into every user binary (init, fat, the compositor…), none of which speak USB. It is bus-family logic, not core runtime. Move it to its domain home, library/device/usb/usb.zig (module "usb"), alongside the usb-abi and usb-ids data modules; fix its internal imports to go through the runtime module; and re-export usb.abi / usb.ids so a class driver reaches the whole USB domain through one import. runtime.usb and runtime's usb-transfer-protocol import are removed; the three class drivers (usb-hid keyboard/mouse, usb-storage) import module "usb" directly. zig build + test green; usb-hid, usb-storage pass. --- build.zig | 20 +++++++++++++++++--- library/{runtime => device/usb}/usb.zig | 10 ++++++++-- library/runtime/runtime.zig | 6 +++--- system/drivers/usb-hid/keyboard.zig | 11 ++++++----- system/drivers/usb-hid/mouse.zig | 11 ++++++----- system/drivers/usb-storage/usb-storage.zig | 15 ++++++++------- 6 files changed, 48 insertions(+), 25 deletions(-) rename library/{runtime => device/usb}/usb.zig (95%) diff --git a/build.zig b/build.zig index 5ac47a1..562938a 100644 --- a/build.zig +++ b/build.zig @@ -403,9 +403,6 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("system/services/device-manager/device-manager-protocol.zig"), }); runtime_module.addImport("device-manager-protocol", device_manager_protocol_module); - // The USB transfer protocol, so runtime.usb (the class-driver client) can speak - // it, the way runtime.input speaks the input protocol. - runtime_module.addImport("usb-transfer-protocol", usb_transfer_protocol_module); // The block protocol, so runtime.block (the block-device client) can speak it. runtime_module.addImport("block-protocol", block_protocol_module); @@ -449,6 +446,20 @@ pub fn build(b: *std.Build) void { }, }); + // The USB class-driver transfer client (library/device/usb/usb.zig): open a device on + // the xHCI bus and drive it (control / interrupt / bulk). Bus-family logic a class + // driver imports directly — no longer funnelled through runtime. Re-exports usb-abi / + // usb-ids as usb.abi / usb.ids for a single USB import. + const usb_module = b.addModule("usb", .{ + .root_source_file = b.path("library/device/usb/usb.zig"), + .imports = &.{ + .{ .name = "runtime", .module = runtime_module }, + .{ .name = "usb-transfer-protocol", .module = usb_transfer_protocol_module }, + .{ .name = "usb-abi", .module = usb_abi_module }, + .{ .name = "usb-ids", .module = usb_ids_module }, + }, + }); + // Keyboard layouts compiled from the X11 xkeyboard-config database into native Zig // (keycode + modifiers -> keysym/character). The `layouts` tables are generated by // tools/make-xkeyboard-config.py; `xkeyboard-config` is the hand-written API over them. @@ -548,12 +559,15 @@ pub fn build(b: *std.Build) void { // opens its device through runtime.usb (the transfer protocol) and publishes to // the input service. They build chapter-9 class requests from usb-abi. const usb_hid_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-hid-keyboard", "system/drivers/usb-hid/keyboard.zig"); + programModule(usb_hid_keyboard_exe).addImport("usb", usb_module); programModule(usb_hid_keyboard_exe).addImport("usb-abi", usb_abi_module); const usb_hid_mouse_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-hid-mouse", "system/drivers/usb-hid/mouse.zig"); + programModule(usb_hid_mouse_exe).addImport("usb", usb_module); programModule(usb_hid_mouse_exe).addImport("usb-abi", usb_abi_module); // The USB mass-storage class driver: opens its device via runtime.usb, drives it // with Bulk-Only Transport + SCSI, and serves the block protocol under `.block`. const usb_storage_exe = addUserBinary(b, kernel_target, runtime_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-storage", "system/drivers/usb-storage/usb-storage.zig"); + programModule(usb_storage_exe).addImport("usb", usb_module); programModule(usb_storage_exe).addImport("block-protocol", block_protocol_module); // The FAT filesystem server: mounts the block device and serves it into the VFS // at /mnt/usb. Its engine (engine.zig / on-disk.zig) is imported relatively. diff --git a/library/runtime/usb.zig b/library/device/usb/usb.zig similarity index 95% rename from library/runtime/usb.zig rename to library/device/usb/usb.zig index 892d80d..759e314 100644 --- a/library/runtime/usb.zig +++ b/library/device/usb/usb.zig @@ -16,10 +16,16 @@ //! the service harness drops buffered-message payloads — see service.zig). const std = @import("std"); -const ipc = @import("ipc.zig"); -const system = @import("system.zig"); +const runtime = @import("runtime"); +const ipc = runtime.ipc; +const system = runtime.system; const protocol = @import("usb-transfer-protocol"); +/// The USB chapter-9 wire ABI and the class taxonomy, re-exported so a class driver reaches +/// the whole USB domain through its one `usb` import (`usb.abi.getDescriptor`, `usb.ids.Class`). +pub const abi = @import("usb-abi"); +pub const ids = @import("usb-ids"); + pub const Endpoint = protocol.Endpoint; pub const InterruptReport = protocol.InterruptReport; pub const max_report_data = protocol.max_report_data; diff --git a/library/runtime/runtime.zig b/library/runtime/runtime.zig index aa71511..7ef40a6 100644 --- a/library/runtime/runtime.zig +++ b/library/runtime/runtime.zig @@ -46,9 +46,9 @@ pub const dma = @import("dma.zig"); /// and docs/display-v2.md. pub const shared_memory = @import("shared-memory.zig"); -/// USB class-driver client: open a device on the xHCI bus and drive it -/// (control / interrupt / bulk transfers). See library/runtime/usb.zig. -pub const usb = @import("usb.zig"); +// The USB class-driver client moved to its domain home, library/device/usb (module +// "usb"): it is bus-family logic, not core runtime, and re-exporting it here compiled it +// into every binary. USB class drivers import it directly with @import("usb"). /// Block-device client: read/write a block device (a USB stick, via /// usb-storage). See library/runtime/block.zig. diff --git a/system/drivers/usb-hid/keyboard.zig b/system/drivers/usb-hid/keyboard.zig index d75baa9..b8aff5b 100644 --- a/system/drivers/usb-hid/keyboard.zig +++ b/system/drivers/usb-hid/keyboard.zig @@ -4,7 +4,7 @@ //! keyboard interface (class 3, subclass 1, protocol 1); its assigned device id //! arrives as argv[1] and an optional layout name ("us", "gb", ...) as argv[2]. //! It owns no hardware: it opens its device through the USB transfer protocol -//! (`runtime.usb`), asks the device for the boot protocol, subscribes to its +//! (`usb`), asks the device for the boot protocol, subscribes to its //! interrupt-IN endpoint, and turns each 8-byte boot report into input-protocol //! events, published to the input service — the USB analogue of ps2-bus/keyboard. //! @@ -16,6 +16,7 @@ const std = @import("std"); const runtime = @import("runtime"); +const usb = @import("usb"); const usb_abi = @import("usb-abi"); const xkb = @import("xkeyboard-config"); const hid = @import("hid-report.zig"); @@ -73,11 +74,11 @@ pub fn main(init: runtime.process.Init) void { // Hello the manager first (meet the spawn deadline), then open the device. if (runtime.device_manager.hello(.device, device_id) == null) return; - var device = runtime.usb.open(device_id) orelse { + var device = usb.open(device_id) orelse { std.log.info("could not open device {d}", .{device_id}); return; }; - const endpoint = device.findEndpoint(runtime.usb.transfer_type_interrupt, true) orelse { + const endpoint = device.findEndpoint(usb.transfer_type_interrupt, true) orelse { _ = runtime.system.write("/system/drivers/usb-hid/keyboard: no interrupt-IN endpoint\n"); return; }; @@ -108,9 +109,9 @@ pub fn main(init: runtime.process.Init) void { if (signals.has(.terminate)) return; continue; } - if (!got.isMessage() or got.len < @sizeOf(runtime.usb.InterruptReport)) continue; + if (!got.isMessage() or got.len < @sizeOf(usb.InterruptReport)) continue; - const message = std.mem.bytesToValue(runtime.usb.InterruptReport, receive[0..@sizeOf(runtime.usb.InterruptReport)]); + const message = std.mem.bytesToValue(usb.InterruptReport, receive[0..@sizeOf(usb.InterruptReport)]); if (message.length < @sizeOf(hid.KeyboardReport)) continue; const report = std.mem.bytesToValue(hid.KeyboardReport, message.data[0..@sizeOf(hid.KeyboardReport)]); const transitions = decoder.feed(report); diff --git a/system/drivers/usb-hid/mouse.zig b/system/drivers/usb-hid/mouse.zig index aa11be2..3467373 100644 --- a/system/drivers/usb-hid/mouse.zig +++ b/system/drivers/usb-hid/mouse.zig @@ -3,7 +3,7 @@ //! Spawned by the device manager when the xHCI bus driver reports a HID / boot / //! mouse interface (class 3, subclass 1, protocol 2); its assigned device id //! arrives as argv[1]. Like the keyboard driver it owns no hardware: it opens its -//! device through the USB transfer protocol (`runtime.usb`), asks for the boot +//! device through the USB transfer protocol (`usb`), asks for the boot //! protocol, subscribes to its interrupt-IN endpoint, and turns each 3- or 4-byte //! boot report into input-protocol mouse events published to the input service. //! @@ -12,6 +12,7 @@ const std = @import("std"); const runtime = @import("runtime"); +const usb = @import("usb"); const usb_abi = @import("usb-abi"); const hid = @import("hid-report.zig"); const ipc = runtime.ipc; @@ -38,11 +39,11 @@ pub fn main(init: runtime.process.Init) void { }; if (runtime.device_manager.hello(.device, device_id) == null) return; - var device = runtime.usb.open(device_id) orelse { + var device = usb.open(device_id) orelse { std.log.info("could not open device {d}", .{device_id}); return; }; - const endpoint = device.findEndpoint(runtime.usb.transfer_type_interrupt, true) orelse { + const endpoint = device.findEndpoint(usb.transfer_type_interrupt, true) orelse { _ = runtime.system.write("/system/drivers/usb-hid/mouse: no interrupt-IN endpoint\n"); return; }; @@ -70,9 +71,9 @@ pub fn main(init: runtime.process.Init) void { if (signals.has(.terminate)) return; continue; } - if (!got.isMessage() or got.len < @sizeOf(runtime.usb.InterruptReport)) continue; + if (!got.isMessage() or got.len < @sizeOf(usb.InterruptReport)) continue; - const message = std.mem.bytesToValue(runtime.usb.InterruptReport, receive[0..@sizeOf(runtime.usb.InterruptReport)]); + const message = std.mem.bytesToValue(usb.InterruptReport, receive[0..@sizeOf(usb.InterruptReport)]); const length = @min(message.length, message.data.len); const report = hid.parseMouse(message.data[0..length]) orelse continue; const mask = buttonMask(report.buttons); diff --git a/system/drivers/usb-storage/usb-storage.zig b/system/drivers/usb-storage/usb-storage.zig index 3659210..61d3a8b 100644 --- a/system/drivers/usb-storage/usb-storage.zig +++ b/system/drivers/usb-storage/usb-storage.zig @@ -3,7 +3,7 @@ //! Spawned by the device manager when the xHCI bus driver reports a mass-storage //! / SCSI / bulk-only interface (class 8, subclass 6, protocol 0x50); its device //! id arrives as argv[1]. It owns no hardware: it opens its device through the -//! USB transfer protocol (`runtime.usb`), then drives it with the BOT command +//! USB transfer protocol (`usb`), then drives it with the BOT command //! cycle — CBW out, an optional data stage, CSW in — carrying SCSI commands //! (READ CAPACITY, READ(10), WRITE(10)). Upward it is a block device: it serves //! the block protocol under `.block`, the storage a FAT filesystem sits on. @@ -13,15 +13,16 @@ const std = @import("std"); const runtime = @import("runtime"); +const usb = @import("usb"); const scsi = @import("scsi.zig"); const bot = @import("bulk-only-transport.zig"); const block_protocol = @import("block-protocol"); const dma = runtime.dma; var device_id: u64 = 0; -var device: runtime.usb.Device = undefined; -var bulk_in: runtime.usb.Endpoint = undefined; -var bulk_out: runtime.usb.Endpoint = undefined; +var device: usb.Device = undefined; +var bulk_in: usb.Endpoint = undefined; +var bulk_out: usb.Endpoint = undefined; // DMA buffers for the transport: the 31-byte CBW, the 13-byte CSW, and a page // for the small command data (INQUIRY / READ CAPACITY / the self-check sector). @@ -70,16 +71,16 @@ var bring_up_failed = false; fn initialise(endpoint: runtime.ipc.Handle) bool { _ = endpoint; if (runtime.device_manager.hello(.device, device_id) == null) return false; - device = runtime.usb.open(device_id) orelse { + device = usb.open(device_id) orelse { std.log.info("could not open device {d}", .{device_id}); return false; }; - bulk_in = device.findEndpoint(runtime.usb.transfer_type_bulk, true) orelse { + bulk_in = device.findEndpoint(usb.transfer_type_bulk, true) orelse { _ = runtime.system.write("/system/drivers/usb-storage: no bulk-IN endpoint\n"); bring_up_failed = true; return false; }; - bulk_out = device.findEndpoint(runtime.usb.transfer_type_bulk, false) orelse { + bulk_out = device.findEndpoint(usb.transfer_type_bulk, false) orelse { _ = runtime.system.write("/system/drivers/usb-storage: no bulk-OUT endpoint\n"); bring_up_failed = true; return false;