diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index dbd24c583..b88be0f80 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -1,683 +1,401 @@ -//! Abstract USB device implementation -//! -//! This can be used to setup a USB device. -//! -//! ## Usage -//! -//! 1. Define the functions (`pub const F = struct { ... }`) required by `Usb()` (see below) -//! 2. Call `pub const device = Usb(F)` -//! 3. Define the device configuration (DeviceConfiguration) -//! 4. Initialize the device in main by calling `usb.init_clk()` and `usb.init_device(device_conf)` -//! 5. Call `usb.task()` within the main loop - const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; +const enumFromInt = std.meta.intToEnum; -/// USB primitive types -pub const types = @import("usb/types.zig"); -/// USB Human Interface Device (HID) -pub const hid = @import("usb/hid.zig"); pub const cdc = @import("usb/cdc.zig"); -pub const vendor = @import("usb/vendor.zig"); -pub const utils = @import("usb/utils.zig"); -pub const templates = @import("usb/templates.zig"); - -const DescType = types.DescType; -const FeatureSelector = types.FeatureSelector; -const Dir = types.Dir; -const Endpoint = types.Endpoint; -const SetupRequest = types.SetupRequest; -const BosConfig = utils.BosConfig; - -/// Create a USB device -/// -/// # Arguments -/// -/// This is a abstract USB device implementation that requires a handful of functions -/// to work correctly: -/// -/// * `usb_init_clk() void` - Initialize the USB clock -/// * `usb_init_device(*DeviceConfiguration) - Initialize the USB device controller (e.g. enable interrupts, etc.) -/// * `usb_start_tx(*EndpointConfiguration, []const u8)` - Transmit the given bytes over the specified endpoint -/// * `usb_start_rx(*usb.EndpointConfiguration, n: usize)` - Receive n bytes over the specified endpoint -/// * `get_interrupts() InterruptStatus` - Return which interrupts haven't been handled yet -/// * `get_setup_packet() SetupPacket` - Return the USB setup packet received (called if SetupReq received). Make sure to clear the status flag yourself! -/// * `bus_reset() void` - Called on a bus reset interrupt -/// * `set_address(addr: u7) void` - Set the given address -/// * `get_EPBIter(*const DeviceConfiguration) EPBIter` - Return an endpoint buffer iterator. Each call to next returns an unhandeled endpoint buffer with data. How next is implemented depends on the system. -/// The functions must be grouped under the same name space and passed to the fuction at compile time. -/// The functions will be accessible to the user through the `callbacks` field. -pub fn Usb(comptime f: anytype) type { - return struct { - /// The usb configuration set - var usb_config: ?*DeviceConfiguration = null; - /// The clock has been initialized [Y/n] - var clk_init: bool = false; - var itf_to_drv: [f.cfg_max_interfaces_count]u8 = @splat(0); - var ep_to_drv: [f.cfg_max_endpoints_count][2]u8 = @splat(@splat(0)); - pub const max_packet_size = if (f.high_speed) 512 else 64; - const drvid_invalid = 0xff; - - /// The callbacks passed provided by the caller - pub const callbacks = f; - - // We'll keep some state in Plain Old Static Local Variables: - const S = struct { - var debug_mode = false; - // When the host gives us a new address, we can't just slap it into - // registers right away, because we have to do an acknowledgement step using - // our _old_ address. - var new_address: ?u8 = null; - // 0 - no config set - var cfg_num: u16 = 0; - // Flag recording whether the host has configured us with a - // `SetConfiguration` message. - var configured = false; - // Flag recording whether we've set up buffer transfers after being - // configured. - var started = false; - // Some scratch space that we'll use for things like preparing string - // descriptors for transmission. - var tmp: [128]u8 = @splat(0); - // Keeps track of sent data from tmp buffer - var buffer_reader = BufferReader{ .buffer = &.{} }; - // Last setup packet request - var setup_packet: types.SetupPacket = undefined; - // Class driver associated with last setup request if any - var driver: ?*types.UsbClassDriver = null; - }; - - // Command endpoint utilities - const CmdEndpoint = struct { - /// Command response utility function that can split long data in multiple packets - fn send_cmd_response(data: []const u8, expected_max_length: u16) void { - S.buffer_reader = BufferReader{ .buffer = data[0..@min(data.len, expected_max_length)] }; - const data_chunk = S.buffer_reader.try_peek(64); - - if (data_chunk.len > 0) { - f.usb_start_tx(Endpoint.EP0_IN_ADDR, data_chunk); - } - } +pub const descriptor = @import("usb/descriptor.zig"); +pub const hid = @import("usb/hid.zig"); +pub const types = @import("usb/types.zig"); - fn send_cmd_ack() void { - f.usb_start_tx(Endpoint.EP0_IN_ADDR, &.{}); - } - }; +test "tests" { + _ = cdc; + _ = descriptor; + _ = hid; + _ = types; +} - /// Initialize the USB clock - pub fn init_clk() void { - f.usb_init_clk(); - clk_init = true; +const EpNum = types.Endpoint.Num; + +pub const DeviceInterface = struct { + pub const Options = union(types.Dir) { + Out: struct { + data: []u8, + listen: ?u16, + }, + In: struct { + data: []const u8, + flush: bool, + }, + + pub fn len(this: @This()) usize { + return switch (this) { + .Out => |x| x.data.len, + .In => |x| x.data.len, + }; } + }; - /// Initialize the usb device using the given configuration - /// - /// This function will return an error if the clock hasn't been initialized. - pub fn init_device(device_config: *DeviceConfiguration) !void { - if (!clk_init) return error.UninitializedClock; + ptr: *anyopaque, + transfer: *const fn (ptr: *anyopaque, opts: Options, ep_num: EpNum, df: bool) ?u16, + + /// Sends all bytes from `data` to the host in a single packet. Returns true if data was sent. + /// If `data` is empty, an ACK is sent. + /// If the transmit buffer was not empty, the old data is flushed and `false` is returned. + /// Panics if `data` is too long to be sent through this endpoint. + pub fn write_exact(this: @This(), data: []const u8, ep_in: EpNum) bool { + if (this.transfer(this.ptr, .{ .In = .{ + .data = data, + .flush = true, + } }, ep_in, true)) |len| { + assert(len == data.len); + return true; + } else return false; + } - f.usb_init_device(device_config); - usb_config = device_config; + /// Writes bytes from `data` into the transmit buffer. + /// `data` must contain at least one buffer, which may be empty. + /// If this fills the transmit buffer or `flush` is true, the transmit buffer is submitted. + /// Returns the number of bytes written, or `null` if no space was available. + pub fn write_buffered(this: @This(), data: []const u8, ep_in: EpNum, flush: bool) ?u16 { + return this.transfer(this.ptr, .{ .In = .{ + .data = data, + .flush = flush, + } }, ep_in, false); + } - const device_interface = device(); - for (usb_config.?.drivers) |*driver| { - driver.init(device_interface); - } - } + /// Reads a full received packet into `data`, emptying the receive buffer. + /// Returns the number of bytes read, or `null` if no data was available. + /// If `listen` is not null, up to `listen` more bytes are requested from the host. + /// Panics if there was residual data in the receive buffer or the packet does not fit in `data`. + pub fn read_exact(this: @This(), data: []u8, ep_out: EpNum, listen: ?u16) ?u16 { + return this.transfer(this.ptr, .{ .Out = .{ + .data = data, + .listen = listen, + } }, ep_out, true); + } - fn device() types.UsbDevice { - return .{ - .fn_ready = device_ready, - .fn_control_transfer = device_control_transfer, - .fn_control_ack = device_control_ack, - .fn_endpoint_open = device_endpoint_open, - .fn_endpoint_transfer = device_endpoint_transfer, - }; - } + /// Reads data from the receive buffer into `data`. + /// `data` must contain at least one buffer, which may be empty. + /// Returns the number of bytes read, or `null` if no data was available. + /// If this empties the receive buffer, more data is requested. + pub fn read_buffered(this: @This(), data: []u8, ep_out: EpNum) ?u16 { + return this.transfer(this.ptr, .{ .Out = .{ + .data = data, + .listen = std.math.maxInt(u16), + } }, ep_out, false); + } +}; - fn device_ready() bool { - return S.started; - } +pub const Config = struct { + /// Manufacturer, product and serial number strings. + pub const DeviceStrings = struct { + manufacturer: []const u8, + product: []const u8, + serial: []const u8, + }; - fn device_control_transfer(setup: *const types.SetupPacket, data: []const u8) void { - CmdEndpoint.send_cmd_response(data, setup.length); - } + pub fn NameValue(T: type) type { + return struct { name: [:0]const u8, value: T }; + } - fn device_control_ack(_: *const types.SetupPacket) void { - CmdEndpoint.send_cmd_ack(); - } + pub const Driver = struct { + name: [:0]const u8, + Type: type, + endpoints: []const NameValue(EpNum), + strings: []const NameValue([]const u8), + }; - fn device_endpoint_open(ep_desc: []const u8) void { - const ep_addr = BosConfig.get_data_u8(ep_desc, 2); - const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); - const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); + /// Bit set of device attributes. + attributes: descriptor.Configuration.Attributes, + /// Maximum device current consumption.. + max_current: descriptor.Configuration.MaxCurrent = .from_ma(100), + /// String descriptor language. + language: descriptor.Language = .English, + /// Device version number as Binary Coded Decimal. + bcd_device: u16 = 0x01_00, + /// Class, subclass and protocol of device. + device_triple: descriptor.Device.DeviceTriple = .unspecified, + /// Manufacturer, product and serial number strings. Leave at null for device default. + strings: ?DeviceStrings = null, + /// Vendor ID. Leave at null for device default. + vid: ?u16 = null, + /// Product ID. Leave at null for device default. + pid: ?u16 = null, + /// Device version number as Binary Coded Decimal. Leave at null for device default. + bcd_usb: ?u16 = null, + /// Maximum endpoint size. Leave at null for device default. + max_transfer_size: ?comptime_int = null, + // Eventually the fields below could be in an array to support multiple drivers. + drivers: []const Driver, +}; - f.endpoint_open(ep_addr, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk); - } +/// Information about a usb driver, each is required to return this struct via `.info()`. +pub fn DriverInfo(T: type, Descriptors: type) type { + return struct { + /// All configuration descriptors concatenated in an extern struct. + descriptors: Descriptors, + /// Handlers of setup packets for each used interface. + interface_handlers: []const struct { itf: u8, func: fn (*T, *const types.SetupPacket) ?[]const u8 }, + }; +} + +pub const PacketUnhandled = error{UsbPacketUnhandled}; +pub const ACK = ""; +pub const NAK = null; - fn device_endpoint_transfer(ep_addr: u8, data: []const u8) void { - if (Endpoint.dir_from_address(ep_addr) == .In) { - f.usb_start_tx(ep_addr, data); - } else { - f.usb_start_rx(ep_addr, max_packet_size); +/// This is an abstract USB device controller that gets events from +/// a usb device and distributes them to usb drivers, depending on config. +pub fn Controller(comptime config: Config) type { + return struct { + /// Driver data is stored within the controller. + const DriverData = blk: { + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.drivers) |drv| { + fields = fields ++ .{Field{ + .name = drv.name, + .type = drv.Type, + .default_value_ptr = null, + .is_comptime = false, + .alignment = @alignOf(drv.Type), + }}; } - } + break :blk @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); + }; + + drivers: ?DriverData, + + /// String descriptors. Each driver can add its own, and its `.init()` gets the list of IDs. + const string = blk: { + var desc: []const []const u8 = &.{&config.language.serialize()}; + + desc = desc ++ .{descriptor.string(config.strings.?.manufacturer)}; + desc = desc ++ .{descriptor.string(config.strings.?.product)}; + desc = desc ++ .{descriptor.string(config.strings.?.serial)}; + + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.drivers) |drv| { + var fields_s: []const Field = &.{}; + + for (config.drivers[0].strings) |fld| { + const id: u8 = desc.len; + fields_s = fields_s ++ .{Field{ + .name = fld.name, + .type = @TypeOf(id), + .default_value_ptr = @ptrCast(&id), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(id)), + }}; + desc = desc ++ .{descriptor.string(fld.value)}; + } - fn get_driver(drv_idx: u8) ?*types.UsbClassDriver { - if (drv_idx == drvid_invalid) { - return null; + const DrvStrings = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields_s, + .decls = &.{}, + .is_tuple = false, + } }); + + fields = fields ++ .{Field{ + .name = drv.name, + .type = DrvStrings, + .default_value_ptr = @ptrCast(&DrvStrings{}), + .is_comptime = true, + .alignment = @alignOf(DrvStrings), + }}; } - return &usb_config.?.drivers[drv_idx]; - } + break :blk .{ + .descriptors = desc, + .ids = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }), + }; + }; - fn get_setup_packet() types.SetupPacket { - const setup = f.get_setup_packet(); - S.setup_packet = setup; - S.driver = null; - return setup; - } + /// Used descriptors and endpoint handlers. + const driver_info = blk: { + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; - fn configuration_reset() void { - @memset(&itf_to_drv, drvid_invalid); - @memset(&ep_to_drv, .{ drvid_invalid, drvid_invalid }); - } + var next_interface = 0; - /// Usb task function meant to be executed in regular intervals after - /// initializing the device. - /// - /// This function will return an error if the device hasn't been initialized. - pub fn task(debug: bool) !void { - if (usb_config == null) return error.UninitializedDevice; - - S.debug_mode = debug; - - // Device Specific Request - const DeviceRequestProcessor = struct { - fn process_setup_request(setup: *const types.SetupPacket) !void { - switch (setup.request_type.type) { - .Class => { - //const itfIndex = setup.index & 0x00ff; - std.log.info("Device.Class", .{}); - }, - .Standard => { - const req = SetupRequest.from_u8(setup.request); - if (req == null) return; - switch (req.?) { - .SetAddress => { - S.new_address = @as(u8, @intCast(setup.value & 0xff)); - CmdEndpoint.send_cmd_ack(); - if (S.debug_mode) std.log.info(" SetAddress: {}", .{S.new_address.?}); - }, - .SetConfiguration => { - if (S.debug_mode) std.log.info(" SetConfiguration", .{}); - const cfg_num = setup.value; - if (S.cfg_num != cfg_num) { - if (S.cfg_num > 0) { - configuration_reset(); - } - - if (cfg_num > 0) { - try process_set_config(cfg_num - 1); - // TODO: call mount callback if any - } else { - // TODO: call umount callback if any - } - } - S.cfg_num = cfg_num; - S.configured = true; - CmdEndpoint.send_cmd_ack(); - }, - .GetDescriptor => { - const descriptor_type = DescType.from_u8(@intCast(setup.value >> 8)); - if (descriptor_type) |dt| { - try process_get_descriptor(setup, dt); - } - }, - .SetFeature => { - const feature = FeatureSelector.from_u8(@intCast(setup.value >> 8)); - if (feature) |feat| { - switch (feat) { - .DeviceRemoteWakeup, .EndpointHalt => CmdEndpoint.send_cmd_ack(), - // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 - .TestMode => {}, - } - } - }, - } - }, - else => {}, - } + for (config.drivers) |drv| { + var ep_fields: []const Field = &.{}; + + for (drv.endpoints) |fld| { + ep_fields = ep_fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(fld.value), + .default_value_ptr = @ptrCast(&fld.value), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(fld.value)), + }}; } - fn process_get_descriptor(setup: *const types.SetupPacket, descriptor_type: DescType) !void { - switch (descriptor_type) { - .Device => { - if (S.debug_mode) std.log.info(" Device", .{}); - - var bw = BufferWriter{ .buffer = &S.tmp }; - try bw.write(&usb_config.?.device_descriptor.serialize()); - - CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); - }, - .Config => { - if (S.debug_mode) std.log.info(" Config", .{}); - - var bw = BufferWriter{ .buffer = &S.tmp }; - try bw.write(usb_config.?.config_descriptor); - - CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); - }, - .String => { - if (S.debug_mode) std.log.info(" String", .{}); - // String descriptor index is in bottom 8 bits of - // `value`. - const i: usize = @intCast(setup.value & 0xff); - const bytes = StringBlk: { - if (i == 0) { - // Special index 0 requests the language - // descriptor. - break :StringBlk usb_config.?.lang_descriptor; - } else { - // Otherwise, set up one of our strings. - const s = usb_config.?.descriptor_strings[i - 1]; - const len = 2 + s.len; - - var wb = BufferWriter{ .buffer = &S.tmp }; - try wb.write_int(u8, @intCast(len)); - try wb.write_int(u8, 0x03); - try wb.write(s); - - break :StringBlk wb.get_written_slice(); - } - }; - - CmdEndpoint.send_cmd_response(bytes, setup.length); - }, - .Interface => { - if (S.debug_mode) std.log.info(" Interface", .{}); - }, - .Endpoint => { - if (S.debug_mode) std.log.info(" Endpoint", .{}); - }, - .DeviceQualifier => { - if (S.debug_mode) std.log.info(" DeviceQualifier", .{}); - // We will just copy parts of the DeviceDescriptor because - // the DeviceQualifierDescriptor can be seen as a subset. - const dqd = types.DeviceQualifierDescriptor{ - .bcd_usb = usb_config.?.device_descriptor.bcd_usb, - .device_class = usb_config.?.device_descriptor.device_class, - .device_subclass = usb_config.?.device_descriptor.device_subclass, - .device_protocol = usb_config.?.device_descriptor.device_protocol, - .max_packet_size0 = usb_config.?.device_descriptor.max_packet_size0, - .num_configurations = usb_config.?.device_descriptor.num_configurations, - }; - - var bw = BufferWriter{ .buffer = &S.tmp }; - try bw.write(&dqd.serialize()); - - CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); - }, - else => {}, - } + const ep_struct = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = ep_fields, + .decls = &.{}, + .is_tuple = false, + } }); + + const string_ids = @field(string.ids{}, drv.name); + const info = drv.Type.info(next_interface, string_ids, ep_struct{}); + fields = fields ++ .{Field{ + .name = drv.name, + .type = @TypeOf(info), + .default_value_ptr = @ptrCast(&info), + .is_comptime = false, + .alignment = @alignOf(@TypeOf(info)), + }}; + for (@typeInfo(@TypeOf(info.descriptors)).@"struct".fields) |fld| { + if (fld.type == descriptor.Interface) + next_interface += 1; } + } - fn process_set_config(_: u16) !void { - // TODO: we support just one config for now so ignore config index - const bos_cfg = usb_config.?.config_descriptor; + break :blk @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }){}; + }; - var curr_bos_cfg = bos_cfg; - var curr_drv_idx: u8 = 0; + pub const init: @This() = .{ .drivers = null }; - if (BosConfig.try_get_desc_as(types.ConfigurationDescriptor, curr_bos_cfg)) |_| { - curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); - } else { - // TODO - error - return; - } - - while (curr_bos_cfg.len > 0) : (curr_drv_idx += 1) { - var assoc_itf_count: u8 = 1; - // New class starts optionally from InterfaceAssociation followed by mandatory Interface - if (BosConfig.try_get_desc_as(types.InterfaceAssociationDescriptor, curr_bos_cfg)) |desc_assoc_itf| { - assoc_itf_count = desc_assoc_itf.interface_count; - curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); - } - - if (BosConfig.get_desc_type(curr_bos_cfg) != DescType.Interface) { - // TODO - error - return; - } - const desc_itf = BosConfig.get_desc_as(types.InterfaceDescriptor, curr_bos_cfg); - - var driver = usb_config.?.drivers[curr_drv_idx]; - const drv_cfg_len = try driver.open(curr_bos_cfg); - - for (0..assoc_itf_count) |itf_offset| { - const itf_num = desc_itf.interface_number + itf_offset; - itf_to_drv[itf_num] = curr_drv_idx; - } - - bind_endpoints_to_driver(curr_bos_cfg[0..drv_cfg_len], curr_drv_idx); - curr_bos_cfg = curr_bos_cfg[drv_cfg_len..]; - - // TODO: TMP solution - just 1 driver so quit while loop - return; - } - } + /// Deinitializes the drivers if the controller has been configured. + pub fn deinit(this: *@This()) void { + if (this.drivers) |*drivers| { + inline for (config.drivers) |drv| + @field(drivers, drv.name).deinit(); + } + this.* = .init; + } - fn bind_endpoints_to_driver(drv_bos_cfg: []const u8, drv_idx: u8) void { - var curr_bos_cfg = drv_bos_cfg; - while (curr_bos_cfg.len > 0) : ({ - curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); - }) { - if (BosConfig.try_get_desc_as(types.EndpointDescriptor, curr_bos_cfg)) |desc_ep| { - const ep_addr = desc_ep.endpoint_address; - ep_to_drv[Endpoint.num_from_address(ep_addr)][Endpoint.dir_from_address(ep_addr).as_number()] = drv_idx; - } - } - } + /// Called whenever a GET_DESCRIPTOR request is received. + pub fn get_descriptor(setup: *const types.SetupPacket) ?[]const u8 { + const device_descriptor: descriptor.Device = comptime .{ + .bcd_usb = .from(config.bcd_usb.?), + .device_triple = config.device_triple, + .max_packet_size0 = config.max_transfer_size.?, + .vendor = .from(config.vid.?), + .product = .from(config.pid.?), + .bcd_device = .from(config.bcd_device), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, }; - // Class/Interface Specific Request - const InterfaceRequestProcessor = struct { - fn process_setup_request(setup: *const types.SetupPacket) !void { - const itf: u8 = @intCast(setup.index & 0xFF); - var driver = get_driver(itf_to_drv[itf]); - if (driver == null) return; - S.driver = driver; + const config_descriptor = comptime blk: { + var ret: [:0]const u8 = ""; + var num_interfaces = 0; - if (driver.?.class_control(.Setup, setup) == false) { - // TODO + for (config.drivers) |drv| { + const info = @field(driver_info, drv.name); + for (@typeInfo(@TypeOf(info.descriptors)).@"struct".fields) |fld| { + if (fld.type == descriptor.Interface) + num_interfaces += 1; } + ret = ret ++ info.descriptors.serialize(); } - }; - // Endpoint Specific Request - const EndpointRequestProcessor = struct { - fn process_setup_request(_: *const types.SetupPacket) !void {} + break :blk (descriptor.Configuration{ + .total_length = .from(@sizeOf(descriptor.Configuration) + ret.len), + .num_interfaces = num_interfaces, + .configuration_value = 1, + .configuration_s = 0, + .attributes = config.attributes, + .max_current = config.max_current, + }).serialize() ++ ret; }; - // Check which interrupt flags are set. - const ints = f.get_interrupts(); + if (enumFromInt(descriptor.Type, setup.value >> 8)) |descriptor_type| { + const data: []const u8 = switch (descriptor_type) { + .Device => comptime &device_descriptor.serialize(), + .Configuration => config_descriptor, + .String => if (setup.value & 0xff < string.descriptors.len) + string.descriptors[setup.value & 0xff] + else + comptime descriptor.string(""), + .DeviceQualifier => comptime &device_descriptor.qualifier().serialize(), + else => return null, + }; + return data[0..@min(data.len, setup.length)]; + } else |_| return null; + } - // Setup request received? - if (ints.SetupReq) { - if (debug) std.log.info("setup req", .{}); + /// Called whenever a SET_CONFIGURATION request is received. + pub fn set_configuration(this: *@This(), device: anytype, setup: *const types.SetupPacket) bool { + if (setup.value == 0) { + this.deinit(); + return true; + } - const setup = get_setup_packet(); + if (setup.value != 1 or this.drivers != null) return true; - // Reset PID to 1 for EP0 IN. Every DATA packet we send in response - // to an IN on EP0 needs to use PID DATA1, and this line will ensure - // that. - // TODO - maybe it can be moved to f.get_setup_packet? - f.reset_ep0(); + // Driver data undefined, but present (not null). + this.drivers = @as(DriverData, undefined); - switch (setup.request_type.recipient) { - .Device => try DeviceRequestProcessor.process_setup_request(&setup), - .Interface => try InterfaceRequestProcessor.process_setup_request(&setup), - .Endpoint => try EndpointRequestProcessor.process_setup_request(&setup), - else => {}, - } - } + inline for (config.drivers) |drv| { + const descriptors = @field(driver_info, drv.name).descriptors; + const fields = @typeInfo(@TypeOf(descriptors)).@"struct".fields; - // Events on one or more buffers? (In practice, always one.) - if (ints.BuffStatus) { - if (debug) std.log.info("buff status", .{}); - var iter = f.get_EPBIter(usb_config.?); - - while (iter.next(&iter)) |epb| { - if (debug) std.log.info(" data: {any}", .{epb.buffer}); - - // Perform any required action on the data. For OUT, the `data` - // will be whatever was sent by the host. For IN, it's a copy of - // whatever we sent. - switch (epb.endpoint_address) { - Endpoint.EP0_IN_ADDR => { - if (debug) std.log.info(" EP0_IN_ADDR", .{}); - - const buffer_reader = &S.buffer_reader; - - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (S.new_address) |addr| { - // Change our address: - f.set_address(@intCast(addr)); - } - - if (epb.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) { - _ = buffer_reader.try_advance(epb.buffer.len); - const next_data_chunk = buffer_reader.try_peek(64); - if (next_data_chunk.len > 0) { - f.usb_start_tx( - Endpoint.EP0_IN_ADDR, - next_data_chunk, - ); - } else { - f.usb_start_rx( - Endpoint.EP0_OUT_ADDR, - 0, - ); - - if (S.driver) |driver| { - _ = driver.class_control(.Ack, &S.setup_packet); - } - } - } else { - // Otherwise, we've just finished sending - // something to the host. We expect an ensuing - // status phase where the host sends us (via EP0 - // OUT) a zero-byte DATA packet, so, set that - // up: - f.usb_start_rx( - Endpoint.EP0_OUT_ADDR, - 0, - ); - - if (S.driver) |driver| { - _ = driver.class_control(.Ack, &S.setup_packet); - } - } - }, - else => { - const ep_num = Endpoint.num_from_address(epb.endpoint_address); - const ep_dir = Endpoint.dir_from_address(epb.endpoint_address).as_number(); - if (get_driver(ep_to_drv[ep_num][ep_dir])) |driver| { - driver.transfer(epb.endpoint_address, epb.buffer); - } - if (Endpoint.dir_from_address(epb.endpoint_address) == .Out) { - f.endpoint_reset_rx(epb.endpoint_address); - } - }, - } + // Driver's init may call `signal_rx_ready()`, so the OUT endpoint is configured first. + inline for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc_ep = @field(descriptors, fld.name); + if (desc_ep.endpoint.dir != .Out) continue; + device.open_out(desc_ep.endpoint.num, desc_ep.attributes.transfer_type); } - } // <-- END of buf status handling - // Has the host signaled a bus reset? - if (ints.BusReset) { - if (debug) std.log.info("bus reset", .{}); + @field(this.drivers.?, drv.name).init(device.interface(), &descriptors); - configuration_reset(); - // Reset the device - f.bus_reset(); - - // Reset our state. - S.new_address = null; - S.configured = false; - S.started = false; - S.buffer_reader = BufferReader{ .buffer = &.{} }; + inline for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc_ep = @field(descriptors, fld.name); + if (desc_ep.endpoint.dir != .In) continue; + device.open_in(desc_ep.endpoint.num, desc_ep.attributes.transfer_type); + } } + return true; + } - // If we have been configured but haven't reached this point yet, set up - // our custom EP OUT's to receive whatever data the host wants to send. - if (S.configured and !S.started) { - S.started = true; + /// Called whenever a SET_FEATURE request is received. + pub fn set_feature(this: *@This(), feature_selector: u8, index: u16, value: bool) bool { + _ = this; + _ = index; + _ = value; + switch (enumFromInt(types.FeatureSelector, feature_selector) catch return false) { + .DeviceRemoteWakeup, .EndpointHalt => return true, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 + .TestMode => return false, } } - }; -} - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Driver support stuctures -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub const DeviceConfiguration = struct { - device_descriptor: *const types.DeviceDescriptor, - config_descriptor: []const u8, - lang_descriptor: []const u8, - descriptor_strings: []const []const u8, - drivers: []types.UsbClassDriver, -}; - -/// USB interrupt status -/// -/// __Note__: Available interrupts may change from device to device. -pub const InterruptStatus = struct { - /// Host: raised every time the host sends a SOF (Start of Frame) - BuffStatus: bool = false, - BusReset: bool = false, - /// Set when the device connection state changes - DevConnDis: bool = false, - /// Set when the device suspend state changes - DevSuspend: bool = false, - /// Set when the device receives a resume from the host - DevResumeFromHost: bool = false, - /// Setup Request - SetupReq: bool = false, -}; -pub const EPBError = error{ - /// The system has received a buffer event for an unknown endpoint (this is super unlikely) - UnknownEndpoint, - /// The buffer is not available (this is super unlikely) - NotAvailable, -}; - -/// Element returned by the endpoint buffer iterator (EPBIter) -pub const EPB = struct { - /// The endpoint the data belongs to - endpoint_address: u8, - /// Data buffer - buffer: []u8, -}; - -/// Iterator over all input buffers that hold data -pub const EPBIter = struct { - /// Bitmask of the input buffers to handle - bufbits: u32, - /// The last input buffer handled. This can be used to flag the input buffer as handled on the - /// next call. - last_bit: ?u32 = null, - /// Point to the device configuration (to get access to the endpoint buffers defined by the user) - device_config: *const DeviceConfiguration, - /// Get the next available input buffer - next: *const fn (self: *@This()) ?EPB, -}; - -const BufferWriter = struct { - buffer: []u8, - pos: usize = 0, - endian: std.builtin.Endian = builtin.cpu.arch.endian(), - - pub const Error = error{EndOfBuffer}; - - /// Moves forward write cursor by the provided number of bytes. - pub fn advance(self: *@This(), bytes: usize) Error!void { - try self.bound_check(bytes); - self.advance_unsafe(bytes); - } - - /// Writes data provided as a slice to the buffer and moves write cursor forward by data size. - pub fn write(self: *@This(), data: []const u8) Error!void { - try self.bound_check(data.len); - defer self.advance_unsafe(data.len); - @memcpy(self.buffer[self.pos .. self.pos + data.len], data); - } - - /// Writes an int with respect to the buffer's endianness and moves write cursor forward by int size. - pub fn write_int(self: *@This(), comptime T: type, value: T) Error!void { - const size = @divExact(@typeInfo(T).int.bits, 8); - try self.bound_check(size); - defer self.advance_unsafe(size); - std.mem.writeInt(T, self.buffer[self.pos..][0..size], value, self.endian); - } - - /// Writes an int with respect to the buffer's endianness but skip bound check. - /// Useful in cases where the bound can be checked once for batch of ints. - pub fn write_int_unsafe(self: *@This(), comptime T: type, value: T) void { - const size = @divExact(@typeInfo(T).int.bits, 8); - defer self.advance_unsafe(size); - std.mem.writeInt(T, self.buffer[self.pos..][0..size], value, self.endian); - } - - /// Returns a slice of the internal buffer containing the written data. - pub fn get_written_slice(self: *const @This()) []const u8 { - return self.buffer[0..self.pos]; - } - - /// Performs a buffer bound check against the current cursor position and the provided number of bytes to check forward. - pub fn bound_check(self: *const @This(), bytes: usize) Error!void { - if (self.pos + bytes > self.buffer.len) return error.EndOfBuffer; - } - - fn advance_unsafe(self: *@This(), bytes: usize) void { - self.pos += bytes; - } -}; - -const BufferReader = struct { - buffer: []const u8, - pos: usize = 0, - endian: std.builtin.Endian = builtin.cpu.arch.endian(), - - /// Attempts to move read cursor forward by the specified number of bytes. - /// Returns the actual number of bytes advanced, up to the specified number. - pub fn try_advance(self: *@This(), bytes: usize) usize { - const size = @min(bytes, self.buffer.len - self.pos); - self.advance_unsafe(size); - return size; - } - - /// Attempts to read the given amount of bytes (or less if close to buffer end) and advances the read cursor. - pub fn try_read(self: *@This(), bytes: usize) []const u8 { - const size = @min(bytes, self.buffer.len - self.pos); - defer self.advance_unsafe(size); - return self.buffer[self.pos .. self.pos + size]; - } - - /// Attempts to read the given amount of bytes (or less if close to buffer end) without advancing the read cursor. - pub fn try_peek(self: *@This(), bytes: usize) []const u8 { - const size = @min(bytes, self.buffer.len - self.pos); - return self.buffer[self.pos .. self.pos + size]; - } - - /// Returns the number of bytes remaining from the current read cursor position to the end of the underlying buffer. - pub fn get_remaining_bytes_count(self: *const @This()) usize { - return self.buffer.len - self.pos; - } - - fn advance_unsafe(self: *@This(), bytes: usize) void { - self.pos += bytes; - } -}; - -pub const UsbUtils = struct { - /// Convert an utf8 into an utf16 (little endian) string - pub fn utf8_to_utf16_le(comptime s: []const u8) [s.len << 1]u8 { - const l = s.len << 1; - var ret: [l]u8 = @splat(0); - var i: usize = 0; - while (i < s.len) : (i += 1) { - ret[i << 1] = s[i]; + /// Called whenever a setup packet is received. + pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + if (this.drivers) |*drivers| { + inline for (config.drivers) |drv| { + const ptr = &@field(drivers, drv.name); + inline for (@field(driver_info, drv.name).interface_handlers) |handler| { + if (handler.itf == setup.index & 0xFF) + return handler.func(ptr, setup); + } + } + } + return NAK; } - return ret; - } -}; - -test "tests" { - _ = hid; -} - -test "utf8 to utf16" { - try std.testing.expectEqualSlices(u8, "M\x00y\x00 \x00V\x00e\x00n\x00d\x00o\x00r\x00", &UsbUtils.utf8_to_utf16_le("My Vendor")); - try std.testing.expectEqualSlices(u8, "R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00", &UsbUtils.utf8_to_utf16_le("Raspberry Pi")); + }; } diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index fc111a107..28935735a 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -1,313 +1,165 @@ const std = @import("std"); +const enumFromInt = std.meta.intToEnum; -const types = @import("types.zig"); -const utils = @import("utils.zig"); +const usb = @import("../usb.zig"); +const descriptor = usb.descriptor; +const types = usb.types; -const utilities = @import("../../utilities.zig"); - -const DescType = types.DescType; -const bos = utils.BosConfig; - -pub const DescSubType = enum(u8) { - Header = 0x00, - CallManagement = 0x01, - ACM = 0x02, - Union = 0x06, - - pub fn from_u16(v: u16) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } -}; - -pub const CdcManagementRequestType = enum(u8) { +pub const ManagementRequestType = enum(u8) { SetLineCoding = 0x20, GetLineCoding = 0x21, SetControlLineState = 0x22, SendBreak = 0x23, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } }; -pub const CdcCommSubClassType = enum(u8) { - AbstractControlModel = 2, -}; +pub const LineCoding = extern struct { + bit_rate: u32 align(1), + stop_bits: u8, + parity: u8, + data_bits: u8, -pub const CdcHeaderDescriptor = extern struct { - length: u8 = 5, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `Header`. - descriptor_subtype: DescSubType = DescSubType.Header, - // USB Class Definitions for Communication Devices Specification release - // number in binary-coded decimal. Typically 0x01_10. - bcd_cdc: u16 align(1), + pub const init: @This() = .{ + .bit_rate = 115200, + .stop_bits = 0, + .parity = 0, + .data_bits = 8, + }; +}; - pub fn serialize(self: *const @This()) [5]u8 { - var out: [5]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = @intCast(self.bcd_cdc & 0xff); - out[4] = @intCast((self.bcd_cdc >> 8) & 0xff); - return out; +pub const Descriptor = extern struct { + itf_assoc: descriptor.InterfaceAssociation, + itf_notifi: descriptor.Interface, + cdc_header: descriptor.cdc.Header, + cdc_call_mgmt: descriptor.cdc.CallManagement, + cdc_acm: descriptor.cdc.AbstractControlModel, + cdc_union: descriptor.cdc.Union, + ep_notifi: descriptor.Endpoint, + itf_data: descriptor.Interface, + ep_out: descriptor.Endpoint, + ep_in: descriptor.Endpoint, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); } }; -pub const CdcCallManagementDescriptor = extern struct { - length: u8 = 5, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `CallManagement`. - descriptor_subtype: DescSubType = DescSubType.CallManagement, - // Capabilities. Should be 0x00 for use as a serial device. - capabilities: u8, - // Data interface number. - data_interface: u8, +pub const CdcClassDriver = struct { + ep_in_notif: types.Endpoint.Num, + ep_in: types.Endpoint.Num, + ep_out: types.Endpoint.Num, + awaiting_data: bool, + + line_coding: LineCoding, + + pub fn info(first_interface: u8, string_ids: anytype, endpoints: anytype) usb.DriverInfo(@This(), Descriptor) { + const endpoint_notifi_size = 8; + const endpoint_size = 64; + return .{ + .interface_handlers = &.{ + .{ .itf = first_interface, .func = interface_setup }, + .{ .itf = first_interface + 1, .func = interface_setup }, + }, + .descriptors = .{ + .itf_assoc = .{ + .first_interface = first_interface, + .interface_count = 2, + .function_class = 2, + .function_subclass = 2, + .function_protocol = 0, + .function = 0, + }, + .itf_notifi = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 1, + .interface_class = 2, + .interface_subclass = 2, + .interface_protocol = 0, + .interface_s = string_ids.name, + }, + .cdc_header = .{ .bcd_cdc = .from(0x0120) }, + .cdc_call_mgmt = .{ + .capabilities = 0, + .data_interface = first_interface + 1, + }, + .cdc_acm = .{ .capabilities = 6 }, + .cdc_union = .{ + .master_interface = first_interface, + .slave_interface_0 = first_interface + 1, + }, + .ep_notifi = .{ + .endpoint = .in(endpoints.notifi), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_notifi_size), + .interval = 16, + }, + .itf_data = .{ + .interface_number = first_interface + 1, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 10, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = 0, + }, + .ep_out = .{ + .endpoint = .out(endpoints.data), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + .ep_in = .{ + .endpoint = .in(endpoints.data), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + }, + }; + } - pub fn serialize(self: *const @This()) [5]u8 { - var out: [5]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = self.capabilities; - out[4] = self.data_interface; - return out; + /// This function is called when the host chooses a configuration that contains this driver. + pub fn init(this: *@This(), _: usb.DeviceInterface, desc: *const Descriptor) void { + this.* = .{ + .line_coding = .init, + .awaiting_data = false, + .ep_in_notif = desc.ep_notifi.endpoint.num, + .ep_out = desc.ep_out.endpoint.num, + .ep_in = desc.ep_in.endpoint.num, + }; } -}; -pub const CdcAcmDescriptor = extern struct { - length: u8 = 4, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `ACM`. - descriptor_subtype: DescSubType = DescSubType.ACM, - // Capabilities. Should be 0x02 for use as a serial device. - capabilities: u8, + /// On bus reset, this function is called followed by init(). + pub fn deinit(_: *@This()) void {} - pub fn serialize(self: *const @This()) [4]u8 { - var out: [4]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = self.capabilities; - return out; + /// Read data from rx buffer into dst. + pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { + return device.read_buffered(dst[0..1], this.ep_out) orelse 0; } -}; -pub const CdcUnionDescriptor = extern struct { - length: u8 = 5, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `Union`. - descriptor_subtype: DescSubType = DescSubType.Union, - // The interface number of the communication or data class interface - // designated as the master or controlling interface for the union. - master_interface: u8, - // The interface number of the first slave or associated interface in the - // union. - slave_interface_0: u8, + /// Write data from src into tx buffer. + pub fn write(this: *@This(), src: []const u8, device: usb.DeviceInterface) usize { + return device.write_buffered(src[0..1], this.ep_in, false) orelse 0; + } - pub fn serialize(self: *const @This()) [5]u8 { - var out: [5]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = self.master_interface; - out[4] = self.slave_interface_0; - return out; + /// Send any buffered data. + pub fn flush(this: *@This(), device: usb.DeviceInterface) void { + _ = device.write_buffered("", this.ep_in, true); } -}; -pub const CdcLineCoding = extern struct { - bit_rate: u32 align(1), - stop_bits: u8, - parity: u8, - data_bits: u8, + /// Callback for setup packets. + pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + return if (enumFromInt( + ManagementRequestType, + setup.request, + )) |request| switch (request) { + .SetLineCoding => usb.ACK, + .GetLineCoding => { + const data = std.mem.asBytes(&this.line_coding); + return data[0..@min(@sizeOf(LineCoding), setup.length)]; + }, + .SetControlLineState => usb.ACK, + .SendBreak => usb.ACK, + } else |_| usb.ACK; + } }; - -pub fn CdcClassDriver(comptime usb: anytype) type { - const FIFO = utilities.CircularBuffer(u8, usb.max_packet_size); - - return struct { - device: ?types.UsbDevice = null, - ep_notif: u8 = 0, - ep_in: u8 = 0, - ep_out: u8 = 0, - - line_coding: CdcLineCoding = undefined, - - rx: FIFO = .empty, - tx: FIFO = .empty, - - epin_buf: [usb.max_packet_size]u8 = undefined, - - pub fn available(self: *@This()) usize { - return self.rx.get_readable_len(); - } - - pub fn read(self: *@This(), dst: []u8) usize { - const read_count = self.rx.read(dst); - self.prep_out_transaction(); - return read_count; - } - - pub fn write(self: *@This(), data: []const u8) []const u8 { - const write_count = @min(self.tx.get_writable_len(), data.len); - - if (write_count > 0) { - self.tx.write_assume_capacity(data[0..write_count]); - } else { - return data[0..]; - } - - if (self.tx.get_writable_len() == 0) { - _ = self.write_flush(); - } - - return data[write_count..]; - } - - pub fn write_flush(self: *@This()) usize { - if (self.device.?.ready() == false) { - return 0; - } - if (self.tx.get_readable_len() == 0) { - return 0; - } - const len = self.tx.read(&self.epin_buf); - self.device.?.endpoint_transfer(self.ep_in, self.epin_buf[0..len]); - return len; - } - - fn prep_out_transaction(self: *@This()) void { - if (self.rx.get_writable_len() >= usb.max_packet_size) { - // Let endpoint know that we are ready for next packet - self.device.?.endpoint_transfer(self.ep_out, &.{}); - } - } - - fn init(ptr: *anyopaque, device: types.UsbDevice) void { - var self: *@This() = @ptrCast(@alignCast(ptr)); - self.device = device; - self.line_coding = .{ - .bit_rate = 115200, - .stop_bits = 0, - .parity = 0, - .data_bits = 8, - }; - } - - fn open(ptr: *anyopaque, cfg: []const u8) !usize { - var self: *@This() = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; - - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) return types.DriverErrors.UnsupportedInterfaceClassType; - if (desc_itf.interface_subclass != @intFromEnum(CdcCommSubClassType.AbstractControlModel)) return types.DriverErrors.UnsupportedInterfaceSubClassType; - } else { - return types.DriverErrors.ExpectedInterfaceDescriptor; - } - - curr_cfg = bos.get_desc_next(curr_cfg); - - while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == DescType.CsInterface) { - curr_cfg = bos.get_desc_next(curr_cfg); - } - - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - self.ep_notif = desc_ep.endpoint_address; - curr_cfg = bos.get_desc_next(curr_cfg); - } - - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { - curr_cfg = bos.get_desc_next(curr_cfg); - for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (types.Endpoint.dir_from_address(desc_ep.endpoint_address)) { - .In => self.ep_in = desc_ep.endpoint_address, - .Out => self.ep_out = desc_ep.endpoint_address, - } - self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); - } - } - } - } - - return cfg.len - curr_cfg.len; - } - - fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - var self: *@This() = @ptrCast(@alignCast(ptr)); - - if (CdcManagementRequestType.from_u8(setup.request)) |request| { - switch (request) { - .SetLineCoding => { - switch (stage) { - .Setup => { - // HACK, we should handle data phase somehow to read sent line_coding - self.device.?.control_ack(setup); - }, - else => {}, - } - }, - .GetLineCoding => { - if (stage == .Setup) { - self.device.?.control_transfer(setup, std.mem.asBytes(&self.line_coding)); - } - }, - .SetControlLineState => { - switch (stage) { - .Setup => { - self.device.?.control_ack(setup); - }, - else => {}, - } - }, - .SendBreak => { - switch (stage) { - .Setup => { - self.device.?.control_ack(setup); - }, - else => {}, - } - }, - } - } - - return true; - } - - fn transfer(ptr: *anyopaque, ep_addr: u8, data: []u8) void { - var self: *@This() = @ptrCast(@alignCast(ptr)); - - if (ep_addr == self.ep_out) { - self.rx.write(data) catch {}; - self.prep_out_transaction(); - } - - if (ep_addr == self.ep_in) { - if (self.write_flush() == 0) { - // If there is no data left, a empty packet should be sent if - // data len is multiple of EP Packet size and not zero - if (self.tx.get_readable_len() == 0 and data.len > 0 and data.len == usb.max_packet_size) { - self.device.?.endpoint_transfer(self.ep_in, &.{}); - } - } - } - } - - pub fn driver(self: *@This()) types.UsbClassDriver { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - .fn_transfer = transfer, - }; - } - }; -} diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig new file mode 100644 index 000000000..9068140e0 --- /dev/null +++ b/core/src/core/usb/descriptor.zig @@ -0,0 +1,297 @@ +pub const cdc = @import("descriptor/cdc.zig"); +pub const hid = @import("descriptor/hid.zig"); +pub const vendor = @import("descriptor/vendor.zig"); + +test "descriptor tests" { + _ = cdc; + _ = hid; + _ = vendor; +} + +const std = @import("std"); +const types = @import("types.zig"); +const assert = std.debug.assert; + +pub const Type = enum(u8) { + Device = 0x01, + Configuration = 0x02, + String = 0x03, + Interface = 0x04, + Endpoint = 0x05, + DeviceQualifier = 0x06, + InterfaceAssociation = 0x0b, + CsDevice = 0x21, + CsConfig = 0x22, + CsString = 0x23, + CsInterface = 0x24, + CsEndpoint = 0x25, + _, +}; + +/// Describes a device. This is the most broad description in USB and is +/// typically the first thing the host asks for. +pub const Device = extern struct { + /// Class, subclass and protocol of device. + pub const DeviceTriple = extern struct { + /// Class of device, giving a broad functional area. + class: types.ClassCode, + /// Subclass of device, refining the class. + subclass: u8, + /// Protocol within the subclass. + protocol: u8, + + pub const unspecified: @This() = .{ + .class = .Unspecified, + .subclass = 0, + .protocol = 0, + }; + }; + + /// USB Device Qualifier Descriptor + /// This descriptor is a subset of the DeviceDescriptor + pub const Qualifier = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 10); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `DeviceQualifier`. + descriptor_type: Type = .DeviceQualifier, + /// Specification version as Binary Coded Decimal + bcd_usb: types.U16Le, + /// Class, subclass and protocol of device. + device_triple: DeviceTriple, + /// Maximum unit of data this device can move. + max_packet_size0: u8, + /// Number of configurations supported by this device. + num_configurations: u8, + /// Reserved for future use; must be 0 + reserved: u8 = 0, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } + }; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 18); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Device`. + descriptor_type: Type = .Device, + /// Specification version as Binary Coded Decimal + bcd_usb: types.U16Le, + /// Class, subclass and protocol of device. + device_triple: DeviceTriple, + /// Maximum length of data this device can move. + max_packet_size0: u8, + /// ID of product vendor. + vendor: types.U16Le, + /// ID of product. + product: types.U16Le, + /// Device version number as Binary Coded Decimal. + bcd_device: types.U16Le, + /// Index of manufacturer name in string descriptor table. + manufacturer_s: u8, + /// Index of product name in string descriptor table. + product_s: u8, + /// Index of serial number in string descriptor table. + serial_s: u8, + /// Number of configurations supported by this device. + num_configurations: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } + + pub fn qualifier(this: @This()) Qualifier { + return .{ + .bcd_usb = this.bcd_usb, + .device_triple = this.device_triple, + .max_packet_size0 = this.max_packet_size0, + .num_configurations = this.num_configurations, + }; + } +}; + +/// Description of a single available device configuration. +pub const Configuration = extern struct { + /// Maximum device current consumption. + pub const MaxCurrent = extern struct { + multiple_of_2ma: u8, + + pub fn from_ma(ma: u9) @This() { + return .{ .multiple_of_2ma = @intCast((ma +| 1) >> 1) }; + } + }; + + /// Bit set of device attributes: + /// + /// - Bit 7 should be set (indicates that device can be bus powered in USB + /// 1.0). + /// - Bit 6 indicates that the device can be self-powered. + /// - Bit 5 indicates that the device can signal remote wakeup of the host + /// (like a keyboard). + /// - The rest are reserved and should be zero. + pub const Attributes = packed struct(u8) { + reserved: u5 = 0, + can_remote_wakeup: bool = false, + self_powered: bool, + usb1_bus_powered: bool = true, + }; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 9); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Configuration`. + descriptor_type: Type = .Configuration, + /// Total length of all descriptors in this configuration, concatenated. + /// This will include this descriptor, plus at least one interface + /// descriptor, plus each interface descriptor's endpoint descriptors. + total_length: types.U16Le, + /// Number of interface descriptors in this configuration. + num_interfaces: u8, + /// Number to use when requesting this configuration via a + /// `SetConfiguration` request. + configuration_value: u8, + /// Index of this configuration's name in the string descriptor table. + configuration_s: u8, + /// Bit set of device attributes. + attributes: Attributes, + /// Maximum device power consumption in units of 2mA. + max_current: MaxCurrent, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +pub fn string(comptime value: []const u8) []const u8 { + @setEvalBranchQuota(10000); + const encoded: []const u8 = @ptrCast(std.unicode.utf8ToUtf16LeStringLiteral(value)); + return &[2]u8{ encoded.len + 2, @intFromEnum(Type.String) } ++ encoded; +} + +/// String descriptor 0. +pub const Language = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 4); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `String`. + descriptor_type: Type = .String, + /// See definitions below for possible values. + lang: types.U16Le, + + pub const English: @This() = .{ .lang = .from(0x0409) }; + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +/// Describes an endpoint within an interface +pub const Endpoint = extern struct { + pub const Attributes = packed struct(u8) { + pub const Synchronisation = enum(u2) { + none = 0, + asynchronous = 1, + adaptive = 2, + synchronous = 3, + }; + + pub const Usage = enum(u2) { + data = 0, + feedback = 1, + implicit_feedback = 2, + reserved = 3, + }; + + transfer_type: types.TransferType, + synchronisation: Synchronisation = .none, + usage: Usage, + reserved: u2 = 0, + }; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 7); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Endpoint`. + descriptor_type: Type = .Endpoint, + /// Address of this endpoint, where the bottom 4 bits give the endpoint + /// number (0..15) and the top bit distinguishes IN (1) from OUT (0). + endpoint: types.Endpoint, + /// Endpoint attributes; the most relevant part is the bottom 2 bits, which + /// control the transfer type using the values from `TransferType`. + attributes: Attributes, + /// Maximum packet size this endpoint can accept/produce. + max_packet_size: types.U16Le, + /// Interval for polling interrupt/isochronous endpoints (which we don't + /// currently support) in milliseconds. + interval: u8, +}; + +/// Description of an interface within a configuration. +pub const Interface = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 9); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Interface`. + descriptor_type: Type = .Interface, + /// ID of this interface. + interface_number: u8, + /// Allows a single `interface_number` to have several alternate interface + /// settings, where each alternate increments this field. Normally there's + /// only one, and `alternate_setting` is zero. + alternate_setting: u8, + /// Number of endpoint descriptors in this interface. + num_endpoints: u8, + /// Interface class code, distinguishing the type of interface. + interface_class: u8, + /// Interface subclass code, refining the class of interface. + interface_subclass: u8, + /// Protocol within the interface class/subclass. + interface_protocol: u8, + /// Index of interface name within string descriptor table. + interface_s: u8, +}; + +/// USB interface association descriptor (IAD) allows the device to group interfaces that belong to a function. +pub const InterfaceAssociation = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 8); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `InterfaceAssociation`. + descriptor_type: Type = .InterfaceAssociation, + // First interface number of the set of interfaces that follow this + // descriptor. + first_interface: u8, + // The number of interfaces that follow this descriptor that are considered + // associated. + interface_count: u8, + // The interface class used for associated interfaces. + function_class: u8, + // The interface subclass used for associated interfaces. + function_subclass: u8, + // The interface protocol used for associated interfaces. + function_protocol: u8, + // Index of the string descriptor describing the associated interfaces. + function: u8, +}; diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig new file mode 100644 index 000000000..0d9f8e5fc --- /dev/null +++ b/core/src/core/usb/descriptor/cdc.zig @@ -0,0 +1,77 @@ +const assert = @import("std").debug.assert; +const Type = @import("../descriptor.zig").Type; +const types = @import("../types.zig"); + +pub const SubType = enum(u8) { + Header = 0x00, + CallManagement = 0x01, + AbstractControlModel = 0x02, + Union = 0x06, +}; + +pub const Header = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `Header`. + descriptor_subtype: SubType = .Header, + // USB Class Definitions for Communication Devices Specification release + // number in binary-coded decimal. Typically 0x01_10. + bcd_cdc: types.U16Le, +}; + +pub const CallManagement = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `CallManagement`. + descriptor_subtype: SubType = .CallManagement, + // Capabilities. Should be 0x00 for use as a serial device. + capabilities: u8, + // Data interface number. + data_interface: u8, +}; + +pub const AbstractControlModel = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 4); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `AbstractControlModel`. + descriptor_subtype: SubType = .AbstractControlModel, + // Capabilities. Should be 0x02 for use as a serial device. + capabilities: u8, +}; + +pub const Union = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `Union`. + descriptor_subtype: SubType = .Union, + // The interface number of the communication or data class interface + // designated as the master or controlling interface for the union. + master_interface: u8, + // The interface number of the first slave or associated interface in the + // union. + slave_interface_0: u8, +}; diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig new file mode 100644 index 000000000..da5e08b0e --- /dev/null +++ b/core/src/core/usb/descriptor/hid.zig @@ -0,0 +1,450 @@ +const std = @import("std"); +const types = @import("../types.zig"); +const assert = std.debug.assert; + +// ... +// | +// v +// ------------------------- +// | InterfaceDescriptor | +// ------------------------- +// | | +// | ----------------- +// | | +// v v +// ... -------------------------- +// | HidDescriptor | +// -------------------------- +// | | +// ------ -------- +// | | +// v v +// ----------------------- --------------------- +// | ReportDescriptor | | PhysicalDesc | +// ----------------------- --------------------- + +pub const SubType = enum(u8) { + Hid = 0x21, + Report = 0x22, + Physical = 0x23, +}; + +pub const RequestType = enum(u8) { + GetReport = 0x01, + GetIdle = 0x02, + GetProtocol = 0x03, + SetReport = 0x09, + SetIdle = 0x0a, + SetProtocol = 0x0b, +}; + +/// USB HID descriptor +pub const Hid = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 9); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor + descriptor_type: SubType = .Hid, + /// Numeric expression identifying the HID Class Specification release + bcd_hid: types.U16Le, + /// Numeric expression identifying country code of the localized hardware + country_code: u8, + /// Numeric expression specifying the number of class descriptors + num_descriptors: u8, + /// Type of HID class report + report_type: SubType = .Report, + /// The total size of the Report descriptor + report_length: types.U16Le, +}; + +/// HID interface Subclass (for UsbInterfaceDescriptor) +pub const Subclass = enum(u8) { + /// No Subclass + None = 0, + /// Boot Interface Subclass + Boot = 1, +}; + +/// HID interface protocol +pub const Protocol = enum(u8) { + /// No protocol + None = 0, + /// Keyboard (only if boot interface) + Keyboard = 1, + /// Mouse (only if boot interface) + Mouse = 2, +}; + +/// HID request report type +pub const ReportType = enum(u8) { + Invalid = 0, + Input = 1, + Output = 2, + Feature = 3, +}; + +/// HID country codes +pub const CountryCode = enum(u8) { + NotSupported = 0, + Arabic, + Belgian, + CanadianBilingual, + CanadianFrench, + CzechRepublic, + Danish, + Finnish, + French, + German, + Greek, + Hebrew, + Hungary, + International, + Italian, + JapanKatakana, + Korean, + LatinAmerica, + NetherlandsDutch, + Norwegian, + PersianFarsi, + Poland, + Portuguese, + Russia, + Slovakia, + Spanish, + Swedish, + SwissFrench, + SwissGerman, + Switzerland, + Taiwan, + TurkishQ, + Uk, + Us, + Yugoslavia, + TurkishF, +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptor Data Types +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const ReportItemTypes = enum(u2) { + Main = 0, + Global = 1, + Local = 2, +}; + +pub const ReportItemMainGroup = enum(u4) { + Input = 8, + Output = 9, + Collection = 10, + Feature = 11, + CollectionEnd = 12, +}; + +pub const CollectionItem = enum(u8) { + Physical = 0, + Application, + Logical, + Report, + NamedArray, + UsageSwitch, + UsageModifier, +}; + +pub const GlobalItem = enum(u4) { + UsagePage = 0, + LogicalMin = 1, + LogicalMax = 2, + PhysicalMin = 3, + PhysicalMax = 4, + UnitExponent = 5, + Unit = 6, + ReportSize = 7, + ReportId = 8, + ReportCount = 9, + Push = 10, + Pop = 11, +}; + +pub const LocalItem = enum(u4) { + Usage = 0, + UsageMin = 1, + UsageMax = 2, + DesignatorIndex = 3, + DesignatorMin = 4, + DesignatorMax = 5, + StringIndex = 7, + StringMin = 8, + StringMax = 9, + Delimiter = 10, +}; + +pub const UsageTable = struct { + pub const desktop: [1]u8 = "\x01".*; + pub const keyboard: [1]u8 = "\x07".*; + pub const led: [1]u8 = "\x08".*; + pub const fido: [2]u8 = "\xD0\xF1".*; + pub const vendor: [2]u8 = "\x00\xFF".*; +}; + +pub const FidoAllianceUsage = struct { + pub const u2fhid: [1]u8 = "\x01".*; + pub const data_in: [1]u8 = "\x20".*; + pub const data_out: [1]u8 = "\x21".*; +}; + +pub const DesktopUsage = struct { + pub const keyboard: [1]u8 = "\x06".*; +}; + +pub const HID_DATA: u8 = 0 << 0; +pub const HID_CONSTANT: u8 = 1 << 0; + +pub const HID_ARRAY = 0 << 1; +pub const HID_VARIABLE = 1 << 1; + +pub const HID_ABSOLUTE = 0 << 2; +pub const HID_RELATIVE = 1 << 2; + +pub const HID_WRAP_NO = 0 << 3; +pub const HID_WRAP = 1 << 3; + +pub const HID_LINEAR = 0 << 4; +pub const HID_NONLINEAR = 1 << 4; + +pub const HID_PREFERRED_STATE = 0 << 5; +pub const HID_PREFERRED_NO = 1 << 5; + +pub const HID_NO_NULL_POSITION = 0 << 6; +pub const HID_NULL_STATE = 1 << 6; + +pub const HID_NON_VOLATILE = 0 << 7; +pub const HID_VOLATILE = 1 << 7; + +pub const HID_BITFIELD = 0 << 8; +pub const HID_BUFFERED_BYTES = 1 << 8; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptor Functions +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub fn report_item( + typ: u2, + tag: u4, + data: anytype, +) [data.len + 1]u8 { + comptime if (data.len != 0) assert(@TypeOf(data[0]) == u8); + const first = (@as(u8, tag) << 4) | (@as(u8, typ) << 2) | @as(u2, data.len + 1); + + return switch (@typeInfo(@TypeOf(data))) { + .array, .@"struct" => .{first} ++ data, + .pointer => .{first} ++ data.*, + else => @compileLog(data), + }; +} + +// Main Items +// ------------------------------------------------- + +pub fn collection(data: CollectionItem) [2]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Collection), + .{@intFromEnum(data)}, + ); +} + +pub fn input(data: u8) [2]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Input), + .{data}, + ); +} + +pub fn output(data: u8) [2]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Output), + .{data}, + ); +} + +pub fn collection_end() [1]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.CollectionEnd), + .{}, + ); +} + +// Global Items +// ------------------------------------------------- + +pub fn usage_page(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.UsagePage), + data, + ); +} + +pub fn logical_min(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.LogicalMin), + data, + ); +} + +pub fn logical_max(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.LogicalMax), + data, + ); +} + +pub fn report_size(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.ReportSize), + data, + ); +} + +pub fn report_count(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.ReportCount), + data, + ); +} + +// Local Items +// ------------------------------------------------- + +pub fn usage(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.Usage), + data, + ); +} + +pub fn usage_min(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.UsageMin), + data, + ); +} + +pub fn usage_max(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.UsageMax), + data, + ); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptors +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const report = struct { + pub const FidoU2f = usage_page(UsageTable.fido) // + ++ usage(FidoAllianceUsage.u2fhid) // + ++ collection(CollectionItem.Application) // + // Usage Data In + ++ usage(FidoAllianceUsage.data_in) // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // Usage Data Out + ++ usage(FidoAllianceUsage.data_out) // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // End + ++ collection_end(); + + pub const GenericInOut = usage_page(UsageTable.vendor) // + ++ usage("\x01") // + ++ collection(CollectionItem.Application) // + // Usage Data In + ++ usage("\x02") // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // Usage Data Out + ++ usage("\x03") // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // End + ++ collection_end(); + + /// Common keyboard report format, conforming to the boot protocol. + /// See Appendix B.1 of the USB HID specification: + /// https://usb.org/sites/default/files/hid1_11.pdf + pub const Keyboard = usage_page(UsageTable.desktop) ++ usage(DesktopUsage.keyboard) ++ collection(.Application) + // Input: modifier key bitmap + ++ usage_page(UsageTable.keyboard) ++ usage_min("\xe0") ++ usage_max("\xe7") ++ logical_min("\x00") ++ logical_max("\x01") ++ report_count("\x08") ++ report_size("\x01") ++ input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // Reserved 8 bits + ++ report_count("\x01") ++ report_size("\x08") ++ input(HID_CONSTANT) + // Output: indicator LEDs + ++ usage_page(UsageTable.led) ++ usage_min("\x01") ++ usage_max("\x05") ++ report_count("\x05") ++ report_size("\x01") ++ output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + // Padding + ++ report_count("\x01") ++ report_size("\x03") ++ output(HID_CONSTANT) + // Input: up to 6 pressed key codes + ++ usage_page(UsageTable.keyboard) ++ usage_min("\x00") ++ usage_max("\xff\x00") ++ logical_min("\x00") ++ logical_max("\xff\x00") ++ report_count("\x06") ++ report_size("\x08") ++ input(HID_DATA | HID_ARRAY | HID_ABSOLUTE) + // End + ++ collection_end(); + + test "create hid report item" { + const r = report_item( + 2, + 0, + 3, + "\x22\x11", + ); + + try std.testing.expectEqual(@as(usize, @intCast(3)), r.len); + try std.testing.expectEqual(@as(u8, @intCast(50)), r[0]); + try std.testing.expectEqual(@as(u8, @intCast(0x22)), r[1]); + try std.testing.expectEqual(@as(u8, @intCast(0x11)), r[2]); + } + + test "create hid fido usage page" { + const f = usage_page(UsageTable.fido); + + try std.testing.expectEqual(@as(usize, @intCast(3)), f.len); + try std.testing.expectEqual(@as(u8, @intCast(6)), f[0]); + try std.testing.expectEqual(@as(u8, @intCast(0xd0)), f[1]); + try std.testing.expectEqual(@as(u8, @intCast(0xf1)), f[2]); + } + + test "report descriptor fido" { + _ = FidoU2f; + } +}; diff --git a/core/src/core/usb/descriptor/vendor.zig b/core/src/core/usb/descriptor/vendor.zig new file mode 100644 index 000000000..6ad46240d --- /dev/null +++ b/core/src/core/usb/descriptor/vendor.zig @@ -0,0 +1,51 @@ +const assert = @import("std").debug.assert; +const descriptor = @import("../descriptor.zig"); +const types = @import("../types.zig"); + +/// Not sure what this was supposed to be, but I am keeping it for now. +pub const Vendor = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + desc1: descriptor.Interface, + desc2: descriptor.Endpoint, + desc3: descriptor.Endpoint, + + pub fn create( + interface_number: u8, + string_index: u8, + endpoint_out: types.Endpoint.Num, + endpoint_in: types.Endpoint.Num, + endpoint_size: u16, + ) @This() { + return .{ + .desc1 = .{ + .interface_number = interface_number, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 0xff, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = string_index, + }, + .desc2 = .{ + .endpoint = .out(endpoint_out), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + .desc3 = .{ + .endpoint = .in(endpoint_in), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + }; + } + + pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 58c8ab8cb..dff8a2945 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -42,643 +42,180 @@ //! The HID descriptor identifies the length and type of subordinate descriptors for device. const std = @import("std"); +const enumFromInt = std.meta.intToEnum; -const types = @import("types.zig"); -const utils = @import("utils.zig"); +const usb = @import("../usb.zig"); +const descriptor = usb.descriptor; +const types = usb.types; -const DescType = types.DescType; -const bos = utils.BosConfig; +pub const InDescriptor = extern struct { + desc1: descriptor.Interface, + desc2: descriptor.hid.Hid, + desc3: descriptor.Endpoint, -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Common Data Types -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -// ... -// | -// v -// ------------------------- -// | InterfaceDescriptor | -// ------------------------- -// | | -// | ----------------- -// | | -// v v -// ... -------------------------- -// | HidDescriptor | -// -------------------------- -// | | -// ------ -------- -// | | -// v v -// ----------------------- --------------------- -// | ReportDescriptor | | PhysicalDesc | -// ----------------------- --------------------- - -pub const HidDescType = enum(u8) { - Hid = 0x21, - Report = 0x22, - Physical = 0x23, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; + fn create(first_interface: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in: types.Endpoint.Num, endpoint_size: u16, endpoint_interval: u16) @This() { + return .{ + .desc1 = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 1, + .interface_class = 3, + .interface_subclass = if (boot_protocol > 0) 1 else 0, + .interface_protocol = boot_protocol, + .interface_s = string_index, + }, + .desc2 = .{ + .bcd_hid = .from(0x0111), + .country_code = 0, + .num_descriptors = 1, + .report_length = .from(report_desc_len), + }, + .desc3 = .{ + .endpoint = .in(endpoint_in), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = endpoint_size, + .interval = endpoint_interval, + }, + }; } -}; - -pub const HidRequestType = enum(u8) { - GetReport = 0x01, - GetIdle = 0x02, - GetProtocol = 0x03, - SetReport = 0x09, - SetIdle = 0x0a, - SetProtocol = 0x0b, - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); } }; -/// USB HID descriptor -pub const HidDescriptor = struct { - pub const const_descriptor_type = HidDescType.Hid; +pub const InOutDescriptor = extern struct { + interface: descriptor.Interface, + hid: descriptor.hid.Hid, + ep_out: descriptor.Endpoint, + ep_in: descriptor.Endpoint, - length: u8 = 9, - /// Type of this descriptor - descriptor_type: HidDescType = const_descriptor_type, - /// Numeric expression identifying the HID Class Specification release - bcd_hid: u16 align(1), - /// Numeric expression identifying country code of the localized hardware - country_code: u8, - /// Numeric expression specifying the number of class descriptors - num_descriptors: u8, - /// Type of HID class report - report_type: HidDescType = HidDescType.Report, - /// The total size of the Report descriptor - report_length: u16 align(1), - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.bcd_hid & 0xff); - out[3] = @intCast((self.bcd_hid >> 8) & 0xff); - out[4] = self.country_code; - out[5] = self.num_descriptors; - out[6] = @intFromEnum(self.report_type); - out[7] = @intCast(self.report_length & 0xff); - out[8] = @intCast((self.report_length >> 8) & 0xff); - return out; + fn create(first_interface: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out: types.Endpoint.Num, endpoint_in: types.Endpoint.Num, endpoint_size: u16, endpoint_interval: u16) @This() { + return .{ + .interface = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 3, + .interface_subclass = if (boot_protocol > 0) 1 else 0, + .interface_protocol = boot_protocol, + .interface_s = string_index, + }, + .hid = .{ + .bcd_hid = .from(0x0111), + .country_code = 0, + .num_descriptors = 1, + .report_length = .from(report_desc_len), + }, + .ep_out = .{ + .endpoint = .out(endpoint_out), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = endpoint_interval, + }, + .ep_in = .{ + .endpoint = .in(endpoint_in), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = endpoint_interval, + }, + }; } -}; - -/// HID interface Subclass (for UsbInterfaceDescriptor) -pub const Subclass = enum(u8) { - /// No Subclass - None = 0, - /// Boot Interface Subclass - Boot = 1, -}; - -/// HID interface protocol -pub const Protocol = enum(u8) { - /// No protocol - None = 0, - /// Keyboard (only if boot interface) - Keyboard = 1, - /// Mouse (only if boot interface) - Mouse = 2, -}; - -/// HID request report type -pub const ReportType = enum(u8) { - Invalid = 0, - Input = 1, - Output = 2, - Feature = 3, -}; - -/// HID country codes -pub const CountryCode = enum(u8) { - NotSupported = 0, - Arabic, - Belgian, - CanadianBilingual, - CanadianFrench, - CzechRepublic, - Danish, - Finnish, - French, - German, - Greek, - Hebrew, - Hungary, - International, - Italian, - JapanKatakana, - Korean, - LatinAmerica, - NetherlandsDutch, - Norwegian, - PersianFarsi, - Poland, - Portuguese, - Russia, - Slovakia, - Spanish, - Swedish, - SwissFrench, - SwissGerman, - Switzerland, - Taiwan, - TurkishQ, - Uk, - Us, - Yugoslavia, - TurkishF, -}; - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Report Descriptor Data Types -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub const ReportItemTypes = enum(u2) { - Main = 0, - Global = 1, - Local = 2, -}; - -pub const ReportItemMainGroup = enum(u4) { - Input = 8, - Output = 9, - Collection = 10, - Feature = 11, - CollectionEnd = 12, -}; - -pub const CollectionItem = enum(u8) { - Physical = 0, - Application, - Logical, - Report, - NamedArray, - UsageSwitch, - UsageModifier, -}; - -pub const GlobalItem = enum(u4) { - UsagePage = 0, - LogicalMin = 1, - LogicalMax = 2, - PhysicalMin = 3, - PhysicalMax = 4, - UnitExponent = 5, - Unit = 6, - ReportSize = 7, - ReportId = 8, - ReportCount = 9, - Push = 10, - Pop = 11, -}; - -pub const LocalItem = enum(u4) { - Usage = 0, - UsageMin = 1, - UsageMax = 2, - DesignatorIndex = 3, - DesignatorMin = 4, - DesignatorMax = 5, - StringIndex = 7, - StringMin = 8, - StringMax = 9, - Delimiter = 10, -}; - -pub const UsageTable = struct { - pub const desktop: [1]u8 = "\x01".*; - pub const keyboard: [1]u8 = "\x07".*; - pub const led: [1]u8 = "\x08".*; - pub const fido: [2]u8 = "\xD0\xF1".*; - pub const vendor: [2]u8 = "\x00\xFF".*; -}; - -pub const FidoAllianceUsage = struct { - pub const u2fhid: [1]u8 = "\x01".*; - pub const data_in: [1]u8 = "\x20".*; - pub const data_out: [1]u8 = "\x21".*; -}; - -pub const DesktopUsage = struct { - pub const keyboard: [1]u8 = "\x06".*; -}; - -pub const HID_DATA: u8 = 0 << 0; -pub const HID_CONSTANT: u8 = 1 << 0; - -pub const HID_ARRAY = 0 << 1; -pub const HID_VARIABLE = 1 << 1; - -pub const HID_ABSOLUTE = 0 << 2; -pub const HID_RELATIVE = 1 << 2; - -pub const HID_WRAP_NO = 0 << 3; -pub const HID_WRAP = 1 << 3; - -pub const HID_LINEAR = 0 << 4; -pub const HID_NONLINEAR = 1 << 4; - -pub const HID_PREFERRED_STATE = 0 << 5; -pub const HID_PREFERRED_NO = 1 << 5; - -pub const HID_NO_NULL_POSITION = 0 << 6; -pub const HID_NULL_STATE = 1 << 6; -pub const HID_NON_VOLATILE = 0 << 7; -pub const HID_VOLATILE = 1 << 7; - -pub const HID_BITFIELD = 0 << 8; -pub const HID_BUFFERED_BYTES = 1 << 8; - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Report Descriptor Functions -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub fn hid_report_item( - comptime n: u2, - typ: u2, - tag: u4, - data: [n]u8, -) [n + 1]u8 { - var out: [n + 1]u8 = undefined; - - out[0] = (@as(u8, @intCast(tag)) << 4) | (@as(u8, @intCast(typ)) << 2) | n; - - var i: usize = 0; - while (i < n) : (i += 1) { - out[i + 1] = data[i]; + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); } - - return out; -} - -// Main Items -// ------------------------------------------------- - -pub fn hid_collection(data: CollectionItem) [2]u8 { - return hid_report_item( - 1, - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.Collection), - std.mem.toBytes(@intFromEnum(data)), - ); -} - -pub fn hid_input(data: u8) [2]u8 { - return hid_report_item( - 1, - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.Input), - std.mem.toBytes(data), - ); -} - -pub fn hid_output(data: u8) [2]u8 { - return hid_report_item( - 1, - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.Output), - std.mem.toBytes(data), - ); -} - -pub fn hid_collection_end() [1]u8 { - return hid_report_item( - 0, - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.CollectionEnd), - .{}, - ); -} - -// Global Items -// ------------------------------------------------- - -pub fn hid_usage_page(comptime n: u2, usage: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.UsagePage), - usage, - ); -} - -pub fn hid_logical_min(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.LogicalMin), - data, - ); -} - -pub fn hid_logical_max(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.LogicalMax), - data, - ); -} - -pub fn hid_report_size(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.ReportSize), - data, - ); -} - -pub fn hid_report_count(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.ReportCount), - data, - ); -} - -// Local Items -// ------------------------------------------------- - -pub fn hid_usage(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Local), - @intFromEnum(LocalItem.Usage), - data, - ); -} - -pub fn hid_usage_min(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Local), - @intFromEnum(LocalItem.UsageMin), - data, - ); -} - -pub fn hid_usage_max(comptime n: u2, data: [n]u8) [n + 1]u8 { - return hid_report_item( - n, - @intFromEnum(ReportItemTypes.Local), - @intFromEnum(LocalItem.UsageMax), - data, - ); -} - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Report Descriptors -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub const ReportDescriptorFidoU2f = hid_usage_page(2, UsageTable.fido) // - ++ hid_usage(1, FidoAllianceUsage.u2fhid) // - ++ hid_collection(CollectionItem.Application) // - // Usage Data In - ++ hid_usage(1, FidoAllianceUsage.data_in) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // - ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Usage Data Out - ++ hid_usage(1, FidoAllianceUsage.data_out) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // - ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // End - ++ hid_collection_end(); - -pub const ReportDescriptorGenericInOut = hid_usage_page(2, UsageTable.vendor) // - ++ hid_usage(1, "\x01".*) // - ++ hid_collection(CollectionItem.Application) // - // Usage Data In - ++ hid_usage(1, "\x02".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // - ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Usage Data Out - ++ hid_usage(1, "\x03".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // - ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // End - ++ hid_collection_end(); - -/// Common keyboard report format, conforming to the boot protocol. -/// See Appendix B.1 of the USB HID specification: -/// https://usb.org/sites/default/files/hid1_11.pdf -pub const ReportDescriptorKeyboard = hid_usage_page(1, UsageTable.desktop) // - ++ hid_usage(1, DesktopUsage.keyboard) // - ++ hid_collection(.Application) // - // Input: modifier key bitmap - ++ hid_usage_page(1, UsageTable.keyboard) // - ++ hid_usage_min(1, "\xe0".*) // - ++ hid_usage_max(1, "\xe7".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(1, "\x01".*) // - ++ hid_report_count(1, "\x08".*) // - ++ hid_report_size(1, "\x01".*) // - ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Reserved 8 bits - ++ hid_report_count(1, "\x01".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_input(HID_CONSTANT) // - // Output: indicator LEDs - ++ hid_usage_page(1, UsageTable.led) // - ++ hid_usage_min(1, "\x01".*) // - ++ hid_usage_max(1, "\x05".*) // - ++ hid_report_count(1, "\x05".*) // - ++ hid_report_size(1, "\x01".*) // - ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Padding - ++ hid_report_count(1, "\x01".*) // - ++ hid_report_size(1, "\x03".*) // - ++ hid_output(HID_CONSTANT) // - // Input: up to 6 pressed key codes - ++ hid_usage_page(1, UsageTable.keyboard) // - ++ hid_usage_min(1, "\x00".*) // - ++ hid_usage_max(2, "\xff\x00".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_count(1, "\x06".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_input(HID_DATA | HID_ARRAY | HID_ABSOLUTE) // - // End - ++ hid_collection_end(); +}; pub const HidClassDriver = struct { - device: ?types.UsbDevice = null, - ep_in: u8 = 0, - ep_out: u8 = 0, + ep_in: types.Endpoint.Num = .ep0, + ep_out: types.Endpoint.Num = .ep0, hid_descriptor: []const u8 = &.{}, report_descriptor: []const u8, - fn init(ptr: *anyopaque, device: types.UsbDevice) void { - var self: *HidClassDriver = @ptrCast(@alignCast(ptr)); - self.device = device; + pub fn info(first_interface: u8, string_ids: anytype, endpoints: anytype) usb.DriverInfo(@This(), InOutDescriptor) { + return .{ + .interface_handlers = &.{ + .{ .itf = first_interface, .func = interface_setup }, + }, + .descriptors = .create( + first_interface, + string_ids.name, + 0, + descriptor.hid.report.GenericInOut.len, + endpoints.main, + endpoints.main, + 64, + 0, + ), + }; } - fn open(ptr: *anyopaque, cfg: []const u8) !usize { - var self: *HidClassDriver = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; - - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return types.DriverErrors.UnsupportedInterfaceClassType; - } else { - return types.DriverErrors.ExpectedInterfaceDescriptor; - } - - curr_cfg = bos.get_desc_next(curr_cfg); - if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| { - self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)]; - curr_cfg = bos.get_desc_next(curr_cfg); - } else { - return types.DriverErrors.UnexpectedDescriptor; - } - - for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (types.Endpoint.dir_from_address(desc_ep.endpoint_address)) { - .In => self.ep_in = desc_ep.endpoint_address, - .Out => self.ep_out = desc_ep.endpoint_address, - } - self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); - } - } - - return cfg.len - curr_cfg.len; + /// This function is called when the host chooses a configuration that contains this driver. + pub fn init(this: *@This(), _: usb.DeviceInterface, desc: *const InOutDescriptor) void { + this.* = .{ + .hid_descriptor = std.mem.asBytes(&desc.hid), + .report_descriptor = undefined, + .ep_in = desc.ep_in.endpoint.num, + .ep_out = desc.ep_out.endpoint.num, + }; } - fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - var self: *HidClassDriver = @ptrCast(@alignCast(ptr)); + /// On bus reset, this function is called followed by init(). + pub fn deinit(_: *@This()) void {} + /// Callback for setup packets. + pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { switch (setup.request_type.type) { .Standard => { - if (stage == .Setup) { - const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff)); - const request_code = types.SetupRequest.from_u8(setup.request); + const hid_desc_type = enumFromInt(descriptor.hid.SubType, (setup.value >> 8) & 0xff) catch return usb.NAK; + const request_code = enumFromInt(types.SetupRequest, setup.request) catch return usb.NAK; - if (hid_desc_type == null or request_code == null) { - return false; - } + if (request_code != .GetDescriptor) return usb.ACK; - if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) { - self.device.?.control_transfer(setup, self.hid_descriptor); - } else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) { - self.device.?.control_transfer(setup, self.report_descriptor); - } else { - return false; - } - } - }, - .Class => { - const hid_request_type = HidRequestType.from_u8(setup.request); - if (hid_request_type == null) return false; + const data = switch (hid_desc_type) { + .Hid => this.hid_descriptor, + .Report => this.report_descriptor, + else => return null, + }; - switch (hid_request_type.?) { - .SetIdle => { - if (stage == .Setup) { - // TODO: The host is attempting to limit bandwidth by requesting that - // the device only return report data when its values actually change, - // or when the specified duration elapses. In practice, the device can - // still send reports as often as it wants, but for completeness this - // should be implemented eventually. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - self.device.?.control_ack(setup); - } - }, - .SetProtocol => { - if (stage == .Setup) { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, - // or vice versa. - // - // For now, this request is ACKed without doing anything; in practice, - // the OS will reuqest the report protocol anyway, so usually only one format is needed. - // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), - // our device might not work in a limited BIOS environment. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - self.device.?.control_ack(setup); - } - }, - .SetReport => { - if (stage == .Setup) { - // TODO: This request sends a feature or output report to the device, - // e.g. turning on the caps lock LED. This must be handled in an - // application-specific way, so notify the application code of the event. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - self.device.?.control_ack(setup); - } - }, - else => { - return false; - }, - } + return data[0..@min(data.len, setup.length)]; }, - else => { - return false; + .Class => return switch (enumFromInt( + descriptor.hid.RequestType, + setup.request, + ) catch return usb.NAK) { + // TODO: The host is attempting to limit bandwidth by requesting that + // the device only return report data when its values actually change, + // or when the specified duration elapses. In practice, the device can + // still send reports as often as it wants, but for completeness this + // should be implemented eventually. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + .SetIdle => usb.ACK, + + // TODO: The device should switch the format of its reports from the + // boot keyboard/mouse protocol to the format described in its report descriptor, + // or vice versa. + // + // For now, this request is ACKed without doing anything; in practice, + // the OS will reuqest the report protocol anyway, so usually only one format is needed. + // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), + // our device might not work in a limited BIOS environment. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + .SetProtocol => usb.ACK, + + // TODO: This request sends a feature or output report to the device, + // e.g. turning on the caps lock LED. This must be handled in an + // application-specific way, so notify the application code of the event. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + .SetReport => usb.ACK, + else => usb.NAK, }, + else => return usb.NAK, } - - return true; - } - - fn transfer(_: *anyopaque, _: u8, _: []u8) void {} - - pub fn driver(self: *@This()) types.UsbClassDriver { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - .fn_transfer = transfer, - }; + return usb.ACK; } }; - -test "create hid report item" { - const r = hid_report_item( - 2, - 0, - 3, - "\x22\x11".*, - ); - - try std.testing.expectEqual(@as(usize, @intCast(3)), r.len); - try std.testing.expectEqual(@as(u8, @intCast(50)), r[0]); - try std.testing.expectEqual(@as(u8, @intCast(0x22)), r[1]); - try std.testing.expectEqual(@as(u8, @intCast(0x11)), r[2]); -} - -test "create hid fido usage page" { - const f = hid_usage_page(2, UsageTable.fido); - - try std.testing.expectEqual(@as(usize, @intCast(3)), f.len); - try std.testing.expectEqual(@as(u8, @intCast(6)), f[0]); - try std.testing.expectEqual(@as(u8, @intCast(0xd0)), f[1]); - try std.testing.expectEqual(@as(u8, @intCast(0xf1)), f[2]); -} - -test "report descriptor fido" { - _ = ReportDescriptorFidoU2f; -} diff --git a/core/src/core/usb/templates.zig b/core/src/core/usb/templates.zig deleted file mode 100644 index 46097c556..000000000 --- a/core/src/core/usb/templates.zig +++ /dev/null @@ -1,56 +0,0 @@ -const types = @import("types.zig"); -const hid = @import("hid.zig"); -const cdc = @import("cdc.zig"); - -pub const DescriptorsConfigTemplates = struct { - pub const config_descriptor_len = 9; - - pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: u8, max_power_ma: u9) [9]u8 { - const desc1 = types.ConfigurationDescriptor{ .total_length = total_len, .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = 0b01000000 | attributes, .max_power = max_power_ma / 2 }; - return desc1.serialize(); - } - - pub const cdc_descriptor_len = 8 + 9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7; - - pub fn cdc_descriptor(interface_number: u8, string_index: u8, endpoint_notifi_address: u8, endpoint_notifi_size: u16, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16) [cdc_descriptor_len]u8 { - const desc1 = types.InterfaceAssociationDescriptor{ .first_interface = interface_number, .interface_count = 2, .function_class = 2, .function_subclass = 2, .function_protocol = 0, .function = 0 }; - const desc2 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, .interface_subclass = 2, .interface_protocol = 0, .interface_s = string_index }; - const desc3 = cdc.CdcHeaderDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .Header, .bcd_cdc = 0x0120 }; - const desc4 = cdc.CdcCallManagementDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1 }; - const desc5 = cdc.CdcAcmDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .ACM, .capabilities = 6 }; - const desc6 = cdc.CdcUnionDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1 }; - const desc7 = types.EndpointDescriptor{ .endpoint_address = endpoint_notifi_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_notifi_size, .interval = 16 }; - const desc8 = types.InterfaceDescriptor{ .interface_number = interface_number + 1, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, .interface_subclass = 0, .interface_protocol = 0, .interface_s = 0 }; - const desc9 = types.EndpointDescriptor{ .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - const desc10 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize() ++ desc5.serialize() ++ desc6.serialize() ++ desc7.serialize() ++ desc8.serialize() ++ desc9.serialize() ++ desc10.serialize(); - } - - pub const hid_in_descriptor_len = 9 + 9 + 7; - - pub fn hid_in_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in_address: u8, endpoint_size: u16, endpoint_interval: u16) [hid_in_descriptor_len]u8 { - const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; - const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); - } - - pub const hid_in_out_descriptor_len = 9 + 9 + 7 + 7; - - pub fn hid_in_out_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16, endpoint_interval: u16) [hid_in_out_descriptor_len]u8 { - const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; - const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = types.EndpointDescriptor{ .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; - const desc4 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize(); - } - - pub const vendor_descriptor_len = 9 + 7 + 7; - - pub fn vendor_descriptor(interface_number: u8, string_index: u8, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16) [vendor_descriptor_len]u8 { - const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xff, .interface_subclass = 0, .interface_protocol = 0, .interface_s = string_index }; - const desc2 = types.EndpointDescriptor{ .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - const desc3 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); - } -}; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index a3a7ab857..b5835c41d 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -1,30 +1,34 @@ const std = @import("std"); +const assert = std.debug.assert; -pub const DescType = enum(u8) { - Device = 0x01, - Config = 0x02, - String = 0x03, - Interface = 0x04, - Endpoint = 0x05, - DeviceQualifier = 0x06, - InterfaceAssociation = 0x0b, - CsDevice = 0x21, - CsConfig = 0x22, - CsString = 0x23, - CsInterface = 0x24, - CsEndpoint = 0x25, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } -}; - +/// Class of device, giving a broad functional area. pub const ClassCode = enum(u8) { - Unspecified = 0, - Audio = 1, - Cdc = 2, - Hid = 3, - CdcData = 10, + Unspecified = 0x00, + Audio = 0x01, + Cdc = 0x02, + Hid = 0x03, + Physical = 0x05, + Image = 0x06, + Printer = 0x07, + MassStorage = 0x08, + Hub = 0x09, + CdcData = 0x0A, + SmartCard = 0x0B, + ContentSecurity = 0x0D, + Video = 0x0E, + PersonalHealthcare = 0x0F, + AudioVideoDevice = 0x10, + BillboardDevice = 0x11, + USBTypeCBridge = 0x12, + USBBulkDisplayProtocol = 0x13, + MCTPoverUSBProtocolEndpoint = 0x14, + I3C = 0x3C, + DiagnosticDevice = 0xDC, + WirelessController = 0xE0, + Miscellaneous = 0xEF, + ApplicationSpecific = 0xFE, + VendorSpecific = 0xFF, + _, }; /// Types of transfer that can be indicated by the `attributes` field on `EndpointDescriptor`. @@ -34,10 +38,6 @@ pub const TransferType = enum(u2) { Bulk = 2, Interrupt = 3, - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } - pub inline fn as_number(self: @This()) u2 { return @intFromEnum(self); } @@ -79,36 +79,40 @@ pub const FeatureSelector = enum(u8) { pub const Dir = enum(u1) { Out = 0, In = 1, - - pub const DIR_IN_MASK = 0x80; - - pub inline fn as_number(self: @This()) u1 { - return @intFromEnum(self); - } - - pub inline fn as_number_reversed(self: @This()) u1 { - return ~@intFromEnum(self); - } }; -pub const Endpoint = struct { - pub inline fn to_address(num: u8, dir: Dir) u8 { - return switch (dir) { - .Out => num, - .In => num | Dir.DIR_IN_MASK, - }; - } +pub const Endpoint = packed struct(u8) { + // There are up to 15 endpoints for each direction. + pub const Num = enum(u4) { + ep0 = 0, + ep1, + ep2, + ep3, + ep4, + ep5, + ep6, + ep7, + ep8, + ep9, + ep10, + ep11, + ep12, + ep13, + ep14, + ep15, + }; - pub inline fn num_from_address(addr: u8) u8 { - return addr & ~@as(u8, @intCast(Dir.DIR_IN_MASK)); - } + num: Num, + _padding: u3 = 0, + dir: Dir, - pub inline fn dir_from_address(addr: u8) Dir { - return if (addr & Dir.DIR_IN_MASK != 0) Dir.In else Dir.Out; + pub inline fn out(num: Num) @This() { + return .{ .num = num, .dir = .Out }; } - pub const EP0_IN_ADDR: u8 = to_address(0, .In); - pub const EP0_OUT_ADDR: u8 = to_address(0, .Out); + pub inline fn in(num: Num) @This() { + return .{ .num = num, .dir = .In }; + } }; pub const RequestType = packed struct(u8) { @@ -123,40 +127,12 @@ pub const RequestType = packed struct(u8) { Other, }; - /// RequestType is created from raw bytes using std.mem.bytesToValue, we need to support all potential values if we don't want to crash such conversion const Recipient = enum(u5) { Device, Interface, Endpoint, Other, - Reserved1, - Reserved2, - Reserved3, - Reserved4, - Reserved5, - Reserved6, - Reserved7, - Reserved8, - Reserved9, - Reserved10, - Reserved11, - Reserved12, - Reserved13, - Reserved14, - Reserved15, - Reserved16, - Reserved17, - Reserved18, - Reserved19, - Reserved20, - Reserved21, - Reserved22, - Reserved23, - Reserved24, - Reserved25, - Reserved26, - Reserved27, - Reserved28, + _, // Reserved }; }; @@ -178,313 +154,16 @@ pub const SetupPacket = extern struct { length: u16, }; -/// Describes an endpoint within an interface -pub const EndpointDescriptor = extern struct { - pub const const_descriptor_type = DescType.Endpoint; - - length: u8 = 7, - /// Type of this descriptor, must be `Endpoint`. - descriptor_type: DescType = const_descriptor_type, - /// Address of this endpoint, where the bottom 4 bits give the endpoint - /// number (0..15) and the top bit distinguishes IN (1) from OUT (0). - endpoint_address: u8, - /// Endpoint attributes; the most relevant part is the bottom 2 bits, which - /// control the transfer type using the values from `TransferType`. - attributes: u8, - /// Maximum packet size this endpoint can accept/produce. - max_packet_size: u16 align(1), - /// Interval for polling interrupt/isochronous endpoints (which we don't - /// currently support) in milliseconds. - interval: u8, - - pub fn serialize(self: *const @This()) [7]u8 { - var out: [7]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.endpoint_address; - out[3] = self.attributes; - out[4] = @intCast(self.max_packet_size & 0xff); - out[5] = @intCast((self.max_packet_size >> 8) & 0xff); - out[6] = self.interval; - return out; - } -}; - -/// Description of an interface within a configuration. -pub const InterfaceDescriptor = extern struct { - pub const const_descriptor_type = DescType.Interface; - - length: u8 = 9, - /// Type of this descriptor, must be `Interface`. - descriptor_type: DescType = const_descriptor_type, - /// ID of this interface. - interface_number: u8, - /// Allows a single `interface_number` to have several alternate interface - /// settings, where each alternate increments this field. Normally there's - /// only one, and `alternate_setting` is zero. - alternate_setting: u8, - /// Number of endpoint descriptors in this interface. - num_endpoints: u8, - /// Interface class code, distinguishing the type of interface. - interface_class: u8, - /// Interface subclass code, refining the class of interface. - interface_subclass: u8, - /// Protocol within the interface class/subclass. - interface_protocol: u8, - /// Index of interface name within string descriptor table. - interface_s: u8, - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.interface_number; - out[3] = self.alternate_setting; - out[4] = self.num_endpoints; - out[5] = self.interface_class; - out[6] = self.interface_subclass; - out[7] = self.interface_protocol; - out[8] = self.interface_s; - return out; - } -}; - -/// USB interface association descriptor (IAD) allows the device to group interfaces that belong to a function. -pub const InterfaceAssociationDescriptor = extern struct { - pub const const_descriptor_type = DescType.InterfaceAssociation; - - length: u8 = 8, - // Type of this descriptor, must be `InterfaceAssociation`. - descriptor_type: DescType = const_descriptor_type, - // First interface number of the set of interfaces that follow this - // descriptor. - first_interface: u8, - // The number of interfaces that follow this descriptor that are considered - // associated. - interface_count: u8, - // The interface class used for associated interfaces. - function_class: u8, - // The interface subclass used for associated interfaces. - function_subclass: u8, - // The interface protocol used for associated interfaces. - function_protocol: u8, - // Index of the string descriptor describing the associated interfaces. - function: u8, - - pub fn serialize(self: *const @This()) [8]u8 { - var out: [8]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.first_interface; - out[3] = self.interface_count; - out[4] = self.function_class; - out[5] = self.function_subclass; - out[6] = self.function_protocol; - out[7] = self.function; - return out; - } -}; - -/// Description of a single available device configuration. -pub const ConfigurationDescriptor = extern struct { - pub const const_descriptor_type = DescType.Config; - - length: u8 = 9, - /// Type of this descriptor, must be `Config`. - descriptor_type: DescType = const_descriptor_type, - /// Total length of all descriptors in this configuration, concatenated. - /// This will include this descriptor, plus at least one interface - /// descriptor, plus each interface descriptor's endpoint descriptors. - total_length: u16 align(1), - /// Number of interface descriptors in this configuration. - num_interfaces: u8, - /// Number to use when requesting this configuration via a - /// `SetConfiguration` request. - configuration_value: u8, - /// Index of this configuration's name in the string descriptor table. - configuration_s: u8, - /// Bit set of device attributes: - /// - /// - Bit 7 should be set (indicates that device can be bus powered in USB - /// 1.0). - /// - Bit 6 indicates that the device can be self-powered. - /// - Bit 5 indicates that the device can signal remote wakeup of the host - /// (like a keyboard). - /// - The rest are reserved and should be zero. - attributes: u8, - /// Maximum device power consumption in units of 2mA. - max_power: u8, - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.total_length & 0xff); - out[3] = @intCast((self.total_length >> 8) & 0xff); - out[4] = self.num_interfaces; - out[5] = self.configuration_value; - out[6] = self.configuration_s; - out[7] = self.attributes; - out[8] = self.max_power; - return out; - } -}; - -/// Describes a device. This is the most broad description in USB and is -/// typically the first thing the host asks for. -pub const DeviceDescriptor = extern struct { - pub const const_descriptor_type = DescType.Device; - - length: u8 = 18, - /// Type of this descriptor, must be `Device`. - descriptor_type: DescType = const_descriptor_type, - /// Version of the device descriptor / USB protocol, in binary-coded - /// decimal. This is typically `0x01_10` for USB 1.1. - bcd_usb: u16 align(1), - /// Class of device, giving a broad functional area. - device_class: u8, - /// Subclass of device, refining the class. - device_subclass: u8, - /// Protocol within the subclass. - device_protocol: u8, - /// Maximum unit of data this device can move. - max_packet_size0: u8, - /// ID of product vendor. - vendor: u16 align(1), - /// ID of product. - product: u16 align(1), - /// Device version number, as BCD again. - bcd_device: u16 align(1), - /// Index of manufacturer name in string descriptor table. - manufacturer_s: u8, - /// Index of product name in string descriptor table. - product_s: u8, - /// Index of serial number in string descriptor table. - serial_s: u8, - /// Number of configurations supported by this device. - num_configurations: u8, - - pub fn serialize(self: *const @This()) [18]u8 { - var out: [18]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.bcd_usb & 0xff); - out[3] = @intCast((self.bcd_usb >> 8) & 0xff); - out[4] = self.device_class; - out[5] = self.device_subclass; - out[6] = self.device_protocol; - out[7] = self.max_packet_size0; - out[8] = @intCast(self.vendor & 0xff); - out[9] = @intCast((self.vendor >> 8) & 0xff); - out[10] = @intCast(self.product & 0xff); - out[11] = @intCast((self.product >> 8) & 0xff); - out[12] = @intCast(self.bcd_device & 0xff); - out[13] = @intCast((self.bcd_device >> 8) & 0xff); - out[14] = self.manufacturer_s; - out[15] = self.product_s; - out[16] = self.serial_s; - out[17] = self.num_configurations; - return out; - } -}; - -/// USB Device Qualifier Descriptor -/// This descriptor is mostly the same as the DeviceDescriptor -pub const DeviceQualifierDescriptor = extern struct { - pub const const_descriptor_type = DescType.DeviceQualifier; - - length: u8 = 10, - /// Type of this descriptor, must be `Device`. - descriptor_type: DescType = const_descriptor_type, - /// Version of the device descriptor / USB protocol, in binary-coded - /// decimal. This is typically `0x01_10` for USB 1.1. - bcd_usb: u16 align(1), - /// Class of device, giving a broad functional area. - device_class: u8, - /// Subclass of device, refining the class. - device_subclass: u8, - /// Protocol within the subclass. - device_protocol: u8, - /// Maximum unit of data this device can move. - max_packet_size0: u8, - /// Number of configurations supported by this device. - num_configurations: u8, - /// Reserved for future use; must be 0 - reserved: u8 = 0, - - pub fn serialize(self: *const @This()) [10]u8 { - var out: [10]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.bcd_usb & 0xff); - out[3] = @intCast((self.bcd_usb >> 8) & 0xff); - out[4] = self.device_class; - out[5] = self.device_subclass; - out[6] = self.device_protocol; - out[7] = self.max_packet_size0; - out[8] = self.num_configurations; - out[9] = self.reserved; - return out; - } -}; - -pub const DriverErrors = error{ - ExpectedInterfaceDescriptor, - UnsupportedInterfaceClassType, - UnsupportedInterfaceSubClassType, - UnexpectedDescriptor, -}; - -pub const UsbDevice = struct { - fn_ready: *const fn () bool, - fn_control_transfer: *const fn (setup: *const SetupPacket, data: []const u8) void, - fn_control_ack: *const fn (setup: *const SetupPacket) void, - fn_endpoint_open: *const fn (ep_desc: []const u8) void, - fn_endpoint_transfer: *const fn (ep_addr: u8, data: []const u8) void, - - pub fn ready(self: *@This()) bool { - return self.fn_ready(); - } - - pub fn control_transfer(self: *@This(), setup: *const SetupPacket, data: []const u8) void { - return self.fn_control_transfer(setup, data); - } - - pub fn control_ack(self: *@This(), setup: *const SetupPacket) void { - return self.fn_control_ack(setup); - } - - pub fn endpoint_open(self: *@This(), ep_desc: []const u8) void { - return self.fn_endpoint_open(ep_desc); - } - - pub fn endpoint_transfer(self: *@This(), ep_addr: u8, data: []const u8) void { - return self.fn_endpoint_transfer(ep_addr, data); - } -}; - -/// USB Class driver interface -pub const UsbClassDriver = struct { - ptr: *anyopaque, - fn_init: *const fn (ptr: *anyopaque, device: UsbDevice) void, - fn_open: *const fn (ptr: *anyopaque, cfg: []const u8) anyerror!usize, - fn_class_control: *const fn (ptr: *anyopaque, stage: ControlStage, setup: *const SetupPacket) bool, - fn_transfer: *const fn (ptr: *anyopaque, ep_addr: u8, data: []u8) void, - - pub fn init(self: *@This(), device: UsbDevice) void { - return self.fn_init(self.ptr, device); - } - - /// Driver open (set config) operation. Must return length of consumed driver config data. - pub fn open(self: *@This(), cfg: []const u8) !usize { - return self.fn_open(self.ptr, cfg); - } +pub const U16Le = extern struct { + value: [2]u8, - pub fn class_control(self: *@This(), stage: ControlStage, setup: *const SetupPacket) bool { - return self.fn_class_control(self.ptr, stage, setup); + pub fn from(val: u16) @This() { + var this: @This() = undefined; + std.mem.writeInt(u16, &this.value, val, .little); + return this; } - pub fn transfer(self: *@This(), ep_addr: u8, data: []u8) void { - return self.fn_transfer(self.ptr, ep_addr, data); + pub fn into(this: @This()) u16 { + return std.mem.readInt(U16Le, &this.value, .little); } }; diff --git a/core/src/core/usb/utils.zig b/core/src/core/usb/utils.zig deleted file mode 100644 index 09da008f4..000000000 --- a/core/src/core/usb/utils.zig +++ /dev/null @@ -1,52 +0,0 @@ -const std = @import("std"); -const types = @import("types.zig"); - -pub const BosConfig = struct { - const DESC_OFFSET_LEN = 0; - const DESC_OFFSET_TYPE = 1; - - pub fn get_desc_len(bos_cfg: []const u8) u8 { - return bos_cfg[DESC_OFFSET_LEN]; - } - - pub fn get_desc_type(bos_cfg: []const u8) ?types.DescType { - return types.DescType.from_u8(bos_cfg[DESC_OFFSET_TYPE]); - } - - pub fn get_data_u8(bos_cfg: []const u8, offset: u16) u8 { - return bos_cfg[offset]; - } - - pub fn get_data_u16(bos_cfg: []const u8, offset: u16) u16 { - const low_byte: u16 = bos_cfg[offset]; - const high_byte: u16 = bos_cfg[offset + 1]; - return (high_byte << 8) | low_byte; - } - - /// Only for temporal u8 fields use as u16 fields will have wrong values because BOS endianness - pub fn get_desc_as(comptime T: type, bos_cfg: []const u8) *const T { - return @ptrCast(@constCast(bos_cfg.ptr)); - } - - /// Only for temporal u8 fields use as u16 fields will have wrong values because BOS endianness - pub fn try_get_desc_as(comptime T: type, bos_cfg: []const u8) ?*const T { - if (bos_cfg.len == 0) return null; - const exp_desc_type = @field(T, "const_descriptor_type"); - const cfg_desc_type = bos_cfg[DESC_OFFSET_TYPE]; - if (cfg_desc_type != @intFromEnum(exp_desc_type)) { - return null; - } else { - return @ptrCast(@constCast(bos_cfg.ptr)); - } - } - - pub fn get_desc_next(bos_cfg: []const u8) []const u8 { - const len = bos_cfg[DESC_OFFSET_LEN]; - return bos_cfg[len..]; - } -}; - -test "Test try_get_desc_as" { - const cfg = [_]u8{ 7, 5, 129, 3, 8, 0, 16 }; - try std.testing.expect(BosConfig.try_get_desc_as(types.EndpointDescriptor, cfg[0..]) != null); -} diff --git a/core/src/core/usb/vendor.zig b/core/src/core/usb/vendor.zig deleted file mode 100644 index 7edd2eb34..000000000 --- a/core/src/core/usb/vendor.zig +++ /dev/null @@ -1,24 +0,0 @@ -const std = @import("std"); - -const types = @import("types.zig"); - -pub const VendorClassDriver = struct { - fn init(_: *anyopaque, _: types.UsbDevice) void {} - - fn open(_: *anyopaque, _: []const u8) !usize { - return 0; - } - - pub fn class_control(_: *anyopaque, _: types.ControlStage, _: *const types.SetupPacket) bool { - return true; - } - - pub fn driver(self: *@This()) types.UsbClassDriver { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - }; - } -}; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 62e723c25..964a5b589 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -5,92 +5,71 @@ const rp2xxx = microzig.hal; const flash = rp2xxx.flash; const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const clocks = rp2xxx.clocks; -const usb = rp2xxx.usb; -const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); -const uart_tx_pin = gpio.num(0); - -const usb_dev = rp2xxx.usb.Usb(.{}); - -const usb_packet_size = 64; -const usb_config_len = usb.templates.config_descriptor_len + usb.templates.hid_in_out_descriptor_len; -const usb_config_descriptor = - usb.templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++ - usb.templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, usb.Endpoint.to_address(1, .Out), usb.Endpoint.to_address(1, .In), usb_packet_size, 0); - -var driver_hid = usb.hid.HidClassDriver{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; -var drivers = [_]usb.types.UsbClassDriver{driver_hid.driver()}; - -// This is our device configuration -pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ - .device_descriptor = &.{ - .descriptor_type = usb.DescType.Device, - .bcd_usb = 0x0200, - .device_class = 0, - .device_subclass = 0, - .device_protocol = 0, - .max_packet_size0 = 64, - .vendor = 0xCafe, - .product = 2, - .bcd_device = 0x0100, - // Those are indices to the descriptor strings - // Make sure to provide enough string descriptors! - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .config_descriptor = &usb_config_descriptor, - .lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409) - .descriptor_strings = &.{ - &usb.utils.utf8_to_utf16_le("Raspberry Pi"), - &usb.utils.utf8_to_utf16_le("Pico Test Device"), - &usb.utils.utf8_to_utf16_le("cafebabe"), - }, - .drivers = &drivers, -}; - -pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { - std.log.err("panic: {s}", .{message}); - @breakpoint(); - while (true) {} -} pub const microzig_options = microzig.Options{ .log_level = .debug, .logFn = rp2xxx.uart.log, }; +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO12 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; +const pins = pin_config.pins(); + +// This is our device configuration +const Usb = rp2xxx.usb.Usb(.{ .controller_config = .{ + .device_triple = .{ + .class = .Miscellaneous, + .subclass = 2, + .protocol = 1, + }, + .attributes = .{ .self_powered = false }, + .drivers = &.{.{ + .name = "hid", + .Type = microzig.core.usb.hid.HidClassDriver, + .endpoints = &.{ + .{ .name = "main", .value = .ep1 }, + }, + .strings = &.{ + .{ .name = "name", .value = "Board HID" }, + }, + }}, +} }); +var usb: Usb = undefined; + pub fn main() !void { + pin_config.apply(); + // init uart logging - uart_tx_pin.set_function(.uart); uart.apply(.{ .clock_config = rp2xxx.clock_config, }); rp2xxx.uart.init_logger(uart); - led.set_function(.sio); - led.set_direction(.out); - led.put(1); - - // First we initialize the USB clock - usb_dev.init_clk(); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&DEVICE_CONFIGURATION) catch unreachable; - var old: u64 = time.get_time_since_boot().to_us(); - var new: u64 = 0; + usb = .init(); + + pins.led.put(1); + var last_led_toggle: u64 = time.get_time_since_boot().to_us(); + const delay_us = 500_000; + while (true) { // You can now poll for USB events - usb_dev.task( - false, // debug output over UART [Y/n] - ) catch unreachable; + usb.poll(); + + const hid = if (usb.controller.drivers) |*drivers| + &drivers.hid + else // This means the USB connection has not yet been established + continue; + hid.report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut; - new = time.get_time_since_boot().to_us(); - if (new - old > 500000) { - old = new; - led.toggle(); + const now = time.get_time_since_boot().to_us(); + if (now - last_led_toggle > delay_us) { + last_led_toggle += delay_us; + pins.led.toggle(); } } } diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 044835318..871cd5dda 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -5,140 +5,93 @@ const rp2xxx = microzig.hal; const flash = rp2xxx.flash; const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const usb = rp2xxx.usb; -const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); -const uart_tx_pin = gpio.num(0); -const uart_rx_pin = gpio.num(1); - -const usb_dev = rp2xxx.usb.Usb(.{}); - -const usb_config_len = usb.templates.config_descriptor_len + usb.templates.cdc_descriptor_len; -const usb_config_descriptor = - usb.templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ - usb.templates.cdc_descriptor(0, 4, usb.Endpoint.to_address(1, .In), 8, usb.Endpoint.to_address(2, .Out), usb.Endpoint.to_address(2, .In), 64); - -var driver_cdc: usb.cdc.CdcClassDriver(usb_dev) = .{}; -var drivers = [_]usb.types.UsbClassDriver{driver_cdc.driver()}; - -// This is our device configuration -pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ - .device_descriptor = &.{ - .descriptor_type = usb.DescType.Device, - .bcd_usb = 0x0200, - .device_class = 0xEF, - .device_subclass = 2, - .device_protocol = 1, - .max_packet_size0 = 64, - .vendor = 0x2E8A, - .product = 0x000a, - .bcd_device = 0x0100, - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 0, - .num_configurations = 1, - }, - .config_descriptor = &usb_config_descriptor, - .lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409) - .descriptor_strings = &.{ - &usb.utils.utf8_to_utf16_le("Raspberry Pi"), - &usb.utils.utf8_to_utf16_le("Pico Test Device"), - &usb.utils.utf8_to_utf16_le("someserial"), - &usb.utils.utf8_to_utf16_le("Board CDC"), - }, - .drivers = &drivers, -}; - -pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { - std.log.err("panic: {s}", .{message}); - @breakpoint(); - while (true) {} -} pub const microzig_options = microzig.Options{ .log_level = .debug, .logFn = rp2xxx.uart.log, }; -pub fn main() !void { - led.set_function(.sio); - led.set_direction(.out); - led.put(1); +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO12 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; +const pins = pin_config.pins(); - inline for (&.{ uart_tx_pin, uart_rx_pin }) |pin| { - pin.set_function(.uart); +const CdcDriver = microzig.core.usb.cdc.CdcClassDriver; + +// This is our device configuration +const Usb = rp2xxx.usb.Usb(.{ .controller_config = .{ + .attributes = .{ .self_powered = false }, + .drivers = &.{.{ + .name = "serial", + .Type = CdcDriver, + .endpoints = &.{ + .{ .name = "notifi", .value = .ep1 }, + .{ .name = "data", .value = .ep2 }, + }, + .strings = &.{ + .{ .name = "name", .value = "Board CDC" }, + }, + }}, +} }); +var usb: Usb = undefined; + +// Poll the USB device for events until all data is sent. +fn write_all(serial: *CdcDriver, data: []const u8) void { + var offset: usize = 0; + while (offset < data.len) { + offset += serial.write(data[offset..], usb.interface()); + usb.poll(); } +} + +pub fn main() !void { + pin_config.apply(); uart.apply(.{ .clock_config = rp2xxx.clock_config, }); - rp2xxx.uart.init_logger(uart); - // First we initialize the USB clock - usb_dev.init_clk(); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&DEVICE_CONFIGURATION) catch unreachable; - var old: u64 = time.get_time_since_boot().to_us(); - var new: u64 = 0; + usb = .init(); + + pins.led.put(1); + var last_led_toggle: u64 = time.get_time_since_boot().to_us(); + const delay_us = 500_000; var i: u32 = 0; while (true) { // You can now poll for USB events - usb_dev.task( - false, // debug output over UART [Y/n] - ) catch unreachable; - - new = time.get_time_since_boot().to_us(); - if (new - old > 500000) { - old = new; - led.toggle(); - i += 1; + usb.poll(); + + const usb_serial = if (usb.controller.drivers) |*drivers| + &drivers.serial + else // This means the USB connection has not yet been established + continue; + + const now = time.get_time_since_boot().to_us(); + if (now - last_led_toggle > delay_us) { + last_led_toggle += delay_us; + pins.led.toggle(); + + i +%= 1; std.log.info("cdc test: {}\r\n", .{i}); - usb_cdc_write("This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); + var tx_buf: [1024]u8 = undefined; + const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); + write_all(usb_serial, text); } // read and print host command if present - const message = usb_cdc_read(); - if (message.len > 0) { - usb_cdc_write("Your message to me was: {s}\r\n", .{message}); + var rx_buf: [64]u8 = undefined; + const len = usb_serial.read(usb.interface(), &rx_buf); + if (len > 0) { + write_all(usb_serial, "Your message to me was: '"); + write_all(usb_serial, rx_buf[0..len]); + write_all(usb_serial, "'\r\n"); } } } - -var usb_tx_buff: [1024]u8 = undefined; - -// Transfer data to host -// NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled -pub fn usb_cdc_write(comptime fmt: []const u8, args: anytype) void { - const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; - - var write_buff = text; - while (write_buff.len > 0) { - write_buff = driver_cdc.write(write_buff); - usb_dev.task(false) catch unreachable; - } - // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them - _ = driver_cdc.write_flush(); - usb_dev.task(false) catch unreachable; -} - -var usb_rx_buff: [1024]u8 = undefined; - -// Receive data from host -// NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation -pub fn usb_cdc_read() []const u8 { - var total_read: usize = 0; - var read_buff: []u8 = usb_rx_buff[0..]; - - while (true) { - const len = driver_cdc.read(read_buff); - read_buff = read_buff[len..]; - total_read += len; - if (len == 0) break; - } - - return usb_rx_buff[0..total_read]; -} diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index a64c0f95f..ce80d0c45 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -1,195 +1,164 @@ //! USB device implementation //! //! Initial inspiration: cbiffle's Rust [implementation](https://github.com/cbiffle/rp2040-usb-device-in-one-file/blob/main/src/main.rs) -//! Currently progressing towards adopting the TinyUSB like API const std = @import("std"); +const assert = std.debug.assert; +const enumFromInt = std.meta.intToEnum; const microzig = @import("microzig"); -const peripherals = microzig.chip.peripherals; -const chip = microzig.hal.compatibility.chip; - -pub const usb = microzig.core.usb; -pub const types = usb.types; -pub const hid = usb.hid; -pub const cdc = usb.cdc; -pub const vendor = usb.vendor; -pub const templates = usb.templates.DescriptorsConfigTemplates; -pub const utils = usb.UsbUtils; - -const resets = @import("resets.zig"); - -pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; - -pub const UsbConfig = struct { - // Comptime defined supported max endpoints number, can be reduced to save RAM space - max_endpoints_count: u8 = RP2XXX_MAX_ENDPOINTS_COUNT, - max_interfaces_count: u8 = 16, +const USB = microzig.chip.peripherals.USB; +const usb = microzig.core.usb; +const EpNum = usb.types.Endpoint.Num; + +pub const default = struct { + pub const strings: usb.Config.DeviceStrings = .{ + .manufacturer = "Raspberry Pi", + .product = "Pico Test Device", + .serial = "00000000", + }; + pub const vid: u16 = 0x2E8A; + pub const pid: u16 = 0x000a; + pub const bcd_usb = 0x02_00; + pub const transfer_size = 64; }; -/// The rp2040 usb device impl -/// -/// We create a concrete implementaion by passing a handful -/// of system specific functions to Usb(). Those functions -/// are used by the abstract USB impl of microzig. -pub fn Usb(comptime config: UsbConfig) type { - return usb.Usb(F(config)); -} - -pub const DeviceConfiguration = usb.DeviceConfiguration; -pub const DeviceDescriptor = usb.DeviceDescriptor; -pub const DescType = usb.types.DescType; -pub const InterfaceDescriptor = usb.types.InterfaceDescriptor; -pub const ConfigurationDescriptor = usb.types.ConfigurationDescriptor; -pub const EndpointDescriptor = usb.types.EndpointDescriptor; -pub const EndpointConfiguration = usb.EndpointConfiguration; -pub const Dir = usb.types.Dir; -pub const TransferType = usb.types.TransferType; -pub const Endpoint = usb.types.Endpoint; - -pub const utf8ToUtf16Le = usb.utf8ToUtf16Le; - -const BufferControlMmio = microzig.mmio.Mmio(@TypeOf(microzig.chip.peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL).underlying_type); -const EndpointControlMimo = microzig.mmio.Mmio(@TypeOf(peripherals.USB_DPRAM.EP1_IN_CONTROL).underlying_type); -const EndpointType = microzig.chip.types.peripherals.USB_DPRAM.EndpointType; - -const HardwareEndpoint = struct { - configured: bool, - ep_addr: u8, - next_pid_1: bool, - transfer_type: types.TransferType, - endpoint_control_index: usize, - buffer_control_index: usize, - awaiting_rx: bool, - - max_packet_size: u11, - buffer_control: ?*BufferControlMmio, - endpoint_control: ?*EndpointControlMimo, - data_buffer: []u8, +pub const Config = struct { + /// How many nops to insert for synchronization with the USB hardware. + synchronization_nops: comptime_int = 3, + /// USB controller configuration. + controller_config: usb.Config, }; -const rp2xxx_buffers = struct { - // Address 0x100-0xfff (3840 bytes) can be used for data buffers - const USB_DPRAM_DATA_BUFFER_BASE = 0x50100100; - - const CTRL_EP_BUFFER_SIZE = 64; - - const USB_EP0_BUFFER0 = USB_DPRAM_DATA_BUFFER_BASE; - const USB_EP0_BUFFER1 = USB_DPRAM_DATA_BUFFER_BASE + CTRL_EP_BUFFER_SIZE; - - const USB_DATA_BUFFER = USB_DPRAM_DATA_BUFFER_BASE + (2 * CTRL_EP_BUFFER_SIZE); - const USB_DATA_BUFFER_SIZE = 3840 - (2 * CTRL_EP_BUFFER_SIZE); - - const ep0_buffer0: *[CTRL_EP_BUFFER_SIZE]u8 = @as(*[CTRL_EP_BUFFER_SIZE]u8, @ptrFromInt(USB_EP0_BUFFER0)); - const ep0_buffer1: *[CTRL_EP_BUFFER_SIZE]u8 = @as(*[CTRL_EP_BUFFER_SIZE]u8, @ptrFromInt(USB_EP0_BUFFER1)); - const data_buffer: *[USB_DATA_BUFFER_SIZE]u8 = @as(*[USB_DATA_BUFFER_SIZE]u8, @ptrFromInt(USB_DATA_BUFFER)); - - fn data_offset(ep_data_buffer: []u8) u16 { - const buf_base = @intFromPtr(&ep_data_buffer[0]); - const dpram_base = @intFromPtr(peripherals.USB_DPRAM); - return @as(u16, @intCast(buf_base - dpram_base)); +const DualPortRam = extern struct { + const buffer_len_mult = 64; + const total_size = 4096; + + const peri = microzig.chip.peripherals.USB_DPRAM; + const EpCtrl = @TypeOf(peri.EP1_IN_CONTROL); + const BufCtrl = @TypeOf(peri.EP0_IN_BUFFER_CONTROL); + + setup: usb.types.SetupPacket, + ep_ctrl_raw: [15][2]EpCtrl, + buf_ctrl_raw: [16][2]BufCtrl, + buffer0: [buffer_len_mult]u8, + buffer1: [buffer_len_mult]u8, + + var alloc_top: u16 = @sizeOf(@This()); + + fn ep_open(this: *@This(), ep_num: EpNum, ep_dir: usb.types.Dir, transfer_type: usb.types.TransferType) u16 { + var reg = this.ep_ctrl(ep_num, ep_dir) orelse + std.debug.panic("Endpoint 0 should not be opened.", .{}); + + const buf_idx = alloc_top; + alloc_top += buffer_len_mult; + if (alloc_top > total_size) + std.debug.panic("USB controller out of memory.", .{}); + + reg.write(.{ + .ENABLE = 1, + .DOUBLE_BUFFERED = 0, + .INTERRUPT_PER_BUFF = 1, + .INTERRUPT_PER_DOUBLE_BUFF = 1, + .ENDPOINT_TYPE = switch (transfer_type) { + .Control => std.debug.panic("Only endpoint 0 can be a control endpoint.", .{}), + .Isochronous => std.debug.panic("Isochronous endpoints are not implemented", .{}), + .Bulk => .bulk, + .Interrupt => .interrupt, + }, + .INTERRUPT_ON_STALL = 0, + .INTERRUPT_ON_NAK = 0, + .BUFFER_ADDRESS = buf_idx, + }); + + return buf_idx; } -}; -const rp2xxx_endpoints = struct { - const USB_DPRAM_BASE = 0x50100000; - const USB_DPRAM_BUFFERS_BASE = USB_DPRAM_BASE + 0x100; - const USB_DPRAM_BUFFERS_CTRL_BASE = USB_DPRAM_BASE + 0x80; - const USB_DPRAM_ENDPOINTS_CTRL_BASE = USB_DPRAM_BASE + 0x8; - - pub fn get_ep_ctrl(ep_num: u8, ep_dir: types.Dir) ?*EndpointControlMimo { - if (ep_num == 0) { + fn ep_ctrl(this: *@This(), num: EpNum, dir: usb.types.Dir) ?*volatile EpCtrl { + if (std.math.sub(u4, @intFromEnum(num), 1)) |idx| + return &this.ep_ctrl_raw[idx][@intFromBool(dir == .Out)] + else |_| return null; - } else { - const dir_index: u8 = if (ep_dir == .In) 0 else 1; - const ep_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_ENDPOINTS_CTRL_BASE)); - return @ptrCast(&ep_ctrl_base[ep_num - 1][dir_index]); - } } - pub fn get_buf_ctrl(ep_num: u8, ep_dir: types.Dir) ?*BufferControlMmio { - const dir_index: u8 = if (ep_dir == .In) 0 else 1; - const buf_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_BUFFERS_CTRL_BASE)); - return @ptrCast(&buf_ctrl_base[ep_num][dir_index]); + fn buf_ctrl(this: *@This(), num: EpNum, dir: usb.types.Dir) *volatile BufCtrl { + return &this.buf_ctrl_raw[@intFromEnum(num)][@intFromBool(dir == .Out)]; + } + + // TODO: Double buffered mode? + fn buffer(this: *@This(), num: EpNum, dir: usb.types.Dir) []u8 { + if (this.ep_ctrl(num, dir)) |ep| { + const addr = ep.read().BUFFER_ADDRESS; + const mem: *[total_size]u8 = @ptrCast(this); + // TODO: Isochronous endpoints can have other sizes. + const size = 64; + return mem[addr .. addr + size]; + } else return &this.buffer0; } }; -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Code -// +++++++++++++++++++++++++++++++++++++++++++++++++ +comptime { + // Sanity check that offsets match the datasheet. + assert(@offsetOf(DualPortRam, "ep_ctrl_raw") == 0x8); + assert(@offsetOf(DualPortRam, "buf_ctrl_raw") == 0x80); + assert(@offsetOf(DualPortRam, "buffer0") == 0x100); + assert(@offsetOf(DualPortRam, "buffer1") == 0x140); + assert(@sizeOf(DualPortRam) == 0x180); +} -/// A set of functions required by the abstract USB impl to -/// create a concrete one. -pub fn F(comptime config: UsbConfig) type { - comptime { - if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) { - @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); - } - } +// `@volatileCast` is sound because the USB hardware only modifies the buffers +// after we transfer ownership by accesing a volatile register. +const dpram: *DualPortRam = @ptrCast(@volatileCast(DualPortRam.peri)); +pub fn Usb(comptime config: Config) type { return struct { - pub const cfg_max_endpoints_count: u8 = config.max_endpoints_count; - pub const cfg_max_interfaces_count: u8 = config.max_interfaces_count; - pub const high_speed = false; - - var endpoints: [config.max_endpoints_count][2]HardwareEndpoint = undefined; - var data_buffer: []u8 = rp2xxx_buffers.data_buffer; - - /// Initialize the USB clock to 48 MHz - /// - /// This requres that the system clock has been set up before hand - /// using the 12 MHz crystal. - pub fn usb_init_clk() void { - // Bring PLL_USB up to 48MHz. PLL_USB is clocked from refclk, which we've - // already moved over to the 12MHz XOSC. We just need to make it x4 that - // clock. - // - // Configure it: - // - // RFDIV = 1 - // FBDIV = 100 => FOUTVC0 = 1200 MHz - peripherals.PLL_USB.CS.modify(.{ .REFDIV = 1 }); - peripherals.PLL_USB.FBDIV_INT.modify(.{ .FBDIV_INT = 100 }); - peripherals.PLL_USB.PWR.modify(.{ .PD = 0, .VCOPD = 0 }); - // Wait for lock - while (peripherals.PLL_USB.CS.read().LOCK == 0) {} - // Set up post dividers to enable output - // - // POSTDIV1 = POSTDIV2 = 5 - // PLL_USB FOUT = 1200 MHz / 25 = 48 MHz - peripherals.PLL_USB.PRIM.modify(.{ .POSTDIV1 = 5, .POSTDIV2 = 5 }); - peripherals.PLL_USB.PWR.modify(.{ .POSTDIVPD = 0 }); - // Switch usbclk to be derived from PLLUSB - peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_pll_usb }); - - // We now have the stable 48MHz reference clock required for USB: + const Controller = usb.Controller(blk: { + var cfg = config.controller_config; + cfg.strings = cfg.strings orelse default.strings; + cfg.vid = cfg.vid orelse default.vid; + cfg.pid = cfg.pid orelse default.pid; + cfg.bcd_usb = cfg.bcd_usb orelse default.bcd_usb; + cfg.max_transfer_size = cfg.max_transfer_size orelse default.transfer_size; + break :blk cfg; + }); + + const State = union(enum) { + sending: []const u8, // Slice of data left to be sent. + no_buffer: ?u7, // Optionally a new address. + ready, + waiting_ack, // Host is expected to send an ACK. + }; + + state: State, + controller: Controller, + + pub fn interface(this: *@This()) usb.DeviceInterface { + return .{ + .ptr = this, + .transfer = &transfer, + }; } - pub fn usb_init_device(_: *usb.DeviceConfiguration) void { - if (chip == .RP2350) { - peripherals.USB.MAIN_CTRL.modify(.{ - .PHY_ISO = 0, - }); - } + /// Initialize USB hardware and request enumertation from USB host. + pub fn init() @This() { + const chip = microzig.hal.compatibility.chip; + + if (chip == .RP2350) + USB.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); // Clear the control portion of DPRAM. This may not be necessary -- the // datasheet is ambiguous -- but the C examples do it, and so do we. - peripherals.USB_DPRAM.SETUP_PACKET_LOW.write_raw(0); - peripherals.USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); - - for (1..cfg_max_endpoints_count) |i| { - rp2xxx_endpoints.get_ep_ctrl(@intCast(i), .In).?.write_raw(0); - rp2xxx_endpoints.get_ep_ctrl(@intCast(i), .Out).?.write_raw(0); - } - - for (0..cfg_max_endpoints_count) |i| { - rp2xxx_endpoints.get_buf_ctrl(@intCast(i), .In).?.write_raw(0); - rp2xxx_endpoints.get_buf_ctrl(@intCast(i), .Out).?.write_raw(0); - } + dpram.* = .{ + .setup = @bitCast(@as(u64, 0)), + .ep_ctrl_raw = @splat(@bitCast(@as(u64, 0))), + .buf_ctrl_raw = @splat(@bitCast(@as(u64, 0))), + .buffer0 = undefined, + .buffer1 = undefined, + }; // Mux the controller to the onboard USB PHY. I was surprised that there are // alternatives to this, but, there are. - peripherals.USB.USB_MUXING.modify(.{ + USB.USB_MUXING.modify(.{ .TO_PHY = 1, // This bit is also set in the SDK example, without any discussion. It's // undocumented (being named does not count as being documented). @@ -200,26 +169,24 @@ pub fn F(comptime config: UsbConfig) type { // let us detect being plugged into a host (the Pi Pico, to its credit, // does). For maximum compatibility, we'll set the hardware to always // pretend VBUS has been detected. - peripherals.USB.USB_PWR.modify(.{ + USB.USB_PWR.modify(.{ .VBUS_DETECT = 1, .VBUS_DETECT_OVERRIDE_EN = 1, }); // Enable controller in device mode. - peripherals.USB.MAIN_CTRL.modify(.{ + USB.MAIN_CTRL.modify(.{ .CONTROLLER_EN = 1, .HOST_NDEVICE = 0, }); // Request to have an interrupt (which really just means setting a bit in // the `buff_status` register) every time a buffer moves through EP0. - peripherals.USB.SIE_CTRL.modify(.{ - .EP0_INT_1BUF = 1, - }); + USB.SIE_CTRL.modify(.{ .EP0_INT_1BUF = 1 }); // Enable interrupts (bits set in the `ints` register) for other conditions // we use: - peripherals.USB.INTE.modify(.{ + USB.INTE.modify(.{ // A buffer is done .BUFF_STATUS = 1, // The host has reset us @@ -228,289 +195,219 @@ pub fn F(comptime config: UsbConfig) type { .SETUP_REQ = 1, }); - @memset(std.mem.asBytes(&endpoints), 0); - endpoint_open(Endpoint.EP0_IN_ADDR, 64, types.TransferType.Control); - endpoint_open(Endpoint.EP0_OUT_ADDR, 64, types.TransferType.Control); - // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. - peripherals.USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); - } - - /// Configures a given endpoint to send data (device-to-host, IN) when the host - /// next asks for it. - /// - /// The contents of `buffer` will be _copied_ into USB SRAM, so you can - /// reuse `buffer` immediately after this returns. No need to wait for the - /// packet to be sent. - pub fn usb_start_tx( - ep_addr: u8, - buffer: []const u8, - ) void { - // It is technically possible to support longer buffers but this demo - // doesn't bother. - // TODO: assert!(buffer.len() <= 64); - // You should only be calling this on IN endpoints. - // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::In); - - const ep = hardware_endpoint_get_by_address(ep_addr); - - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, ep.data_buffer[0..buffer.len], buffer); - - // Configure the IN: - const np: u1 = if (ep.next_pid_1) 1 else 0; - - // The AVAILABLE bit in the buffer control register should be set - // separately to the rest of the data in the buffer control register, - // so that the rest of the data in the buffer control register is - // accurate when the AVAILABLE bit is set. - - // Write the buffer information to the buffer control register - ep.buffer_control.?.modify(.{ - .PID_0 = np, // DATA0/1, depending - .FULL_0 = 1, // We have put data in - .LENGTH_0 = @as(u10, @intCast(buffer.len)), // There are this many bytes - }); - - // Nop for some clock cycles - // use volatile so the compiler doesn't optimize the nops away - asm volatile ( - \\ nop - \\ nop - \\ nop - ); - - // Set available bit - ep.buffer_control.?.modify(.{ - .AVAILABLE_0 = 1, // The data is for the computer to use now - }); - - ep.next_pid_1 = !ep.next_pid_1; - } - - pub fn usb_start_rx( - ep_addr: u8, - len: usize, - ) void { - // It is technically possible to support longer buffers but this demo - // doesn't bother. - // TODO: assert!(len <= 64); - // You should only be calling this on OUT endpoints. - // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::Out); - - const ep = hardware_endpoint_get_by_address(ep_addr); - - if (ep.awaiting_rx) - return; - - // Check which DATA0/1 PID this endpoint is expecting next. - const np: u1 = if (ep.next_pid_1) 1 else 0; - // Configure the OUT: - ep.buffer_control.?.modify(.{ - .PID_0 = np, // DATA0/1 depending - .FULL_0 = 0, // Buffer is NOT full, we want the computer to fill it - .AVAILABLE_0 = 1, // It is, however, available to be filled - .LENGTH_0 = @as(u10, @intCast(len)), // Up tho this many bytes - }); - - // Flip the DATA0/1 PID for the next receive - ep.next_pid_1 = !ep.next_pid_1; - ep.awaiting_rx = true; - } - - pub fn endpoint_reset_rx(ep_addr: u8) void { - const ep = hardware_endpoint_get_by_address(ep_addr); - ep.awaiting_rx = false; - } - - /// Check which interrupt flags are set - pub fn get_interrupts() usb.InterruptStatus { - const ints = peripherals.USB.INTS.read(); + USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); return .{ - .BuffStatus = if (ints.BUFF_STATUS == 1) true else false, - .BusReset = if (ints.BUS_RESET == 1) true else false, - .DevConnDis = if (ints.DEV_CONN_DIS == 1) true else false, - .DevSuspend = if (ints.DEV_SUSPEND == 1) true else false, - .DevResumeFromHost = if (ints.DEV_RESUME_FROM_HOST == 1) true else false, - .SetupReq = if (ints.SETUP_REQ == 1) true else false, + .state = .ready, + .controller = .init, }; } - /// Returns a received USB setup packet - /// - /// Side effect: The setup request status flag will be cleared - /// - /// One can assume that this function is only called if the - /// setup request falg is set. - pub fn get_setup_packet() usb.types.SetupPacket { - // Clear the status flag (write-one-to-clear) - peripherals.USB.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); - - // This assumes that the setup packet is arriving on EP0, our - // control endpoint. Which it should be. We don't have any other - // Control endpoints. - - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers, - // which is, like, not _wrong_ but slightly awkward since it means - // we can't just treat it as bytes. Instead, copy it out to a byte - // array. - var setup_packet: [8]u8 = @splat(0); - const spl: u32 = peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw; - const sph: u32 = peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw; - @memcpy(setup_packet[0..4], std.mem.asBytes(&spl)); - @memcpy(setup_packet[4..8], std.mem.asBytes(&sph)); - // Reinterpret as setup packet - return std.mem.bytesToValue(usb.types.SetupPacket, &setup_packet); + /// Called when a setup packet is received. + fn process_setup(this: *@This()) ?[]const u8 { + // Copy the setup packet out of its dedicated buffer at the base of USB SRAM. + const setup = dpram.setup; + + std.log.info("setup: {any}", .{setup}); + + // Reset PID to 1 for EP0 IN. Every DATA packet we send in response + // to an IN on EP0 needs to use PID DATA1. + dpram.buf_ctrl(.ep0, .In).modify(.{ .PID_0 = 0 }); + + switch (setup.request_type.recipient) { + .Device => blk: { + if (setup.request_type.type != .Standard) break :blk; + switch (enumFromInt( + usb.types.SetupRequest, + setup.request, + ) catch break :blk) { + .SetAddress => { + this.state = .{ .no_buffer = @intCast(setup.value) }; + return usb.ACK; + }, + .SetConfiguration => if (this.controller.set_configuration(this, &setup)) + return usb.ACK, + .GetDescriptor => if (Controller.get_descriptor(&setup)) |desc| + return desc, + .SetFeature => if (this.controller.set_feature( + @intCast(setup.value >> 8), + setup.index, + true, + )) return usb.ACK, + } + }, + .Interface => if (this.controller.interface_setup(&setup)) |data| + return data, + else => {}, + } + return usb.NAK; } - /// Called on a bus reset interrupt - pub fn bus_reset() void { - // Acknowledge by writing the write-one-to-clear status bit. - peripherals.USB.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); - peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = 0 }); - } + /// Polls the device for events. Not thread safe, this must be called + /// from the same thread that interacts with all the drivers. + pub fn poll(this: *@This()) void { + const ints = USB.INTS.read(); + const SieStatus = @TypeOf(USB.SIE_STATUS).underlying_type; + + switch (this.state) { + .ready => |_| if (ints.SETUP_REQ != 0) { + // Clear the status flag (write one to clear) + var sie_status: SieStatus = @bitCast(@as(u32, 0)); + sie_status.SETUP_REC = 1; + USB.SIE_STATUS.write(sie_status); + if (this.process_setup()) |data| { + std.log.info("setup response: '{}' {}", .{ data.len, dpram.buf_ctrl(.ep0, .In).read().AVAILABLE_0 }); + const len = this.interface().write_buffered(data, .ep0, true).?; + + if (len != 0) { + this.state = .{ .sending = data[len..] }; + } else if (this.state != .no_buffer) { + this.state = .{ .no_buffer = null }; + } + } + }, + else => {}, + } + if (ints.BUFF_STATUS != 0) + USB.BUFF_STATUS.write(USB.BUFF_STATUS.read()); + + const buf_ctrl_in = dpram.buf_ctrl(.ep0, .In).read(); + switch (this.state) { + .sending => |data| if (this.interface().write_buffered(data, .ep0, true)) |len| { + if (len != 0) + this.state = .{ .sending = data[len..] } + else { + // TODO: Check return value. + // _ = this.interface().read_exact("", .ep0, 0); + listen(.ep0, 0); + this.state = .waiting_ack; + } + }, + .no_buffer => |new_address| if (buf_ctrl_in.AVAILABLE_0 == 0) { + // Finish the delayed SetAddress request, if there is one: + if (new_address) |addr| + USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + this.state = .ready; + }, + .ready => {}, + .waiting_ack => if (buf_ctrl_in.AVAILABLE_0 == 0) { + this.state = .ready; + } else if (this.interface().read_exact("", .ep0, null)) |len| { + assert(len == 0); + this.state = .ready; + }, + } - pub fn set_address(addr: u7) void { - peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = addr }); - } + if (ints.BUS_RESET != 0) { + this.controller.deinit(); + this.controller = .init; - pub fn reset_ep0() void { - var ep = hardware_endpoint_get_by_address(Endpoint.EP0_IN_ADDR); - ep.next_pid_1 = true; - } - - fn hardware_endpoint_get_by_address(ep_addr: u8) *HardwareEndpoint { - const num = Endpoint.num_from_address(ep_addr); - const dir = Endpoint.dir_from_address(ep_addr); - return &endpoints[num][dir.as_number()]; + var sie_status: SieStatus = @bitCast(@as(u32, 0)); + sie_status.BUS_RESET = 1; + USB.SIE_STATUS.write(sie_status); + USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); + } } - pub fn endpoint_open(ep_addr: u8, max_packet_size: u11, transfer_type: types.TransferType) void { - const ep_num = Endpoint.num_from_address(ep_addr); - const ep = hardware_endpoint_get_by_address(ep_addr); - - endpoint_init(ep_addr, max_packet_size, transfer_type); + fn submit_buffer_in(ep_in: EpNum, len: u10) void { + // Write the buffer information to the buffer control register + const buf_ctrl = dpram.buf_ctrl(ep_in, .In); + var rmw = buf_ctrl.read(); + rmw.PID_0 ^= 1; // Flip DATA0/1 + rmw.FULL_0 = 1; // We have put data in + rmw.LENGTH_0 = len; // There are this many bytes + + // If the CPU is running at a higher clock speed than USB, + // the AVAILABLE bit in the buffer control register should be set + // separately to the rest of the data in the buffer control register, + // so that the rest of the data in the buffer control register is + // accurate when the AVAILABLE bit is set. - if (ep_num != 0) { - endpoint_alloc(ep) catch {}; - endpoint_enable(ep); + if (config.synchronization_nops != 0) { + buf_ctrl.write(rmw); + asm volatile ("nop\n" ** config.synchronization_nops); } - } - fn endpoint_init(ep_addr: u8, max_packet_size: u11, transfer_type: types.TransferType) void { - const ep_num = Endpoint.num_from_address(ep_addr); - const ep_dir = Endpoint.dir_from_address(ep_addr); + rmw.AVAILABLE_0 = 1; + buf_ctrl.write(rmw); + } - std.debug.assert(ep_num <= cfg_max_endpoints_count); + /// See interface description. + pub fn transfer(ptr: *anyopaque, opts: usb.DeviceInterface.Options, ep_num: EpNum, df: bool) ?u16 { + const this: *@This() = @ptrCast(@alignCast(ptr)); + _ = this; - var ep = hardware_endpoint_get_by_address(ep_addr); - ep.ep_addr = ep_addr; - ep.max_packet_size = max_packet_size; - ep.transfer_type = transfer_type; - ep.next_pid_1 = false; - ep.awaiting_rx = false; + const buf_ctrl = dpram.buf_ctrl(ep_num, opts); + var rmw = buf_ctrl.read(); + if (rmw.AVAILABLE_0 == 1) return null; - ep.buffer_control = rp2xxx_endpoints.get_buf_ctrl(ep_num, ep_dir); - ep.endpoint_control = rp2xxx_endpoints.get_ep_ctrl(ep_num, ep_dir); + const buffer = dpram.buffer(ep_num, opts); + const avail = switch (opts) { + .Out => buffer[if (rmw.FULL_0 == 1) 0 else rmw.LENGTH_1..rmw.LENGTH_0], + .In => buffer[if (rmw.FULL_0 == 0) 0 else rmw.LENGTH_0..], + }; - if (ep_num == 0) { - // ep0 has fixed data buffer - ep.data_buffer = rp2xxx_buffers.ep0_buffer0; + if (df and buffer.ptr != avail.ptr) + std.debug.panic("residual {any} data on {any}: {}", .{ opts, ep_num, if (opts == .In) rmw.LENGTH_0 else rmw.LENGTH_1 }); + + const len = @min(avail.len, opts.len()); + rmw.FULL_0 = @intFromEnum(opts); + + switch (opts) { + .Out => |out| { + if (df and len != avail.len) + std.debug.panic("could not read full packet on {any} {} {}", .{ ep_num, len, avail.len }); + + std.mem.copyForwards(u8, out.data[0..len], avail[0..len]); + + if (len == avail.len and out.listen != null) + listen(ep_num, out.listen.?) + else { + rmw.LENGTH_1 = @intCast(avail.ptr - buffer.ptr + len); + buf_ctrl.write(rmw); + } + }, + .In => |in| { + if (df and len != in.data.len) + std.debug.panic("could not send full packet on {any}", .{ep_num}); + + std.mem.copyForwards(u8, avail[0..len], in.data[0..len]); + + rmw.LENGTH_0 = @intCast(avail.ptr - buffer.ptr + len); + if (df or len == avail.len or in.flush) + submit_buffer_in(ep_num, rmw.LENGTH_0) + else { + buf_ctrl.write(rmw); + } + }, } + return @intCast(len); } - fn endpoint_alloc(ep: *HardwareEndpoint) !void { - // round up size to multiple of 64 - var size = try std.math.divCeil(u11, ep.max_packet_size, 64) * 64; - // double buffered Bulk endpoint - if (ep.transfer_type == .Bulk) { - size *= 2; - } + /// See interface description. + pub fn listen(ep_out: EpNum, len: u16) void { + // Configure the OUT: + const buf_ctrl = dpram.buf_ctrl(ep_out, .Out); + var rmw = buf_ctrl.read(); - std.debug.assert(data_buffer.len >= size); + if (rmw.AVAILABLE_0 == 1) return; - ep.data_buffer = data_buffer[0..size]; - data_buffer = data_buffer[size..]; + rmw.PID_0 ^= 1; // Flip DATA0/1 + rmw.FULL_0 = 0; // Buffer is empty + rmw.AVAILABLE_0 = 1; // And ready to be filled + rmw.LENGTH_1 = 0; + rmw.LENGTH_0 = @min(len, default.transfer_size); + buf_ctrl.write(rmw); } - fn endpoint_enable(ep: *HardwareEndpoint) void { - ep.endpoint_control.?.modify(.{ - .ENABLE = 1, - .INTERRUPT_PER_BUFF = 1, - .ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(ep.transfer_type.as_number())), - .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep.data_buffer), - }); + pub fn open_in(_: *@This(), ep_num: EpNum, transfer_type: usb.types.TransferType) void { + _ = dpram.ep_open(ep_num, .In, transfer_type); } - /// Iterator over endpoint buffers events - pub fn get_EPBIter(dc: *const usb.DeviceConfiguration) usb.EPBIter { - return .{ - .bufbits = peripherals.USB.BUFF_STATUS.raw, - .device_config = dc, - .next = next, - }; - } - - pub fn next(self: *usb.EPBIter) ?usb.EPB { - if (self.last_bit) |lb| { - // Acknowledge the last handled buffer - peripherals.USB.BUFF_STATUS.write_raw(lb); - self.last_bit = null; - } - // All input buffers handled? - if (self.bufbits == 0) return null; - - // Who's still outstanding? Find their bit index by counting how - // many LSBs are zero. - var lowbit_index: u5 = 0; - while ((self.bufbits >> lowbit_index) & 0x01 == 0) : (lowbit_index += 1) {} - // Remove their bit from our set. - const lowbit = @as(u32, @intCast(1)) << lowbit_index; - self.last_bit = lowbit; - self.bufbits ^= lowbit; - - // Here we exploit knowledge of the ordering of buffer control - // registers in the peripheral. Each endpoint has a pair of - // registers, so we can determine the endpoint number by: - const epnum = @as(u8, @intCast(lowbit_index >> 1)); - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - const dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out; - - const ep_addr = Endpoint.to_address(epnum, dir); - // Process the buffer-done event. - - // Process the buffer-done event. - // - // Scan the device table to figure out which endpoint struct - // corresponds to this address. We could use a smarter - // method here, but in practice, the number of endpoints is - // small so a linear scan doesn't kill us. - - const ep = hardware_endpoint_get_by_address(ep_addr); - - // We should only get here if we've been notified that - // the buffer is ours again. This is indicated by the hw - // _clearing_ the AVAILABLE bit. - // - // This ensures that we can return a shared reference to - // the databuffer contents without races. - // TODO: if ((bc & (1 << 10)) == 1) return EPBError.NotAvailable; - - // Cool. Checks out. - - // Get the actual length of the data, which may be less - // than the buffer size. - const len = ep.buffer_control.?.read().LENGTH_0; - - // Copy the data from SRAM - return usb.EPB{ - .endpoint_address = ep_addr, - .buffer = ep.data_buffer[0..len], - }; + pub fn open_out(_: *@This(), ep_num: EpNum, transfer_type: usb.types.TransferType) void { + _ = dpram.ep_open(ep_num, .Out, transfer_type); } }; }