diff --git a/build.zig b/build.zig index 082e705..269bef1 100644 --- a/build.zig +++ b/build.zig @@ -331,6 +331,7 @@ pub fn build(b: *std.Build) void { const ps2_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-bus", "system/drivers/ps2-bus/ps2-bus.zig"); const ps2_keyboard_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-keyboard", "system/drivers/ps2-bus/keyboard.zig"); const ps2_mouse_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "ps2-mouse", "system/drivers/ps2-bus/mouse.zig"); + const usb_xhci_bus_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "usb-xhci-bus", "system/drivers/usb-xhci-bus/usb-xhci-bus.zig"); const device_manager_exe = addUserBinary(b, kernel_target, runtime_module, posix_module, mmio_module, xkeyboard_config_module, acpi_ids_module, "device-manager", "system/services/device-manager/device-manager.zig"); // The input service and its exercisers: the fan-out server, a hardware-free synthetic // source, and a subscriber that doubles as the `input` test's oracle. See docs/input.md. @@ -360,6 +361,8 @@ pub fn build(b: *std.Build) void { mk_run.addFileArg(ps2_keyboard_exe.getEmittedBin()); mk_run.addArg("ps2-mouse"); mk_run.addFileArg(ps2_mouse_exe.getEmittedBin()); + mk_run.addArg("usb-xhci-bus"); + mk_run.addFileArg(usb_xhci_bus_exe.getEmittedBin()); mk_run.addArg("device-manager"); mk_run.addFileArg(device_manager_exe.getEmittedBin()); mk_run.addArg("input"); @@ -384,6 +387,7 @@ pub fn build(b: *std.Build) void { .{ ps2_bus_exe, "system/drivers" }, .{ ps2_keyboard_exe, "system/drivers" }, .{ ps2_mouse_exe, "system/drivers" }, + .{ usb_xhci_bus_exe, "system/drivers" }, }) |entry| { const step = b.addInstallArtifact(entry[0], .{ .dest_dir = .{ .override = .{ .custom = entry[1] } } }); b.getInstallStep().dependOn(&step.step); @@ -526,6 +530,8 @@ pub fn build(b: *std.Build) void { "system/devices/device-abi.zig", "system/devices/pci-class.zig", // class/subclass/prog-IF name decoding "system/devices/acpi-ids.zig", // _HID name decoding + "system/devices/usb-abi.zig", // wire sizes + bit packings + set-up packet encodings + "system/devices/usb-ids.zig", // class/subclass/protocol code assignments "library/mmio/mmio.zig", // barriers assemble + registers round-trip "system/drivers/ps2-bus/scancode.zig", // set-2 decode + keyboard state machine "system/drivers/ps2-bus/mouse-packet.zig", // 3-byte mouse packet assembly diff --git a/docs/README.md b/docs/README.md index bd69c58..11d2cde 100644 --- a/docs/README.md +++ b/docs/README.md @@ -52,11 +52,22 @@ rather than restate it. Roughly in the order things happen at runtime: microkernel's `ps`/`kill`/SIGCHLD: enumerate as a table snapshot, the supervision link as the kill authority, and child-exit notifications over the same endpoints IRQs arrive on. -16. **[input.md](input.md) — the input module.** Broadcasting input events (keyboard, +16. **[process-lifecycle.md](process-lifecycle.md) — the process lifecycle.** Design: + signals over IPC as the one lifecycle vocabulary every process speaks — the + POSIX.1-1990 words with message delivery instead of stack hijack, the stable + `runtime.process` interface, exit reasons, published exit events any stateful + service can subscribe to (the VFS releasing dead clients' handles), and the two + iron rules (cleanup is the kernel's job; kill is not a signal). +17. **[device-manager.md](device-manager.md) — the device manager.** Design: the + tree, the matcher, and the supervisor. Tree structure lives in the manager, + authority stays in the kernel; bus drivers report what they see; drivers are + restarted through the lifecycle vocabulary — the plan that turns + [resilience.md](resilience.md)'s restart goal into increments. +18. **[input.md](input.md) — the input module.** Broadcasting input events (keyboard, mouse, joystick): why a synchronous rendezvous can't fan out to many listeners, the asynchronous `ipc_send` primitive built to fix it, and the per-device subscribe/publish service layered on top. -17. **[halting.md](halting.md) — halting.** Why a kernel can't just "exit", and +19. **[halting.md](halting.md) — halting.** Why a kernel can't just "exit", and how `while (true) hlt` parks the CPU safely once there's nothing left to do. Start with the north star: diff --git a/docs/coding-standards.md b/docs/coding-standards.md index 5b5e678..d074949 100644 --- a/docs/coding-standards.md +++ b/docs/coding-standards.md @@ -150,3 +150,19 @@ input output" in code — that expansion is what the acronym *is for*. But `msg` 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. diff --git a/library/runtime/device.zig b/library/runtime/device.zig index 6cba82d..3bb5217 100644 --- a/library/runtime/device.zig +++ b/library/runtime/device.zig @@ -37,6 +37,10 @@ pub fn mmioMap(device_id: u64, resource_index: u64) ?usize { /// `DeviceDescriptor.parent` for a device with no parent. pub const no_parent = device_abi.no_parent; +/// `DeviceDescriptor.pci_class` for a device that is not a PCI function. Set this on +/// descriptors passed to `register` unless the child really is one. +pub const no_pci_class = device_abi.no_pci_class; + /// 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. diff --git a/system/devices/device-abi.zig b/system/devices/device-abi.zig index 35f4c96..9b04bf4 100644 --- a/system/devices/device-abi.zig +++ b/system/devices/device-abi.zig @@ -56,6 +56,10 @@ pub const maximum_device_resources = 8; /// `DeviceDescriptor.parent` for a device with no parent — a root of the device tree. pub const no_parent: u64 = ~@as(u64, 0); +/// `DeviceDescriptor.pci_class` for a device that is not a PCI function. (Zero would be +/// ambiguous: 0x000000 is a real class code, "unclassified device".) +pub const no_pci_class: u64 = ~@as(u64, 0); + /// A device, as snapshotted for user space by `device_enumerate`. A driver scans /// these to find the hardware it owns, claims it, and maps its MMIO. /// @@ -69,6 +73,11 @@ pub const DeviceDescriptor = extern struct { id: u64, parent: u64, // a device id, or `no_parent` class: u64, // a DeviceClass value + // The PCI class/subclass/prog-IF triple packed as 0xCCSSPP when this device is a PCI + // function, or `no_pci_class` otherwise. This is how a manager tells *what* a + // `pci_device` is (an xHCI controller, an AHCI controller) — decode the triple into + // names with the pci-class module. + pci_class: u64, hid_len: u64, resource_count: u64, hid: [8]u8, diff --git a/system/devices/usb-abi.zig b/system/devices/usb-abi.zig new file mode 100644 index 0000000..d94bb6c --- /dev/null +++ b/system/devices/usb-abi.zig @@ -0,0 +1,857 @@ +//! USB device-framework wire ABI: the set-up packets, standard requests, and standard +//! descriptors every USB device speaks over its default control pipe, as defined by chapter 9 +//! of the USB 2.0 specification (see https://wiki.osdev.org/Universal_Serial_Bus). Pure data +//! definitions — no hardware access — shared by the host-controller bus drivers (which build +//! the requests) and anything that parses what devices return (device naming, driver +//! matching, configuration). The structs mirror the wire byte-for-byte: multi-byte fields are +//! little-endian and align(1), so a descriptor can be bit-cast straight out of a transfer +//! buffer at any offset, and bitmap bytes are packed structs so no caller ever needs a magic +//! mask. Class, subclass, and protocol code tables live in usb-ids.zig. + +const DeviceState = enum(u8) { + // Immediately after the USB device is attached to the USB system, it is in this state. + // The USB specifications do not define the state of a USB device that is detached from + // a USB system. + attached, + // A device is in this state after it has both been attached to the bus, and the VBUS line is + // applied to the device (the host controller drives the VBUS at +5V, however this is only + // particularly important for hardware developers). In this state, the device must not respond + // to any bus transactions. The USB specification recognizes three potential scenarios with + // respect to how a device draws power: + // - Self-Powered Devices draw power from an external power source (e.g, a USB printer plugs + // into the wall as well as a USB port). Although the device may be considered + // technically "powered" even before attachment to the USB, it is still only considered + // powered after the VBUS line is applied to the device. + // - Bus-Powered Devices draw power solely from the USB up to 100mA. + // - Self- or Bus-Powered Devices may draw power from either the bus or an external power + // source, depending on the configuration. These devices may change power source at any + // time. If a device is currently self-powered and requires more than 100mA of power, but + // switches to being bus-powered, then the device must return to the Address state. + powered, + // A device in the powered state enters the default state after receiving a bus reset. In this + // state, the device is addressable at the default, reserved address of 0. At this point, the + // device is operating at the correct speed. The host is expected to allow 10 milliseconds + // before expecting the device to respond to data transfers after reset. + default, + // A device enters this state after the host assigns it an address via the default control pipe, + // which is always accessible whether the device's address has been set or not. + address, + // A device is in this state after the host examines its possible configurations and selects + // one. All endpoint's data toggle bits are initialized to zero when a device enters this state. + configured, + // When no traffic is observed on the bus for a period of 1 millisecond, a USB device enters + // this state, characterized by its low power consumption. The device's address and + // configuration settings are maintained while suspended. A device exits the suspended state as + // soon as it begins seeing bus activity again. The host is expected to allow 10 milliseconds + // before expecting the device to respond to data transfers after resume. + suspended, +}; + +const RequestCode = enum(u8) { + get_status = 0, + clear_feature = 1, + set_feature = 3, + set_address = 5, + get_descriptor = 6, + set_descriptor = 7, + get_configuration = 8, + set_configuration = 9, + get_interface = 10, + set_interface = 11, + sync_frame = 12, +}; + +// Direction of an endpoint, from the host's point of view +const EndpointDirection = enum(u1) { + out = 0, + in = 1, +}; + +// Identifier newtypes: distinct wire-sized types for values that identify something on the +// device rather than count something. Each is a non-exhaustive enum whose values originate +// in the descriptors below and flow, still typed, into the standard request constructors — +// so an interface number can never be passed where a configuration value is expected. + +// The bus address of a device, assigned by the host with SET_ADDRESS. Addresses are 7 bits +// wide. +const DeviceAddress = enum(u7) { + // The default address every device answers at after a reset, until SET_ADDRESS + // completes + default = 0, + _, +}; + +// Identifies a configuration; from ConfigurationDescriptor.configuration_value. +const ConfigurationValue = enum(u8) { + // Not configured: returned by GET_CONFIGURATION while the device is in the address + // state, and passed to SET_CONFIGURATION to return a configured device to the address + // state + none = 0, + _, +}; + +// Identifies an interface within a configuration; from +// InterfaceDescriptor.interface_number. +const InterfaceNumber = enum(u8) { _ }; + +// Selects between the alternate settings of one interface; from +// InterfaceDescriptor.alternate_setting. +const AlternateSetting = enum(u8) { + // The default setting of an interface + default = 0, + _, +}; + +// The number of an endpoint within a device, 4 bits wide. The direction bit carried +// alongside it tells the two endpoints sharing a number apart. +const EndpointNumber = enum(u4) { + // Endpoint zero: the default control pipe every device provides + default_control = 0, + _, +}; + +// Index of a STRING descriptor, stored in descriptors that reference a string and passed to +// GET_DESCRIPTOR to read it. +const StringIndex = enum(u8) { + // The device has no string descriptor for this field + none = 0, + _, +}; + +// Characteristics of a device request (the bmRequestType field of a set-up packet). Fields are +// declared least-significant first: recipient occupies bits 4...0, kind bits 6...5, and +// direction bit 7. +const RequestType = packed struct(u8) { + // The recipient of the request (values 4...31 are reserved) + recipient: Recipient, + // The type of the request + kind: Kind, + // Data transfer direction. The value of this bit is ignored when length is zero. + direction: Direction, + + const Recipient = enum(u5) { + device = 0, + interface = 1, + endpoint = 2, + other = 3, + }; + + const Kind = enum(u2) { + standard = 0, + class = 1, + vendor = 2, + reserved = 3, + }; + + const Direction = enum(u1) { + host_to_device = 0, + device_to_host = 1, + }; +}; + +const Request = extern struct { + // Characteristics of the request + request_type: RequestType, + // Specific request + request_code: RequestCode, + // Word-sized field that may (or may not) serve as a parameter to the request, depending + // on the specific request. For GET_DESCRIPTOR and SET_DESCRIPTOR, bit-cast a + // DescriptorValue into this field. + value: u16 align(1), + // Word-sized field that may (or may not) serve as a parameter to the request, depending + // on the specific request. Typically this field holds an index or an offset value. When + // request_type specifies an endpoint or an interface as the recipient, bit-cast an + // EndpointIndex or an InterfaceIndex into this field. + index: u16 align(1), + // Number of bytes to transfer if there is a DATA stage. + // - If this field is non-zero, and request_type indicates a transfer from + // device-to-host, then the device must never return more than length bytes of data. + // However, a device may return less. + // - If this field is non-zero, and request_type indicates a transfer from + // host-to-device, then the host must send exactly length bytes of data. If the host + // sends more than length bytes, the behavior of the device is undefined. + length: u16 align(1), + + // The format of the index field when request_type specifies an endpoint as the + // recipient. The host should always set the direction bit to zero (but the device + // should accept either value) when the endpoint is part of a control pipe. + const EndpointIndex = packed struct(u16) { + // Endpoint number + number: EndpointNumber, + // Reserved (reset to zero) + reserved: u3 = 0, + // Selects the OUT or the IN endpoint with the specified endpoint number + direction: EndpointDirection, + // Reserved (reset to zero) + reserved_high: u8 = 0, + }; + + // The format of the index field when request_type specifies an interface as the + // recipient. + const InterfaceIndex = packed struct(u16) { + // Interface number + number: u8, + // Reserved (reset to zero) + reserved: u8 = 0, + }; + + // The format of the value field of GET_DESCRIPTOR and SET_DESCRIPTOR requests: the + // descriptor type in the high byte, and the descriptor index in the low byte. The index + // is used to select a specific descriptor (only for CONFIGURATION and STRING + // descriptors) when several descriptors of that type are implemented by a device. + const DescriptorValue = packed struct(u16) { + // Descriptor index + index: u8 = 0, + // Descriptor type + kind: DescriptorType, + }; +}; + +// Feature selectors, used as the value field of CLEAR_FEATURE and SET_FEATURE requests. The +// comment on each value notes the recipient the selector applies to. +const FeatureSelector = enum(u16) { + // Halts an endpoint (recipient: endpoint) + endpoint_halt = 0, + // Enables or disables the device's remote wakeup capability (recipient: device) + device_remote_wakeup = 1, + // Puts a hi-speed device into a test mode, selected by a TestMode value in the high + // byte of the index field (recipient: device) + test_mode = 2, +}; + +// Test mode selectors, passed in the high byte of the index field of a SET_FEATURE request +// with the test_mode feature selector. Values 06h...3Fh are reserved for standard test +// selectors and C0h...FFh for vendor-specific test modes; all other unlisted values are +// reserved. +const TestMode = enum(u8) { + test_j = 0x01, + test_k = 0x02, + test_se0_nak = 0x03, + test_packet = 0x04, + test_force_enable = 0x05, + _, +}; + +// The two bytes returned by a GET_STATUS request directed at a device. Fields are declared +// least-significant first. +const DeviceStatus = packed struct(u16) { + // Whether the device is currently self-powered (as opposed to bus-powered). This bit + // cannot be changed with the SET_FEATURE or CLEAR_FEATURE requests. + self_powered: bool, + // Whether the device is currently enabled to request remote wakeup. Changed with the + // SET_FEATURE and CLEAR_FEATURE requests using the device_remote_wakeup feature + // selector. + remote_wakeup: bool, + // Reserved (reset to zero) + reserved: u14, +}; + +// The two bytes returned by a GET_STATUS request directed at an endpoint. (A GET_STATUS +// request directed at an interface returns two bytes that are entirely reserved.) +const EndpointStatus = packed struct(u16) { + // Whether the endpoint is currently halted. Set with the SET_FEATURE request using the + // endpoint_halt feature selector, and cleared with CLEAR_FEATURE. + halted: bool, + // Reserved (reset to zero) + reserved: u15, +}; + +// A target for the standard requests that may be directed at the device, an interface, or +// an endpoint. +const Target = union(enum) { + device, + interface: InterfaceNumber, + endpoint: Request.EndpointIndex, + + fn recipient(target: Target) RequestType.Recipient { + return switch (target) { + .device => .device, + .interface => .interface, + .endpoint => .endpoint, + }; + } + + fn index(target: Target) u16 { + return switch (target) { + .device => 0, + .interface => |number| @intFromEnum(number), + .endpoint => |endpoint| @bitCast(endpoint), + }; + } +}; + +// Constructors for the standard device requests, one per RequestCode. Each returns a +// ready-to-send set-up packet with the request_type, value, index, and length fields the +// specification prescribes for that request. + +// Reads the status of the given target: bit-cast the two bytes the device returns into a +// DeviceStatus or an EndpointStatus. (The two bytes returned for an interface are entirely +// reserved.) +fn getStatus(target: Target) Request { + return .{ + .request_type = .{ + .recipient = target.recipient(), + .kind = .standard, + .direction = .device_to_host, + }, + .request_code = .get_status, + .value = 0, + .index = target.index(), + .length = 2, + }; +} + +// Clears or disables the given feature. A device cannot be taken out of a test mode with +// this request; test_mode is only cleared by cycling power. +fn clearFeature(feature: FeatureSelector, target: Target) Request { + return .{ + .request_type = .{ + .recipient = target.recipient(), + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .clear_feature, + .value = @intFromEnum(feature), + .index = target.index(), + .length = 0, + }; +} + +// Sets or enables the given feature. For the test_mode feature selector, use setTestMode +// instead: the test selector rides in the high byte of the index field. +fn setFeature(feature: FeatureSelector, target: Target) Request { + return .{ + .request_type = .{ + .recipient = target.recipient(), + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .set_feature, + .value = @intFromEnum(feature), + .index = target.index(), + .length = 0, + }; +} + +// Puts a hi-speed device into the given test mode: a SET_FEATURE request with the test_mode +// feature selector and the test selector in the high byte of the index field. +fn setTestMode(mode: TestMode) Request { + return .{ + .request_type = .{ + .recipient = .device, + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .set_feature, + .value = @intFromEnum(FeatureSelector.test_mode), + .index = @as(u16, @intFromEnum(mode)) << 8, + .length = 0, + }; +} + +// Assigns the device its bus address, moving it from the default state to the address +// state. The device does not answer at the new address until the status stage of this +// request completes. +fn setAddress(address: DeviceAddress) Request { + return .{ + .request_type = .{ + .recipient = .device, + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .set_address, + .value = @intFromEnum(address), + .index = 0, + .length = 0, + }; +} + +// Reads a descriptor from the device. +// - descriptor_index selects among descriptors of the same type, and is only used for +// configuration and string descriptors. +// - language_id selects the language of a string descriptor, and is zero otherwise. +// - length is the number of bytes to read; a device never returns more than length bytes, +// but may return less if the descriptor is shorter. +fn getDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request { + return .{ + .request_type = .{ + .recipient = .device, + .kind = .standard, + .direction = .device_to_host, + }, + .request_code = .get_descriptor, + .value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }), + .index = language_id, + .length = length, + }; +} + +// Updates an existing descriptor or adds a new one (optional; many devices do not support +// this request). The parameters mirror getDescriptor; the descriptor itself is sent in the +// DATA stage. +fn setDescriptor(kind: DescriptorType, descriptor_index: u8, language_id: u16, length: u16) Request { + return .{ + .request_type = .{ + .recipient = .device, + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .set_descriptor, + .value = @bitCast(Request.DescriptorValue{ .index = descriptor_index, .kind = kind }), + .index = language_id, + .length = length, + }; +} + +// Reads the currently active configuration: @enumFromInt the byte the device returns into a +// ConfigurationValue, which is none while the device is not configured. +fn getConfiguration() Request { + return .{ + .request_type = .{ + .recipient = .device, + .kind = .standard, + .direction = .device_to_host, + }, + .request_code = .get_configuration, + .value = 0, + .index = 0, + .length = 1, + }; +} + +// Selects the configuration with the given configuration_value (from +// ConfigurationDescriptor.configuration_value), moving the device from the address state to +// the configured state. Selecting none returns the device to the address state. +fn setConfiguration(configuration_value: ConfigurationValue) Request { + return .{ + .request_type = .{ + .recipient = .device, + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .set_configuration, + .value = @intFromEnum(configuration_value), + .index = 0, + .length = 0, + }; +} + +// Reads the alternate setting currently selected for the given interface: @enumFromInt the +// byte the device returns into an AlternateSetting. +fn getInterface(interface: InterfaceNumber) Request { + return .{ + .request_type = .{ + .recipient = .interface, + .kind = .standard, + .direction = .device_to_host, + }, + .request_code = .get_interface, + .value = 0, + .index = @intFromEnum(interface), + .length = 1, + }; +} + +// Selects an alternate setting (from InterfaceDescriptor.alternate_setting) for the given +// interface. +fn setInterface(interface: InterfaceNumber, alternate_setting: AlternateSetting) Request { + return .{ + .request_type = .{ + .recipient = .interface, + .kind = .standard, + .direction = .host_to_device, + }, + .request_code = .set_interface, + .value = @intFromEnum(alternate_setting), + .index = @intFromEnum(interface), + .length = 0, + }; +} + +// Reads the two-byte number of the frame in which the given isochronous endpoint's +// repeating pattern of transfers begins. +fn syncFrame(endpoint: Request.EndpointIndex) Request { + return .{ + .request_type = .{ + .recipient = .endpoint, + .kind = .standard, + .direction = .device_to_host, + }, + .request_code = .sync_frame, + .value = 0, + .index = @bitCast(endpoint), + .length = 2, + }; +} + +const DescriptorType = enum(u8) { + device = 1, + configuration = 2, + string = 3, + interface = 4, + endpoint = 5, + device_qualifier = 6, + other_speed_configuration = 7, + interface_power = 8, + _, +}; + +const DeviceDescriptor = extern struct { + // Size of this descriptor in bytes + length: u8, + // DEVICE Descriptor Type + descriptor_type: DescriptorType, + // USB Specification Release Number in Binary-Coded Decimal (i.e, 2.10 is expressed as 210h). + // Identifies the release of the USB Specification with with the device and its + // descriptors are compliant. + bcd_usb: u16 align(1), + // Class code (assigned by the USB-IF) + // - This field is reset to zero if each interface within a configuration specifies its own + // class information and the various interfaces operate independently. + // - A value of FFh in this field indicates the device class is vendor-specific. + device_class: u8, + // Subclass Code (assigned by the USB-IF) + // - The subclass code of a device is qualified by the class code of that device. + // - If device_class is reset to zero, then this field must also be reset to zero. + // - When device_class is not set to FFh, then all values for this field are reserved for + // assignment by the USB-IF. + device_subclass: u8, + // Protocol code (assigned by the USB-IF) + // - The protocol code of a device is qualified by both the class and subclass codes of + // that device. + // - A value of 00h in this field means that the device may specify class-specific + // protocols on an interface basis, though this is not a requirement. + // - If this field is set to FFh, then the device uses a vendor-specific protocol. + device_protocol: u8, + // Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options) + max_packet_size_0: u8, + // Vendor ID (assigned by the USB-IF) + vendor_id: u16 align(1), + // Product ID (assigned by the USB-IF) + product_id: u16 align(1), + // Device release number in binary-coded decimal + bcd_device: u16 align(1), + // Index of STRING descriptor describing manufacturer + manufacturer_index: StringIndex, + // Index of STRING descriptor describing product + product_index: StringIndex, + // Index of STRING descriptor describing the device's serial number + serial_number_index: StringIndex, + // Number of possible configurations + configuration_count: u8, +}; + +const DeviceQualifierDescriptor = extern struct { + // Size of this descriptor in bytes + length: u8, + // DEVICE_QUALIFIER Descriptor Type + descriptor_type: DescriptorType, + // USB Specification Release Number in Binary-Coded Decimal (i.e, 2.00 is expressed as 200h). + // Identifies the release of the USB Specification with with the device and its + // descriptors are compliant. This field must be at least 0200h. + bcd_usb: u16 align(1), + // Class code (assigned by the USB-IF) + device_class: u8, + // Subclass Code (assigned by the USB-IF) + device_subclass: u8, + // Protocol code (assigned by the USB-IF) + device_protocol: u8, + // Maximum packet size for endpoint zero (8, 16, 32, or 64 are the only valid options) + max_packet_size_0: u8, + // Number of possible configurations + configuration_count: u8, + // Reserved for future uses, must be zero. + reserved: u8, +}; + +const ConfigurationDescriptor = extern struct { + // Size of this descriptor in bytes + length: u8, + // CONFIGURATION Descriptor Type + descriptor_type: DescriptorType, + // The total combined length in bytes of all the descriptors returned with the request for + // this CONFIGURATION descriptor (including CONFIGURATION, INTERFACE, ENDPOINT, class- and + // vendor-specific descriptors). + total_length: u16 align(1), + // Number of interfaces supported by this configuration + interface_count: u8, + // Value which when used as an argument in the SET_CONFIGURATION request, causes the device + // to assume the configuration described by this descriptor. + configuration_value: ConfigurationValue, + // Index of STRING descriptor describing this configuration. + configuration_index: StringIndex, + // Configuration Characteristics + attributes: Attributes, + // Maximum power consumption of this device from the bus when fully operational and using + // this configuration. Expressed in units of 2mA (i.e., a value of 50 in this field + // indicates 100mA). + // - A device reports with the attributes field whether the configuration is bus- or + // self-powered, but the device status (retrieved with a GET_STATUS request) reports + // whether the device is currently self-powered. + // - If a device is disconnected from an external power source, it may not draw more + // power from the bus than specified in this field. + max_power: u8, + + // Configuration characteristics. Fields are declared least-significant first. + const Attributes = packed struct(u8) { + // Reserved, reset to zero (D4...0) + reserved: u5, + // Whether Remote Wakeup is supported by this configuration (D5) + remote_wakeup: bool, + // Self-Powered (D6) + // - false: Device runs on power supplied by the bus + // - true: Device provides a local power source; if max_power is non-zero, the + // device also may use bus power. + self_powered: bool, + // Reserved, must be set to one for historical reasons (D7) + reserved_one: u1, + }; +}; + +// This descriptor describes the configuration of a high-speed device if it were operating at +// its alternative speed. The structure of the OTHER_SPEED_CONFIGURATION is identical to that +// of the CONFIGURATION descriptor; the only difference is that the descriptor_type field +// reflects that the descriptor is an OTHER_SPEED_CONFIGURATION descriptor. +const OtherSpeedConfigurationDescriptor = ConfigurationDescriptor; + +const InterfaceDescriptor = extern struct { + // Size of this descriptor in bytes + length: u8, + // INTERFACE Descriptor Type + descriptor_type: DescriptorType, + // Number of this interface. Zero-based value which identifies the index of this interface + // in the array of interfaces supported within a configuration. + interface_number: InterfaceNumber, + // Value used to select the alternate settings described by this INTERFACE descriptor for + // the interface with the interface_number in the previous field. This value is zero if + // this descriptor describes the default settings for a particular interface. + alternate_setting: AlternateSetting, + // Number of endpoints used by this interface, not including endpoint zero. + endpoint_count: u8, + // Class code (assigned by the USB-IF) + // - A value of zero here is reserved for future standardization. + // - If this value is FFh, the interface class is vendor-specific. + // - All other values are reserved for assignment by the USB-IF. + interface_class: u8, + // Subclass code (assigned by the USB-IF) + // - The subclass code in this field is qualified by the value of the interface_class + // field. + // - If interface_class is reset to zero, then this field must also be reset to zero. + // - If interface_class is not set to the value of FFh, then all values of this field are + // reserved for assignment by the USB-IF. + interface_subclass: u8, + // Protocol code (assigned by the USB-IF) + // - The protocol code in this field is qualified by the values of the interface_class + // and interface_subclass fields. + // - If an interface supports class-specific requests, then this field identifies the + // protocols that the device uses as defined by the specifications of the device class. + // - If this field is reset to zero, then the device does not use a class-specific + // protocol on this interface. + // - If this field is set to FFh, then the device uses a vendor-specific protocol on + // this interface. + interface_protocol: u8, + // Index of STRING descriptor describing this interface + interface_index: StringIndex, +}; + +const EndpointDescriptor = extern struct { + // Size of this descriptor in bytes + length: u8, + // ENDPOINT Descriptor Type + descriptor_type: DescriptorType, + // The address of the endpoint on the USB device described by this descriptor + endpoint_address: Address, + // The endpoint's attributes + attributes: Attributes, + // Maximum packet size that this endpoint is capable of sending or receiving. For + // isochronous endpoints, this value is used to reserve bus time; the pipe, however, may + // not always use all of the reserved bus time. + max_packet_size: MaxPacketSize align(1), + // Interval for polling a device during a data transfer, expressed in units of microframes + // for high-speed devices, and frames for low- and full-speed devices. The exact meaning of + // the value in this field depends on the endpoint type and the operating speed of the + // device: + // - Full- and High-speed isochronous endpoints, and high-speed interrupt endpoints: + // This field must be in the range from 1 to 16, and is used to calculate the period + // as 2^(interval - 1). That is, a value of 4 calculates to 2^(4 - 1) = 2^3 = 8. + // - Full- and Low-speed interrupt endpoints: This field must be in the range from + // 1 to 255. + // - High-speed bulk and control OUT endpoints: This field must be in the range from + // 0 to 255, and specifies the maximum NAK rate of the endpoint. A value of zero + // indicates that the endpoint never NAKs; other values indicate at most 1 NAK each + // interval number of microframes. + interval: u8, + + // The address of an endpoint. Fields are declared least-significant first. + const Address = packed struct(u8) { + // Endpoint Number (D3...0) + number: EndpointNumber, + // Reserved, reset to zero (D6...4) + reserved: u3, + // Direction, ignored for control endpoints (D7) + direction: EndpointDirection, + }; + + // An endpoint's attributes. Fields are declared least-significant first. + const Attributes = packed struct(u8) { + // Transfer Type (D1...0) + transfer_type: TransferType, + // Synchronization Type; isochronous endpoints only, reserved and reset to zero for + // other endpoint types (D3...2) + synchronization: Synchronization, + // Usage Type; isochronous endpoints only, reserved and reset to zero for other + // endpoints (D5...4) + usage: Usage, + // Reserved, reset to zero (D7...6) + reserved: u2, + }; + + const TransferType = enum(u2) { + control = 0, + isochronous = 1, + bulk = 2, + interrupt = 3, + }; + + const Synchronization = enum(u2) { + none = 0, + asynchronous = 1, + adaptive = 2, + synchronous = 3, + }; + + const Usage = enum(u2) { + data = 0, + feedback = 1, + implicit_feedback_data = 2, + _, + }; + + // The maximum packet size of an endpoint. Fields are declared least-significant first. + const MaxPacketSize = packed struct(u16) { + // Maximum packet size in bytes (bits 10...0) + size: u11, + // Number of additional transaction opportunities per microframe, for high-speed + // isochronous and interrupt endpoints; reserved and reset to zero for other + // endpoints (bits 12...11) + additional_transactions: AdditionalTransactions, + // Reserved, must be reset to zero (bits 15...13) + reserved: u3, + }; + + const AdditionalTransactions = enum(u2) { + // None (1 transaction per microframe) + none = 0, + // 1 additional (2 transactions per microframe) + one = 1, + // 2 additional (3 transactions per microframe) + two = 2, + _, + }; +}; + +// A STRING descriptor at index zero returns the list of LANGID codes supported by the +// device; all other indices return a Unicode string. Both forms start with this two-byte +// header, followed by the variable-length payload: +// - index 0: an array of two-byte LANGID codes (wLangID[0] through wLangID[x]) +// - other indices: a Unicode string of N bytes +const StringDescriptor = extern struct { + // Size of this descriptor in bytes + length: u8, + // STRING Descriptor Type + descriptor_type: DescriptorType, +}; + +const std = @import("std"); + +test "wire sizes and offsets match the specification" { + const expectEqual = std.testing.expectEqual; + + try expectEqual(8, @sizeOf(Request)); + try expectEqual(18, @sizeOf(DeviceDescriptor)); + try expectEqual(10, @sizeOf(DeviceQualifierDescriptor)); + try expectEqual(9, @sizeOf(ConfigurationDescriptor)); + try expectEqual(9, @sizeOf(InterfaceDescriptor)); + try expectEqual(7, @sizeOf(EndpointDescriptor)); + try expectEqual(2, @sizeOf(StringDescriptor)); + + try expectEqual(2, @offsetOf(DeviceDescriptor, "bcd_usb")); + try expectEqual(8, @offsetOf(DeviceDescriptor, "vendor_id")); + try expectEqual(17, @offsetOf(DeviceDescriptor, "configuration_count")); + try expectEqual(2, @offsetOf(ConfigurationDescriptor, "total_length")); + try expectEqual(4, @offsetOf(EndpointDescriptor, "max_packet_size")); +} + +test "bitmap packings match the specification" { + const expectEqual = std.testing.expectEqual; + const expect = std.testing.expect; + + // bmRequestType for GET_DESCRIPTOR: device-to-host | standard | device = 80h + const request_type = RequestType{ + .recipient = .device, + .kind = .standard, + .direction = .device_to_host, + }; + try expectEqual(0x80, @as(u8, @bitCast(request_type))); + + // wValue for GET_DESCRIPTOR(CONFIGURATION, index 0) = 0200h + const descriptor_value = Request.DescriptorValue{ .kind = .configuration }; + try expectEqual(0x0200, @as(u16, @bitCast(descriptor_value))); + + // wIndex for the IN endpoint 1 = 0081h + const endpoint_index = Request.EndpointIndex{ .number = @enumFromInt(1), .direction = .in }; + try expectEqual(0x0081, @as(u16, @bitCast(endpoint_index))); + + // Endpoint address 81h = IN endpoint 1 + const address: EndpointDescriptor.Address = @bitCast(@as(u8, 0x81)); + try expectEqual(1, @intFromEnum(address.number)); + try expectEqual(.in, address.direction); + + // Endpoint attributes 03h = interrupt transfer + const attributes: EndpointDescriptor.Attributes = @bitCast(@as(u8, 0x03)); + try expectEqual(.interrupt, attributes.transfer_type); + + // wMaxPacketSize 0008h = 8 bytes, no additional transactions + const max_packet_size: EndpointDescriptor.MaxPacketSize = @bitCast(@as(u16, 0x0008)); + try expectEqual(8, max_packet_size.size); + try expectEqual(.none, max_packet_size.additional_transactions); + + // Configuration attributes C0h = self-powered, with the historical D7 bit set + const configuration_attributes: ConfigurationDescriptor.Attributes = @bitCast(@as(u8, 0xC0)); + try expect(configuration_attributes.self_powered); + try expect(!configuration_attributes.remote_wakeup); + try expectEqual(1, configuration_attributes.reserved_one); + + // GET_STATUS words: device 0001h = self-powered; endpoint 0001h = halted + const device_status: DeviceStatus = @bitCast(@as(u16, 0x0001)); + try expect(device_status.self_powered and !device_status.remote_wakeup); + const endpoint_status: EndpointStatus = @bitCast(@as(u16, 0x0001)); + try expect(endpoint_status.halted); + + // DescriptorType is non-exhaustive: class-specific values (HID = 21h) pass through + const hid_type: DescriptorType = @enumFromInt(0x21); + try expectEqual(0x21, @intFromEnum(hid_type)); + try expect(hid_type != .device); +} + +fn expectRequestBytes(request: Request, expected: [8]u8) !void { + try std.testing.expectEqualSlices(u8, &expected, std.mem.asBytes(&request)); +} + +test "standard request constructors encode the specification's set-up packets" { + try expectRequestBytes(getStatus(.device), .{ 0x80, 0, 0, 0, 0, 0, 2, 0 }); + try expectRequestBytes(getStatus(.{ .interface = @enumFromInt(3) }), .{ 0x81, 0, 0, 0, 3, 0, 2, 0 }); + try expectRequestBytes(getStatus(.{ .endpoint = .{ .number = @enumFromInt(2), .direction = .in } }), .{ 0x82, 0, 0, 0, 0x82, 0, 2, 0 }); + try expectRequestBytes(clearFeature(.endpoint_halt, .{ .endpoint = .{ .number = @enumFromInt(1), .direction = .out } }), .{ 0x02, 1, 0, 0, 0x01, 0, 0, 0 }); + try expectRequestBytes(setFeature(.device_remote_wakeup, .device), .{ 0x00, 3, 1, 0, 0, 0, 0, 0 }); + try expectRequestBytes(setTestMode(.test_packet), .{ 0x00, 3, 2, 0, 0, 0x04, 0, 0 }); + try expectRequestBytes(setAddress(@enumFromInt(5)), .{ 0x00, 5, 5, 0, 0, 0, 0, 0 }); + try expectRequestBytes(getDescriptor(.device, 0, 0, 18), .{ 0x80, 6, 0, 1, 0, 0, 18, 0 }); + try expectRequestBytes(getDescriptor(.string, 2, 0x0409, 255), .{ 0x80, 6, 2, 3, 0x09, 0x04, 255, 0 }); + try expectRequestBytes(setDescriptor(.string, 2, 0x0409, 16), .{ 0x00, 7, 2, 3, 0x09, 0x04, 16, 0 }); + try expectRequestBytes(getConfiguration(), .{ 0x80, 8, 0, 0, 0, 0, 1, 0 }); + try expectRequestBytes(setConfiguration(@enumFromInt(1)), .{ 0x00, 9, 1, 0, 0, 0, 0, 0 }); + try expectRequestBytes(getInterface(@enumFromInt(2)), .{ 0x81, 10, 0, 0, 2, 0, 1, 0 }); + try expectRequestBytes(setInterface(@enumFromInt(2), @enumFromInt(1)), .{ 0x01, 11, 1, 0, 2, 0, 0, 0 }); + try expectRequestBytes(syncFrame(.{ .number = @enumFromInt(3), .direction = .in }), .{ 0x82, 12, 0, 0, 0x83, 0, 2, 0 }); +} diff --git a/system/devices/usb-ids.zig b/system/devices/usb-ids.zig new file mode 100644 index 0000000..4c7278e --- /dev/null +++ b/system/devices/usb-ids.zig @@ -0,0 +1,264 @@ +//! USB class-code decoding: turn the (class, subclass, protocol) triple a USB device or +//! interface reports in its descriptors into typed values. The device descriptor carries one +//! triple for the whole device, and each interface descriptor carries its own; a class code +//! of zero at the device level defers entirely to the interfaces. Subclass and protocol +//! codes are qualified by the class code — the same value means different things under +//! different classes — so there is no single SubClass or Protocol enum: each class with +//! spec-defined codes gets its own namespace below. Pure reference data (from the USB-IF +//! defined class codes; see https://www.usb.org/defined-class-codes) — no hardware access — +//! so it is shared by kernel discovery and any user-space tool (device naming, driver +//! matching). + +// Base class codes (assigned by the USB-IF). The comment on each value notes where the code +// may legally appear: in the device descriptor, in interface descriptors, or both. +const Class = enum(u8) { + // Use class information in the interface descriptors (device descriptor only). Each + // interface within a configuration specifies its own class information and the various + // interfaces operate independently. + per_interface = 0x00, + // Audio: speakers, microphones, sound cards (interface) + audio = 0x01, + // Communications and CDC control: modems, network adapters (both) + communications = 0x02, + // Human Interface Device: keyboards, mice, game controllers (interface) + hid = 0x03, + // Physical: force-feedback devices (interface) + physical = 0x05, + // Image: still-imaging cameras, scanners (interface) + image = 0x06, + // Printer (interface) + printer = 0x07, + // Mass storage: flash drives, external disks, card readers (interface) + mass_storage = 0x08, + // Hub (device descriptor only) + hub = 0x09, + // CDC-Data: the data interfaces paired with a communications control interface + // (interface) + cdc_data = 0x0A, + // Smart card readers (interface) + smart_card = 0x0B, + // Content security (interface) + content_security = 0x0D, + // Video: webcams (interface) + video = 0x0E, + // Personal healthcare devices (interface) + personal_healthcare = 0x0F, + // Audio/Video devices (interface) + audio_video = 0x10, + // Billboard: describes alternate modes a USB Type-C device supports (device descriptor + // only) + billboard = 0x11, + // USB Type-C bridge (interface) + type_c_bridge = 0x12, + // USB Bulk Display Protocol devices (interface) + bulk_display = 0x13, + // MCTP over USB protocol endpoint devices (interface) + mctp = 0x14, + // I3C devices (interface) + i3c = 0x3C, + // Diagnostic devices (both) + diagnostic = 0xDC, + // Wireless controllers: Bluetooth adapters (interface) + wireless_controller = 0xE0, + // Miscellaneous (both) + miscellaneous = 0xEF, + // Application-specific: firmware upgrade, IrDA bridges, test and measurement + // (interface) + application_specific = 0xFE, + // Vendor-specific (both) + vendor_specific = 0xFF, + _, +}; + +// Subclass and protocol codes qualified by Class.hub. Hubs have no subclass codes; the +// protocol distinguishes the hub's transaction-translator arrangement. +const hub = struct { + const Protocol = enum(u8) { + // Full-speed hub + full_speed = 0x00, + // Hi-speed hub with a single transaction translator + hi_speed_single_tt = 0x01, + // Hi-speed hub with multiple transaction translators + hi_speed_multi_tt = 0x02, + // SuperSpeed hub (USB 3) + super_speed = 0x03, + _, + }; +}; + +// Subclass and protocol codes qualified by Class.hid. +const hid = struct { + const SubClass = enum(u8) { + // No subclass + none = 0x00, + // Boot interface: the device also supports the simplified boot protocol, usable by + // firmware before a full HID report-descriptor parser is available + boot = 0x01, + _, + }; + + // Only meaningful when the subclass is boot + const Protocol = enum(u8) { + none = 0x00, + keyboard = 0x01, + mouse = 0x02, + _, + }; +}; + +// Subclass and protocol codes qualified by Class.mass_storage. The subclass identifies the +// command set the device understands; the protocol identifies the transport used to carry +// commands, data, and status over the bus. +const mass_storage = struct { + const SubClass = enum(u8) { + // SCSI command set not reported; de facto, treat as scsi + not_reported = 0x00, + // Reduced Block Commands: typically flash devices + rbc = 0x01, + // MMC-5 (ATAPI): CD and DVD drives + atapi = 0x02, + // QIC-157 tape drives (obsolete) + qic_157 = 0x03, + // UFI: floppy disk drives + ufi = 0x04, + // SFF-8070i (obsolete) + sff_8070i = 0x05, + // Transparent SCSI command set: the common case for flash drives and disks + scsi = 0x06, + // LSD FS: negotiated access to large storage devices + lsd_fs = 0x07, + // IEEE 1667 + ieee_1667 = 0x08, + // Vendor-specific + vendor_specific = 0xFF, + _, + }; + + const Protocol = enum(u8) { + // Control/Bulk/Interrupt with command completion interrupt + cbi_completion_interrupt = 0x00, + // Control/Bulk/Interrupt without command completion interrupt + cbi = 0x01, + // Bulk-only transport: the common case for flash drives and disks + bulk_only = 0x50, + // USB attached SCSI + uas = 0x62, + // Vendor-specific + vendor_specific = 0xFF, + _, + }; +}; + +// Subclass and protocol codes qualified by Class.communications (CDC). The protocol codes +// are model-specific; the useful invariant is the subclass, which selects the control model +// the interface implements. +const communications = struct { + const SubClass = enum(u8) { + // Direct line control model + direct_line = 0x01, + // Abstract control model: USB modems and serial adapters + abstract_control = 0x02, + // Telephone control model + telephone = 0x03, + // Multi-channel control model + multi_channel = 0x04, + // CAPI control model + capi = 0x05, + // Ethernet networking control model + ethernet = 0x06, + // ATM networking control model + atm = 0x07, + // Wireless handset control model + wireless_handset = 0x08, + // Device management + device_management = 0x09, + // Mobile direct line model + mobile_direct_line = 0x0A, + // OBEX + obex = 0x0B, + // Ethernet emulation model + ethernet_emulation = 0x0C, + // Network control model + network_control = 0x0D, + _, + }; +}; + +// Subclass and protocol codes qualified by Class.wireless_controller. +const wireless_controller = struct { + const SubClass = enum(u8) { + // Radio frequency controllers + radio_frequency = 0x01, + _, + }; + + // Only meaningful when the subclass is radio_frequency + const Protocol = enum(u8) { + // Bluetooth programming interface + bluetooth = 0x01, + // Ultra-wideband radio control + ultra_wideband = 0x02, + // Remote NDIS + remote_ndis = 0x03, + // Bluetooth AMP controller + bluetooth_amp = 0x04, + _, + }; +}; + +// Subclass and protocol codes qualified by Class.miscellaneous. +const miscellaneous = struct { + const SubClass = enum(u8) { + // Common class + common = 0x02, + _, + }; + + // Only meaningful when the subclass is common + const Protocol = enum(u8) { + // Interface association descriptor: at the device level, announces that the + // configuration groups interfaces into functions with IADs + interface_association = 0x01, + _, + }; +}; + +// Subclass and protocol codes qualified by Class.application_specific. +const application_specific = struct { + const SubClass = enum(u8) { + // Device firmware upgrade + firmware_upgrade = 0x01, + // IrDA bridge + irda_bridge = 0x02, + // Test and measurement + test_and_measurement = 0x03, + _, + }; +}; + +test "class codes match the USB-IF assignments" { + const std = @import("std"); + const expectEqual = std.testing.expectEqual; + + try expectEqual(0x03, @intFromEnum(Class.hid)); + try expectEqual(0x09, @intFromEnum(Class.hub)); + try expectEqual(0xFF, @intFromEnum(Class.vendor_specific)); + + // A typical flash drive: mass storage, transparent SCSI, bulk-only transport. + try expectEqual(0x06, @intFromEnum(mass_storage.SubClass.scsi)); + try expectEqual(0x50, @intFromEnum(mass_storage.Protocol.bulk_only)); + + // A boot keyboard: HID, boot subclass, keyboard protocol. + try expectEqual(0x01, @intFromEnum(hid.SubClass.boot)); + try expectEqual(0x01, @intFromEnum(hid.Protocol.keyboard)); + + // Class codes are non-exhaustive: unlisted values pass through undamaged. + const unknown: Class = @enumFromInt(0x42); + try expectEqual(0x42, @intFromEnum(unknown)); + + _ = hub.Protocol.hi_speed_multi_tt; + _ = communications.SubClass.abstract_control; + _ = wireless_controller.Protocol.bluetooth; + _ = miscellaneous.Protocol.interface_association; + _ = application_specific.SubClass.firmware_upgrade; +} diff --git a/system/drivers/bus/bus.zig b/system/drivers/bus/bus.zig index 07e9d33..9203290 100644 --- a/system/drivers/bus/bus.zig +++ b/system/drivers/bus/bus.zig @@ -100,6 +100,7 @@ pub fn main() void { while (n < n_children) : (n += 1) { var child = std.mem.zeroes(device.DeviceDescriptor); child.class = @intFromEnum(device.DeviceClass.timer); + child.pci_class = device.no_pci_class; child.hid_len = 6; child.hid[0..6].* = "hpet-t".*; child.resource_count = 1; diff --git a/system/drivers/usb-xhci-bus/usb-xhci-bus.zig b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig new file mode 100644 index 0000000..f13694a --- /dev/null +++ b/system/drivers/usb-xhci-bus/usb-xhci-bus.zig @@ -0,0 +1,71 @@ +//! /system/drivers/usb-xhci-bus — the xHCI (USB 3) host-controller bus driver. +//! The device manager spawns **one instance per controller** it discovers (a machine +//! can carry several), passing the controller's device-tree id as argv[1]; this +//! instance claims that device and no other, so multiple instances never fight over +//! hardware. This increment proves the plumbing: parse the id, claim the controller, +//! and report its MMIO window. The next increments map the registers and bring the +//! controller up (reset, rings, port scan), then enumerate the USB devices on the +//! bus with the usb-abi request builders and publish each with `device_register`. + +const std = @import("std"); +const runtime = @import("runtime"); +const device = runtime.device; + +/// Format one whole log line and emit it in a single `debug_write`, so concurrent +/// instances (one per controller) can never interleave mid-line. +fn writeLine(comptime fmt: []const u8, arguments: anytype) void { + var line: [128]u8 = undefined; + _ = runtime.system.write(std.fmt.bufPrint(&line, fmt, arguments) catch return); +} + +pub fn main(init: runtime.process.Init) void { + const argument = init.arguments.get(1) orelse { + _ = runtime.system.write("usb-xhci-bus: missing controller device id (argv[1])\n"); + return; + }; + const controller_id = std.fmt.parseInt(u64, argument, 10) catch { + writeLine("usb-xhci-bus: malformed controller device id '{s}'\n", .{argument}); + return; + }; + + if (!device.claim(controller_id)) { + writeLine("usb-xhci-bus: unable to claim controller device {d}\n", .{controller_id}); + return; + } + + // Fetch our own descriptor back for the controller's resources. + const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { + _ = runtime.system.write("usb-xhci-bus: out of memory\n"); + return; + }; + const total = device.enumerate(buffer); + const descriptor = for (buffer[0..@min(total, buffer.len)]) |d| { + if (d.id == controller_id) break d; + } else { + writeLine("usb-xhci-bus: device {d} not in the device tree\n", .{controller_id}); + return; + }; + + // The controller's operational registers live behind BAR0, enumerated as the + // device's first memory resource. + const register_window = for (descriptor.resources[0..@intCast(descriptor.resource_count)]) |resource| { + if (resource.kind == @intFromEnum(device.ResourceKind.memory)) break resource; + } else { + writeLine("usb-xhci-bus: controller device {d} has no MMIO window\n", .{controller_id}); + return; + }; + writeLine("usb-xhci-bus: claimed controller device {d} (registers at 0x{x}, {d} bytes)\n", .{ + controller_id, + register_window.start, + register_window.len, + }); + + // Controller bring-up (map the window, reset, rings, port scan) is the next + // increment; stay resident as the bus's supervisor in the meantime. + while (true) runtime.system.sleep(1000); +} + +pub const panic = runtime.panic; +comptime { + _ = &runtime.start._start; // pull the runtime entry shim into the image +} diff --git a/system/drivers/usb-xhci-bus/usb-xhci-libary.zig b/system/drivers/usb-xhci-bus/usb-xhci-libary.zig new file mode 100644 index 0000000..e69de29 diff --git a/system/kernel/devices-broker.zig b/system/kernel/devices-broker.zig index 6422e5c..49b90f9 100644 --- a/system/kernel/devices-broker.zig +++ b/system/kernel/devices-broker.zig @@ -66,6 +66,7 @@ fn record(node: *platform.Device, parent_id: u64) u64 { d.id = count; d.parent = parent_id; d.class = @intFromEnum(node.class); + d.pci_class = if (node.ids.pci_class) |code| code else device_abi.no_pci_class; const h = node.hid(); d.hid_len = @min(h.len, d.hid.len); @memcpy(d.hid[0..d.hid_len], h[0..d.hid_len]); @@ -170,6 +171,7 @@ pub fn register(parent_id: u64, owner: u32, descriptor: *const device_abi.Device d.id = count; d.parent = parent_id; d.class = descriptor.class; + d.pci_class = descriptor.pci_class; d.hid_len = @min(descriptor.hid_len, d.hid.len); @memcpy(d.hid[0..@intCast(d.hid_len)], descriptor.hid[0..@intCast(d.hid_len)]); d.resource_count = descriptor.resource_count; diff --git a/system/services/device-manager/device-manager.zig b/system/services/device-manager/device-manager.zig index d400407..82310c3 100644 --- a/system/services/device-manager/device-manager.zig +++ b/system/services/device-manager/device-manager.zig @@ -42,6 +42,36 @@ fn driverFor(d: device.DeviceDescriptor) ?[]const u8 { }; } +/// The PCI class/subclass/prog-IF triple of an xHCI (USB 3) host controller: +/// Serial Bus Controller (0x0C) / USB Controller (0x03) / XHCI (0x30) — the names +/// pci-class.zig decodes. +const xhci_pci_class: u64 = 0x0C_03_30; + +/// The bus driver that serves a PCI function, or null. Unlike the singleton drivers +/// in `driverFor`, a machine can carry several identical controllers — so the caller +/// spawns one driver instance *per device*, passing the device id as argv[1] for the +/// instance to claim. +fn pciDriverFor(d: device.DeviceDescriptor) ?[]const u8 { + if (d.class != @intFromEnum(device.DeviceClass.pci_device)) return null; + return switch (d.pci_class) { + xhci_pci_class => "usb-xhci-bus", + else => null, + }; +} + +/// Spawn one instance of `driver_name` to serve the specific device `id` — the id +/// arrives as argv[1]. No isProcessRunning gate here: the name alone cannot tell two +/// instances apart, and this manager is the sole spawner of drivers. +fn spawnForDevice(driver_name: []const u8, id: u64) void { + var text: [20]u8 = undefined; + const id_text = std.fmt.bufPrint(&text, "{d}", .{id}) catch return; + if (system.spawnWithArguments(driver_name, &.{id_text}) != null) { + writeLine("device-manager: spawned {s} for device {d}\n", .{ driver_name, id }); + } else { + writeLine("device-manager: failed to spawn {s} for device {d}\n", .{ driver_name, id }); + } +} + pub fn main() void { // Enumerate into a heap buffer (too big for the one-page user stack). const buffer = runtime.allocator().alloc(device.DeviceDescriptor, 64) catch { @@ -53,6 +83,11 @@ pub fn main() void { var matched: usize = 0; for (buffer[0..count]) |descriptor| { + if (pciDriverFor(descriptor)) |driver_name| { + matched += 1; + spawnForDevice(driver_name, descriptor.id); + continue; + } const driver_name = driverFor(descriptor) orelse continue; matched += 1; if (!system.isProcessRunning(driver_name)) {