diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index d5e04641b..b4ff02188 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -1,9 +1,11 @@ const std = @import("std"); -const log = std.log.scoped(.usb); +const assert = std.debug.assert; +const log = std.log.scoped(.usb_ctrl); pub const descriptor = @import("usb/descriptor.zig"); pub const drivers = struct { pub const cdc = @import("usb/drivers/cdc.zig"); + pub const echo = @import("usb/drivers/example.zig"); pub const hid = @import("usb/drivers/hid.zig"); }; pub const types = @import("usb/types.zig"); @@ -11,37 +13,76 @@ pub const types = @import("usb/types.zig"); pub const ack: []const u8 = ""; pub const nak: ?[]const u8 = null; +pub const DescriptorAllocator = struct { + next_ep_num: [2]u8, + next_itf_num: u8, + unique_endpoints: bool, + + pub fn init(unique_endpoints: bool) @This() { + return .{ + .next_ep_num = @splat(1), + .next_itf_num = 0, + .unique_endpoints = unique_endpoints, + }; + } + + pub fn next_ep(self: *@This(), dir: types.Dir) types.Endpoint { + const idx: u1 = @intFromEnum(dir); + const ret = self.next_ep_num[idx]; + self.next_ep_num[idx] += 1; + if (self.unique_endpoints) + self.next_ep_num[idx ^ 1] += 1; + return .{ .dir = dir, .num = @enumFromInt(ret) }; + } + + pub fn next_itf(self: *@This()) u8 { + defer self.next_itf_num += 1; + return self.next_itf_num; + } +}; + /// USB Device interface /// Any device implementation used with DeviceController must implement those functions pub const DeviceInterface = struct { pub const VTable = struct { - start_tx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, buffer: []const u8) void, - start_rx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: usize) void, - endpoint_open: *const fn (self: *DeviceInterface, desc: *const descriptor.Endpoint) void, - set_address: *const fn (self: *DeviceInterface, addr: u7) void, + ep_writev: *const fn (*DeviceInterface, types.Endpoint.Num, []const []const u8) types.Len, + ep_readv: *const fn (*DeviceInterface, types.Endpoint.Num, []const []u8) types.Len, + ep_listen: *const fn (*DeviceInterface, types.Endpoint.Num, types.Len) void, + ep_open: *const fn (*DeviceInterface, *const descriptor.Endpoint) void, + set_address: *const fn (*DeviceInterface, u7) void, }; vtable: *const VTable, /// Called by drivers to send a packet. - /// Submitting an empty slice signals an ACK. - /// If you intend to send ACK, please use the constant `usb.ack`. - pub fn start_tx(self: *@This(), ep_num: types.Endpoint.Num, buffer: []const u8) void { - return self.vtable.start_tx(self, ep_num, buffer); + pub fn ep_writev(self: *@This(), ep_num: types.Endpoint.Num, data: []const []const u8) types.Len { + return self.vtable.ep_writev(self, ep_num, data); + } + + /// Send ack on given IN endpoint. + pub fn ep_ack(self: *@This(), ep_num: types.Endpoint.Num) void { + assert(0 == self.ep_writev(ep_num, &.{ack})); + } + + /// Called by drivers to retrieve a received packet. + /// Must be called exactly once for each packet. + /// Buffers in `data` must collectively be long enough to fit the whole packet. + pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) types.Len { + return self.vtable.ep_readv(self, ep_num, data); } /// Called by drivers to report readiness to receive up to `len` bytes. - /// Must be called exactly once before each packet. - pub fn start_rx(self: *@This(), ep_num: types.Endpoint.Num, len: usize) void { - return self.vtable.start_rx(self, ep_num, len); + /// After being called it may not be called again until a packet is received. + pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: types.Len) void { + return self.vtable.ep_listen(self, ep_num, len); } /// Opens an endpoint according to the descriptor. Note that if the endpoint /// direction is IN this may call the controller's `on_buffer` function, /// so driver initialization must be done before this function is called /// on IN endpoint descriptors. - pub fn endpoint_open(self: *@This(), desc: *const descriptor.Endpoint) void { - return self.vtable.endpoint_open(self, desc); + pub fn ep_open(self: *@This(), desc: *const descriptor.Endpoint) void { + return self.vtable.ep_open(self, desc); } /// Immediately sets the device address. @@ -50,6 +91,30 @@ pub const DeviceInterface = struct { } }; +pub fn DriverHadlers(Driver: type) type { + const EndpointHandler = fn (*Driver, types.Endpoint.Num) void; + + const Field = std.builtin.Type.StructField; + var ret_fields: []const Field = &.{}; + const desc_fields = @typeInfo(Driver.Descriptor).@"struct".fields; + for (desc_fields) |fld| switch (fld.type) { + descriptor.Endpoint => ret_fields = ret_fields ++ &[1]Field{.{ + .alignment = @alignOf(usize), + .default_value_ptr = null, + .is_comptime = false, + .name = fld.name, + .type = EndpointHandler, + }}, + else => {}, + }; + return @Type(.{ .@"struct" = .{ + .decls = &.{}, + .fields = ret_fields, + .is_tuple = false, + .layout = .auto, + } }); +} + pub const Config = struct { pub const Configuration = struct { num: u8, @@ -61,11 +126,27 @@ pub const Config = struct { device_descriptor: descriptor.Device, string_descriptors: []const descriptor.String, - debug: bool = false, /// Currently only a single configuration is supported. configurations: []const Configuration, + /// Only use either IN or OUT on each endpoint. Useful for debugging. + /// Realistically, it should only be turned off if you are exhausting + /// the 15 endpoint limit. + unique_endpoints: bool = true, + /// Device specific, either 8, 16, 32 or 64. + max_supported_packet_size: types.Len, }; +pub fn validate_controller(T: type) void { + comptime { + const info = @typeInfo(T); + if (info != .pointer or info.pointer.is_const or info.pointer.size != .one) + @compileError("Expected a mutable pointer to the usb controller, got: " ++ @typeName(T)); + const Controller = info.pointer.child; + _ = Controller; + // More checks + } +} + /// USB device controller /// /// This code handles usb enumeration and configuration and routes packets to drivers. @@ -77,54 +158,57 @@ pub fn DeviceController(config: Config) type { const driver_fields = @typeInfo(config0.Drivers).@"struct".fields; const DriverEnum = std.meta.FieldEnum(config0.Drivers); const config_descriptor = blk: { - var num_interfaces = 0; - var num_strings = 4; - var num_ep_in = 1; - var num_ep_out = 1; + const max_psize = config.max_supported_packet_size; + assert(max_psize == 8 or max_psize == 16 or max_psize == 32 or max_psize == 64); + + var alloc: DescriptorAllocator = .init(config.unique_endpoints); + var next_string = 4; var size = @sizeOf(descriptor.Configuration); var fields: [driver_fields.len + 1]std.builtin.Type.StructField = undefined; for (driver_fields, 1..) |drv, i| { - const payload = drv.type.Descriptor.create(num_interfaces, num_strings, num_ep_in, num_ep_out); + const payload = drv.type.Descriptor.create(&alloc, next_string, max_psize); const Payload = @TypeOf(payload); size += @sizeOf(Payload); - fields[i] = .{ - .name = drv.name, - .type = Payload, - .default_value_ptr = &payload, - .is_comptime = false, - .alignment = 1, - }; - for (@typeInfo(Payload).@"struct".fields) |fld| { const desc = @field(payload, fld.name); switch (fld.type) { descriptor.Interface => { - num_interfaces += 1; if (desc.interface_s != 0) - num_strings += 1; - }, - descriptor.Endpoint => switch (desc.endpoint.dir) { - .In => num_ep_in += 1, - .Out => num_ep_out += 1, + next_string += 1; }, + descriptor.Endpoint, descriptor.InterfaceAssociation, descriptor.cdc.Header, descriptor.cdc.CallManagement, descriptor.cdc.AbstractControlModel, descriptor.cdc.Union, - descriptor.hid.Hid, + descriptor.hid.HID, => {}, else => @compileLog(fld), } } + + fields[i] = .{ + .name = drv.name, + .type = Payload, + .default_value_ptr = &payload, + .is_comptime = false, + .alignment = 1, + }; } + if (next_string != config.string_descriptors.len) + @compileError(std.fmt.comptimePrint( + "expected {} string descriptros, got {}", + .{ next_string, config.string_descriptors.len }, + )); + const desc_cfg: descriptor.Configuration = .{ .total_length = .from(size), - .num_interfaces = num_interfaces, + .num_interfaces = alloc.next_itf_num, .configuration_value = config0.num, .configuration_s = config0.configuration_s, .attributes = config0.attributes, @@ -147,27 +231,106 @@ pub fn DeviceController(config: Config) type { } }){}; }; - /// When the host sets the device address, the acknowledgement - /// step must use the _old_ address. - new_address: ?u8, + const handlers = blk: { + @setEvalBranchQuota(10000); + + const Field = std.builtin.Type.StructField; + var drivers_ep: struct { + In: [16][]const u8 = @splat(""), + Out: [16][]const u8 = @splat(""), + } = .{}; + var handlers_ep: struct { + const default_field: Field = .{ + .alignment = 1, + .default_value_ptr = null, + .is_comptime = false, + .name = "", + .type = void, + }; + In: [16]Field = @splat(default_field), + Out: [16]Field = @splat(default_field), + } = .{}; + var itf_handlers: []const DriverEnum = &.{}; + + for (0..16) |i| { + const name = std.fmt.comptimePrint("{}", .{i}); + handlers_ep.In[i].name = name; + handlers_ep.Out[i].name = name; + } + + for (driver_fields) |fld_drv| { + const cfg = @field(config_descriptor, fld_drv.name); + const fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; + + const itf0 = @field(cfg, fields[0].name); + const itf_start, const itf_count = if (fields[0].type == descriptor.InterfaceAssociation) + .{ itf0.first_interface, itf0.interface_count } + else + .{ itf0.interface_number, 1 }; + + if (itf_start != itf_handlers.len) + @compileError("interface numbering mismatch"); + + itf_handlers = itf_handlers ++ &[1]DriverEnum{@field(DriverEnum, fld_drv.name)} ** itf_count; + + for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc: descriptor.Endpoint = @field(cfg, fld.name); + const tag = @tagName(desc.endpoint.dir); + const ep_num = @intFromEnum(desc.endpoint.num); + + const driver = &@field(drivers_ep, tag)[ep_num]; + if (driver.*.len != 0) + @compileError(std.fmt.comptimePrint( + "ep{} {t}: multiple handlers: {s} and {s}", + .{ ep_num, desc.endpoint.dir, driver.*, fld_drv.name }, + )); + + driver.* = fld_drv.name; + const func = @field(fld_drv.type.handlers, fld.name); + const handler = &@field(handlers_ep, tag)[ep_num]; + handler.alignment = @alignOf(@TypeOf(func)); + handler.default_value_ptr = &func; + handler.type = @TypeOf(func); + handler.is_comptime = true; + } + } + break :blk .{ + .ep_drv = drivers_ep, + .ep_han = .{ + .In = @Type(.{ .@"struct" = .{ + .decls = &.{}, + .fields = &handlers_ep.In, + .is_tuple = true, + .layout = .auto, + } }){}, + .Out = @Type(.{ .@"struct" = .{ + .decls = &.{}, + .fields = &handlers_ep.Out, + .is_tuple = true, + .layout = .auto, + } }){}, + }, + .itf = itf_handlers, + }; + }; + + /// If not zero, change the device address at the next opportunity. + /// Necessary because when the host sets the device address, + /// the acknowledgement step must use the _old_ address. + new_address: u8, /// 0 - no config set cfg_num: u16, /// Ep0 data waiting to be sent - tx_slice: []const u8, - /// Last setup packet request - setup_packet: types.SetupPacket, - /// Class driver associated with last setup request if any - driver_last: ?DriverEnum, + tx_slice: ?[]const u8, /// Driver state driver_data: ?config0.Drivers, /// Initial values pub const init: @This() = .{ - .new_address = null, + .new_address = 0, .cfg_num = 0, - .tx_slice = "", - .setup_packet = undefined, - .driver_last = null, + .tx_slice = null, .driver_data = null, }; @@ -179,101 +342,69 @@ pub fn DeviceController(config: Config) type { /// Called by the device implementation when a setup request has been received. pub fn on_setup_req(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) void { - if (config.debug) log.info("setup req", .{}); - - self.setup_packet = setup.*; - self.driver_last = null; - - switch (setup.request_type.recipient) { - .Device => try self.process_setup_request(device_itf, setup), - .Interface => switch (@as(u8, @truncate(setup.index))) { - inline else => |itf_num| inline for (driver_fields) |fld_drv| { - const cfg = @field(config_descriptor, fld_drv.name); - comptime var fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; - - const itf_count = if (fields[0].type != descriptor.InterfaceAssociation) - 1 - else blk: { - defer fields = fields[1..]; - break :blk @field(cfg, fields[0].name).interface_count; - }; - - const itf_start = @field(cfg, fields[0].name).interface_number; - - if (comptime itf_num >= itf_start and itf_num < itf_start + itf_count) { - const drv = @field(DriverEnum, fld_drv.name); - self.driver_last = drv; - self.driver_class_control(device_itf, drv, .Setup, setup); - } - }, - }, - .Endpoint => {}, - else => {}, + log.debug("on_setup_req", .{}); + + const ret = switch (setup.request_type.recipient) { + .Device => self.process_device_setup(device_itf, setup), + .Interface => self.process_interface_setup(setup), + else => nak, + }; + if (ret) |data| { + if (data.len == 0) + device_itf.ep_ack(.ep0) + else { + const limited = data[0..@min(data.len, setup.length.into())]; + const len = device_itf.ep_writev(.ep0, &.{limited}); + assert(len <= config.device_descriptor.max_packet_size0); + self.tx_slice = limited[len..]; + } } } /// Called by the device implementation when a packet has been sent or received. - pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, ep: types.Endpoint, buffer: []u8) void { - if (config.debug) log.info("buff status", .{}); - - if (config.debug) log.info(" data: {any}", .{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 (ep.num) { - .ep0 => if (ep.dir == .In) { - if (config.debug) log.info(" EP0_IN_ADDR", .{}); - - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (self.new_address) |addr| { - // Change our address: - device_itf.set_address(@intCast(addr)); - self.new_address = null; - } - - if (buffer.len > 0 and self.tx_slice.len > 0) { - self.tx_slice = self.tx_slice[buffer.len..]; - - const next_data_chunk = self.tx_slice[0..@min(64, self.tx_slice.len)]; - if (next_data_chunk.len > 0) { - device_itf.start_tx(.ep0, next_data_chunk); - } else { - device_itf.start_rx(.ep0, 0); + pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint) void { + log.debug("on_buffer {t} {t}", .{ ep.num, ep.dir }); + + const driver = comptime @field(handlers.ep_drv, @tagName(ep.dir))[@intFromEnum(ep.num)]; + const function = comptime @field(handlers.ep_han, @tagName(ep.dir))[@intFromEnum(ep.num)]; + + if (comptime ep == types.Endpoint.in(.ep0)) { + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (self.new_address != 0) { + device_itf.set_address(@intCast(self.new_address)); + self.new_address = 0; + } - if (self.driver_last) |drv| - self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); - } + if (self.tx_slice) |slice| { + if (slice.len > 0) { + const len = device_itf.ep_writev(.ep0, &.{slice}); + self.tx_slice = slice[len..]; } 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: - device_itf.start_rx(.ep0, 0); - - if (self.driver_last) |drv| - self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); + // Otherwise, we've just finished sending tx_slice. + // We expect an ensuing status phase where the host + // sends us a zero-byte DATA packet via EP0 OUT. + // device_itf.ep_listen(.ep0, 0); + self.tx_slice = null; + + // None of the drivers so far are using the ACK phase + // if (self.driver_last) |drv| + // self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); } - }, - inline else => |ep_num| inline for (driver_fields) |fld_drv| { - const cfg = @field(config_descriptor, fld_drv.name); - const fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; - inline for (fields) |fld| { - const desc = @field(cfg, fld.name); - if (comptime fld.type == descriptor.Endpoint and desc.endpoint.num == ep_num) { - if (ep.dir == desc.endpoint.dir) - @field(self.driver_data.?, fld_drv.name).transfer(ep, buffer); - } - } - }, + } + } else if (comptime ep == types.Endpoint.out(.ep0)) + log.warn("Unhandled packet on ep0 Out", .{}); + + if (comptime driver.len != 0) { + function(&@field(self.driver_data.?, driver), ep.num); } } /// Called by the device implementation on bus reset. - pub fn on_bus_reset(self: *@This()) void { - if (config.debug) log.info("bus reset", .{}); + pub fn on_bus_reset(self: *@This(), device_itf: *DeviceInterface) void { + log.debug("on_bus_reset", .{}); + + self.process_set_config(device_itf, 0); // Reset our state. self.* = .init; @@ -281,124 +412,112 @@ pub fn DeviceController(config: Config) type { // Utility functions - /// Command response utility function that can split long data in multiple packets - fn send_cmd_response(self: *@This(), device_itf: *DeviceInterface, data: []const u8, expected_max_length: u16) void { - self.tx_slice = data[0..@min(data.len, expected_max_length)]; - const len = @min(config.device_descriptor.max_packet_size0, self.tx_slice.len); - device_itf.start_tx(.ep0, data[0..len]); - } - - fn driver_class_control(self: *@This(), device_itf: *DeviceInterface, driver: DriverEnum, stage: types.ControlStage, setup: *const types.SetupPacket) void { - return switch (driver) { - inline else => |d| { - const drv = &@field(self.driver_data.?, @tagName(d)); - if (drv.class_control(stage, setup)) |response| - self.send_cmd_response(device_itf, response, setup.length); - }, - }; - } - - fn process_setup_request(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) !void { + fn process_device_setup(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) ?[]const u8 { switch (setup.request_type.type) { - .Class => { - //const itfIndex = setup.index & 0x00ff; - log.info("Device.Class", .{}); - }, .Standard => { - switch (std.meta.intToEnum(types.SetupRequest, setup.request) catch return) { - .SetAddress => { - self.new_address = @as(u8, @intCast(setup.value & 0xff)); - device_itf.start_tx(.ep0, ack); - if (config.debug) log.info(" SetAddress: {}", .{self.new_address.?}); - }, - .SetConfiguration => { - if (config.debug) log.info(" SetConfiguration", .{}); - if (self.cfg_num != setup.value) { - self.cfg_num = setup.value; - - if (self.cfg_num > 0) { - try self.process_set_config(device_itf, self.cfg_num - 1); - // TODO: call mount callback if any - } else { - // TODO: call umount callback if any - } - } - device_itf.start_tx(.ep0, ack); + const request: types.SetupRequest = @enumFromInt(setup.request); + log.debug("Device setup: {any}", .{request}); + switch (request) { + .GetStatus => { + const attr = config_descriptor.__configuration_descriptor.attributes; + const status: types.DeviceStatus = comptime .create(attr.self_powered, false); + return std.mem.asBytes(&status); }, - .GetDescriptor => { - const descriptor_type = std.meta.intToEnum(descriptor.Type, setup.value >> 8) catch null; - if (descriptor_type) |dt| { - try self.process_get_descriptor(device_itf, setup, dt); + .SetAddress => self.new_address = @truncate(setup.value.into()), + .SetConfiguration => self.process_set_config(device_itf, setup.value.into()), + .GetDescriptor => return get_descriptor(setup.value.into()), + .SetFeature => { + const feature: types.FeatureSelector = @enumFromInt(setup.value.into() >> 8); + switch (feature) { + .DeviceRemoteWakeup, .EndpointHalt => {}, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 + .TestMode => return nak, + else => return nak, } }, - .SetFeature => { - if (std.meta.intToEnum(types.FeatureSelector, setup.value >> 8)) |feat| { - switch (feat) { - .DeviceRemoteWakeup, .EndpointHalt => device_itf.start_tx(.ep0, ack), - // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 - .TestMode => {}, - } - } else |_| {} + else => { + log.warn("Unsupported standard request: {}", .{setup.request}); + return nak; }, } + return ack; + }, + else => |t| { + log.warn("Unhandled device setup request: {any}", .{t}); + return nak; }, - else => {}, } } - fn process_get_descriptor(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket, descriptor_type: descriptor.Type) !void { - switch (descriptor_type) { - .Device => { - if (config.debug) log.info(" Device", .{}); - - self.send_cmd_response(device_itf, @ptrCast(&config.device_descriptor), setup.length); + fn process_interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + const itf_num: u8 = @truncate(setup.index.into()); + switch (itf_num) { + inline else => |itf| if (comptime itf < handlers.itf.len) { + const drv = handlers.itf[itf]; + return @field(self.driver_data.?, @tagName(drv)).class_request(setup); + } else { + log.warn("Interface index ({}) out of range ({})", .{ itf_num, handlers.itf.len }); + return nak; }, - .Configuration => { - if (config.debug) log.info(" Config", .{}); + } + } - self.send_cmd_response(device_itf, @ptrCast(&config_descriptor), setup.length); - }, - .String => { - if (config.debug) log.info(" String", .{}); - // String descriptor index is in bottom 8 bits of - // `value`. - const i: usize = @intCast(setup.value & 0xff); - if (i >= config.string_descriptors.len) - log.warn("host requested invalid string descriptor {}", .{i}) - else - self.send_cmd_response( - device_itf, - config.string_descriptors[i].data, - setup.length, - ); - }, - .Interface => { - if (config.debug) log.info(" Interface", .{}); - }, - .Endpoint => { - if (config.debug) log.info(" Endpoint", .{}); + fn get_descriptor(value: u16) ?[]const u8 { + const asBytes = std.mem.asBytes; + const desc_type: descriptor.Type = @enumFromInt(value >> 8); + const desc_idx: u8 = @truncate(value); + log.debug("Request for {any} descriptor {}", .{ desc_type, desc_idx }); + return switch (desc_type) { + .Device => asBytes(&config.device_descriptor), + .DeviceQualifier => asBytes(comptime &config.device_descriptor.qualifier()), + .Configuration => asBytes(&config_descriptor), + .String => if (desc_idx < config.string_descriptors.len) + config.string_descriptors[desc_idx].data + else { + log.warn( + "Descriptor index ({}) out of range ({})", + .{ desc_idx, config.string_descriptors.len }, + ); + return nak; }, - .DeviceQualifier => { - if (config.debug) log.info(" DeviceQualifier", .{}); - // We will just copy parts of the DeviceDescriptor because - // the DeviceQualifierDescriptor can be seen as a subset. - const qualifier = comptime &config.device_descriptor.qualifier(); - self.send_cmd_response(device_itf, @ptrCast(qualifier), setup.length); - }, - else => {}, - } + else => nak, + }; } - fn process_set_config(self: *@This(), device_itf: *DeviceInterface, _: u16) !void { - // TODO: we support just one config for now so ignore config index + fn process_set_config(self: *@This(), device_itf: *DeviceInterface, cfg_num: u16) void { + if (cfg_num == self.cfg_num) return; + + if (self.driver_data) |data_old| { + _ = data_old; + // deinitialize drivers + } + + self.cfg_num = cfg_num; + if (cfg_num == 0) { + self.driver_data = null; + return; + } + + // We support just one config for now so ignore config index self.driver_data = @as(config0.Drivers, undefined); inline for (driver_fields) |fld_drv| { const cfg = @field(config_descriptor, fld_drv.name); - @field(self.driver_data.?, fld_drv.name) = .init(&cfg, device_itf); + const desc_fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; + + // Open OUT endpoint first so that the driver can call ep_listen in init + inline for (desc_fields) |fld| { + const desc = &@field(cfg, fld.name); + if (comptime fld.type == descriptor.Endpoint and desc.endpoint.dir == .Out) + device_itf.ep_open(desc); + } + + @field(self.driver_data.?, fld_drv.name).init(&cfg, device_itf); - inline for (@typeInfo(@TypeOf(cfg)).@"struct".fields) |fld| { - if (comptime fld.type == descriptor.Endpoint) - device_itf.endpoint_open(&@field(cfg, fld.name)); + // Open IN endpoint last so that callbacks can happen + inline for (desc_fields) |fld| { + const desc = &@field(cfg, fld.name); + if (comptime fld.type == descriptor.Endpoint and desc.endpoint.dir == .In) + device_itf.ep_open(desc); } } } diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 63777e064..3d6109a1a 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -17,7 +17,8 @@ pub const Type = enum(u8) { Interface = 0x04, Endpoint = 0x05, DeviceQualifier = 0x06, - InterfaceAssociation = 0x0b, + InterfaceAssociation = 0x0B, + BOS = 0x0F, CsDevice = 0x21, CsConfig = 0x22, CsString = 0x23, @@ -29,22 +30,6 @@ pub const Type = enum(u8) { /// 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 { @@ -57,9 +42,9 @@ pub const Device = extern struct { /// Type of this descriptor, must be `DeviceQualifier`. descriptor_type: Type = .DeviceQualifier, /// Specification version as Binary Coded Decimal - bcd_usb: types.U16Le, + bcd_usb: types.U16_Le, /// Class, subclass and protocol of device. - device_triple: DeviceTriple, + device_triple: types.ClassSubclassProtocol, /// Maximum unit of data this device can move. max_packet_size0: u8, /// Number of configurations supported by this device. @@ -77,17 +62,17 @@ pub const Device = extern struct { /// Type of this descriptor, must be `Device`. descriptor_type: Type = .Device, /// Specification version as Binary Coded Decimal - bcd_usb: types.U16Le, + bcd_usb: types.U16_Le, /// Class, subclass and protocol of device. - device_triple: DeviceTriple, + device_triple: types.ClassSubclassProtocol, /// Maximum length of data this device can move. max_packet_size0: u8, /// ID of product vendor. - vendor: types.U16Le, + vendor: types.U16_Le, /// ID of product. - product: types.U16Le, + product: types.U16_Le, /// Device version number as Binary Coded Decimal. - bcd_device: types.U16Le, + bcd_device: types.U16_Le, /// Index of manufacturer name in string descriptor table. manufacturer_s: u8, /// Index of product name in string descriptor table. @@ -144,7 +129,7 @@ pub const Configuration = extern struct { /// 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, + total_length: types.U16_Le, /// Number of interface descriptors in this configuration. num_interfaces: u8, /// Number to use when requesting this configuration via a @@ -167,13 +152,13 @@ pub const String = struct { data: []const u8, - pub fn from_lang(lang: Language) @This() { + pub fn from_lang(comptime lang: Language) @This() { const ret: *const extern struct { length: u8 = @sizeOf(@This()), descriptor_type: Type = .String, - lang: types.U16Le, + lang: types.U16_Le, } = comptime &.{ .lang = .from(@intFromEnum(lang)) }; - return .{ .data = @ptrCast(ret) }; + return .{ .data = std.mem.asBytes(ret) }; } pub fn from_str(comptime string: []const u8) @This() { @@ -221,10 +206,37 @@ pub const Endpoint = extern struct { /// control the transfer type using the values from `TransferType`. attributes: Attributes, /// Maximum packet size this endpoint can accept/produce. - max_packet_size: types.U16Le, + max_packet_size: types.U16_Le, /// Interval for polling interrupt/isochronous endpoints (which we don't /// currently support) in milliseconds. interval: u8, + + pub fn control(ep: types.Endpoint, max_packet_size: types.Len) @This() { + return .{ + .endpoint = ep, + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .max_packet_size = .from(max_packet_size), + .interval = 0, // Unused for bulk endpoints + }; + } + + pub fn bulk(ep: types.Endpoint, max_packet_size: types.Len) @This() { + return .{ + .endpoint = ep, + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(max_packet_size), + .interval = 0, // Unused for bulk endpoints + }; + } + + pub fn interrupt(ep: types.Endpoint, max_packet_size: types.Len, poll_interval: u8) @This() { + return .{ + .endpoint = ep, + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(max_packet_size), + .interval = poll_interval, + }; + } }; /// Description of an interface within a configuration. @@ -245,12 +257,8 @@ pub const Interface = extern struct { 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, + /// Interface class, subclass and protocol. + interface_triple: types.ClassSubclassProtocol, /// Index of interface name within string descriptor table. interface_s: u8, }; @@ -280,3 +288,20 @@ pub const InterfaceAssociation = extern struct { // Index of the string descriptor describing the associated interfaces. function: u8, }; + +pub const BOS = struct { + pub const Object = union(enum) {}; + + data: []const u8, + + pub fn from(comptime objects: []const Object) @This() { + const data: []const u8 = ""; + const header: []const u8 = std.mem.asBytes(&extern struct { + length: u8 = @sizeOf(@This()), + descriptor_type: Type = .BOS, + total_length: types.U16_Le = .from(@sizeOf(@This()) + data.len), + num_descriptors: u8 = @intCast(objects.len), + }{}); + return .{ .data = header ++ data }; + } +}; diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index 0d9f8e5fc..1230b29cd 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -22,10 +22,21 @@ pub const Header = extern struct { descriptor_subtype: SubType = .Header, // USB Class Definitions for Communication Devices Specification release // number in binary-coded decimal. Typically 0x01_10. - bcd_cdc: types.U16Le, + bcd_cdc: types.U16_Le, }; pub const CallManagement = extern struct { + pub const Capabilities = packed struct(u8) { + handles_call_mgmt: bool, + call_mgmt_over_data: bool, + reserved: u6 = 0, + + pub const none: @This() = .{ + .handles_call_mgmt = false, + .call_mgmt_over_data = false, + }; + }; + comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 5); @@ -37,12 +48,27 @@ pub const CallManagement = extern struct { // Subtype of this descriptor, must be `CallManagement`. descriptor_subtype: SubType = .CallManagement, // Capabilities. Should be 0x00 for use as a serial device. - capabilities: u8, + capabilities: Capabilities, // Data interface number. data_interface: u8, }; pub const AbstractControlModel = extern struct { + pub const Capabilities = packed struct(u8) { + /// Device supports the request combination of Set_Comm_Feature, + /// Clear_Comm_Feature, and Get_Comm_Feature + comm_feature: bool, + /// Device supports the request combination of + /// Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, + /// and the notification Serial_State + line_coding: bool, + /// Device supports the request Send_Break + send_break: bool, + /// Device supports the notification Network_Connection + network_connection: bool, + reserved: u4 = 0, + }; + comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 4); @@ -54,7 +80,7 @@ pub const AbstractControlModel = extern struct { // Subtype of this descriptor, must be `AbstractControlModel`. descriptor_subtype: SubType = .AbstractControlModel, // Capabilities. Should be 0x02 for use as a serial device. - capabilities: u8, + capabilities: Capabilities, }; pub const Union = extern struct { diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index c3ce76afb..641a715e5 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -11,10 +11,11 @@ pub const RequestType = enum(u8) { SetReport = 0x09, SetIdle = 0x0a, SetProtocol = 0x0b, + _, }; /// USB HID descriptor -pub const Hid = extern struct { +pub const HID = extern struct { /// HID country codes pub const CountryCode = enum(u8) { NotSupported = 0, @@ -53,12 +54,14 @@ pub const Hid = extern struct { Us, Yugoslavia, TurkishF, + _, }; pub const Type = enum(u8) { - Hid = 0x21, + HID = 0x21, Report = 0x22, Physical = 0x23, + _, }; comptime { @@ -70,7 +73,7 @@ pub const Hid = extern struct { /// Type of this descriptor descriptor_type: descriptor.Type = .CsDevice, /// Numeric expression identifying the HID Class Specification release - bcd_hid: types.U16Le, + bcd_hid: types.U16_Le, /// Numeric expression identifying country code of the localized hardware country_code: CountryCode, /// Numeric expression specifying the number of class descriptors @@ -78,7 +81,7 @@ pub const Hid = extern struct { /// Type of HID class report report_type: Type = .Report, /// The total size of the Report descriptor - report_length: types.U16Le, + report_length: types.U16_Le, }; // +++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 30b80ff7b..001a229b2 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -1,21 +1,47 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const descriptor = usb.descriptor; -const types = usb.types; - -const utilities = @import("../../../utilities.zig"); +const assert = std.debug.assert; +const EP_Num = usb.types.Endpoint.Num; +const log = std.log.scoped(.usb_cdc); pub const ManagementRequestType = enum(u8) { + SetCommFeature = 0x02, + GetCommFeature = 0x03, + ClearCommFeature = 0x04, + SetAuxLineState = 0x10, + SetHookState = 0x11, + PulseSetup = 0x12, + SendPulse = 0x13, + SetPulseTime = 0x14, + RingAuxJack = 0x15, SetLineCoding = 0x20, GetLineCoding = 0x21, SetControlLineState = 0x22, SendBreak = 0x23, + SetRingerParams = 0x30, + GetRingerParams = 0x31, + SetOperationParams = 0x32, + GetOperationParams = 0x33, + SetLineParams = 0x34, + GetLineParams = 0x35, + DialDigits = 0x36, + _, }; pub const LineCoding = extern struct { - bit_rate: u32 align(1), - stop_bits: u8, - parity: u8, + pub const StopBits = enum(u8) { @"1" = 0, @"1.5" = 1, @"2" = 2, _ }; + pub const Parity = enum(u8) { + none = 0, + odd = 1, + even = 2, + mark = 3, + space = 4, + _, + }; + + bit_rate: usb.types.U32_Le, + stop_bits: StopBits, + parity: Parity, data_bits: u8, pub const init: @This() = .{ @@ -31,31 +57,31 @@ pub const Options = struct { }; pub fn CdcClassDriver(options: Options) type { - const FIFO = utilities.CircularBuffer(u8, options.max_packet_size); - return struct { 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, + const desc = usb.descriptor; + + itf_assoc: desc.InterfaceAssociation, + itf_notifi: desc.Interface, + cdc_header: desc.cdc.Header, + cdc_call_mgmt: desc.cdc.CallManagement, + cdc_acm: desc.cdc.AbstractControlModel, + cdc_union: desc.cdc.Union, + ep_notifi: desc.Endpoint, + itf_data: desc.Interface, + ep_out: desc.Endpoint, + ep_in: desc.Endpoint, pub fn create( - first_interface: u8, + alloc: *usb.DescriptorAllocator, first_string: u8, - first_endpoint_in: u4, - first_endpoint_out: u4, + max_supported_packet_size: usb.types.Len, ) @This() { - const endpoint_notifi_size = 8; + const itf_notifi = alloc.next_itf(); + const itf_data = alloc.next_itf(); return .{ .itf_assoc = .{ - .first_interface = first_interface, + .first_interface = itf_notifi, .interface_count = 2, .function_class = 2, .function_subclass = 2, @@ -63,156 +89,174 @@ pub fn CdcClassDriver(options: Options) type { .function = 0, }, .itf_notifi = .{ - .interface_number = first_interface, + .interface_number = itf_notifi, .alternate_setting = 0, .num_endpoints = 1, - .interface_class = 2, - .interface_subclass = 2, - .interface_protocol = 0, + .interface_triple = .from(.CDC, .Abstract, .NoneRequired), .interface_s = first_string, }, .cdc_header = .{ .bcd_cdc = .from(0x0120) }, .cdc_call_mgmt = .{ - .capabilities = 0, - .data_interface = first_interface + 1, + .capabilities = .none, + .data_interface = itf_data, }, - .cdc_acm = .{ .capabilities = 6 }, - .cdc_union = .{ - .master_interface = first_interface, - .slave_interface_0 = first_interface + 1, + .cdc_acm = .{ + .capabilities = .{ + .comm_feature = false, + .send_break = false, + // Line coding requests get sent regardless of this bit + .line_coding = true, + .network_connection = false, + }, }, - .ep_notifi = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, - .max_packet_size = .from(endpoint_notifi_size), - .interval = 16, + .cdc_union = .{ + .master_interface = itf_notifi, + .slave_interface_0 = itf_data, }, + .ep_notifi = .interrupt(alloc.next_ep(.In), 16, 16), .itf_data = .{ - .interface_number = first_interface + 1, + .interface_number = itf_data, .alternate_setting = 0, .num_endpoints = 2, - .interface_class = 10, - .interface_subclass = 0, - .interface_protocol = 0, + .interface_triple = .from(.CDC_Data, .Unused, .NoneRequired), .interface_s = 0, }, - .ep_out = .{ - .endpoint = .out(@enumFromInt(first_endpoint_out)), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, - .max_packet_size = .from(options.max_packet_size), - .interval = 0, - }, - .ep_in = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in + 1)), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, - .max_packet_size = .from(options.max_packet_size), - .interval = 0, - }, + .ep_out = .bulk(alloc.next_ep(.Out), max_supported_packet_size), + .ep_in = .bulk(alloc.next_ep(.In), max_supported_packet_size), }; } }; + pub const handlers: usb.DriverHadlers(@This()) = .{ + .ep_notifi = on_notifi_ready, + .ep_out = on_rx, + .ep_in = on_tx_ready, + }; + device: *usb.DeviceInterface, - ep_notif: types.Endpoint.Num, - ep_in: types.Endpoint.Num, - ep_out: types.Endpoint.Num, + ep_notifi: EP_Num, line_coding: LineCoding align(4), - rx: FIFO = .empty, - tx: FIFO = .empty, + /// OUT endpoint on which there is data ready to be read, + /// or .ep0 when no data is available. + ep_out: EP_Num, + rx_data: [options.max_packet_size]u8, + rx_seek: usb.types.Len, + rx_end: usb.types.Len, - epin_buf: [options.max_packet_size]u8 = undefined, + /// IN endpoint where data can be sent, + /// or .ep0 when data is being sent. + ep_in: EP_Num, + tx_data: [options.max_packet_size]u8, + tx_end: usb.types.Len, - pub fn available(self: *@This()) usize { - return self.rx.get_readable_len(); + pub fn available(self: *@This()) usb.types.Len { + return self.rx_end - self.rx_seek; } pub fn read(self: *@This(), dst: []u8) usize { - const read_count = self.rx.read(dst); - self.prep_out_transaction(); - return read_count; - } + const len = @min(dst.len, self.rx_end - self.rx_seek); + @memcpy(dst[0..len], self.rx_data[self.rx_seek..][0..len]); + self.rx_seek += len; - pub fn write(self: *@This(), data: []const u8) []const u8 { - const write_count = @min(self.tx.get_writable_len(), data.len); + if (self.available() > 0) return len; - if (write_count > 0) { - self.tx.write_assume_capacity(data[0..write_count]); - } else { - return data[0..]; + // request more data + const ep_out = @atomicLoad(EP_Num, &self.ep_out, .seq_cst); + if (ep_out != .ep0) { + self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); + self.rx_seek = 0; + @atomicStore(EP_Num, &self.ep_out, .ep0, .seq_cst); + self.device.ep_listen(ep_out, options.max_packet_size); } - if (self.tx.get_writable_len() == 0) { - _ = self.write_flush(); - } - - return data[write_count..]; + return len; } - pub fn write_flush(self: *@This()) usize { - if (self.tx.get_readable_len() == 0) { - return 0; - } - const len = self.tx.read(&self.epin_buf); - self.device.start_tx(self.ep_in, self.epin_buf[0..len]); + pub fn write(self: *@This(), data: []const u8) usize { + const len = @min(self.tx_data.len - self.tx_end, data.len); + + if (len == 0) return 0; + + @memcpy(self.tx_data[self.tx_end..][0..len], data[0..len]); + self.tx_end += @intCast(len); + + if (self.tx_end == self.tx_data.len) + _ = self.flush(); + return len; } - fn prep_out_transaction(self: *@This()) void { - if (self.rx.get_writable_len() >= options.max_packet_size) { - // Let endpoint know that we are ready for next packet - self.device.start_rx(self.ep_out, options.max_packet_size); - } + /// Returns true if flush operation succeded. + pub fn flush(self: *@This()) bool { + if (self.tx_end == 0) + return true; + + const ep_in = @atomicLoad(EP_Num, &self.ep_in, .seq_cst); + if (ep_in == .ep0) + return false; + + @atomicStore(EP_Num, &self.ep_in, .ep0, .seq_cst); + + assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); + self.tx_end = 0; + return true; } - pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - return .{ + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + self.* = .{ .device = device, - .ep_notif = desc.ep_notifi.endpoint.num, - .ep_in = desc.ep_in.endpoint.num, - .ep_out = desc.ep_out.endpoint.num, + .ep_notifi = desc.ep_notifi.endpoint.num, .line_coding = .{ - .bit_rate = 115200, - .stop_bits = 0, - .parity = 0, + .bit_rate = .from(115200), + .stop_bits = .@"1", + .parity = .none, .data_bits = 8, }, + + .ep_out = .ep0, + .rx_data = undefined, + .rx_seek = 0, + .rx_end = 0, + + .ep_in = desc.ep_in.endpoint.num, + .tx_data = undefined, + .tx_end = 0, }; + device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); } - pub fn class_control(self: *@This(), stage: types.ControlStage, setup: *const types.SetupPacket) ?[]const u8 { - if (std.meta.intToEnum(ManagementRequestType, setup.request)) |request| { - if (stage == .Setup) switch (request) { - .SetLineCoding => return usb.ack, // HACK, we should handle data phase somehow to read sent line_coding - .GetLineCoding => return std.mem.asBytes(&self.line_coding), - .SetControlLineState => { - // const DTR_BIT = 1; - // self.is_ready = (setup.value & DTR_BIT) != 0; - // self.line_state = @intCast(setup.value & 0xFF); - return usb.ack; - }, - .SendBreak => return usb.ack, - }; - } else |_| {} + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { + const mgmt_request: ManagementRequestType = @enumFromInt(setup.request); + log.debug("cdc setup: {any} {} {}", .{ mgmt_request, setup.length.into(), setup.value.into() }); - return usb.nak; + return switch (mgmt_request) { + .SetLineCoding => usb.ack, // we should handle data phase somehow to read sent line_coding + .GetLineCoding => std.mem.asBytes(&self.line_coding), + .SetControlLineState => blk: { + // const DTR_BIT = 1; + // self.is_ready = (setup.value & DTR_BIT) != 0; + // self.line_state = @intCast(setup.value & 0xFF); + break :blk usb.ack; + }, + .SendBreak => usb.ack, + else => usb.nak, + }; } - pub fn transfer(self: *@This(), ep: types.Endpoint, data: []u8) void { - if (ep == types.Endpoint.out(self.ep_out)) { - self.rx.write(data) catch {}; - self.prep_out_transaction(); - } + pub fn on_rx(self: *@This(), ep_num: EP_Num) void { + assert(.ep0 == @atomicLoad(EP_Num, &self.ep_out, .seq_cst)); + @atomicStore(EP_Num, &self.ep_out, ep_num, .seq_cst); + } - if (ep == types.Endpoint.in(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 == options.max_packet_size) { - self.device.start_tx(self.ep_in, &.{}); - } - } - } + pub fn on_tx_ready(self: *@This(), ep_num: EP_Num) void { + assert(.ep0 == @atomicLoad(EP_Num, &self.ep_in, .seq_cst)); + @atomicStore(EP_Num, &self.ep_in, ep_num, .seq_cst); + } + + pub fn on_notifi_ready(self: *@This(), ep_num: EP_Num) void { + assert(.ep0 == @atomicLoad(EP_Num, &self.ep_notifi, .seq_cst)); + @atomicStore(EP_Num, &self.ep_notifi, ep_num, .seq_cst); } }; } diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig new file mode 100644 index 000000000..67cfd5dee --- /dev/null +++ b/core/src/core/usb/drivers/example.zig @@ -0,0 +1,99 @@ +const std = @import("std"); +const usb = @import("../../usb.zig"); +const EP_Num = usb.types.Endpoint.Num; +const log = std.log.scoped(.usb_echo); + +/// This is an example driver that sends any data received on ep2 to ep1. +pub const EchoExampleDriver = struct { + /// The descriptors need to have the same memory layout as the sent data. + pub const Descriptor = extern struct { + const desc = usb.descriptor; + + example_interface: desc.Interface, + ep_out: desc.Endpoint, + ep_in: desc.Endpoint, + + /// This function is used during descriptor creation. If multiple instances + /// of a driver are used, a descriptor will be created for each. + /// Endpoint numbers are allocated automatically, this function should + /// use placeholder .ep0 values on all endpoints. + pub fn create( + alloc: *usb.DescriptorAllocator, + first_string: u8, + max_supported_packet_size: usb.types.Len, + ) @This() { + return .{ + .example_interface = .{ + .interface_number = alloc.next_itf(), + .alternate_setting = 0, + .num_endpoints = 2, + .interface_triple = .vendor_specific, + .interface_s = first_string, + }, + .ep_out = .bulk(alloc.next_ep(.Out), max_supported_packet_size), + .ep_in = .bulk(alloc.next_ep(.In), max_supported_packet_size), + }; + } + }; + + /// This is a mapping from endpoint descriptor field names to handler + /// function names. Counterintuitively, usb devices send data on 'in' + /// endpoints and receive on 'out' endpoints. + pub const handlers: usb.DriverHadlers(@This()) = .{ + .ep_in = on_tx_ready, + .ep_out = on_rx, + }; + + device: *usb.DeviceInterface, + ep_tx: EP_Num, + + /// This function is called when the host chooses a configuration + /// that contains this driver. `self` points to undefined memory. + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + self.* = .{ + .device = device, + .ep_tx = desc.ep_in.endpoint.num, + }; + device.ep_listen( + desc.ep_out.endpoint.num, + @intCast(desc.ep_out.max_packet_size.into()), + ); + } + + /// Used for interface configuration through endpoint 0. + /// Data returned by this function is sent on endpoint 0. + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { + _ = self; + _ = setup; + return usb.ack; + } + + /// Each endpoint (as defined in the descriptor) has its own handler. + /// Endpoint number is passed as an argument so that it does not need + /// to be stored in the driver. + pub fn on_tx_ready(self: *@This(), ep_tx: EP_Num) void { + log.info("tx ready", .{}); + // Mark transmission as available + @atomicStore(EP_Num, &self.ep_tx, ep_tx, .seq_cst); + } + + pub fn on_rx(self: *@This(), ep_rx: EP_Num) void { + var buf: [64]u8 = undefined; + // Read incoming packet into a local buffer + const len_rx = self.device.ep_readv(ep_rx, &.{&buf}); + log.info("Received: {s}", .{buf[0..len_rx]}); + // Check if we can transmit + const ep_tx = @atomicLoad(EP_Num, &self.ep_tx, .seq_cst); + if (ep_tx != .ep0) { + // Mark transmission as not available + @atomicStore(EP_Num, &self.ep_tx, .ep0, .seq_cst); + // Send received packet + log.info("Sending {} bytes", .{len_rx}); + const len_tx = self.device.ep_writev(ep_tx, &.{buf[0..len_rx]}); + if (len_tx != len_rx) + log.err("Only sent {} bytes", .{len_tx}); + } + // Listen for next packet + self.device.ep_listen(ep_rx, 64); + } +}; diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index a12f4f64e..8f6a0d734 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -1,102 +1,100 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const descriptor = usb.descriptor; -const types = usb.types; +const EP_Num = usb.types.Endpoint.Num; +const log = std.log.scoped(.usb_hid); pub const Options = struct { - max_packet_size: u16, boot_protocol: bool, - endpoint_interval: u8, + poll_interval: u8, }; pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return struct { pub const Descriptor = extern struct { - interface: descriptor.Interface, - hid: descriptor.hid.Hid, - ep_out: descriptor.Endpoint, - ep_in: descriptor.Endpoint, + const desc = usb.descriptor; + + interface: desc.Interface, + hid: desc.hid.HID, + ep_out: desc.Endpoint, + ep_in: desc.Endpoint, pub fn create( - first_interface: u8, + alloc: *usb.DescriptorAllocator, first_string: u8, - first_endpoint_in: u4, - first_endpoint_out: u4, + max_supported_packet_size: usb.types.Len, ) @This() { return .{ .interface = .{ - .interface_number = first_interface, + .interface_number = alloc.next_itf(), .alternate_setting = 0, .num_endpoints = 2, - .interface_class = 3, - .interface_subclass = @intFromBool(options.boot_protocol), - .interface_protocol = @intFromBool(options.boot_protocol), + .interface_triple = .from( + .HID, + if (options.boot_protocol) .Boot else .Unspecified, + if (options.boot_protocol) .Boot else .None, + ), .interface_s = first_string, }, .hid = hid_descriptor, - .ep_out = .{ - .endpoint = .in(@enumFromInt(first_endpoint_out)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, - .max_packet_size = .from(options.max_packet_size), - .interval = options.endpoint_interval, - }, - .ep_in = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, - .max_packet_size = .from(options.max_packet_size), - .interval = options.endpoint_interval, - }, + .ep_out = .interrupt(alloc.next_ep(.Out), max_supported_packet_size, options.poll_interval), + .ep_in = .interrupt(alloc.next_ep(.In), max_supported_packet_size, options.poll_interval), }; } }; - const hid_descriptor: descriptor.hid.Hid = .{ + const hid_descriptor: usb.descriptor.hid.HID = .{ .bcd_hid = .from(0x0111), .country_code = .NotSupported, .num_descriptors = 1, .report_length = .from(@sizeOf(@TypeOf(report_descriptor))), }; + pub const handlers: usb.DriverHadlers(@This()) = .{ + .ep_out = on_rx, + .ep_in = on_tx_ready, + }; + device: *usb.DeviceInterface, - ep_in: types.Endpoint.Num, - ep_out: types.Endpoint.Num, + ep_in: EP_Num, + ep_out: EP_Num, - pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - return .{ + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + self.* = .{ .device = device, .ep_in = desc.ep_in.endpoint.num, .ep_out = desc.ep_out.endpoint.num, }; } - pub fn class_control(self: *@This(), stage: types.ControlStage, setup: *const types.SetupPacket) ?[]const u8 { + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { _ = self; - if (stage == .Setup) switch (setup.request_type.type) { + switch (setup.request_type.type) { .Standard => { - const hid_desc_type = std.meta.intToEnum(descriptor.hid.Hid.Type, (setup.value >> 8) & 0xff) catch return usb.nak; - const request_code = std.meta.intToEnum(types.SetupRequest, setup.request) catch return usb.nak; + const hid_desc_type: usb.descriptor.hid.HID.Type = @enumFromInt(setup.value.into() >> 8); + const request_code: usb.types.SetupRequest = @enumFromInt(setup.request); - if (request_code == .GetDescriptor and hid_desc_type == .Hid) - return @as([]const u8, @ptrCast(&hid_descriptor)) + if (request_code == .GetDescriptor and hid_desc_type == .HID) + return std.mem.asBytes(&hid_descriptor) else if (request_code == .GetDescriptor and hid_desc_type == .Report) - return @as([]const u8, @ptrCast(&report_descriptor)); + return std.mem.asBytes(&report_descriptor); }, .Class => { - const hid_request_type = std.meta.intToEnum(descriptor.hid.RequestType, setup.request) catch return usb.nak; + const hid_request_type: usb.descriptor.hid.RequestType = @enumFromInt(setup.request); switch (hid_request_type) { .SetIdle => { - // TODO: The host is attempting to limit bandwidth by requesting that + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 + // 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 return usb.ack; }, .SetProtocol => { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 + // 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, @@ -104,29 +102,32 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { // 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 return usb.ack; }, .SetReport => { - // TODO: This request sends a feature or output report to the device, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 + // 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 return usb.ack; }, else => {}, } }, else => {}, - }; + } return usb.nak; } - pub fn transfer(self: *@This(), ep: types.Endpoint, data: []u8) void { + pub fn on_tx_ready(self: *@This(), ep: EP_Num) void { + _ = self; + _ = ep; + } + + pub fn on_rx(self: *@This(), ep: EP_Num) void { _ = self; _ = ep; - _ = data; } }; } diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index d0f010825..bfcd04ba0 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -1,34 +1,286 @@ const std = @import("std"); const assert = std.debug.assert; -/// Class of device, giving a broad functional area. -pub const ClassCode = enum(u8) { - 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, - _, +pub const ClassSubclassProtocol = extern struct { + /// Class of device, giving a broad functional area. + pub const ClassCode = enum(u8) { + Unspecified = 0x00, + Audio = 0x01, + CDC = 0x02, + HID = 0x03, + Physical = 0x05, + Image = 0x06, + Printer = 0x07, + MassStorage = 0x08, + Hub = 0x09, + CDC_Data = 0x0A, + SmartCard = 0x0B, + ContentSecurity = 0x0D, + Video = 0x0E, + PersonalHealthcare = 0x0F, + AudioVideoDevice = 0x10, + BillboardDevice = 0x11, + Type_C_Bridge = 0x12, + BulkDisplayProtocol = 0x13, + MCTP_Over_USB_ProtocolEndpoint = 0x14, + I3C = 0x3C, + DiagnosticDevice = 0xDC, + WirelessController = 0xE0, + Miscellaneous = 0xEF, + ApplicationSpecific = 0xFE, + VendorSpecific = 0xFF, + _, + + pub fn Subclass(self: @This()) type { + return @field(ClassSubclassProtocol.Subclass, @tagName(self)); + } + + pub fn Protocol(self: @This()) type { + return @field(ClassSubclassProtocol.Protocol, @tagName(self)); + } + }; + + pub const Subclass = struct { + pub const Default = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + _, + }; + + pub const Unspecified = Default; + pub const Audio = Default; + + pub const CDC = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + /// Direct Line Control Model + DirectLine = 0x01, + /// Abstract Control Model + Abstract = 0x02, + /// Telephone Control Model + Telephone = 0x03, + /// Multi-Channel Control Model + MultChannel = 0x04, + /// CAPI Control Model + CAPI = 0x05, + /// Ethernet Networking Control Model + Ethernet = 0x06, + /// ATM Networking Control Model + ATM_Networking = 0x07, + /// Wireless Handset Control Model + WirelessHeadest = 0x08, + /// Device Management + DeviceManagement = 0x09, + /// Mobile Direct Line Model + MobileDirect = 0x0A, + /// OBEX + OBEX = 0x0B, + /// Ethernet Emulation Model + EthernetEmulation = 0x0C, + /// Network Control Model + Network = 0x0D, + _, + }; + + pub const HID = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + /// + Boot = 0x01, + _, + }; + + pub const Physical = Default; + pub const Image = Default; + pub const Printer = Default; + + pub const MassStorage = enum(u8) { + /// SCSI command set not reported. De facto use. + Unspecified = 0x00, + /// Allocated by USB-IF for RBC. RBC is defined outside of USB. + RBC = 0x01, + /// Allocated by USB-IF for MMC-5 (ATAPI). MMC-5 is defined outside of USB. + MMC_5 = 0x02, + /// Obsolete. Was QIC-157 + QIC_157 = 0x03, + /// Specifies how to interface Floppy Disk Drives to USB + UFI = 0x04, + /// Obsolete. Was SFF-8070i + SFF_8070i = 0x05, + /// SCSI transparent command set. Allocated by USB-IF for SCSI. SCSI standards are defined outside of USB. + SCSI = 0x06, + /// LSDFS. specifies how host has to negotiate access before trying SCSI + LSD_FS = 0x07, + /// Allocated by USB-IF for IEEE 1667. IEEE 1667 is defined outside of USB. + IEEE_1667 = 0x08, + /// Specific to device vendor. De facto use + VendorSpecific = 0xFF, + _, + }; + + pub const Hub = Default; + + pub const CDC_Data = enum(u8) { + Unused = 0, + VendorSpecific = 0xFF, + _, + }; + + pub const SmartCard = Default; + pub const ContentSecurity = Default; + pub const Video = Default; + pub const PersonalHealthcare = Default; + pub const AudioVideoDevice = Default; + pub const BillboardDevice = Default; + pub const Type_C_Bridge = Default; + pub const BulkDisplayProtocol = Default; + pub const MCTP_Over_USB_ProtocolEndpoint = Default; + pub const I3C = Default; + pub const DiagnosticDevice = Default; + pub const WirelessController = Default; + pub const Miscellaneous = Default; + pub const ApplicationSpecific = Default; + pub const VendorSpecific = Default; + }; + + pub const Protocol = struct { + pub const Default = enum(u8) { + NoneRequired = 0x00, + VendorSpecific = 0xFF, + _, + }; + + pub const Unspecified = Default; + pub const Audio = Default; + + pub const CDC = enum(u8) { + /// USB specification No class specific protocol required + NoneRequired = 0x00, + /// ITU-T V.250 AT Commands: V.250 etc + AT_ITU_T_V_250 = 0x01, + /// PCCA-101 AT Commands defined by PCCA-101 + AT_PCCA_101 = 0x02, + /// PCCA-101 AT Commands defined by PCCA-101 & Annex O + AT_PCCA_101_O = 0x03, + /// GSM 7.07 AT Commands defined by GSM 07.07 + AT_GSM_7_07 = 0x04, + /// 3GPP 27.07 AT Commands defined by 3GPP 27.007 + AT_3GPP_27_07 = 0x05, + /// C-S0017-0 AT Commands defined by TIA for CDMA + AT_C_S0017_0 = 0x06, + /// USB EEM Ethernet Emulation module + USB_EEM = 0x07, + /// External Protocol: Commands defined by Command Set Functional Descriptor + External = 0xFE, + /// USB Specification Vendor-specific + VendorSpecific = 0xFF, + _, + }; + + pub const HID = enum(u8) { + NoneRequired = 0x00, + VendorSpecific = 0xFF, + /// + Boot = 0x01, + _, + }; + + pub const Physical = Default; + pub const Image = Default; + pub const Printer = Default; + + pub const MassStorage = enum(u8) { + /// USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport with command completion interrupt + CBI_with_interrupt = 0x00, + /// USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport with no command completion interrupt + CBI_no_interrupt = 0x01, + /// USB Mass Storage Class Bulk-Only (BBB) Transport + BulkOnly = 0x50, + /// Allocated by USB-IF for UAS. UAS is defined outside of USB. + UAS = 0x62, + /// Specific to device vendor De facto use + VendorSpecific = 0xFF, + _, + }; + + pub const Hub = Default; + + pub const CDC_Data = enum(u8) { + NoneRequired = 0, + VendorSpecific = 0xFF, + /// Network Transfer Block + NetworkTransferBlock = 0x01, + /// Physical interface protocol for ISDN BRI + ISDN_BRI = 0x30, + /// HDLC + HDLC = 0x31, + /// Transparent + Transparent = 0x32, + /// Management protocol for Q.921 data link protocol + Management_Q_921 = 0x50, + /// Data link protocol for Q.931 + DataLink_Q_931 = 0x51, + /// TEI-multiplexor for Q.921 data link protocol + TEI_Multiplexor_Q_921 = 0x52, + /// Data compression procedures + DataCompressionProcedures = 0x90, + /// Euro-ISDN protocol control + Euro_ISDN = 0x91, + /// V.24 rate adaptation to ISDN + RateAdaptation_V_24 = 0x92, + /// CAPI Commands + CAPI = 0x93, + /// Host based driver. Note: This protocol code should only be used + /// in messages between host and device to identify the host driver + /// portion of a protocol stack. + HostBasedDriver = 0xFD, + /// CDC Specification The protocol(s) are described using a Protocol + /// Unit Functional Descriptors on Communications Class Interface + SpecifiedIn_PUF_Descriptor = 0xFE, + _, + }; + + pub const SmartCard = Default; + pub const ContentSecurity = Default; + pub const Video = Default; + pub const PersonalHealthcare = Default; + pub const AudioVideoDevice = Default; + pub const BillboardDevice = Default; + pub const Type_C_Bridge = Default; + pub const BulkDisplayProtocol = Default; + pub const MCTP_Over_USB_ProtocolEndpoint = Default; + pub const I3C = Default; + pub const DiagnosticDevice = Default; + pub const WirelessController = Default; + pub const Miscellaneous = Default; + pub const ApplicationSpecific = Default; + pub const VendorSpecific = Default; + }; + + /// Class code, distinguishing the type of interface. + class: ClassCode, + /// Interface subclass code, refining the class of interface. + subclass: u8, + /// Protocol within the interface class/subclass. + protocol: u8, + + pub const unspecified: @This() = + .from(.Unspecified, .Unspecified, .NoneRequired); + + pub const vendor_specific: @This() = + .from(.VendorSpecific, .VendorSpecific, .VendorSpecific); + + pub fn from( + comptime class: ClassCode, + subclass: class.Subclass(), + protocol: class.Protocol(), + ) @This() { + return .{ + .class = class, + .subclass = @intFromEnum(subclass), + .protocol = @intFromEnum(protocol), + }; + } }; /// Types of transfer that can be indicated by the `attributes` field on `EndpointDescriptor`. @@ -39,19 +291,32 @@ pub const TransferType = enum(u2) { Interrupt = 3, }; -pub const ControlStage = enum { - Idle, - Setup, - Data, - Ack, -}; - /// The types of USB SETUP requests that we understand. pub const SetupRequest = enum(u8) { + GetStatus = 0x00, + ClearFeature = 0x02, SetFeature = 0x03, SetAddress = 0x05, GetDescriptor = 0x06, + SetDescriptor = 0x07, + GetConfiguration = 0x08, SetConfiguration = 0x09, + _, +}; + +pub const DeviceStatus = extern struct { + const Flags = packed struct(u8) { + self_powered: bool, + remote_wakeup: bool, + reserved: u6 = 0, + }; + + flags: Flags, + reserved: u8 = 0, + + pub fn create(self_powered: bool, remote_wakeup: bool) @This() { + return .{ .flags = .{ .self_powered = self_powered, .remote_wakeup = remote_wakeup } }; + } }; pub const FeatureSelector = enum(u8) { @@ -59,6 +324,7 @@ pub const FeatureSelector = enum(u8) { DeviceRemoteWakeup = 0x01, TestMode = 0x02, // The remaining features only apply to OTG devices. + _, }; /// USB deals in two different transfer directions, called OUT (host-to-device) @@ -133,26 +399,39 @@ pub const SetupPacket = extern struct { /// conflict. request: u8, /// A simple argument of up to 16 bits, specific to the request. - value: u16, - /// Not used in the requests we support. - index: u16, + value: U16_Le, + index: U16_Le, /// If data will be transferred after this request (in the direction given /// by `request_type`), this gives the number of bytes (OUT) or maximum /// number of bytes (IN). - length: u16, + length: U16_Le, }; +/// Represents packet length. +pub const Len = u11; + /// u16 value, little endian regardless of native endianness. -pub const U16Le = extern struct { - value: [2]u8, +pub const U16_Le = extern struct { + value_le: u16 align(1), pub fn from(val: u16) @This() { - var self: @This() = undefined; - std.mem.writeInt(u16, &self.value, val, .little); - return self; + return .{ .value_le = std.mem.nativeToLittle(u16, val) }; } pub fn into(self: @This()) u16 { - return std.mem.readInt(u16, &self.value, .little); + return std.mem.littleToNative(u16, self.value_le); + } +}; + +/// u32 value, little endian regardless of native endianness. +pub const U32_Le = extern struct { + value_le: u32 align(1), + + pub fn from(val: u32) @This() { + return .{ .value_le = std.mem.nativeToLittle(u32, val) }; + } + + pub fn into(self: @This()) u32 { + return std.mem.littleToNative(u32, self.value_le); } }; diff --git a/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig b/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig index 7fe3c7968..950df80c3 100644 --- a/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig +++ b/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig @@ -73,7 +73,7 @@ const system_clock_cfg: GlobalConfig = val: { .integer_divisor = 1, }, - // Change peri to also run off XOSC, not neccessarily reccomended, but interesting! + // Change peri to also run off XOSC, not necessarily reccomended, but interesting! .peri = .{ .input = .{ .source = .src_xosc, diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 2d39844db..81018ab47 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -4,50 +4,41 @@ const microzig = @import("microzig"); const rp2xxx = microzig.hal; const time = rp2xxx.time; const gpio = rp2xxx.gpio; - const usb = microzig.core.usb; - -const led = gpio.num(25); -const uart = rp2xxx.uart.instance.num(0); -const uart_tx_pin = gpio.num(0); - -const UsbSerial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); - -var usb_dev: rp2xxx.usb.Polled( - usb.Config{ - .device_descriptor = .{ - .bcd_usb = .from(0x0200), - .device_triple = .{ - .class = .Miscellaneous, - .subclass = 2, - .protocol = 1, - }, - .max_packet_size0 = 64, - .vendor = .from(0x2E8A), - .product = .from(0x000A), - .bcd_device = .from(0x0100), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .string_descriptors = &.{ - .from_lang(.English), - .from_str("Raspberry Pi"), - .from_str("Pico Test Device"), - .from_str("someserial"), - .from_str("Board CDC"), - }, - .configurations = &.{.{ - .num = 1, - .configuration_s = 0, - .attributes = .{ .self_powered = true }, - .max_current_ma = 100, - .Drivers = struct { serial: UsbSerial }, - }}, +const USB_Device = rp2xxx.usb.Polled(.{}); +const USB_Serial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = USB_Device.max_supported_packet_size }); + +var usb_device: USB_Device = undefined; + +var usb_controller: usb.DeviceController(.{ + .device_descriptor = .{ + .bcd_usb = USB_Device.max_supported_bcd_usb, + .device_triple = .from(.Miscellaneous, @enumFromInt(2), @enumFromInt(1)), + .max_packet_size0 = USB_Device.max_supported_packet_size, + .vendor = .from(0x2E8A), + .product = .from(0x000A), + .bcd_device = .from(0x0100), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, }, - .{}, -) = undefined; + .string_descriptors = &.{ + .from_lang(.English), + .from_str("Raspberry Pi"), + .from_str("Pico Test Device"), + .from_str("someserial"), + .from_str("Board CDC"), + }, + .configurations = &.{.{ + .num = 1, + .configuration_s = 0, + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, + .Drivers = struct { serial: USB_Serial }, + }}, + .max_supported_packet_size = USB_Device.max_supported_packet_size, +}) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -57,22 +48,32 @@ pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noretu pub const microzig_options = microzig.Options{ .log_level = .debug, + .log_scope_levels = &.{ + .{ .scope = .usb_dev, .level = .warn }, + .{ .scope = .usb_ctrl, .level = .warn }, + .{ .scope = .usb_cdc, .level = .warn }, + }, .logFn = rp2xxx.uart.log, }; +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO0 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; + pub fn main() !void { - uart_tx_pin.set_function(.uart); + const pins = pin_config.apply(); + + const uart = rp2xxx.uart.instance.num(0); uart.apply(.{ .clock_config = rp2xxx.clock_config, }); rp2xxx.uart.init_logger(uart); - led.set_function(.sio); - led.set_direction(.out); - led.put(1); + pins.led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev = .init(); + usb_device = .init(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -80,15 +81,15 @@ pub fn main() !void { var i: u32 = 0; while (true) { // You can now poll for USB events - usb_dev.poll(); + usb_device.poll(&usb_controller); - if (usb_dev.controller.drivers()) |drivers| { + if (usb_controller.drivers()) |drivers| { new = time.get_time_since_boot().to_us(); if (new - old > 500000) { old = new; - led.toggle(); + pins.led.toggle(); i += 1; - std.log.info("cdc test: {}\r\n", .{i}); + std.log.info("cdc test: {}", .{i}); usb_cdc_write(&drivers.serial, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); } @@ -106,17 +107,16 @@ 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(serial: *UsbSerial, comptime fmt: []const u8, args: anytype) void { - const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; +pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytype) void { + var tx = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; - var write_buff = text; - while (write_buff.len > 0) { - write_buff = serial.write(write_buff); - usb_dev.poll(); + while (tx.len > 0) { + tx = tx[serial.write(tx)..]; + usb_device.poll(&usb_controller); } // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them - _ = serial.write_flush(); - usb_dev.poll(); + while (!serial.flush()) + usb_device.poll(&usb_controller); } var usb_rx_buff: [1024]u8 = undefined; @@ -124,17 +124,15 @@ 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( - serial: *UsbSerial, + serial: *USB_Serial, ) []const u8 { - var total_read: usize = 0; - var read_buff: []u8 = usb_rx_buff[0..]; + var rx_len: usize = 0; while (true) { - const len = serial.read(read_buff); - read_buff = read_buff[len..]; - total_read += len; + const len = serial.read(usb_rx_buff[rx_len..]); + rx_len += len; if (len == 0) break; } - return usb_rx_buff[0..total_read]; + return usb_rx_buff[0..rx_len]; } diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 2e66911fa..03fd4548d 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -4,55 +4,44 @@ const microzig = @import("microzig"); const rp2xxx = microzig.hal; const time = rp2xxx.time; const gpio = rp2xxx.gpio; - const usb = microzig.core.usb; - -const led = gpio.num(25); -const uart = rp2xxx.uart.instance.num(0); -const uart_tx_pin = gpio.num(0); - -const HidDriver = usb.drivers.hid.HidClassDriver( - .{ .max_packet_size = 64, .boot_protocol = true, .endpoint_interval = 0 }, +const USB_Device = rp2xxx.usb.Polled(.{}); +const HID_Driver = usb.drivers.hid.HidClassDriver( + .{ .boot_protocol = true, .poll_interval = 0 }, usb.descriptor.hid.ReportDescriptorKeyboard, ); -const usb_config_descriptor = microzig.core.usb.descriptor.Configuration.create(); +var usb_device: USB_Device = undefined; -var usb_dev: rp2xxx.usb.Polled( - usb.Config{ - .device_descriptor = .{ - .bcd_usb = .from(0x0200), - .device_triple = .{ - .class = .Unspecified, - .subclass = 0, - .protocol = 0, - }, - .max_packet_size0 = 64, - .vendor = .from(0x2E8A), - .product = .from(0x000A), - .bcd_device = .from(0x0100), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .string_descriptors = &.{ - .from_lang(.English), - .from_str("Raspberry Pi"), - .from_str("Pico Test Device"), - .from_str("someserial"), - .from_str("Boot Keyboard"), - }, - .configurations = &.{.{ - .num = 1, - .configuration_s = 0, - .attributes = .{ .self_powered = true }, - .max_current_ma = 100, - .Drivers = struct { hid: HidDriver }, - }}, +var usb_controller: usb.DeviceController(.{ + .device_descriptor = .{ + .bcd_usb = USB_Device.max_supported_bcd_usb, + .device_triple = .unspecified, + .max_packet_size0 = USB_Device.max_supported_packet_size, + .vendor = .from(0x2E8A), + .product = .from(0x000A), + .bcd_device = .from(0x0100), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }, + .string_descriptors = &.{ + .from_lang(.English), + .from_str("Raspberry Pi"), + .from_str("Pico Test Device"), + .from_str("someserial"), + .from_str("Boot Keyboard"), }, - .{}, -) = undefined; + .configurations = &.{.{ + .num = 1, + .configuration_s = 0, + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, + .Drivers = struct { hid: HID_Driver }, + }}, + .max_supported_packet_size = USB_Device.max_supported_packet_size, +}) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -62,36 +51,46 @@ pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noretu pub const microzig_options = microzig.Options{ .log_level = .debug, + .log_scope_levels = &.{ + .{ .scope = .usb_dev, .level = .warn }, + .{ .scope = .usb_ctrl, .level = .warn }, + .{ .scope = .usb_hid, .level = .warn }, + }, .logFn = rp2xxx.uart.log, }; +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO0 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; + pub fn main() !void { - uart_tx_pin.set_function(.uart); + const pins = pin_config.apply(); + + const uart = rp2xxx.uart.instance.num(0); uart.apply(.{ .clock_config = rp2xxx.clock_config, }); rp2xxx.uart.init_logger(uart); - led.set_function(.sio); - led.set_direction(.out); - led.put(1); + pins.led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev = .init(); + usb_device = .init(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { // You can now poll for USB events - usb_dev.poll(); + usb_device.poll(&usb_controller); - if (usb_dev.controller.drivers()) |drivers| { + if (usb_controller.drivers()) |drivers| { _ = drivers; // TODO new = time.get_time_since_boot().to_us(); if (new - old > 500000) { old = new; - led.toggle(); + pins.led.toggle(); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig b/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig index 903e9c359..00b6e3219 100644 --- a/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig +++ b/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig @@ -7,7 +7,7 @@ const CLOCKS = peripherals.CLOCKS; const XOSC = peripherals.XOSC; /// The current HAL requires XOSC configuration with the RP2xxx chip, although this isn't -/// strictly neccessary as the system could be driven from an external clock. +/// strictly necessary as the system could be driven from an external clock. /// TODO: Find a way to allow this to be "null" as it's not explicitly required as long /// as a user isn't using XOSC functionality in their clock setup. pub const xosc_freq = microzig.board.xosc_freq; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 20bcc5136..acd867145 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -5,6 +5,7 @@ const std = @import("std"); const assert = std.debug.assert; +const log = std.log.scoped(.usb_dev); const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; @@ -21,7 +22,6 @@ pub const Config = struct { }; const HardwareEndpointData = struct { - awaiting_rx: bool, data_buffer: []align(64) u8, }; @@ -75,115 +75,101 @@ const endpoint_control: *volatile [15]PerEndpoint(EndpointControlMimo) = @ptrCas /// A set of functions required by the abstract USB impl to /// create a concrete one. -pub fn Polled( - controller_config: usb.Config, - config: Config, -) type { +pub fn Polled(config: Config) 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"); } return struct { + pub const max_supported_packet_size = 64; + pub const max_supported_bcd_usb: usb.types.U16_Le = .from(0x0110); + const vtable: usb.DeviceInterface.VTable = .{ - .start_tx = start_tx, - .start_rx = start_rx, + .ep_writev = ep_writev, + .ep_readv = ep_readv, + .ep_listen = ep_listen, + .ep_open = ep_open, .set_address = set_address, - .endpoint_open = endpoint_open, }; endpoints: [config.max_endpoints_count][2]HardwareEndpointData, data_buffer: []align(64) u8, - controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, - pub fn poll(self: *@This()) void { - // Check which interrupt flags are set. + pub fn poll(self: *@This(), controller: anytype) void { + comptime usb.validate_controller(@TypeOf(controller)); + // Check which interrupt flags are set. const ints = peripherals.USB.INTS.read(); // Setup request received? if (ints.SETUP_REQ != 0) { // 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. + // to an IN on EP0 needs to use PID DATA1. buffer_control[0].in.modify(.{ .PID_0 = 0 }); - const setup = get_setup_packet(); - self.controller.on_setup_req(&self.interface, &setup); + // Clear the status flag (write-one-to-clear) + peripherals.USB.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); + + // The PAC models this buffer as two 32-bit registers. + const setup: usb.types.SetupPacket = @bitCast([2]u32{ + peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw, + peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw, + }); + + log.debug("setup {any}", .{setup}); + controller.on_setup_req(&self.interface, &setup); } // Events on one or more buffers? (In practice, always one.) if (ints.BUFF_STATUS != 0) { - const bufbits_init = peripherals.USB.BUFF_STATUS.raw; - var bufbits = bufbits_init; - - while (true) { - // Who's still outstanding? Find their bit index by counting how - // many LSBs are zero. - const lowbit_index = std.math.cast(u5, @ctz(bufbits)) orelse break; - // Remove their bit from our set. - bufbits ^= @as(u32, @intCast(1)) << lowbit_index; - - // 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(u4, @intCast(lowbit_index >> 1)); - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - const dir: usb.types.Dir = if (lowbit_index & 1 == 0) .In else .Out; - - const ep: usb.types.Endpoint = .{ .num = @enumFromInt(epnum), .dir = 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_hard = self.hardware_endpoint_get_by_address(ep); - - // 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 = buffer_control[@intFromEnum(ep.num)].get(ep.dir).read().LENGTH_0; - - self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); - - if (ep.dir == .Out) - ep_hard.awaiting_rx = false; + const buff_status = peripherals.USB.BUFF_STATUS.raw; + + inline for (0..2 * config.max_endpoints_count) |shift| { + if (buff_status & (@as(u32, 1) << shift) != 0) { + // Here we exploit knowledge of the ordering of buffer control + // registers in the peripheral. Each endpoint has a pair of + // registers, IN being first + const ep_num = shift / 2; + const ep: usb.types.Endpoint = comptime .{ + .num = @enumFromInt(ep_num), + .dir = if (shift % 2 == 0) .In else .Out, + }; + + // 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. + // + // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS + // So we wait for it just to be sure. + while (buffer_control[ep_num].get(ep.dir).read().AVAILABLE_0 != 0) {} + + log.debug("buffer ep{} {t}", .{ ep_num, ep.dir }); + controller.on_buffer(&self.interface, ep); + } } - - peripherals.USB.BUFF_STATUS.write_raw(bufbits_init); - } // <-- END of buf status handling + peripherals.USB.BUFF_STATUS.raw = buff_status; + } // Has the host signaled a bus reset? if (ints.BUS_RESET != 0) { + log.info("bus reset", .{}); + + // Abort all endpoints + peripherals.USB.EP_ABORT.raw = 0xFFFFFFFF; // 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 }); - - self.controller.on_bus_reset(); + set_address(&self.interface, 0); + controller.on_bus_reset(&self.interface); + while (peripherals.USB.EP_ABORT_DONE.raw != 0xFFFFFFFF) {} + peripherals.USB.EP_ABORT.raw = 0; } } pub fn init() @This() { - if (chip == .RP2350) { - peripherals.USB.MAIN_CTRL.modify(.{ - .PHY_ISO = 0, - }); - } + if (chip == .RP2350) + peripherals.USB.MAIN_CTRL.write(.{ .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. @@ -245,27 +231,19 @@ pub fn Polled( .endpoints = undefined, .data_buffer = rp2xxx_buffers.data_buffer, .interface = .{ .vtable = &vtable }, - .controller = .init, }; @memset(std.mem.asBytes(&self.endpoints), 0); - endpoint_open(&self.interface, &.{ - .endpoint = .in(.ep0), - .max_packet_size = .from(64), - .attributes = .{ .transfer_type = .Control, .usage = .data }, - .interval = 0, - }); - endpoint_open(&self.interface, &.{ - .endpoint = .out(.ep0), - .max_packet_size = .from(64), - .attributes = .{ .transfer_type = .Control, .usage = .data }, - .interval = 0, - }); + ep_open(&self.interface, &.control(.in(.ep0), max_supported_packet_size)); + ep_open(&self.interface, &.control(.out(.ep0), max_supported_packet_size)); // 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 }); + // Listen for ACKs + self.interface.ep_listen(.ep0, max_supported_packet_size); + return self; } @@ -274,38 +252,29 @@ pub fn Polled( /// 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. - fn start_tx( + fn ep_writev( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, - buffer: []const u8, - ) void { - const self: *@This() = @fieldParentPtr("interface", itf); + data: []const []const u8, + ) usb.types.Len { + log.debug("writev {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); - // It is technically possible to support longer buffers but this demo - // doesn't bother. - assert(buffer.len <= 64); + const self: *@This() = @fieldParentPtr("interface", itf); const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].in; const ep = self.hardware_endpoint_get_by_address(.in(ep_num)); - // Wait for controller to give processor ownership of the buffer before writing it. - // This is technically not neccessary, but the usb cdc driver is bugged. - while (bufctrl_ptr.read().AVAILABLE_0 == 1) {} + var hw_buf: []align(1) u8 = ep.data_buffer; - const len = buffer.len; - switch (chip) { - .RP2040 => @memcpy(ep.data_buffer[0..len], buffer[0..len]), - .RP2350 => { - const dst: [*]align(4) u32 = @ptrCast(ep.data_buffer.ptr); - const src: [*]align(1) const u32 = @ptrCast(buffer.ptr); - for (0..len / 4) |i| - dst[i] = src[i]; - for (0..len % 4) |i| - ep.data_buffer[len - i - 1] = buffer[len - i - 1]; - }, + for (data) |src| { + const len = @min(src.len, hw_buf.len); + dpram_memcpy(hw_buf[0..len], src[0..len]); + hw_buf = hw_buf[len..]; } - var bufctrl = bufctrl_ptr.read(); + const len: usb.types.Len = @intCast(ep.data_buffer.len - hw_buf.len); + var bufctrl = bufctrl_ptr.read(); + assert(bufctrl.AVAILABLE_0 == 0); // Write the buffer information to the buffer control register bufctrl.PID_0 ^= 1; // flip DATA0/1 bufctrl.FULL_0 = 1; // We have put data in @@ -322,30 +291,56 @@ pub fn Polled( // Set available bit bufctrl.AVAILABLE_0 = 1; bufctrl_ptr.write(bufctrl); + + return len; } - fn start_rx( + /// Copies the last sent packet from USB SRAM into the provided buffer. + /// Slices in `data` must collectively be long enough to store the full packet. + fn ep_readv( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, - len: usize, - ) void { - const self: *@This() = @fieldParentPtr("interface", itf); + data: []const []u8, + ) usb.types.Len { + var total_len: usize = data[0].len; + for (data[1..]) |d| total_len += d.len; + log.debug("readv {t} {}", .{ ep_num, total_len }); - // It is technically possible to support longer buffers but this demo doesn't bother. - assert(len <= 64); + const self: *@This() = @fieldParentPtr("interface", itf); + assert(data.len > 0); - const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].out; + const bufctrl = buffer_control[@intFromEnum(ep_num)].out.read(); const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); + var hw_buf: []align(1) u8 = ep.data_buffer[0..bufctrl.LENGTH_0]; + for (data) |dst| { + const len = @min(dst.len, hw_buf.len); + dpram_memcpy(dst[0..len], hw_buf[0..len]); + + hw_buf = hw_buf[len..]; + if (hw_buf.len == 0) + return @intCast(hw_buf.ptr - ep.data_buffer.ptr); + } + log.warn("discarding rx data on ep {t}, {} bytes received", .{ ep_num, bufctrl.LENGTH_0 }); + return @intCast(total_len); + } - // This function should only be called when the buffer is known to be available, - // but the current driver implementations do not conform to that. - if (ep.awaiting_rx) return; + fn ep_listen( + itf: *usb.DeviceInterface, + ep_num: usb.types.Endpoint.Num, + len: usb.types.Len, + ) void { + log.debug("listen {t} {}", .{ ep_num, len }); + + const self: *@This() = @fieldParentPtr("interface", itf); + const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].out; - // Configure the OUT: var bufctrl = bufctrl_ptr.read(); + const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); + assert(bufctrl.AVAILABLE_0 == 0); + // Configure the OUT: bufctrl.PID_0 ^= 1; // Flip DATA0/1 bufctrl.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it - bufctrl.LENGTH_0 = @intCast(len); // Up tho this many bytes + bufctrl.LENGTH_0 = @intCast(@min(len, ep.data_buffer.len)); // Up tho this many bytes if (config.sync_noops != 0) { bufctrl_ptr.write(bufctrl); @@ -358,52 +353,49 @@ pub fn Polled( // Set available bit bufctrl.AVAILABLE_0 = 1; bufctrl_ptr.write(bufctrl); - - ep.awaiting_rx = true; } - /// 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 flag is set. - 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. - - // The PAC models this buffer as two 32-bit registers. - return @bitCast([2]u32{ - peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw, - peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw, - }); - } + fn set_address(_: *usb.DeviceInterface, addr: u7) void { + log.debug("set addr {}", .{addr}); - fn set_address(itf: *usb.DeviceInterface, addr: u7) void { - const self: *@This() = @fieldParentPtr("interface", itf); - _ = self; + peripherals.USB.ADDR_ENDP.write(.{ .ADDRESS = addr }); + } - peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = addr }); + fn dpram_memcpy(dst: []u8, src: []const u8) void { + assert(dst.len == src.len); + switch (chip) { + .RP2040 => @memcpy(dst, src), + .RP2350 => { + // Could be optimized for aligned data, for now just copy + // byte by byte. Atomic operations are used so that + // the compiler does not try to optimize this. + for (dst, src) |*d, *s| { + const tmp = @atomicLoad(u8, s, .unordered); + @atomicStore(u8, d, tmp, .unordered); + } + }, + } } fn hardware_endpoint_get_by_address(self: *@This(), ep: usb.types.Endpoint) *HardwareEndpointData { return &self.endpoints[@intFromEnum(ep.num)][@intFromEnum(ep.dir)]; } - fn endpoint_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { + fn ep_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { + const ep = desc.endpoint; + const attr = desc.attributes; + log.debug( + "ep open {t} {t} {{ type: {t}, sync: {t}, usage: {t}, size: {} }}", + .{ ep.num, ep.dir, attr.transfer_type, attr.synchronisation, attr.usage, desc.max_packet_size.into() }, + ); + const self: *@This() = @fieldParentPtr("interface", itf); assert(@intFromEnum(desc.endpoint.num) <= config.max_endpoints_count); - const ep = desc.endpoint; const ep_hard = self.hardware_endpoint_get_by_address(ep); - assert(desc.max_packet_size.into() <= 64); - ep_hard.awaiting_rx = false; + assert(desc.max_packet_size.into() <= max_supported_packet_size); buffer_control[@intFromEnum(ep.num)].get(ep.dir).modify(.{ .PID_0 = 1 }); @@ -415,10 +407,11 @@ pub fn Polled( endpoint_control[@intFromEnum(ep.num) - 1].get(ep.dir).write(.{ .ENABLE = 1, .INTERRUPT_PER_BUFF = 1, - .ENDPOINT_TYPE = @enumFromInt(@intFromEnum(desc.attributes.transfer_type)), + .ENDPOINT_TYPE = @enumFromInt(@intFromEnum(attr.transfer_type)), .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data_buffer), }); } + @memset(ep_hard.data_buffer, 0); } fn endpoint_alloc(self: *@This(), desc: *const usb.descriptor.Endpoint) ![]align(64) u8 {