From f327e4efae83e2b50bbf19cdcc995e398cc4ef2c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 15 Aug 2025 18:17:49 +0200 Subject: [PATCH 01/33] improve endpoint type safety --- core/src/core/usb.zig | 107 ++++++------- core/src/core/usb/cdc.zig | 45 +++--- core/src/core/usb/hid.zig | 10 +- core/src/core/usb/templates.zig | 25 +-- core/src/core/usb/types.zig | 71 ++++++--- examples/raspberrypi/rp2xxx/build.zig | 27 +++- .../rp2xxx/src/rp2040_only/usb_hid.zig | 2 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 149 +++++++++--------- 9 files changed, 234 insertions(+), 204 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index dbd24c583..797fe3bdc 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -95,12 +95,12 @@ pub fn Usb(comptime f: anytype) type { const data_chunk = S.buffer_reader.try_peek(64); if (data_chunk.len > 0) { - f.usb_start_tx(Endpoint.EP0_IN_ADDR, data_chunk); + f.usb_start_tx(.ep0, data_chunk); } } fn send_cmd_ack() void { - f.usb_start_tx(Endpoint.EP0_IN_ADDR, &.{}); + f.usb_start_tx(.ep0, &.{}); } }; @@ -149,17 +149,18 @@ pub fn Usb(comptime f: anytype) type { fn device_endpoint_open(ep_desc: []const u8) void { const ep_addr = BosConfig.get_data_u8(ep_desc, 2); + const ep: Endpoint = .from_address(ep_addr); const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); - f.endpoint_open(ep_addr, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk); + f.endpoint_open(ep, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk); } - fn device_endpoint_transfer(ep_addr: u8, data: []const u8) void { - if (Endpoint.dir_from_address(ep_addr) == .In) { - f.usb_start_tx(ep_addr, data); + fn device_endpoint_transfer(ep: Endpoint, data: []const u8) void { + if (ep.dir == .In) { + f.usb_start_tx(ep.num, data); } else { - f.usb_start_rx(ep_addr, max_packet_size); + f.usb_start_rx(ep.num, max_packet_size); } } @@ -371,8 +372,11 @@ pub fn Usb(comptime f: anytype) type { curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); }) { if (BosConfig.try_get_desc_as(types.EndpointDescriptor, curr_bos_cfg)) |desc_ep| { - const ep_addr = desc_ep.endpoint_address; - ep_to_drv[Endpoint.num_from_address(ep_addr)][Endpoint.dir_from_address(ep_addr).as_number()] = drv_idx; + const dir_as_number: u1 = switch (desc_ep.endpoint.dir) { + .Out => 0, + .In => 1, + }; + ep_to_drv[desc_ep.endpoint.num.to_int()][dir_as_number] = drv_idx; } } } @@ -431,63 +435,54 @@ pub fn Usb(comptime f: anytype) type { // Perform any required action on the data. For OUT, the `data` // will be whatever was sent by the host. For IN, it's a copy of // whatever we sent. - switch (epb.endpoint_address) { - Endpoint.EP0_IN_ADDR => { - if (debug) std.log.info(" EP0_IN_ADDR", .{}); - - const buffer_reader = &S.buffer_reader; + const ep: Endpoint = .from_address(epb.endpoint_address); + if (ep.num == .ep0 and ep.dir == .In) { + if (debug) std.log.info(" EP0_IN_ADDR", .{}); - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (S.new_address) |addr| { - // Change our address: - f.set_address(@intCast(addr)); - } + const buffer_reader = &S.buffer_reader; - if (epb.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) { - _ = buffer_reader.try_advance(epb.buffer.len); - const next_data_chunk = buffer_reader.try_peek(64); - if (next_data_chunk.len > 0) { - f.usb_start_tx( - Endpoint.EP0_IN_ADDR, - next_data_chunk, - ); - } else { - f.usb_start_rx( - Endpoint.EP0_OUT_ADDR, - 0, - ); + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (S.new_address) |addr| { + // Change our address: + f.set_address(@intCast(addr)); + } - if (S.driver) |driver| { - _ = driver.class_control(.Ack, &S.setup_packet); - } - } + if (epb.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) { + _ = buffer_reader.try_advance(epb.buffer.len); + const next_data_chunk = buffer_reader.try_peek(64); + if (next_data_chunk.len > 0) { + f.usb_start_tx(.ep0, next_data_chunk); } else { - // Otherwise, we've just finished sending - // something to the host. We expect an ensuing - // status phase where the host sends us (via EP0 - // OUT) a zero-byte DATA packet, so, set that - // up: - f.usb_start_rx( - Endpoint.EP0_OUT_ADDR, - 0, - ); + f.usb_start_rx(.ep0, 0); if (S.driver) |driver| { _ = driver.class_control(.Ack, &S.setup_packet); } } - }, - else => { - const ep_num = Endpoint.num_from_address(epb.endpoint_address); - const ep_dir = Endpoint.dir_from_address(epb.endpoint_address).as_number(); - if (get_driver(ep_to_drv[ep_num][ep_dir])) |driver| { - driver.transfer(epb.endpoint_address, epb.buffer); - } - if (Endpoint.dir_from_address(epb.endpoint_address) == .Out) { - f.endpoint_reset_rx(epb.endpoint_address); + } else { + // Otherwise, we've just finished sending + // something to the host. We expect an ensuing + // status phase where the host sends us (via EP0 + // OUT) a zero-byte DATA packet, so, set that + // up: + f.usb_start_rx(.ep0, 0); + + if (S.driver) |driver| { + _ = driver.class_control(.Ack, &S.setup_packet); } - }, + } + } else { + const dir_as_number: u1 = switch (ep.dir) { + .Out => 0, + .In => 1, + }; + if (get_driver(ep_to_drv[ep.num.to_int()][dir_as_number])) |driver| { + driver.transfer(epb.endpoint_address, epb.buffer); + } + if (ep.dir == .Out) { + f.endpoint_reset_rx(ep); + } } } } // <-- END of buf status handling diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index d55fb44b8..a5a5154f7 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -130,9 +130,9 @@ pub fn CdcClassDriver(comptime usb: anytype) type { return struct { device: ?types.UsbDevice = null, - ep_notif: u8 = 0, - ep_in: u8 = 0, - ep_out: u8 = 0, + ep_in_notif: types.Endpoint.Num = .ep0, + ep_in: types.Endpoint.Num = .ep0, + ep_out: types.Endpoint.Num = .ep0, line_coding: CdcLineCoding = undefined, @@ -175,14 +175,14 @@ pub fn CdcClassDriver(comptime usb: anytype) type { return 0; } const len = self.tx.read(&self.epin_buf); - self.device.?.endpoint_transfer(self.ep_in, self.epin_buf[0..len]); + self.device.?.endpoint_transfer(.in(self.ep_in), self.epin_buf[0..len]); return len; } fn prep_out_transaction(self: *@This()) void { if (self.rx.writableLength() >= usb.max_packet_size) { // Let endpoint know that we are ready for next packet - self.device.?.endpoint_transfer(self.ep_out, &.{}); + self.device.?.endpoint_transfer(.out(self.ep_out), &.{}); } } @@ -215,7 +215,8 @@ pub fn CdcClassDriver(comptime usb: anytype) type { } if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - self.ep_notif = desc_ep.endpoint_address; + std.debug.assert(desc_ep.endpoint.dir == .In); + self.ep_in_notif = desc_ep.endpoint.num; curr_cfg = bos.get_desc_next(curr_cfg); } @@ -224,9 +225,9 @@ pub fn CdcClassDriver(comptime usb: anytype) type { curr_cfg = bos.get_desc_next(curr_cfg); for (0..2) |_| { if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (types.Endpoint.dir_from_address(desc_ep.endpoint_address)) { - .In => self.ep_in = desc_ep.endpoint_address, - .Out => self.ep_out = desc_ep.endpoint_address, + switch (desc_ep.endpoint.dir) { + .In => self.ep_in = desc_ep.endpoint.num, + .Out => self.ep_out = desc_ep.endpoint.num, } self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); curr_cfg = bos.get_desc_next(curr_cfg); @@ -282,19 +283,21 @@ pub fn CdcClassDriver(comptime usb: anytype) type { fn transfer(ptr: *anyopaque, ep_addr: u8, data: []u8) void { var self: *@This() = @ptrCast(@alignCast(ptr)); - if (ep_addr == self.ep_out) { - self.rx.write(data) catch {}; - self.prep_out_transaction(); - } - - if (ep_addr == self.ep_in) { - if (self.write_flush() == 0) { - // If there is no data left, a empty packet should be sent if - // data len is multiple of EP Packet size and not zero - if (self.tx.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { - self.device.?.endpoint_transfer(self.ep_in, &.{}); + const ep: types.Endpoint = .from_address(ep_addr); + switch (ep.dir) { + .Out => if (ep.num == self.ep_out) { + self.rx.write(data) catch {}; + self.prep_out_transaction(); + }, + .In => if (ep.num == 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.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { + self.device.?.endpoint_transfer(.in(self.ep_in), &.{}); + } } - } + }, } } diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 58c8ab8cb..56bcb3ec8 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -524,8 +524,8 @@ pub const ReportDescriptorKeyboard = hid_usage_page(1, UsageTable.desktop) // pub const HidClassDriver = struct { device: ?types.UsbDevice = null, - ep_in: u8 = 0, - ep_out: u8 = 0, + ep_in: types.Endpoint.Num = .ep0, + ep_out: types.Endpoint.Num = .ep0, hid_descriptor: []const u8 = &.{}, report_descriptor: []const u8, @@ -554,9 +554,9 @@ pub const HidClassDriver = struct { for (0..2) |_| { if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (types.Endpoint.dir_from_address(desc_ep.endpoint_address)) { - .In => self.ep_in = desc_ep.endpoint_address, - .Out => self.ep_out = desc_ep.endpoint_address, + switch (desc_ep.endpoint.dir) { + .In => self.ep_in = desc_ep.endpoint.num, + .Out => self.ep_out = desc_ep.endpoint.num, } self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); curr_cfg = bos.get_desc_next(curr_cfg); diff --git a/core/src/core/usb/templates.zig b/core/src/core/usb/templates.zig index 46097c556..fd92c04b9 100644 --- a/core/src/core/usb/templates.zig +++ b/core/src/core/usb/templates.zig @@ -1,6 +1,7 @@ const types = @import("types.zig"); const hid = @import("hid.zig"); const cdc = @import("cdc.zig"); +const Ep = types.Endpoint; pub const DescriptorsConfigTemplates = struct { pub const config_descriptor_len = 9; @@ -12,45 +13,45 @@ pub const DescriptorsConfigTemplates = struct { pub const cdc_descriptor_len = 8 + 9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7; - pub fn cdc_descriptor(interface_number: u8, string_index: u8, endpoint_notifi_address: u8, endpoint_notifi_size: u16, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16) [cdc_descriptor_len]u8 { + pub fn cdc_descriptor(interface_number: u8, string_index: u8, endpoint_in_notifi: Ep.Num, endpoint_notifi_size: u16, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16) [cdc_descriptor_len]u8 { const desc1 = types.InterfaceAssociationDescriptor{ .first_interface = interface_number, .interface_count = 2, .function_class = 2, .function_subclass = 2, .function_protocol = 0, .function = 0 }; const desc2 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, .interface_subclass = 2, .interface_protocol = 0, .interface_s = string_index }; const desc3 = cdc.CdcHeaderDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .Header, .bcd_cdc = 0x0120 }; const desc4 = cdc.CdcCallManagementDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1 }; const desc5 = cdc.CdcAcmDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .ACM, .capabilities = 6 }; const desc6 = cdc.CdcUnionDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1 }; - const desc7 = types.EndpointDescriptor{ .endpoint_address = endpoint_notifi_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_notifi_size, .interval = 16 }; + const desc7 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in_notifi), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_notifi_size, .interval = 16 }; const desc8 = types.InterfaceDescriptor{ .interface_number = interface_number + 1, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, .interface_subclass = 0, .interface_protocol = 0, .interface_s = 0 }; - const desc9 = types.EndpointDescriptor{ .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - const desc10 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; + const desc9 = types.EndpointDescriptor{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; + const desc10 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize() ++ desc5.serialize() ++ desc6.serialize() ++ desc7.serialize() ++ desc8.serialize() ++ desc9.serialize() ++ desc10.serialize(); } pub const hid_in_descriptor_len = 9 + 9 + 7; - pub fn hid_in_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in_address: u8, endpoint_size: u16, endpoint_interval: u16) [hid_in_descriptor_len]u8 { + pub fn hid_in_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in: Ep.Num, endpoint_size: u16, endpoint_interval: u16) [hid_in_descriptor_len]u8 { const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; + const desc3 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); } pub const hid_in_out_descriptor_len = 9 + 9 + 7 + 7; - pub fn hid_in_out_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16, endpoint_interval: u16) [hid_in_out_descriptor_len]u8 { + pub fn hid_in_out_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16, endpoint_interval: u16) [hid_in_out_descriptor_len]u8 { const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = types.EndpointDescriptor{ .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; - const desc4 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; + const desc3 = types.EndpointDescriptor{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; + const desc4 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize(); } pub const vendor_descriptor_len = 9 + 7 + 7; - pub fn vendor_descriptor(interface_number: u8, string_index: u8, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16) [vendor_descriptor_len]u8 { + pub fn vendor_descriptor(interface_number: u8, string_index: u8, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16) [vendor_descriptor_len]u8 { const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xff, .interface_subclass = 0, .interface_protocol = 0, .interface_s = string_index }; - const desc2 = types.EndpointDescriptor{ .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - const desc3 = types.EndpointDescriptor{ .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; + const desc2 = types.EndpointDescriptor{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; + const desc3 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); } }; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index a3a7ab857..2d3fce534 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; pub const DescType = enum(u8) { Device = 0x01, @@ -79,36 +80,56 @@ pub const FeatureSelector = enum(u8) { pub const Dir = enum(u1) { Out = 0, In = 1, +}; - pub const DIR_IN_MASK = 0x80; +pub const Endpoint = packed struct(u8) { + // There are up to 15 endpoints for eqch direction. + pub const Num = enum(u4) { + ep0 = 0, + ep1, + ep2, + ep3, + ep4, + ep5, + ep6, + ep7, + ep8, + ep9, + ep10, + ep11, + ep12, + ep13, + ep14, + ep15, + + pub fn from_int(int: u4) @This() { + return @enumFromInt(int); + } + + pub fn to_int(this: @This()) u4 { + return @intFromEnum(this); + } + }; - pub inline fn as_number(self: @This()) u1 { - return @intFromEnum(self); - } + num: Num, + _padding: u3 = 0, + dir: Dir, - pub inline fn as_number_reversed(self: @This()) u1 { - return ~@intFromEnum(self); + pub inline fn to_address(this: @This()) u8 { + return @bitCast(this); } -}; -pub const Endpoint = struct { - pub inline fn to_address(num: u8, dir: Dir) u8 { - return switch (dir) { - .Out => num, - .In => num | Dir.DIR_IN_MASK, - }; + pub inline fn from_address(addr: u8) @This() { + return @bitCast(addr & 0x8F); } - pub inline fn num_from_address(addr: u8) u8 { - return addr & ~@as(u8, @intCast(Dir.DIR_IN_MASK)); + pub inline fn out(num: Num) @This() { + return .{ .num = num, .dir = .Out }; } - pub inline fn dir_from_address(addr: u8) Dir { - return if (addr & Dir.DIR_IN_MASK != 0) Dir.In else Dir.Out; + pub inline fn in(num: Num) @This() { + return .{ .num = num, .dir = .In }; } - - pub const EP0_IN_ADDR: u8 = to_address(0, .In); - pub const EP0_OUT_ADDR: u8 = to_address(0, .Out); }; pub const RequestType = packed struct(u8) { @@ -187,7 +208,7 @@ pub const EndpointDescriptor = extern struct { descriptor_type: DescType = const_descriptor_type, /// Address of this endpoint, where the bottom 4 bits give the endpoint /// number (0..15) and the top bit distinguishes IN (1) from OUT (0). - endpoint_address: u8, + endpoint: Endpoint, /// Endpoint attributes; the most relevant part is the bottom 2 bits, which /// control the transfer type using the values from `TransferType`. attributes: u8, @@ -201,7 +222,7 @@ pub const EndpointDescriptor = extern struct { var out: [7]u8 = undefined; out[0] = out.len; out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.endpoint_address; + out[2] = self.endpoint.to_address(); out[3] = self.attributes; out[4] = @intCast(self.max_packet_size & 0xff); out[5] = @intCast((self.max_packet_size >> 8) & 0xff); @@ -440,7 +461,7 @@ pub const UsbDevice = struct { fn_control_transfer: *const fn (setup: *const SetupPacket, data: []const u8) void, fn_control_ack: *const fn (setup: *const SetupPacket) void, fn_endpoint_open: *const fn (ep_desc: []const u8) void, - fn_endpoint_transfer: *const fn (ep_addr: u8, data: []const u8) void, + fn_endpoint_transfer: *const fn (ep_addr: Endpoint, data: []const u8) void, pub fn ready(self: *@This()) bool { return self.fn_ready(); @@ -458,8 +479,8 @@ pub const UsbDevice = struct { return self.fn_endpoint_open(ep_desc); } - pub fn endpoint_transfer(self: *@This(), ep_addr: u8, data: []const u8) void { - return self.fn_endpoint_transfer(ep_addr, data); + pub fn endpoint_transfer(self: *@This(), ep: Endpoint, data: []const u8) void { + return self.fn_endpoint_transfer(ep, data); } }; diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 1315569a8..2a440e716 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -92,6 +92,12 @@ pub fn build(b: *std.Build) void { } } + const no_bin = b.option( + bool, + "no-bin", + "skip emitting binaries", + ) orelse false; + for (available_examples.items) |example| { // If we specify example, only select the ones that match if (maybe_example) |selected_example| @@ -110,14 +116,19 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path(example.file), }); - // `install_firmware()` is the MicroZig pendant to `Build.installArtifact()` - // and allows installing the firmware as a typical firmware file. - // - // This will also install into `$prefix/firmware` instead of `$prefix/bin`. - mb.install_firmware(firmware, .{}); - - // For debugging, we also always install the firmware as an ELF file - mb.install_firmware(firmware, .{ .format = .elf }); + if (no_bin) { + // Skip emitting binaries to get compile errors faster (great with --watch) + b.getInstallStep().dependOn(&firmware.artifact.step); + } else { + // `install_firmware()` is the MicroZig pendant to `Build.installArtifact()` + // and allows installing the firmware as a typical firmware file. + // + // This will also install into `$prefix/firmware` instead of `$prefix/bin`. + mb.install_firmware(firmware, .{}); + + // For debugging, we also always install the firmware as an ELF file + mb.install_firmware(firmware, .{ .format = .elf }); + } } } diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 9047518b6..665c4cdff 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -19,7 +19,7 @@ const usb_packet_size = 64; const usb_config_len = usb.templates.config_descriptor_len + usb.templates.hid_in_out_descriptor_len; const usb_config_descriptor = usb.templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++ - usb.templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, usb.Endpoint.to_address(1, .Out), usb.Endpoint.to_address(1, .In), usb_packet_size, 0); + usb.templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, .ep1, .ep1, usb_packet_size, 0); var driver_hid = usb.hid.HidClassDriver{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; var drivers = [_]usb.types.UsbClassDriver{driver_hid.driver()}; diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 9fda51a22..76e745a26 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -18,7 +18,7 @@ const usb_dev = rp2xxx.usb.Usb(.{}); const usb_config_len = usb.templates.config_descriptor_len + usb.templates.cdc_descriptor_len; const usb_config_descriptor = usb.templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ - usb.templates.cdc_descriptor(0, 4, usb.Endpoint.to_address(1, .In), 8, usb.Endpoint.to_address(2, .Out), usb.Endpoint.to_address(2, .In), 64); + usb.templates.cdc_descriptor(0, 4, .ep1, 8, .ep2, .ep2, 64); var driver_cdc: usb.cdc.CdcClassDriver(usb_dev) = .{}; var drivers = [_]usb.types.UsbClassDriver{driver_cdc.driver()}; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index a64c0f95f..8b0399f71 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -55,7 +55,7 @@ const EndpointType = microzig.chip.types.peripherals.USB_DPRAM.EndpointType; const HardwareEndpoint = struct { configured: bool, - ep_addr: u8, + ep: Endpoint, next_pid_1: bool, transfer_type: types.TransferType, endpoint_control_index: usize, @@ -97,20 +97,25 @@ const rp2xxx_endpoints = struct { const USB_DPRAM_BUFFERS_CTRL_BASE = USB_DPRAM_BASE + 0x80; const USB_DPRAM_ENDPOINTS_CTRL_BASE = USB_DPRAM_BASE + 0x8; - pub fn get_ep_ctrl(ep_num: u8, ep_dir: types.Dir) ?*EndpointControlMimo { - if (ep_num == 0) { + fn dir_index(dir: types.Dir) u1 { + return switch (dir) { + .In => 0, + .Out => 1, + }; + } + + pub fn get_ep_ctrl(ep: Endpoint) ?*EndpointControlMimo { + if (ep.num == .ep0) { return null; } else { - const dir_index: u8 = if (ep_dir == .In) 0 else 1; const ep_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_ENDPOINTS_CTRL_BASE)); - return @ptrCast(&ep_ctrl_base[ep_num - 1][dir_index]); + return @ptrCast(&ep_ctrl_base[ep.num.to_int() - 1][dir_index(ep.dir)]); } } - pub fn get_buf_ctrl(ep_num: u8, ep_dir: types.Dir) ?*BufferControlMmio { - const dir_index: u8 = if (ep_dir == .In) 0 else 1; + pub fn get_buf_ctrl(ep: Endpoint) ?*BufferControlMmio { const buf_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_BUFFERS_CTRL_BASE)); - return @ptrCast(&buf_ctrl_base[ep_num][dir_index]); + return @ptrCast(&buf_ctrl_base[ep.num.to_int()][dir_index(ep.dir)]); } }; @@ -178,13 +183,13 @@ pub fn F(comptime config: UsbConfig) type { peripherals.USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); for (1..cfg_max_endpoints_count) |i| { - rp2xxx_endpoints.get_ep_ctrl(@intCast(i), .In).?.write_raw(0); - rp2xxx_endpoints.get_ep_ctrl(@intCast(i), .Out).?.write_raw(0); + rp2xxx_endpoints.get_ep_ctrl(.in(.from_int(@intCast(i)))).?.write_raw(0); + rp2xxx_endpoints.get_ep_ctrl(.out(.from_int(@intCast(i)))).?.write_raw(0); } for (0..cfg_max_endpoints_count) |i| { - rp2xxx_endpoints.get_buf_ctrl(@intCast(i), .In).?.write_raw(0); - rp2xxx_endpoints.get_buf_ctrl(@intCast(i), .Out).?.write_raw(0); + rp2xxx_endpoints.get_buf_ctrl(.in(.from_int(@intCast(i)))).?.write_raw(0); + rp2xxx_endpoints.get_buf_ctrl(.out(.from_int(@intCast(i)))).?.write_raw(0); } // Mux the controller to the onboard USB PHY. I was surprised that there are @@ -229,8 +234,8 @@ pub fn F(comptime config: UsbConfig) type { }); @memset(std.mem.asBytes(&endpoints), 0); - endpoint_open(Endpoint.EP0_IN_ADDR, 64, types.TransferType.Control); - endpoint_open(Endpoint.EP0_OUT_ADDR, 64, types.TransferType.Control); + endpoint_open(.in(.ep0), 64, types.TransferType.Control); + endpoint_open(.out(.ep0), 64, types.TransferType.Control); // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. @@ -244,22 +249,20 @@ pub fn F(comptime config: UsbConfig) type { /// reuse `buffer` immediately after this returns. No need to wait for the /// packet to be sent. pub fn usb_start_tx( - ep_addr: u8, + ep_in: Endpoint.Num, buffer: []const u8, ) void { // It is technically possible to support longer buffers but this demo // doesn't bother. // TODO: assert!(buffer.len() <= 64); - // You should only be calling this on IN endpoints. - // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::In); - const ep = hardware_endpoint_get_by_address(ep_addr); + const ep_hard = hardware_endpoint(.in(ep_in)); // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, ep.data_buffer[0..buffer.len], buffer); + std.mem.copyForwards(u8, ep_hard.data_buffer[0..buffer.len], buffer); // Configure the IN: - const np: u1 = if (ep.next_pid_1) 1 else 0; + const np: u1 = if (ep_hard.next_pid_1) 1 else 0; // The AVAILABLE bit in the buffer control register should be set // separately to the rest of the data in the buffer control register, @@ -267,7 +270,7 @@ pub fn F(comptime config: UsbConfig) type { // accurate when the AVAILABLE bit is set. // Write the buffer information to the buffer control register - ep.buffer_control.?.modify(.{ + ep_hard.buffer_control.?.modify(.{ .PID_0 = np, // DATA0/1, depending .FULL_0 = 1, // We have put data in .LENGTH_0 = @as(u10, @intCast(buffer.len)), // There are this many bytes @@ -282,32 +285,30 @@ pub fn F(comptime config: UsbConfig) type { ); // Set available bit - ep.buffer_control.?.modify(.{ + ep_hard.buffer_control.?.modify(.{ .AVAILABLE_0 = 1, // The data is for the computer to use now }); - ep.next_pid_1 = !ep.next_pid_1; + ep_hard.next_pid_1 = !ep_hard.next_pid_1; } pub fn usb_start_rx( - ep_addr: u8, + ep_out: Endpoint.Num, len: usize, ) void { // It is technically possible to support longer buffers but this demo // doesn't bother. // TODO: assert!(len <= 64); - // You should only be calling this on OUT endpoints. - // TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::Out); - const ep = hardware_endpoint_get_by_address(ep_addr); + const ep_hard = hardware_endpoint(.out(ep_out)); - if (ep.awaiting_rx) + if (ep_hard.awaiting_rx) return; // Check which DATA0/1 PID this endpoint is expecting next. - const np: u1 = if (ep.next_pid_1) 1 else 0; + const np: u1 = if (ep_hard.next_pid_1) 1 else 0; // Configure the OUT: - ep.buffer_control.?.modify(.{ + ep_hard.buffer_control.?.modify(.{ .PID_0 = np, // DATA0/1 depending .FULL_0 = 0, // Buffer is NOT full, we want the computer to fill it .AVAILABLE_0 = 1, // It is, however, available to be filled @@ -315,13 +316,13 @@ pub fn F(comptime config: UsbConfig) type { }); // Flip the DATA0/1 PID for the next receive - ep.next_pid_1 = !ep.next_pid_1; - ep.awaiting_rx = true; + ep_hard.next_pid_1 = !ep_hard.next_pid_1; + ep_hard.awaiting_rx = true; } - pub fn endpoint_reset_rx(ep_addr: u8) void { - const ep = hardware_endpoint_get_by_address(ep_addr); - ep.awaiting_rx = false; + pub fn endpoint_reset_rx(ep: Endpoint) void { + const ep_hard = hardware_endpoint(ep); + ep_hard.awaiting_rx = false; } /// Check which interrupt flags are set @@ -378,47 +379,45 @@ pub fn F(comptime config: UsbConfig) type { } pub fn reset_ep0() void { - var ep = hardware_endpoint_get_by_address(Endpoint.EP0_IN_ADDR); + var ep = hardware_endpoint(.in(.ep0)); ep.next_pid_1 = true; } - fn hardware_endpoint_get_by_address(ep_addr: u8) *HardwareEndpoint { - const num = Endpoint.num_from_address(ep_addr); - const dir = Endpoint.dir_from_address(ep_addr); - return &endpoints[num][dir.as_number()]; + fn hardware_endpoint(ep: Endpoint) *HardwareEndpoint { + const dir_as_number: u1 = switch (ep.dir) { + .Out => 0, + .In => 1, + }; + return &endpoints[ep.num.to_int()][dir_as_number]; } - pub fn endpoint_open(ep_addr: u8, max_packet_size: u11, transfer_type: types.TransferType) void { - const ep_num = Endpoint.num_from_address(ep_addr); - const ep = hardware_endpoint_get_by_address(ep_addr); + pub fn endpoint_open(ep: Endpoint, max_packet_size: u11, transfer_type: types.TransferType) void { + const ep_hard = hardware_endpoint(ep); - endpoint_init(ep_addr, max_packet_size, transfer_type); + endpoint_init(ep, max_packet_size, transfer_type); - if (ep_num != 0) { - endpoint_alloc(ep) catch {}; - endpoint_enable(ep); + if (ep.num != .ep0) { + endpoint_alloc(ep_hard) catch {}; + endpoint_enable(ep_hard); } } - fn endpoint_init(ep_addr: u8, max_packet_size: u11, transfer_type: types.TransferType) void { - const ep_num = Endpoint.num_from_address(ep_addr); - const ep_dir = Endpoint.dir_from_address(ep_addr); + fn endpoint_init(ep: Endpoint, max_packet_size: u11, transfer_type: types.TransferType) void { + std.debug.assert(ep.num.to_int() <= cfg_max_endpoints_count); - std.debug.assert(ep_num <= cfg_max_endpoints_count); + var ep_hard = hardware_endpoint(ep); + ep_hard.ep = ep; + ep_hard.max_packet_size = max_packet_size; + ep_hard.transfer_type = transfer_type; + ep_hard.next_pid_1 = false; + ep_hard.awaiting_rx = false; - var ep = hardware_endpoint_get_by_address(ep_addr); - ep.ep_addr = ep_addr; - ep.max_packet_size = max_packet_size; - ep.transfer_type = transfer_type; - ep.next_pid_1 = false; - ep.awaiting_rx = false; + ep_hard.buffer_control = rp2xxx_endpoints.get_buf_ctrl(ep); + ep_hard.endpoint_control = rp2xxx_endpoints.get_ep_ctrl(ep); - ep.buffer_control = rp2xxx_endpoints.get_buf_ctrl(ep_num, ep_dir); - ep.endpoint_control = rp2xxx_endpoints.get_ep_ctrl(ep_num, ep_dir); - - if (ep_num == 0) { + if (ep.num == .ep0) { // ep0 has fixed data buffer - ep.data_buffer = rp2xxx_buffers.ep0_buffer0; + ep_hard.data_buffer = rp2xxx_buffers.ep0_buffer0; } } @@ -472,15 +471,15 @@ pub fn F(comptime config: UsbConfig) type { self.last_bit = lowbit; self.bufbits ^= lowbit; - // Here we exploit knowledge of the ordering of buffer control - // registers in the peripheral. Each endpoint has a pair of - // registers, so we can determine the endpoint number by: - const epnum = @as(u8, @intCast(lowbit_index >> 1)); - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - const dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out; - - const ep_addr = Endpoint.to_address(epnum, dir); + const ep: Endpoint = .{ + // 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: + .num = .from_int(@intCast(lowbit_index >> 1)), + // Of the pair, the IN endpoint comes first, followed by OUT, so + // we can get the direction by: + .dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out, + }; // Process the buffer-done event. // Process the buffer-done event. @@ -490,7 +489,7 @@ pub fn F(comptime config: UsbConfig) type { // method here, but in practice, the number of endpoints is // small so a linear scan doesn't kill us. - const ep = hardware_endpoint_get_by_address(ep_addr); + const ep_hard = hardware_endpoint(ep); // We should only get here if we've been notified that // the buffer is ours again. This is indicated by the hw @@ -504,12 +503,12 @@ pub fn F(comptime config: UsbConfig) type { // Get the actual length of the data, which may be less // than the buffer size. - const len = ep.buffer_control.?.read().LENGTH_0; + const len = ep_hard.buffer_control.?.read().LENGTH_0; // Copy the data from SRAM return usb.EPB{ - .endpoint_address = ep_addr, - .buffer = ep.data_buffer[0..len], + .endpoint_address = ep.to_address(), + .buffer = ep_hard.data_buffer[0..len], }; } }; From e071ff104bbbd2ab558af53bc0700fb6dc39bf00 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 15 Aug 2025 20:38:42 +0200 Subject: [PATCH 02/33] moved clock initialization out of USB abstract device --- core/src/core/usb.zig | 14 +----- .../rp2xxx/src/rp2040_only/usb_hid.zig | 4 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 4 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 43 ++++++------------- 4 files changed, 19 insertions(+), 46 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 797fe3bdc..c5c6fbceb 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -51,8 +51,6 @@ pub fn Usb(comptime f: anytype) type { return struct { /// The usb configuration set var usb_config: ?*DeviceConfiguration = null; - /// The clock has been initialized [Y/n] - var clk_init: bool = false; var itf_to_drv: [f.cfg_max_interfaces_count]u8 = @splat(0); var ep_to_drv: [f.cfg_max_endpoints_count][2]u8 = @splat(@splat(0)); pub const max_packet_size = if (f.high_speed) 512 else 64; @@ -104,18 +102,10 @@ pub fn Usb(comptime f: anytype) type { } }; - /// Initialize the USB clock - pub fn init_clk() void { - f.usb_init_clk(); - clk_init = true; - } - /// Initialize the usb device using the given configuration /// - /// This function will return an error if the clock hasn't been initialized. - pub fn init_device(device_config: *DeviceConfiguration) !void { - if (!clk_init) return error.UninitializedClock; - + /// You have to ensure that the device is getting an appropiate clock signal. + pub fn init_device(device_config: *DeviceConfiguration) void { f.usb_init_device(device_config); usb_config = device_config; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 665c4cdff..18ef710f6 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -78,9 +78,9 @@ pub fn main() !void { led.put(1); // First we initialize the USB clock - usb_dev.init_clk(); + usb.init_clk(.pll_usb); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&DEVICE_CONFIGURATION) catch unreachable; + usb_dev.init_device(&DEVICE_CONFIGURATION); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 76e745a26..99508fece 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -79,9 +79,9 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // First we initialize the USB clock - usb_dev.init_clk(); + usb.init_clk(.pll_usb); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&DEVICE_CONFIGURATION) catch unreachable; + usb_dev.init_device(&DEVICE_CONFIGURATION); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 8b0399f71..1661807d5 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -119,6 +119,19 @@ const rp2xxx_endpoints = struct { } }; +pub const ClkSrc = enum { pll_usb, pll_sys, xosc, gpin0, gpin1 }; + +/// You have to guarantee that the used source is 48 MHz. +pub fn init_clk(clk_src: ClkSrc) void { + switch (clk_src) { + .pll_usb => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_pll_usb }), + .pll_sys => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_pll_sys }), + .xosc => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .xosc_clksrc }), + .gpin0 => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_gpin0 }), + .gpin1 => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_gpin1 }), + } +} + // +++++++++++++++++++++++++++++++++++++++++++++++++ // Code // +++++++++++++++++++++++++++++++++++++++++++++++++ @@ -140,36 +153,6 @@ pub fn F(comptime config: UsbConfig) type { var endpoints: [config.max_endpoints_count][2]HardwareEndpoint = undefined; var data_buffer: []u8 = rp2xxx_buffers.data_buffer; - /// Initialize the USB clock to 48 MHz - /// - /// This requres that the system clock has been set up before hand - /// using the 12 MHz crystal. - pub fn usb_init_clk() void { - // Bring PLL_USB up to 48MHz. PLL_USB is clocked from refclk, which we've - // already moved over to the 12MHz XOSC. We just need to make it x4 that - // clock. - // - // Configure it: - // - // RFDIV = 1 - // FBDIV = 100 => FOUTVC0 = 1200 MHz - peripherals.PLL_USB.CS.modify(.{ .REFDIV = 1 }); - peripherals.PLL_USB.FBDIV_INT.modify(.{ .FBDIV_INT = 100 }); - peripherals.PLL_USB.PWR.modify(.{ .PD = 0, .VCOPD = 0 }); - // Wait for lock - while (peripherals.PLL_USB.CS.read().LOCK == 0) {} - // Set up post dividers to enable output - // - // POSTDIV1 = POSTDIV2 = 5 - // PLL_USB FOUT = 1200 MHz / 25 = 48 MHz - peripherals.PLL_USB.PRIM.modify(.{ .POSTDIV1 = 5, .POSTDIV2 = 5 }); - peripherals.PLL_USB.PWR.modify(.{ .POSTDIVPD = 0 }); - // Switch usbclk to be derived from PLLUSB - peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_pll_usb }); - - // We now have the stable 48MHz reference clock required for USB: - } - pub fn usb_init_device(_: *usb.DeviceConfiguration) void { if (chip == .RP2350) { peripherals.USB.MAIN_CTRL.modify(.{ From a93e6dea7c4c2a30e2a4b7a8a50fabe2c7d81a12 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 15 Aug 2025 21:02:45 +0200 Subject: [PATCH 03/33] make debug a funcion argument --- core/src/core/usb.zig | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index c5c6fbceb..17d12b148 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -61,7 +61,6 @@ pub fn Usb(comptime f: anytype) type { // We'll keep some state in Plain Old Static Local Variables: const S = struct { - var debug_mode = false; // When the host gives us a new address, we can't just slap it into // registers right away, because we have to do an acknowledgement step using // our _old_ address. @@ -180,11 +179,9 @@ pub fn Usb(comptime f: anytype) type { pub fn task(debug: bool) !void { if (usb_config == null) return error.UninitializedDevice; - S.debug_mode = debug; - // Device Specific Request const DeviceRequestProcessor = struct { - fn process_setup_request(setup: *const types.SetupPacket) !void { + fn process_setup_request(setup: *const types.SetupPacket, debug_mode: bool) !void { switch (setup.request_type.type) { .Class => { //const itfIndex = setup.index & 0x00ff; @@ -197,10 +194,10 @@ pub fn Usb(comptime f: anytype) type { .SetAddress => { S.new_address = @as(u8, @intCast(setup.value & 0xff)); CmdEndpoint.send_cmd_ack(); - if (S.debug_mode) std.log.info(" SetAddress: {}", .{S.new_address.?}); + if (debug_mode) std.log.info(" SetAddress: {}", .{S.new_address.?}); }, .SetConfiguration => { - if (S.debug_mode) std.log.info(" SetConfiguration", .{}); + if (debug_mode) std.log.info(" SetConfiguration", .{}); const cfg_num = setup.value; if (S.cfg_num != cfg_num) { if (S.cfg_num > 0) { @@ -221,7 +218,7 @@ pub fn Usb(comptime f: anytype) type { .GetDescriptor => { const descriptor_type = DescType.from_u8(@intCast(setup.value >> 8)); if (descriptor_type) |dt| { - try process_get_descriptor(setup, dt); + try process_get_descriptor(setup, dt, debug_mode); } }, .SetFeature => { @@ -240,10 +237,10 @@ pub fn Usb(comptime f: anytype) type { } } - fn process_get_descriptor(setup: *const types.SetupPacket, descriptor_type: DescType) !void { + fn process_get_descriptor(setup: *const types.SetupPacket, descriptor_type: DescType, debug_mode: bool) !void { switch (descriptor_type) { .Device => { - if (S.debug_mode) std.log.info(" Device", .{}); + if (debug_mode) std.log.info(" Device", .{}); var bw = BufferWriter{ .buffer = &S.tmp }; try bw.write(&usb_config.?.device_descriptor.serialize()); @@ -251,7 +248,7 @@ pub fn Usb(comptime f: anytype) type { CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); }, .Config => { - if (S.debug_mode) std.log.info(" Config", .{}); + if (debug_mode) std.log.info(" Config", .{}); var bw = BufferWriter{ .buffer = &S.tmp }; try bw.write(usb_config.?.config_descriptor); @@ -259,7 +256,7 @@ pub fn Usb(comptime f: anytype) type { CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); }, .String => { - if (S.debug_mode) std.log.info(" String", .{}); + if (debug_mode) std.log.info(" String", .{}); // String descriptor index is in bottom 8 bits of // `value`. const i: usize = @intCast(setup.value & 0xff); @@ -285,13 +282,13 @@ pub fn Usb(comptime f: anytype) type { CmdEndpoint.send_cmd_response(bytes, setup.length); }, .Interface => { - if (S.debug_mode) std.log.info(" Interface", .{}); + if (debug_mode) std.log.info(" Interface", .{}); }, .Endpoint => { - if (S.debug_mode) std.log.info(" Endpoint", .{}); + if (debug_mode) std.log.info(" Endpoint", .{}); }, .DeviceQualifier => { - if (S.debug_mode) std.log.info(" DeviceQualifier", .{}); + if (debug_mode) std.log.info(" DeviceQualifier", .{}); // We will just copy parts of the DeviceDescriptor because // the DeviceQualifierDescriptor can be seen as a subset. const dqd = types.DeviceQualifierDescriptor{ @@ -407,7 +404,7 @@ pub fn Usb(comptime f: anytype) type { f.reset_ep0(); switch (setup.request_type.recipient) { - .Device => try DeviceRequestProcessor.process_setup_request(&setup), + .Device => try DeviceRequestProcessor.process_setup_request(&setup, debug), .Interface => try InterfaceRequestProcessor.process_setup_request(&setup), .Endpoint => try EndpointRequestProcessor.process_setup_request(&setup), else => {}, From 563d07395852b4d7ea86e889e42ba0238d9d2981 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 15 Aug 2025 23:30:04 +0200 Subject: [PATCH 04/33] change usb write signature --- core/src/core/usb.zig | 17 +++------- core/src/core/usb/cdc.zig | 7 +++-- core/src/core/usb/types.zig | 11 +++++-- port/raspberrypi/rp2xxx/src/hal/usb.zig | 42 +++++++++++++++---------- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 17d12b148..6bba4c436 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -92,12 +92,12 @@ pub fn Usb(comptime f: anytype) type { const data_chunk = S.buffer_reader.try_peek(64); if (data_chunk.len > 0) { - f.usb_start_tx(.ep0, data_chunk); + _ = f.usb_start_tx(.ep0, &.{data_chunk}); } } fn send_cmd_ack() void { - f.usb_start_tx(.ep0, &.{}); + _ = f.usb_start_tx(.ep0, &.{}); } }; @@ -120,7 +120,8 @@ pub fn Usb(comptime f: anytype) type { .fn_control_transfer = device_control_transfer, .fn_control_ack = device_control_ack, .fn_endpoint_open = device_endpoint_open, - .fn_endpoint_transfer = device_endpoint_transfer, + .fn_endpoint_tx = f.usb_start_tx, + .fn_endpoint_rx = f.usb_start_rx, }; } @@ -145,14 +146,6 @@ pub fn Usb(comptime f: anytype) type { f.endpoint_open(ep, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk); } - fn device_endpoint_transfer(ep: Endpoint, data: []const u8) void { - if (ep.dir == .In) { - f.usb_start_tx(ep.num, data); - } else { - f.usb_start_rx(ep.num, max_packet_size); - } - } - fn get_driver(drv_idx: u8) ?*types.UsbClassDriver { if (drv_idx == drvid_invalid) { return null; @@ -439,7 +432,7 @@ pub fn Usb(comptime f: anytype) type { _ = buffer_reader.try_advance(epb.buffer.len); const next_data_chunk = buffer_reader.try_peek(64); if (next_data_chunk.len > 0) { - f.usb_start_tx(.ep0, next_data_chunk); + _ = f.usb_start_tx(.ep0, &.{next_data_chunk}); } else { f.usb_start_rx(.ep0, 0); diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index a5a5154f7..e1b4d63cc 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -175,14 +175,15 @@ pub fn CdcClassDriver(comptime usb: anytype) type { return 0; } const len = self.tx.read(&self.epin_buf); - self.device.?.endpoint_transfer(.in(self.ep_in), self.epin_buf[0..len]); + const tx_len = self.device.?.endpoint_tx(self.ep_in, &.{self.epin_buf[0..len]}); + if (tx_len != len) @panic("bruh"); return len; } fn prep_out_transaction(self: *@This()) void { if (self.rx.writableLength() >= usb.max_packet_size) { // Let endpoint know that we are ready for next packet - self.device.?.endpoint_transfer(.out(self.ep_out), &.{}); + self.device.?.endpoint_rx(self.ep_out, usb.max_packet_size); } } @@ -294,7 +295,7 @@ pub fn CdcClassDriver(comptime usb: anytype) type { // 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.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { - self.device.?.endpoint_transfer(.in(self.ep_in), &.{}); + _ = self.device.?.endpoint_tx(self.ep_in, &.{&.{}}); } } }, diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 2d3fce534..4af31c7e5 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -461,7 +461,8 @@ pub const UsbDevice = struct { fn_control_transfer: *const fn (setup: *const SetupPacket, data: []const u8) void, fn_control_ack: *const fn (setup: *const SetupPacket) void, fn_endpoint_open: *const fn (ep_desc: []const u8) void, - fn_endpoint_transfer: *const fn (ep_addr: Endpoint, data: []const u8) void, + fn_endpoint_tx: *const fn (ep_in: Endpoint.Num, data: []const []const u8) usize, + fn_endpoint_rx: *const fn (ep_out: Endpoint.Num, len: usize) void, pub fn ready(self: *@This()) bool { return self.fn_ready(); @@ -479,8 +480,12 @@ pub const UsbDevice = struct { return self.fn_endpoint_open(ep_desc); } - pub fn endpoint_transfer(self: *@This(), ep: Endpoint, data: []const u8) void { - return self.fn_endpoint_transfer(ep, data); + pub fn endpoint_tx(self: *@This(), ep_in: Endpoint.Num, data: []const []const u8) usize { + return self.fn_endpoint_tx(ep_in, data); + } + + pub fn endpoint_rx(self: *@This(), ep_out: Endpoint.Num, len: usize) void { + return self.fn_endpoint_rx(ep_out, len); } }; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 1661807d5..925bd78d6 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -4,6 +4,7 @@ //! Currently progressing towards adopting the TinyUSB like API const std = @import("std"); +const assert = std.debug.assert; const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; @@ -228,24 +229,29 @@ pub fn F(comptime config: UsbConfig) type { /// Configures a given endpoint to send data (device-to-host, IN) when the host /// next asks for it. /// - /// The contents of `buffer` will be _copied_ into USB SRAM, so you can - /// reuse `buffer` immediately after this returns. No need to wait for the - /// packet to be sent. + /// The contents of each of the slices in `data` will be _copied_ into USB SRAM, + /// so you can reuse them immediately after this returns. + /// No need to wait for the packet to be sent. pub fn usb_start_tx( ep_in: Endpoint.Num, - buffer: []const u8, - ) void { - // It is technically possible to support longer buffers but this demo - // doesn't bother. - // TODO: assert!(buffer.len() <= 64); - + data: []const []const u8, + ) usize { const ep_hard = hardware_endpoint(.in(ep_in)); - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, ep_hard.data_buffer[0..buffer.len], buffer); - - // Configure the IN: - const np: u1 = if (ep_hard.next_pid_1) 1 else 0; + // It is technically possible to support longer buffers but this demo doesn't bother. + const dst_buf_len = 64; + var n: usize = 0; + for (data) |buffer| { + const space_left = dst_buf_len - n; + if (space_left >= buffer.len) { + // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 + std.mem.copyForwards(u8, ep_hard.data_buffer[n .. n + buffer.len], buffer); + n += buffer.len; + } else { + std.mem.copyForwards(u8, ep_hard.data_buffer[n..], buffer[0..space_left]); + n = dst_buf_len; + } + } // The AVAILABLE bit in the buffer control register should be set // separately to the rest of the data in the buffer control register, @@ -254,9 +260,9 @@ pub fn F(comptime config: UsbConfig) type { // Write the buffer information to the buffer control register ep_hard.buffer_control.?.modify(.{ - .PID_0 = np, // DATA0/1, depending + .PID_0 = @as(u1, @intFromBool(ep_hard.next_pid_1)), // DATA0/1, depending .FULL_0 = 1, // We have put data in - .LENGTH_0 = @as(u10, @intCast(buffer.len)), // There are this many bytes + .LENGTH_0 = @as(u10, @intCast(n)), // There are this many bytes }); // Nop for some clock cycles @@ -273,6 +279,8 @@ pub fn F(comptime config: UsbConfig) type { }); ep_hard.next_pid_1 = !ep_hard.next_pid_1; + + return n; } pub fn usb_start_rx( @@ -386,7 +394,7 @@ pub fn F(comptime config: UsbConfig) type { } fn endpoint_init(ep: Endpoint, max_packet_size: u11, transfer_type: types.TransferType) void { - std.debug.assert(ep.num.to_int() <= cfg_max_endpoints_count); + assert(ep.num.to_int() <= cfg_max_endpoints_count); var ep_hard = hardware_endpoint(ep); ep_hard.ep = ep; From 59ef47fdce5f6b3a49a1fc1db00e01eb1b3802d1 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 16 Aug 2025 00:13:53 +0200 Subject: [PATCH 05/33] change UsbClassDriver --- core/src/core/usb.zig | 26 +++++++++--------- core/src/core/usb/cdc.zig | 35 +++++++++++++------------ core/src/core/usb/hid.zig | 6 +++-- core/src/core/usb/types.zig | 11 +++++--- port/raspberrypi/rp2xxx/src/hal/usb.zig | 2 +- 5 files changed, 45 insertions(+), 35 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 6bba4c436..9419399cc 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -415,8 +415,7 @@ pub fn Usb(comptime f: anytype) type { // 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. - const ep: Endpoint = .from_address(epb.endpoint_address); - if (ep.num == .ep0 and ep.dir == .In) { + if (epb.endpoint.num == .ep0 and epb.endpoint.dir == .In) { if (debug) std.log.info(" EP0_IN_ADDR", .{}); const buffer_reader = &S.buffer_reader; @@ -453,15 +452,18 @@ pub fn Usb(comptime f: anytype) type { } } } else { - const dir_as_number: u1 = switch (ep.dir) { - .Out => 0, - .In => 1, - }; - if (get_driver(ep_to_drv[ep.num.to_int()][dir_as_number])) |driver| { - driver.transfer(epb.endpoint_address, epb.buffer); - } - if (ep.dir == .Out) { - f.endpoint_reset_rx(ep); + const drv = ep_to_drv[epb.endpoint.num.to_int()]; + switch (epb.endpoint.dir) { + .Out => { + if (get_driver(drv[0])) |driver| + driver.receive(epb.endpoint.num, epb.buffer); + + f.endpoint_reset_rx(epb.endpoint); + }, + .In => { + if (get_driver(drv[1])) |driver| + driver.send(epb.endpoint.num, epb.buffer); + }, } } } @@ -530,7 +532,7 @@ pub const EPBError = error{ /// Element returned by the endpoint buffer iterator (EPBIter) pub const EPB = struct { /// The endpoint the data belongs to - endpoint_address: u8, + endpoint: Endpoint, /// Data buffer buffer: []u8, }; diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index e1b4d63cc..8cd42a86a 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -281,24 +281,24 @@ pub fn CdcClassDriver(comptime usb: anytype) type { return true; } - fn transfer(ptr: *anyopaque, ep_addr: u8, data: []u8) void { + fn send(ptr: *anyopaque, ep_in: types.Endpoint.Num, data: []const u8) void { var self: *@This() = @ptrCast(@alignCast(ptr)); - const ep: types.Endpoint = .from_address(ep_addr); - switch (ep.dir) { - .Out => if (ep.num == self.ep_out) { - self.rx.write(data) catch {}; - self.prep_out_transaction(); - }, - .In => if (ep.num == 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.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { - _ = self.device.?.endpoint_tx(self.ep_in, &.{&.{}}); - } - } - }, + if (ep_in == self.ep_in and 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.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { + _ = self.device.?.endpoint_tx(self.ep_in, &.{&.{}}); + } + } + } + + fn receive(ptr: *anyopaque, ep_out: types.Endpoint.Num, data: []const u8) void { + var self: *@This() = @ptrCast(@alignCast(ptr)); + + if (ep_out == self.ep_out) { + self.rx.write(data) catch {}; + self.prep_out_transaction(); } } @@ -308,7 +308,8 @@ pub fn CdcClassDriver(comptime usb: anytype) type { .fn_init = init, .fn_open = open, .fn_class_control = class_control, - .fn_transfer = transfer, + .fn_send = send, + .fn_receive = receive, }; } }; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 56bcb3ec8..1ed619e26 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -643,7 +643,8 @@ pub const HidClassDriver = struct { return true; } - fn transfer(_: *anyopaque, _: u8, _: []u8) void {} + fn send(_: *anyopaque, _: types.Endpoint.Num, _: []const u8) void {} + fn receive(_: *anyopaque, _: types.Endpoint.Num, _: []const u8) void {} pub fn driver(self: *@This()) types.UsbClassDriver { return .{ @@ -651,7 +652,8 @@ pub const HidClassDriver = struct { .fn_init = init, .fn_open = open, .fn_class_control = class_control, - .fn_transfer = transfer, + .fn_send = send, + .fn_receive = receive, }; } }; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 4af31c7e5..379b2f93c 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -495,7 +495,8 @@ pub const UsbClassDriver = struct { fn_init: *const fn (ptr: *anyopaque, device: UsbDevice) void, fn_open: *const fn (ptr: *anyopaque, cfg: []const u8) anyerror!usize, fn_class_control: *const fn (ptr: *anyopaque, stage: ControlStage, setup: *const SetupPacket) bool, - fn_transfer: *const fn (ptr: *anyopaque, ep_addr: u8, data: []u8) void, + fn_send: *const fn (ptr: *anyopaque, ep_in: Endpoint.Num, data: []const u8) void, + fn_receive: *const fn (ptr: *anyopaque, ep_out: Endpoint.Num, data: []const u8) void, pub fn init(self: *@This(), device: UsbDevice) void { return self.fn_init(self.ptr, device); @@ -510,7 +511,11 @@ pub const UsbClassDriver = struct { return self.fn_class_control(self.ptr, stage, setup); } - pub fn transfer(self: *@This(), ep_addr: u8, data: []u8) void { - return self.fn_transfer(self.ptr, ep_addr, data); + pub fn send(self: *@This(), ep_in: Endpoint.Num, data: []const u8) void { + return self.fn_send(self.ptr, ep_in, data); + } + + pub fn receive(self: *@This(), ep_out: Endpoint.Num, len: []const u8) void { + return self.fn_receive(self.ptr, ep_out, len); } }; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 925bd78d6..7c54fee77 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -498,7 +498,7 @@ pub fn F(comptime config: UsbConfig) type { // Copy the data from SRAM return usb.EPB{ - .endpoint_address = ep.to_address(), + .endpoint = ep, .buffer = ep_hard.data_buffer[0..len], }; } From 5f15c82be471f0277f173521b749d6b8728673ea Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 17 Aug 2025 18:29:22 +0200 Subject: [PATCH 06/33] redo iteration over endpoint buffers --- core/src/core/usb.zig | 31 ++----- port/raspberrypi/rp2xxx/src/hal/usb.zig | 103 +++++++++--------------- 2 files changed, 44 insertions(+), 90 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 9419399cc..9e49b33aa 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -407,9 +407,10 @@ pub fn Usb(comptime f: anytype) type { // Events on one or more buffers? (In practice, always one.) if (ints.BuffStatus) { if (debug) std.log.info("buff status", .{}); - var iter = f.get_EPBIter(usb_config.?); + var iter: f.UnhandledEndpointIterator = .init(); + defer iter.deinit(); - while (iter.next(&iter)) |epb| { + while (iter.next()) |epb| { if (debug) std.log.info(" data: {any}", .{epb.buffer}); // Perform any required action on the data. For OUT, the `data` @@ -458,7 +459,7 @@ pub fn Usb(comptime f: anytype) type { if (get_driver(drv[0])) |driver| driver.receive(epb.endpoint.num, epb.buffer); - f.endpoint_reset_rx(epb.endpoint); + f.endpoint_reset_rx(epb.endpoint.num); }, .In => { if (get_driver(drv[1])) |driver| @@ -522,34 +523,14 @@ pub const InterruptStatus = struct { SetupReq: bool = false, }; -pub const EPBError = error{ - /// The system has received a buffer event for an unknown endpoint (this is super unlikely) - UnknownEndpoint, - /// The buffer is not available (this is super unlikely) - NotAvailable, -}; - -/// Element returned by the endpoint buffer iterator (EPBIter) -pub const EPB = struct { +/// And endpoint and its corresponding buffer. +pub const EndpointAndBuffer = struct { /// The endpoint the data belongs to endpoint: Endpoint, /// Data buffer buffer: []u8, }; -/// Iterator over all input buffers that hold data -pub const EPBIter = struct { - /// Bitmask of the input buffers to handle - bufbits: u32, - /// The last input buffer handled. This can be used to flag the input buffer as handled on the - /// next call. - last_bit: ?u32 = null, - /// Point to the device configuration (to get access to the endpoint buffers defined by the user) - device_config: *const DeviceConfiguration, - /// Get the next available input buffer - next: *const fn (self: *@This()) ?EPB, -}; - const BufferWriter = struct { buffer: []u8, pos: usize = 0, diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 7c54fee77..e3bc40f83 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -311,8 +311,8 @@ pub fn F(comptime config: UsbConfig) type { ep_hard.awaiting_rx = true; } - pub fn endpoint_reset_rx(ep: Endpoint) void { - const ep_hard = hardware_endpoint(ep); + pub fn endpoint_reset_rx(ep_out: Endpoint.Num) void { + const ep_hard = hardware_endpoint(.out(ep_out)); ep_hard.awaiting_rx = false; } @@ -435,72 +435,45 @@ pub fn F(comptime config: UsbConfig) type { }); } - /// Iterator over endpoint buffers events - pub fn get_EPBIter(dc: *const usb.DeviceConfiguration) usb.EPBIter { - return .{ - .bufbits = peripherals.USB.BUFF_STATUS.raw, - .device_config = dc, - .next = next, - }; - } + pub const UnhandledEndpointIterator = struct { + initial_unhandled_mask: u32, + currently_unhandled_mask: u32, - pub fn next(self: *usb.EPBIter) ?usb.EPB { - if (self.last_bit) |lb| { - // Acknowledge the last handled buffer - peripherals.USB.BUFF_STATUS.write_raw(lb); - self.last_bit = null; + pub fn init() @This() { + const mask = peripherals.USB.BUFF_STATUS.raw; + return .{ + .initial_unhandled_mask = mask, + .currently_unhandled_mask = mask, + }; } - // All input buffers handled? - if (self.bufbits == 0) return null; - - // Who's still outstanding? Find their bit index by counting how - // many LSBs are zero. - var lowbit_index: u5 = 0; - while ((self.bufbits >> lowbit_index) & 0x01 == 0) : (lowbit_index += 1) {} - // Remove their bit from our set. - const lowbit = @as(u32, @intCast(1)) << lowbit_index; - self.last_bit = lowbit; - self.bufbits ^= lowbit; - - const ep: Endpoint = .{ - // 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: - .num = .from_int(@intCast(lowbit_index >> 1)), - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - .dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out, - }; - // 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 = hardware_endpoint(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 = ep_hard.buffer_control.?.read().LENGTH_0; + pub fn deinit(this: @This()) void { + const handled_mask = this.initial_unhandled_mask ^ this.currently_unhandled_mask; + peripherals.USB.BUFF_STATUS.write_raw(handled_mask); + } - // Copy the data from SRAM - return usb.EPB{ - .endpoint = ep, - .buffer = ep_hard.data_buffer[0..len], - }; - } + pub fn next(this: *@This()) ?usb.EndpointAndBuffer { + const idx = std.math.cast(u5, @ctz(this.currently_unhandled_mask)) orelse return null; + this.currently_unhandled_mask &= this.currently_unhandled_mask -% 1; // clear lowest bit + + const ep: Endpoint = .{ + // 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: + .num = .from_int(@intCast(idx >> 1)), + // Of the pair, the IN endpoint comes first, followed by OUT, so + // we can get the direction by: + .dir = if (idx & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out, + }; + + const ep_hard = hardware_endpoint(ep); + const len = ep_hard.buffer_control.?.read().LENGTH_0; + + return .{ + .endpoint = ep, + .buffer = ep_hard.data_buffer[0..len], + }; + } + }; }; } From 00b1a6218f14f44321c6b26b302d373e4d76a641 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 17 Aug 2025 22:40:12 +0200 Subject: [PATCH 07/33] separate some logic into HardwareBuffer and do various improvements --- core/src/core/usb.zig | 28 +- .../rp2xxx/src/rp2040_only/usb_hid.zig | 25 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 26 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 356 ++++++++---------- 4 files changed, 195 insertions(+), 240 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 9e49b33aa..9e5eddf97 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -7,7 +7,7 @@ //! 1. Define the functions (`pub const F = struct { ... }`) required by `Usb()` (see below) //! 2. Call `pub const device = Usb(F)` //! 3. Define the device configuration (DeviceConfiguration) -//! 4. Initialize the device in main by calling `usb.init_clk()` and `usb.init_device(device_conf)` +//! 4. Initialize the device in main by calling `usb.init_device(device_conf)` //! 5. Call `usb.task()` within the main loop const std = @import("std"); @@ -36,7 +36,6 @@ const BosConfig = utils.BosConfig; /// This is a abstract USB device implementation that requires a handful of functions /// to work correctly: /// -/// * `usb_init_clk() void` - Initialize the USB clock /// * `usb_init_device(*DeviceConfiguration) - Initialize the USB device controller (e.g. enable interrupts, etc.) /// * `usb_start_tx(*EndpointConfiguration, []const u8)` - Transmit the given bytes over the specified endpoint /// * `usb_start_rx(*usb.EndpointConfiguration, n: usize)` - Receive n bytes over the specified endpoint @@ -143,7 +142,7 @@ pub fn Usb(comptime f: anytype) type { const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); - f.endpoint_open(ep, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk); + f.endpoint_open(ep, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk) catch unreachable; } fn get_driver(drv_idx: u8) ?*types.UsbClassDriver { @@ -260,7 +259,7 @@ pub fn Usb(comptime f: anytype) type { break :StringBlk usb_config.?.lang_descriptor; } else { // Otherwise, set up one of our strings. - const s = usb_config.?.descriptor_strings[i - 1]; + const s: []const u8 = @ptrCast(usb_config.?.descriptor_strings[i - 1]); const len = 2 + s.len; var wb = BufferWriter{ .buffer = &S.tmp }; @@ -502,7 +501,7 @@ pub const DeviceConfiguration = struct { device_descriptor: *const types.DeviceDescriptor, config_descriptor: []const u8, lang_descriptor: []const u8, - descriptor_strings: []const []const u8, + descriptor_strings: []const []const u16, drivers: []types.UsbClassDriver, }; @@ -618,24 +617,7 @@ const BufferReader = struct { } }; -pub const UsbUtils = struct { - /// Convert an utf8 into an utf16 (little endian) string - pub fn utf8_to_utf16_le(comptime s: []const u8) [s.len << 1]u8 { - const l = s.len << 1; - var ret: [l]u8 = @splat(0); - var i: usize = 0; - while (i < s.len) : (i += 1) { - ret[i << 1] = s[i]; - } - return ret; - } -}; - test "tests" { _ = hid; -} - -test "utf8 to utf16" { - try std.testing.expectEqualSlices(u8, "M\x00y\x00 \x00V\x00e\x00n\x00d\x00o\x00r\x00", &UsbUtils.utf8_to_utf16_le("My Vendor")); - try std.testing.expectEqualSlices(u8, "R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00", &UsbUtils.utf8_to_utf16_le("Raspberry Pi")); + _ = utils; } diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 18ef710f6..f9d4a252a 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -5,8 +5,7 @@ const rp2xxx = microzig.hal; const flash = rp2xxx.flash; const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const clocks = rp2xxx.clocks; -const usb = rp2xxx.usb; +const usb = microzig.core.usb; const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); @@ -15,11 +14,12 @@ const uart_tx_pin = gpio.num(0); const usb_dev = rp2xxx.usb.Usb(.{}); +const usb_templates = usb.templates.DescriptorsConfigTemplates; const usb_packet_size = 64; -const usb_config_len = usb.templates.config_descriptor_len + usb.templates.hid_in_out_descriptor_len; +const usb_config_len = usb_templates.config_descriptor_len + usb_templates.hid_in_out_descriptor_len; const usb_config_descriptor = - usb.templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++ - usb.templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, .ep1, .ep1, usb_packet_size, 0); + usb_templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++ + usb_templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, .ep1, .ep1, usb_packet_size, 0); var driver_hid = usb.hid.HidClassDriver{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; var drivers = [_]usb.types.UsbClassDriver{driver_hid.driver()}; @@ -27,7 +27,7 @@ var drivers = [_]usb.types.UsbClassDriver{driver_hid.driver()}; // This is our device configuration pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ .device_descriptor = &.{ - .descriptor_type = usb.DescType.Device, + .descriptor_type = .Device, .bcd_usb = 0x0200, .device_class = 0, .device_subclass = 0, @@ -45,10 +45,13 @@ pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ }, .config_descriptor = &usb_config_descriptor, .lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409) - .descriptor_strings = &.{ - &usb.utils.utf8_to_utf16_le("Raspberry Pi"), - &usb.utils.utf8_to_utf16_le("Pico Test Device"), - &usb.utils.utf8_to_utf16_le("cafebabe"), + .descriptor_strings = blk: { + @setEvalBranchQuota(2000); + break :blk &.{ + std.unicode.utf8ToUtf16LeStringLiteral("Raspberry Pi"), + std.unicode.utf8ToUtf16LeStringLiteral("Pico Test Device"), + std.unicode.utf8ToUtf16LeStringLiteral("cafebabe"), + }; }, .drivers = &drivers, }; @@ -77,8 +80,6 @@ pub fn main() !void { led.set_direction(.out); led.put(1); - // First we initialize the USB clock - usb.init_clk(.pll_usb); // Then initialize the USB device using the configuration defined above usb_dev.init_device(&DEVICE_CONFIGURATION); var old: u64 = time.get_time_since_boot().to_us(); diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 99508fece..29c838595 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -5,7 +5,7 @@ const rp2xxx = microzig.hal; const flash = rp2xxx.flash; const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const usb = rp2xxx.usb; +const usb = microzig.core.usb; const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); @@ -15,10 +15,11 @@ const uart_rx_pin = gpio.num(1); const usb_dev = rp2xxx.usb.Usb(.{}); -const usb_config_len = usb.templates.config_descriptor_len + usb.templates.cdc_descriptor_len; +const usb_templates = usb.templates.DescriptorsConfigTemplates; +const usb_config_len = usb_templates.config_descriptor_len + usb_templates.cdc_descriptor_len; const usb_config_descriptor = - usb.templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ - usb.templates.cdc_descriptor(0, 4, .ep1, 8, .ep2, .ep2, 64); + usb_templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ + usb_templates.cdc_descriptor(0, 4, .ep1, 8, .ep2, .ep2, 64); var driver_cdc: usb.cdc.CdcClassDriver(usb_dev) = .{}; var drivers = [_]usb.types.UsbClassDriver{driver_cdc.driver()}; @@ -26,7 +27,7 @@ var drivers = [_]usb.types.UsbClassDriver{driver_cdc.driver()}; // This is our device configuration pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ .device_descriptor = &.{ - .descriptor_type = usb.DescType.Device, + .descriptor_type = .Device, .bcd_usb = 0x0200, .device_class = 0xEF, .device_subclass = 2, @@ -42,11 +43,14 @@ pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ }, .config_descriptor = &usb_config_descriptor, .lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409) - .descriptor_strings = &.{ - &usb.utils.utf8_to_utf16_le("Raspberry Pi"), - &usb.utils.utf8_to_utf16_le("Pico Test Device"), - &usb.utils.utf8_to_utf16_le("someserial"), - &usb.utils.utf8_to_utf16_le("Board CDC"), + .descriptor_strings = blk: { + @setEvalBranchQuota(2000); + break :blk &.{ + std.unicode.utf8ToUtf16LeStringLiteral("Raspberry Pi"), + std.unicode.utf8ToUtf16LeStringLiteral("Pico Test Device"), + std.unicode.utf8ToUtf16LeStringLiteral("someserial"), + std.unicode.utf8ToUtf16LeStringLiteral("Board CDC"), + }; }, .drivers = &drivers, }; @@ -78,8 +82,6 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); - // First we initialize the USB clock - usb.init_clk(.pll_usb); // Then initialize the USB device using the configuration defined above usb_dev.init_device(&DEVICE_CONFIGURATION); var old: u64 = time.get_time_since_boot().to_us(); diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index e3bc40f83..dc99d4960 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -8,15 +8,12 @@ const assert = std.debug.assert; const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; +const peri_usb = microzig.chip.peripherals.USB; +const peri_dpram = microzig.chip.peripherals.USB_DPRAM; const chip = microzig.hal.compatibility.chip; -pub const usb = microzig.core.usb; -pub const types = usb.types; -pub const hid = usb.hid; -pub const cdc = usb.cdc; -pub const vendor = usb.vendor; -pub const templates = usb.templates.DescriptorsConfigTemplates; -pub const utils = usb.UsbUtils; +const usb = microzig.core.usb; +const types = usb.types; const resets = @import("resets.zig"); @@ -24,50 +21,12 @@ pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; pub const UsbConfig = struct { // Comptime defined supported max endpoints number, can be reduced to save RAM space - max_endpoints_count: u8 = RP2XXX_MAX_ENDPOINTS_COUNT, - max_interfaces_count: u8 = 16, + max_endpoints_count: comptime_int = RP2XXX_MAX_ENDPOINTS_COUNT, + max_interfaces_count: comptime_int = 16, + synchronization_nops: comptime_int = 3, }; -/// The rp2040 usb device impl -/// -/// We create a concrete implementaion by passing a handful -/// of system specific functions to Usb(). Those functions -/// are used by the abstract USB impl of microzig. -pub fn Usb(comptime config: UsbConfig) type { - return usb.Usb(F(config)); -} - -pub const DeviceConfiguration = usb.DeviceConfiguration; -pub const DeviceDescriptor = usb.DeviceDescriptor; -pub const DescType = usb.types.DescType; -pub const InterfaceDescriptor = usb.types.InterfaceDescriptor; -pub const ConfigurationDescriptor = usb.types.ConfigurationDescriptor; -pub const EndpointDescriptor = usb.types.EndpointDescriptor; -pub const EndpointConfiguration = usb.EndpointConfiguration; -pub const Dir = usb.types.Dir; -pub const TransferType = usb.types.TransferType; -pub const Endpoint = usb.types.Endpoint; - -pub const utf8ToUtf16Le = usb.utf8ToUtf16Le; - -const BufferControlMmio = microzig.mmio.Mmio(@TypeOf(microzig.chip.peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL).underlying_type); -const EndpointControlMimo = microzig.mmio.Mmio(@TypeOf(peripherals.USB_DPRAM.EP1_IN_CONTROL).underlying_type); -const EndpointType = microzig.chip.types.peripherals.USB_DPRAM.EndpointType; - -const HardwareEndpoint = struct { - configured: bool, - ep: Endpoint, - next_pid_1: bool, - transfer_type: types.TransferType, - endpoint_control_index: usize, - buffer_control_index: usize, - awaiting_rx: bool, - - max_packet_size: u11, - buffer_control: ?*BufferControlMmio, - endpoint_control: ?*EndpointControlMimo, - data_buffer: []u8, -}; +const Endpoint = usb.types.Endpoint; const rp2xxx_buffers = struct { // Address 0x100-0xfff (3840 bytes) can be used for data buffers @@ -86,95 +45,125 @@ const rp2xxx_buffers = struct { const data_buffer: *[USB_DATA_BUFFER_SIZE]u8 = @as(*[USB_DATA_BUFFER_SIZE]u8, @ptrFromInt(USB_DATA_BUFFER)); fn data_offset(ep_data_buffer: []u8) u16 { - const buf_base = @intFromPtr(&ep_data_buffer[0]); + const buf_base = @intFromPtr(ep_data_buffer.ptr); const dpram_base = @intFromPtr(peripherals.USB_DPRAM); return @as(u16, @intCast(buf_base - dpram_base)); } }; -const rp2xxx_endpoints = struct { - const USB_DPRAM_BASE = 0x50100000; - const USB_DPRAM_BUFFERS_BASE = USB_DPRAM_BASE + 0x100; - const USB_DPRAM_BUFFERS_CTRL_BASE = USB_DPRAM_BASE + 0x80; - const USB_DPRAM_ENDPOINTS_CTRL_BASE = USB_DPRAM_BASE + 0x8; - - fn dir_index(dir: types.Dir) u1 { - return switch (dir) { - .In => 0, - .Out => 1, - }; - } +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Code +// +++++++++++++++++++++++++++++++++++++++++++++++++ - pub fn get_ep_ctrl(ep: Endpoint) ?*EndpointControlMimo { - if (ep.num == .ep0) { - return null; - } else { - const ep_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_ENDPOINTS_CTRL_BASE)); - return @ptrCast(&ep_ctrl_base[ep.num.to_int() - 1][dir_index(ep.dir)]); +/// The rp2040 usb device impl +/// +/// We create a concrete implementaion by passing a handful +/// of system specific functions to Usb(). Those functions +/// are used by the abstract USB impl of microzig. +pub fn Usb(comptime config: UsbConfig) type { + // A set of functions required by the abstract USB impl to + // create a concrete one. + const f = struct { + comptime { + if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) + @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); } - } - pub fn get_buf_ctrl(ep: Endpoint) ?*BufferControlMmio { - const buf_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_BUFFERS_CTRL_BASE)); - return @ptrCast(&buf_ctrl_base[ep.num.to_int()][dir_index(ep.dir)]); - } -}; + pub const cfg_max_endpoints_count = config.max_endpoints_count; + pub const cfg_max_interfaces_count = config.max_interfaces_count; + pub const high_speed = false; -pub const ClkSrc = enum { pll_usb, pll_sys, xosc, gpin0, gpin1 }; + const HardwareEndpoint = packed struct(u5) { + // DPRAM is better modelled with atomics than with volatile, because + // the control registers contain "pointers" (offsets) into the shared memory. + const Atomic = std.atomic.Value; -/// You have to guarantee that the used source is 48 MHz. -pub fn init_clk(clk_src: ClkSrc) void { - switch (clk_src) { - .pll_usb => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_pll_usb }), - .pll_sys => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_pll_sys }), - .xosc => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .xosc_clksrc }), - .gpin0 => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_gpin0 }), - .gpin1 => peripherals.CLOCKS.CLK_USB_CTRL.modify(.{ .AUXSRC = .clksrc_gpin1 }), - } -} + const EpCtrl = Atomic(@TypeOf(peri_dpram.EP1_IN_CONTROL).underlying_type); + const ep_ctrl_all: *[2 * (cfg_max_endpoints_count - 1)]EpCtrl = + @volatileCast(@ptrCast(&peri_dpram.EP1_IN_CONTROL)); -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Code -// +++++++++++++++++++++++++++++++++++++++++++++++++ + const BufCtrl = Atomic(@TypeOf(peri_dpram.EP0_IN_BUFFER_CONTROL).underlying_type); + const buf_ctrl_all: *[2 * (cfg_max_endpoints_count)]BufCtrl = + @volatileCast(@ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL)); -/// A set of functions required by the abstract USB impl to -/// create a concrete one. -pub fn F(comptime config: UsbConfig) type { - comptime { - if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) { - @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); - } - } + const Data = struct { + transfer_type: types.TransferType, + awaiting_rx: bool, - return struct { - pub const cfg_max_endpoints_count: u8 = config.max_endpoints_count; - pub const cfg_max_interfaces_count: u8 = config.max_interfaces_count; - pub const high_speed = false; + data_buffer: []u8, + }; + var global_data: [2 * config.max_endpoints_count]Data = undefined; + + is_out: bool, + num: types.Endpoint.Num, + + inline fn int(this: @This()) u5 { + return @bitCast(this); + } + + inline fn data(this: @This()) *Data { + return &global_data[this.int()]; + } + + fn from(ep: types.Endpoint) @This() { + return .{ + .num = ep.num, + .is_out = switch (ep.dir) { + .In => false, + .Out => true, + }, + }; + } + + fn into(this: @This()) types.Endpoint { + return .{ + .num = this.num, + .dir = if (this.is_out) .Out else .In, + }; + } + + fn in(num: types.Endpoint.Num) @This() { + return .{ + .num = num, + .is_out = false, + }; + } + + fn out(num: types.Endpoint.Num) @This() { + return .{ + .num = num, + .is_out = true, + }; + } + + fn ep_ctrl(this: @This()) ?*EpCtrl { + if (this.num == .ep0) + return null + else { + return &ep_ctrl_all[this.int() - 2]; + } + } + + fn buf_ctrl(this: @This()) *BufCtrl { + return &buf_ctrl_all[this.int()]; + } + }; - var endpoints: [config.max_endpoints_count][2]HardwareEndpoint = undefined; var data_buffer: []u8 = rp2xxx_buffers.data_buffer; pub fn usb_init_device(_: *usb.DeviceConfiguration) void { - if (chip == .RP2350) { - peripherals.USB.MAIN_CTRL.modify(.{ - .PHY_ISO = 0, - }); - } + if (chip == .RP2350) + peripherals.USB.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); // Clear the control portion of DPRAM. This may not be necessary -- the // datasheet is ambiguous -- but the C examples do it, and so do we. peripherals.USB_DPRAM.SETUP_PACKET_LOW.write_raw(0); peripherals.USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); - for (1..cfg_max_endpoints_count) |i| { - rp2xxx_endpoints.get_ep_ctrl(.in(.from_int(@intCast(i)))).?.write_raw(0); - rp2xxx_endpoints.get_ep_ctrl(.out(.from_int(@intCast(i)))).?.write_raw(0); - } - - for (0..cfg_max_endpoints_count) |i| { - rp2xxx_endpoints.get_buf_ctrl(.in(.from_int(@intCast(i)))).?.write_raw(0); - rp2xxx_endpoints.get_buf_ctrl(.out(.from_int(@intCast(i)))).?.write_raw(0); - } + for (HardwareEndpoint.ep_ctrl_all) |*ep_ctrl| + ep_ctrl.store(@bitCast(@as(u32, 0)), .seq_cst); + for (HardwareEndpoint.buf_ctrl_all) |*buf_ctrl| + buf_ctrl.store(@bitCast(@as(u32, 0)), .seq_cst); // Mux the controller to the onboard USB PHY. I was surprised that there are // alternatives to this, but, there are. @@ -202,9 +191,7 @@ pub fn F(comptime config: UsbConfig) type { // Request to have an interrupt (which really just means setting a bit in // the `buff_status` register) every time a buffer moves through EP0. - peripherals.USB.SIE_CTRL.modify(.{ - .EP0_INT_1BUF = 1, - }); + peripherals.USB.SIE_CTRL.modify(.{ .EP0_INT_1BUF = 1 }); // Enable interrupts (bits set in the `ints` register) for other conditions // we use: @@ -217,9 +204,9 @@ pub fn F(comptime config: UsbConfig) type { .SETUP_REQ = 1, }); - @memset(std.mem.asBytes(&endpoints), 0); - endpoint_open(.in(.ep0), 64, types.TransferType.Control); - endpoint_open(.out(.ep0), 64, types.TransferType.Control); + @memset(std.mem.asBytes(&HardwareEndpoint.global_data), 0); + endpoint_open(.in(.ep0), 64, types.TransferType.Control) catch unreachable; + endpoint_open(.out(.ep0), 64, types.TransferType.Control) catch unreachable; // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. @@ -236,7 +223,7 @@ pub fn F(comptime config: UsbConfig) type { ep_in: Endpoint.Num, data: []const []const u8, ) usize { - const ep_hard = hardware_endpoint(.in(ep_in)); + const ep_hard: HardwareEndpoint = .in(ep_in); // It is technically possible to support longer buffers but this demo doesn't bother. const dst_buf_len = 64; @@ -245,10 +232,10 @@ pub fn F(comptime config: UsbConfig) type { const space_left = dst_buf_len - n; if (space_left >= buffer.len) { // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, ep_hard.data_buffer[n .. n + buffer.len], buffer); + std.mem.copyForwards(u8, ep_hard.data().data_buffer[n .. n + buffer.len], buffer); n += buffer.len; } else { - std.mem.copyForwards(u8, ep_hard.data_buffer[n..], buffer[0..space_left]); + std.mem.copyForwards(u8, ep_hard.data().data_buffer[n..], buffer[0..space_left]); n = dst_buf_len; } } @@ -259,26 +246,21 @@ pub fn F(comptime config: UsbConfig) type { // accurate when the AVAILABLE bit is set. // Write the buffer information to the buffer control register - ep_hard.buffer_control.?.modify(.{ - .PID_0 = @as(u1, @intFromBool(ep_hard.next_pid_1)), // DATA0/1, depending - .FULL_0 = 1, // We have put data in - .LENGTH_0 = @as(u10, @intCast(n)), // There are this many bytes - }); + const buf_ctrl = ep_hard.buf_ctrl(); + var rmw = buf_ctrl.load(.seq_cst); + rmw.PID_0 ^= 1; // Flip DATA0/1 + rmw.FULL_0 = 1; // We have put data in + rmw.LENGTH_0 = @as(u10, @intCast(n)); // There are this many bytes + buf_ctrl.store(rmw, .seq_cst); // Nop for some clock cycles // use volatile so the compiler doesn't optimize the nops away - asm volatile ( - \\ nop - \\ nop - \\ nop - ); + inline for (0..config.synchronization_nops) |_| + asm volatile ("nop"); // Set available bit - ep_hard.buffer_control.?.modify(.{ - .AVAILABLE_0 = 1, // The data is for the computer to use now - }); - - ep_hard.next_pid_1 = !ep_hard.next_pid_1; + rmw.AVAILABLE_0 = 1; // The data is for the computer to use now + buf_ctrl.store(rmw, .seq_cst); return n; } @@ -291,29 +273,27 @@ pub fn F(comptime config: UsbConfig) type { // doesn't bother. // TODO: assert!(len <= 64); - const ep_hard = hardware_endpoint(.out(ep_out)); + const ep_hard: HardwareEndpoint = .out(ep_out); - if (ep_hard.awaiting_rx) + if (ep_hard.data().awaiting_rx) return; - // Check which DATA0/1 PID this endpoint is expecting next. - const np: u1 = if (ep_hard.next_pid_1) 1 else 0; // Configure the OUT: - ep_hard.buffer_control.?.modify(.{ - .PID_0 = np, // DATA0/1 depending - .FULL_0 = 0, // Buffer is NOT full, we want the computer to fill it - .AVAILABLE_0 = 1, // It is, however, available to be filled - .LENGTH_0 = @as(u10, @intCast(len)), // Up tho this many bytes - }); + const buf_ctrl = ep_hard.buf_ctrl(); + var rmw = buf_ctrl.load(.seq_cst); + rmw.PID_0 ^= 1; // Flip DATA0/1 + rmw.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it + rmw.AVAILABLE_0 = 1; // It is, however, available to be filled + rmw.LENGTH_0 = @intCast(len); // Up tho this many bytes + buf_ctrl.store(rmw, .seq_cst); // Flip the DATA0/1 PID for the next receive - ep_hard.next_pid_1 = !ep_hard.next_pid_1; - ep_hard.awaiting_rx = true; + ep_hard.data().awaiting_rx = true; } pub fn endpoint_reset_rx(ep_out: Endpoint.Num) void { - const ep_hard = hardware_endpoint(.out(ep_out)); - ep_hard.awaiting_rx = false; + const ep_hard: HardwareEndpoint = .out(ep_out); + ep_hard.data().awaiting_rx = false; } /// Check which interrupt flags are set @@ -370,69 +350,57 @@ pub fn F(comptime config: UsbConfig) type { } pub fn reset_ep0() void { - var ep = hardware_endpoint(.in(.ep0)); - ep.next_pid_1 = true; + // Next packet ID will be DATA1 + peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); } - fn hardware_endpoint(ep: Endpoint) *HardwareEndpoint { - const dir_as_number: u1 = switch (ep.dir) { - .Out => 0, - .In => 1, - }; - return &endpoints[ep.num.to_int()][dir_as_number]; - } + pub fn endpoint_open(ep: Endpoint, packet_size: u11, transfer_type: types.TransferType) error{OutOfBufferMemory}!void { + const ep_hard: HardwareEndpoint = .from(ep); - pub fn endpoint_open(ep: Endpoint, max_packet_size: u11, transfer_type: types.TransferType) void { - const ep_hard = hardware_endpoint(ep); - - endpoint_init(ep, max_packet_size, transfer_type); + endpoint_init(ep, transfer_type); if (ep.num != .ep0) { - endpoint_alloc(ep_hard) catch {}; + try endpoint_alloc(ep_hard, packet_size); endpoint_enable(ep_hard); } } - fn endpoint_init(ep: Endpoint, max_packet_size: u11, transfer_type: types.TransferType) void { + fn endpoint_init(ep: Endpoint, transfer_type: types.TransferType) void { assert(ep.num.to_int() <= cfg_max_endpoints_count); - var ep_hard = hardware_endpoint(ep); - ep_hard.ep = ep; - ep_hard.max_packet_size = max_packet_size; - ep_hard.transfer_type = transfer_type; - ep_hard.next_pid_1 = false; - ep_hard.awaiting_rx = false; - - ep_hard.buffer_control = rp2xxx_endpoints.get_buf_ctrl(ep); - ep_hard.endpoint_control = rp2xxx_endpoints.get_ep_ctrl(ep); + var ep_hard: HardwareEndpoint = .from(ep); + ep_hard.data().transfer_type = transfer_type; + ep_hard.data().awaiting_rx = false; if (ep.num == .ep0) { // ep0 has fixed data buffer - ep_hard.data_buffer = rp2xxx_buffers.ep0_buffer0; + ep_hard.data().data_buffer = rp2xxx_buffers.ep0_buffer0; } } - fn endpoint_alloc(ep: *HardwareEndpoint) !void { + fn endpoint_alloc(ep_hard: HardwareEndpoint, packet_size: u11) error{OutOfBufferMemory}!void { // round up size to multiple of 64 - var size = try std.math.divCeil(u11, ep.max_packet_size, 64) * 64; + var size = (std.math.divCeil(u11, packet_size, 64) catch unreachable) * 64; // double buffered Bulk endpoint - if (ep.transfer_type == .Bulk) { + if (ep_hard.data().transfer_type == .Bulk) { size *= 2; } std.debug.assert(data_buffer.len >= size); - ep.data_buffer = data_buffer[0..size]; + ep_hard.data().data_buffer = data_buffer[0..size]; data_buffer = data_buffer[size..]; } - fn endpoint_enable(ep: *HardwareEndpoint) void { - ep.endpoint_control.?.modify(.{ - .ENABLE = 1, - .INTERRUPT_PER_BUFF = 1, - .ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(ep.transfer_type.as_number())), - .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep.data_buffer), - }); + fn endpoint_enable(ep_hard: HardwareEndpoint) void { + const EndpointType = microzig.chip.types.peripherals.USB_DPRAM.EndpointType; + var ep_ctrl = ep_hard.ep_ctrl().?; + var rmw = ep_ctrl.load(.seq_cst); + rmw.ENABLE = 1; + rmw.INTERRUPT_PER_BUFF = 1; + rmw.ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(ep_hard.data().transfer_type.as_number())); + rmw.BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data().data_buffer); + ep_ctrl.store(rmw, .seq_cst); } pub const UnhandledEndpointIterator = struct { @@ -463,17 +431,19 @@ pub fn F(comptime config: UsbConfig) type { .num = .from_int(@intCast(idx >> 1)), // Of the pair, the IN endpoint comes first, followed by OUT, so // we can get the direction by: - .dir = if (idx & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out, + .dir = if (idx & 1 == 0) .In else .Out, }; - const ep_hard = hardware_endpoint(ep); - const len = ep_hard.buffer_control.?.read().LENGTH_0; + const ep_hard = HardwareEndpoint.from(ep); + const len = ep_hard.buf_ctrl().load(.seq_cst).LENGTH_0; return .{ .endpoint = ep, - .buffer = ep_hard.data_buffer[0..len], + .buffer = ep_hard.data().data_buffer[0..len], }; } }; }; + + return usb.Usb(f); } From b5fdd568c18f755df41cc619eeb71c00c71be1f1 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 18 Aug 2025 14:31:57 +0200 Subject: [PATCH 08/33] remove most global data from usb --- core/src/core/usb.zig | 104 ++++--- examples/raspberrypi/rp2xxx/build.zig | 88 +++--- port/raspberrypi/rp2xxx/src/hal/usb.zig | 355 +++++++++++------------- 3 files changed, 257 insertions(+), 290 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 9e5eddf97..f24694275 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -142,7 +142,7 @@ pub fn Usb(comptime f: anytype) type { const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); - f.endpoint_open(ep, ep_max_packet_size, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk) catch unreachable; + f.endpoint_open(ep, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk, ep_max_packet_size) catch unreachable; } fn get_driver(drv_idx: u8) ?*types.UsbClassDriver { @@ -407,66 +407,60 @@ pub fn Usb(comptime f: anytype) type { if (ints.BuffStatus) { if (debug) std.log.info("buff status", .{}); var iter: f.UnhandledEndpointIterator = .init(); - defer iter.deinit(); - while (iter.next()) |epb| { - if (debug) std.log.info(" data: {any}", .{epb.buffer}); + while (iter.next()) |result| switch (result) { + .In => |in| { + if (debug) std.log.info(" data: {any}", .{in.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. - if (epb.endpoint.num == .ep0 and epb.endpoint.dir == .In) { - if (debug) std.log.info(" EP0_IN_ADDR", .{}); + // 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. + if (in.ep_num == .ep0) { + if (debug) std.log.info(" EP0_IN_ADDR", .{}); - const buffer_reader = &S.buffer_reader; + const buffer_reader = &S.buffer_reader; - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (S.new_address) |addr| { - // Change our address: - f.set_address(@intCast(addr)); - } + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (S.new_address) |addr| { + // Change our address: + f.set_address(@intCast(addr)); + } + + if (in.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) { + _ = buffer_reader.try_advance(in.buffer.len); + const next_data_chunk = buffer_reader.try_peek(64); + if (next_data_chunk.len > 0) { + _ = f.usb_start_tx(.ep0, &.{next_data_chunk}); + } else { + f.usb_start_rx(.ep0, 0); - if (epb.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) { - _ = buffer_reader.try_advance(epb.buffer.len); - const next_data_chunk = buffer_reader.try_peek(64); - if (next_data_chunk.len > 0) { - _ = f.usb_start_tx(.ep0, &.{next_data_chunk}); + if (S.driver) |driver| { + _ = driver.class_control(.Ack, &S.setup_packet); + } + } } else { + // Otherwise, we've just finished sending + // something to the host. We expect an ensuing + // status phase where the host sends us (via EP0 + // OUT) a zero-byte DATA packet, so, set that + // up: f.usb_start_rx(.ep0, 0); - if (S.driver) |driver| { + if (S.driver) |driver| _ = driver.class_control(.Ack, &S.setup_packet); - } } - } else { - // Otherwise, we've just finished sending - // something to the host. We expect an ensuing - // status phase where the host sends us (via EP0 - // OUT) a zero-byte DATA packet, so, set that - // up: - f.usb_start_rx(.ep0, 0); - - if (S.driver) |driver| { - _ = driver.class_control(.Ack, &S.setup_packet); - } - } - } else { - const drv = ep_to_drv[epb.endpoint.num.to_int()]; - switch (epb.endpoint.dir) { - .Out => { - if (get_driver(drv[0])) |driver| - driver.receive(epb.endpoint.num, epb.buffer); - - f.endpoint_reset_rx(epb.endpoint.num); - }, - .In => { - if (get_driver(drv[1])) |driver| - driver.send(epb.endpoint.num, epb.buffer); - }, - } - } - } + } else if (get_driver(ep_to_drv[in.ep_num.to_int()][1])) |driver| + driver.send(in.ep_num, in.buffer); + }, + .Out => |out| { + if (debug) std.log.info(" data: {any}", .{out.buffer}); + if (get_driver(ep_to_drv[out.ep_num.to_int()][0])) |driver| + driver.receive(out.ep_num, out.buffer); + + f.endpoint_reset_rx(out.ep_num); + }, + }; } // <-- END of buf status handling // Has the host signaled a bus reset? @@ -523,11 +517,9 @@ pub const InterruptStatus = struct { }; /// And endpoint and its corresponding buffer. -pub const EndpointAndBuffer = struct { - /// The endpoint the data belongs to - endpoint: Endpoint, - /// Data buffer - buffer: []u8, +pub const EndpointAndBuffer = union(Dir) { + Out: struct { ep_num: Endpoint.Num, buffer: []const u8 }, + In: struct { ep_num: Endpoint.Num, buffer: []u8 }, }; const BufferWriter = struct { diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 2a440e716..0c3d83617 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -12,60 +12,60 @@ pub fn build(b: *std.Build) void { const mz_dep = b.dependency("microzig", .{}); const mb = MicroBuild.init(b, mz_dep) orelse return; - const raspberrypi = mb.ports.rp2xxx.boards.raspberrypi; + // const raspberrypi = mb.ports.rp2xxx.boards.raspberrypi; const specific_examples: []const Example = &.{ // RaspberryPi Boards: - .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, - .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, - .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, - .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, - - .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, - .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, - - .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - - // WaveShare Boards: - .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, + // .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, + // .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, + // .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, + + // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, + // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, + + // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + + // // WaveShare Boards: + // .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, // .{ .target = "board:waveshare/rp2040_eth", .name = "rp2040-eth" }, // .{ .target = "board:waveshare/rp2040_plus_4m", .name = "rp2040-plus-4m" }, // .{ .target = "board:waveshare/rp2040_plus_16m", .name = "rp2040-plus-16m" }, }; const chip_agnostic_examples: []const ChipAgnosticExample = &.{ - .{ .name = "adc", .file = "src/adc.zig" }, - .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, - .{ .name = "pwm", .file = "src/pwm.zig" }, - .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, - .{ .name = "uart-log", .file = "src/uart_log.zig" }, - .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, - .{ .name = "spi-master", .file = "src/spi_master.zig" }, - .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, - .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, - .{ .name = "squarewave", .file = "src/squarewave.zig" }, - .{ .name = "ws2812", .file = "src/ws2812.zig" }, - .{ .name = "blinky", .file = "src/blinky.zig" }, - .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, - .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, - .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, - .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, - .{ .name = "interrupts", .file = "src/interrupts.zig" }, - .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, - .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, + // .{ .name = "adc", .file = "src/adc.zig" }, + // .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, + // .{ .name = "pwm", .file = "src/pwm.zig" }, + // .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, + // .{ .name = "uart-log", .file = "src/uart_log.zig" }, + // .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, + // .{ .name = "spi-master", .file = "src/spi_master.zig" }, + // .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, + // .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, + // .{ .name = "squarewave", .file = "src/squarewave.zig" }, + // .{ .name = "ws2812", .file = "src/ws2812.zig" }, + // .{ .name = "blinky", .file = "src/blinky.zig" }, + // .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, + // .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, + // .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, + // .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, + // .{ .name = "interrupts", .file = "src/interrupts.zig" }, + // .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, + // .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "usb-cdc", .file = "src/usb_cdc.zig" }, - .{ .name = "dma", .file = "src/dma.zig" }, - .{ .name = "cyw43", .file = "src/cyw43.zig" }, - .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, + // .{ .name = "dma", .file = "src/dma.zig" }, + // .{ .name = "cyw43", .file = "src/cyw43.zig" }, + // .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, }; var available_examples = std.ArrayList(Example).init(b.allocator); diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index dc99d4960..0169fc645 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -7,13 +7,10 @@ const std = @import("std"); const assert = std.debug.assert; const microzig = @import("microzig"); -const peripherals = microzig.chip.peripherals; -const peri_usb = microzig.chip.peripherals.USB; -const peri_dpram = microzig.chip.peripherals.USB_DPRAM; const chip = microzig.hal.compatibility.chip; - +const peri_dpram = microzig.chip.peripherals.USB_DPRAM; +const peri_usb = microzig.chip.peripherals.USB; const usb = microzig.core.usb; -const types = usb.types; const resets = @import("resets.zig"); @@ -24,30 +21,66 @@ pub const UsbConfig = struct { max_endpoints_count: comptime_int = RP2XXX_MAX_ENDPOINTS_COUNT, max_interfaces_count: comptime_int = 16, synchronization_nops: comptime_int = 3, + dpram_allocator: type = DpramAllocatorBump, + swap_dpdm: bool = false, }; const Endpoint = usb.types.Endpoint; +const EpCtrl = @TypeOf(peri_dpram.EP1_IN_CONTROL); +const BufCtrl = @TypeOf(peri_dpram.EP0_IN_BUFFER_CONTROL); + +const dpram_size = 4096; +pub const DpramBuffer = struct { + const chunk_len = 1 << Index.ignored_lsbs; + const start_align = chunk_len; + + const Chunk = struct { data: [chunk_len]u8 align(start_align) = undefined }; + + const memory_raw: *[dpram_size / chunk_len]Chunk = + @alignCast(@volatileCast(@ptrCast(peri_dpram))); -const rp2xxx_buffers = struct { - // Address 0x100-0xfff (3840 bytes) can be used for data buffers - const USB_DPRAM_DATA_BUFFER_BASE = 0x50100100; + pub const Len = + std.meta.Int(.unsigned, std.math.log2_int_ceil(u16, dpram_size)); + + pub const Index = enum(Len) { + const ignored_lsbs = 6; + + invalid = 0, + ep0buf0 = (0x100 >> ignored_lsbs), + ep0buf1, + data_start, + _, + + fn from_reg(reg: EpCtrl.underlying_type) @This() { + return @enumFromInt(@shrExact(reg.BUFFER_ADDRESS, ignored_lsbs)); + } + + fn to_u16(this: @This()) u16 { + return @as(u16, @intFromEnum(this)) << ignored_lsbs; + } + + fn start(this: @This()) [*]align(start_align) u8 { + return @ptrCast(&memory_raw[@intFromEnum(this)]); + } + }; +}; - const CTRL_EP_BUFFER_SIZE = 64; +pub const DpramAllocatorError = error{OutOfBufferMemory}; - const USB_EP0_BUFFER0 = USB_DPRAM_DATA_BUFFER_BASE; - const USB_EP0_BUFFER1 = USB_DPRAM_DATA_BUFFER_BASE + CTRL_EP_BUFFER_SIZE; +pub const DpramAllocatorBump = struct { + // First 0x100 bytes contain control registers and first 2 buffers are for endpoint 0. + var top: DpramBuffer.Index = .data_start; - const USB_DATA_BUFFER = USB_DPRAM_DATA_BUFFER_BASE + (2 * CTRL_EP_BUFFER_SIZE); - const USB_DATA_BUFFER_SIZE = 3840 - (2 * CTRL_EP_BUFFER_SIZE); + fn alloc(len: DpramBuffer.Len) DpramAllocatorError!DpramBuffer.Index { + if (top == .invalid) return error.OutOfBufferMemory; - const ep0_buffer0: *[CTRL_EP_BUFFER_SIZE]u8 = @as(*[CTRL_EP_BUFFER_SIZE]u8, @ptrFromInt(USB_EP0_BUFFER0)); - const ep0_buffer1: *[CTRL_EP_BUFFER_SIZE]u8 = @as(*[CTRL_EP_BUFFER_SIZE]u8, @ptrFromInt(USB_EP0_BUFFER1)); - const data_buffer: *[USB_DATA_BUFFER_SIZE]u8 = @as(*[USB_DATA_BUFFER_SIZE]u8, @ptrFromInt(USB_DATA_BUFFER)); + const next, const ovf = @addWithOverflow(len, @intFromEnum(top)); + if (ovf != 0 and next != 0) + return error.OutOfBufferMemory; - fn data_offset(ep_data_buffer: []u8) u16 { - const buf_base = @intFromPtr(ep_data_buffer.ptr); - const dpram_base = @intFromPtr(peripherals.USB_DPRAM); - return @as(u16, @intCast(buf_base - dpram_base)); + const ret: DpramBuffer.Index = @enumFromInt(next); + defer top = ret; + return ret; } }; @@ -73,101 +106,99 @@ pub fn Usb(comptime config: UsbConfig) type { pub const cfg_max_interfaces_count = config.max_interfaces_count; pub const high_speed = false; - const HardwareEndpoint = packed struct(u5) { - // DPRAM is better modelled with atomics than with volatile, because - // the control registers contain "pointers" (offsets) into the shared memory. - const Atomic = std.atomic.Value; + const HardwareEndpoint = packed struct(u7) { + const ep_ctrl_all: *volatile [2 * (cfg_max_endpoints_count - 1)]EpCtrl = + @ptrCast(&peri_dpram.EP1_IN_CONTROL); - const EpCtrl = Atomic(@TypeOf(peri_dpram.EP1_IN_CONTROL).underlying_type); - const ep_ctrl_all: *[2 * (cfg_max_endpoints_count - 1)]EpCtrl = - @volatileCast(@ptrCast(&peri_dpram.EP1_IN_CONTROL)); + const buf_ctrl_all: *volatile [2 * (cfg_max_endpoints_count)]BufCtrl = + @ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL); - const BufCtrl = Atomic(@TypeOf(peri_dpram.EP0_IN_BUFFER_CONTROL).underlying_type); - const buf_ctrl_all: *[2 * (cfg_max_endpoints_count)]BufCtrl = - @volatileCast(@ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL)); + var awaiting_rx: u32 = 0; - const Data = struct { - transfer_type: types.TransferType, - awaiting_rx: bool, + _padding: u2 = 0, // 2 bits of padding so that address generation is a nop. + is_out: bool, + num: Endpoint.Num, - data_buffer: []u8, - }; - var global_data: [2 * config.max_endpoints_count]Data = undefined; + inline fn to_idx(this: @This()) u5 { + return @intCast(@shrExact(@as(u7, @bitCast(this)), 2)); + } - is_out: bool, - num: types.Endpoint.Num, + inline fn from_idx(idx: u5) @This() { + return @bitCast(@shlExact(@as(u7, idx), 2)); + } - inline fn int(this: @This()) u5 { - return @bitCast(this); + inline fn mask(this: @This()) u32 { + return @as(u32, 1) << this.to_idx(); } - inline fn data(this: @This()) *Data { - return &global_data[this.int()]; + inline fn awaiting_rx_get(this: @This()) bool { + return (this.mask() & @atomicLoad(u32, &awaiting_rx, .seq_cst)) != 0; } - fn from(ep: types.Endpoint) @This() { - return .{ - .num = ep.num, - .is_out = switch (ep.dir) { - .In => false, - .Out => true, - }, - }; + inline fn awaiting_rx_set(this: @This()) void { + _ = @atomicRmw(u32, &awaiting_rx, .Or, this.mask(), .seq_cst); } - fn into(this: @This()) types.Endpoint { - return .{ - .num = this.num, - .dir = if (this.is_out) .Out else .In, - }; + inline fn awaiting_rx_clr(this: @This()) void { + _ = @atomicRmw(u32, &awaiting_rx, .And, ~this.mask(), .seq_cst); } - fn in(num: types.Endpoint.Num) @This() { - return .{ - .num = num, - .is_out = false, - }; + fn from(ep: Endpoint) @This() { + return .{ .num = ep.num, .is_out = ep.dir == .Out }; } - fn out(num: types.Endpoint.Num) @This() { - return .{ - .num = num, - .is_out = true, - }; + fn into(this: @This()) Endpoint { + assert(this._padding == 0); + return .{ .num = this.num, .dir = if (this.is_out) .Out else .In }; } - fn ep_ctrl(this: @This()) ?*EpCtrl { - if (this.num == .ep0) - return null - else { - return &ep_ctrl_all[this.int() - 2]; - } + fn in(num: Endpoint.Num) @This() { + return .{ .num = num, .is_out = false }; } - fn buf_ctrl(this: @This()) *BufCtrl { - return &buf_ctrl_all[this.int()]; + fn out(num: Endpoint.Num) @This() { + return .{ .num = num, .is_out = true }; + } + + fn ep_ctrl(this: @This()) ?*volatile EpCtrl { + const i, const ovf = @subWithOverflow(this.to_idx(), 2); + return if (ovf != 0) null else &ep_ctrl_all[i]; + } + + fn buf_ctrl(this: @This()) *volatile BufCtrl { + return &buf_ctrl_all[this.to_idx()]; + } + + fn buffer(this: @This()) []u8 { + const buf: DpramBuffer.Index = if (this.ep_ctrl()) |reg| + .from_reg(reg.read()) + else + .ep0buf0; + return buf.start()[0..DpramBuffer.chunk_len]; } - }; - var data_buffer: []u8 = rp2xxx_buffers.data_buffer; + fn buffer_ready(this: @This()) []u8 { + return this.buffer()[0..this.buf_ctrl().read().LENGTH_0]; + } + }; pub fn usb_init_device(_: *usb.DeviceConfiguration) void { if (chip == .RP2350) - peripherals.USB.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); + peri_usb.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); // Clear the control portion of DPRAM. This may not be necessary -- the // datasheet is ambiguous -- but the C examples do it, and so do we. - peripherals.USB_DPRAM.SETUP_PACKET_LOW.write_raw(0); - peripherals.USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); + peri_dpram.SETUP_PACKET_LOW.write_raw(0); + peri_dpram.SETUP_PACKET_HIGH.write_raw(0); for (HardwareEndpoint.ep_ctrl_all) |*ep_ctrl| - ep_ctrl.store(@bitCast(@as(u32, 0)), .seq_cst); + ep_ctrl.write_raw(0); for (HardwareEndpoint.buf_ctrl_all) |*buf_ctrl| - buf_ctrl.store(@bitCast(@as(u32, 0)), .seq_cst); + buf_ctrl.write_raw(0); // Mux the controller to the onboard USB PHY. I was surprised that there are // alternatives to this, but, there are. - peripherals.USB.USB_MUXING.modify(.{ + peri_usb.USB_MUXING.modify(.{ .TO_PHY = 1, // This bit is also set in the SDK example, without any discussion. It's // undocumented (being named does not count as being documented). @@ -178,24 +209,24 @@ pub fn Usb(comptime config: UsbConfig) type { // let us detect being plugged into a host (the Pi Pico, to its credit, // does). For maximum compatibility, we'll set the hardware to always // pretend VBUS has been detected. - peripherals.USB.USB_PWR.modify(.{ + peri_usb.USB_PWR.modify(.{ .VBUS_DETECT = 1, .VBUS_DETECT_OVERRIDE_EN = 1, }); // Enable controller in device mode. - peripherals.USB.MAIN_CTRL.modify(.{ + peri_usb.MAIN_CTRL.modify(.{ .CONTROLLER_EN = 1, .HOST_NDEVICE = 0, }); // Request to have an interrupt (which really just means setting a bit in // the `buff_status` register) every time a buffer moves through EP0. - peripherals.USB.SIE_CTRL.modify(.{ .EP0_INT_1BUF = 1 }); + peri_usb.SIE_CTRL.modify(.{ .EP0_INT_1BUF = 1 }); // Enable interrupts (bits set in the `ints` register) for other conditions // we use: - peripherals.USB.INTE.modify(.{ + peri_usb.INTE.modify(.{ // A buffer is done .BUFF_STATUS = 1, // The host has reset us @@ -204,13 +235,12 @@ pub fn Usb(comptime config: UsbConfig) type { .SETUP_REQ = 1, }); - @memset(std.mem.asBytes(&HardwareEndpoint.global_data), 0); - endpoint_open(.in(.ep0), 64, types.TransferType.Control) catch unreachable; - endpoint_open(.out(.ep0), 64, types.TransferType.Control) catch unreachable; + endpoint_open(.in(.ep0), .Control, 0) catch unreachable; + endpoint_open(.out(.ep0), .Control, 0) catch unreachable; // 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 }); + peri_usb.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); } /// Configures a given endpoint to send data (device-to-host, IN) when the host @@ -219,24 +249,21 @@ pub fn Usb(comptime config: UsbConfig) type { /// The contents of each of the slices in `data` will be _copied_ into USB SRAM, /// so you can reuse them immediately after this returns. /// No need to wait for the packet to be sent. - pub fn usb_start_tx( - ep_in: Endpoint.Num, - data: []const []const u8, - ) usize { + pub fn usb_start_tx(ep_in: Endpoint.Num, data: []const []const u8) usize { const ep_hard: HardwareEndpoint = .in(ep_in); + const buf = ep_hard.buffer(); // It is technically possible to support longer buffers but this demo doesn't bother. - const dst_buf_len = 64; var n: usize = 0; for (data) |buffer| { - const space_left = dst_buf_len - n; + const space_left = buf.len - n; if (space_left >= buffer.len) { // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, ep_hard.data().data_buffer[n .. n + buffer.len], buffer); + std.mem.copyForwards(u8, buf[n .. n + buffer.len], buffer); n += buffer.len; } else { - std.mem.copyForwards(u8, ep_hard.data().data_buffer[n..], buffer[0..space_left]); - n = dst_buf_len; + std.mem.copyForwards(u8, buf[n..DpramBuffer.chunk_len], buffer[0..space_left]); + n = DpramBuffer.chunk_len; } } @@ -247,11 +274,11 @@ pub fn Usb(comptime config: UsbConfig) type { // Write the buffer information to the buffer control register const buf_ctrl = ep_hard.buf_ctrl(); - var rmw = buf_ctrl.load(.seq_cst); + var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 1; // We have put data in rmw.LENGTH_0 = @as(u10, @intCast(n)); // There are this many bytes - buf_ctrl.store(rmw, .seq_cst); + buf_ctrl.write(rmw); // Nop for some clock cycles // use volatile so the compiler doesn't optimize the nops away @@ -260,45 +287,37 @@ pub fn Usb(comptime config: UsbConfig) type { // Set available bit rmw.AVAILABLE_0 = 1; // The data is for the computer to use now - buf_ctrl.store(rmw, .seq_cst); + buf_ctrl.write(rmw); return n; } - pub fn usb_start_rx( - ep_out: Endpoint.Num, - len: usize, - ) void { - // It is technically possible to support longer buffers but this demo - // doesn't bother. - // TODO: assert!(len <= 64); - + pub fn usb_start_rx(ep_out: Endpoint.Num, len: usize) void { const ep_hard: HardwareEndpoint = .out(ep_out); - if (ep_hard.data().awaiting_rx) + if (ep_hard.awaiting_rx_get()) return; // Configure the OUT: const buf_ctrl = ep_hard.buf_ctrl(); - var rmw = buf_ctrl.load(.seq_cst); + var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it rmw.AVAILABLE_0 = 1; // It is, however, available to be filled rmw.LENGTH_0 = @intCast(len); // Up tho this many bytes - buf_ctrl.store(rmw, .seq_cst); + buf_ctrl.write(rmw); - // Flip the DATA0/1 PID for the next receive - ep_hard.data().awaiting_rx = true; + ep_hard.awaiting_rx_set(); } pub fn endpoint_reset_rx(ep_out: Endpoint.Num) void { const ep_hard: HardwareEndpoint = .out(ep_out); - ep_hard.data().awaiting_rx = false; + ep_hard.awaiting_rx_clr(); } /// Check which interrupt flags are set pub fn get_interrupts() usb.InterruptStatus { - const ints = peripherals.USB.INTS.read(); + const ints = peri_usb.INTS.read(); return .{ .BuffStatus = if (ints.BUFF_STATUS == 1) true else false, @@ -318,7 +337,7 @@ pub fn Usb(comptime config: UsbConfig) type { /// setup request falg is set. pub fn get_setup_packet() usb.types.SetupPacket { // Clear the status flag (write-one-to-clear) - peripherals.USB.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); + peri_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 @@ -330,8 +349,8 @@ pub fn Usb(comptime config: UsbConfig) type { // we can't just treat it as bytes. Instead, copy it out to a byte // array. var setup_packet: [8]u8 = @splat(0); - const spl: u32 = peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw; - const sph: u32 = peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw; + const spl: u32 = peri_dpram.SETUP_PACKET_LOW.raw; + const sph: u32 = peri_dpram.SETUP_PACKET_HIGH.raw; @memcpy(setup_packet[0..4], std.mem.asBytes(&spl)); @memcpy(setup_packet[4..8], std.mem.asBytes(&sph)); // Reinterpret as setup packet @@ -341,106 +360,62 @@ pub fn Usb(comptime config: UsbConfig) type { /// Called on a bus reset interrupt pub fn bus_reset() void { // Acknowledge by writing the write-one-to-clear status bit. - peripherals.USB.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); - peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = 0 }); + peri_usb.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); + peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); } pub fn set_address(addr: u7) void { - peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = addr }); + peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); } pub fn reset_ep0() void { // Next packet ID will be DATA1 - peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); + peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); } - pub fn endpoint_open(ep: Endpoint, packet_size: u11, transfer_type: types.TransferType) error{OutOfBufferMemory}!void { + pub fn endpoint_open(ep: Endpoint, transfer_type: usb.types.TransferType, buf_size_hint: usize) error{OutOfBufferMemory}!void { + _ = buf_size_hint; + const ep_hard: HardwareEndpoint = .from(ep); - endpoint_init(ep, transfer_type); + assert(ep.num.to_int() < cfg_max_endpoints_count); + ep_hard.awaiting_rx_clr(); if (ep.num != .ep0) { - try endpoint_alloc(ep_hard, packet_size); - endpoint_enable(ep_hard); - } - } - - fn endpoint_init(ep: Endpoint, transfer_type: types.TransferType) void { - assert(ep.num.to_int() <= cfg_max_endpoints_count); - - var ep_hard: HardwareEndpoint = .from(ep); - ep_hard.data().transfer_type = transfer_type; - ep_hard.data().awaiting_rx = false; - - if (ep.num == .ep0) { - // ep0 has fixed data buffer - ep_hard.data().data_buffer = rp2xxx_buffers.ep0_buffer0; + var ep_ctrl = ep_hard.ep_ctrl().?; + var rmw = ep_ctrl.read(); + rmw.ENABLE = 1; + rmw.INTERRUPT_PER_BUFF = 1; + rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); + rmw.BUFFER_ADDRESS = (try config.dpram_allocator.alloc(1)).to_u16(); + ep_ctrl.write(rmw); } } - fn endpoint_alloc(ep_hard: HardwareEndpoint, packet_size: u11) error{OutOfBufferMemory}!void { - // round up size to multiple of 64 - var size = (std.math.divCeil(u11, packet_size, 64) catch unreachable) * 64; - // double buffered Bulk endpoint - if (ep_hard.data().transfer_type == .Bulk) { - size *= 2; - } - - std.debug.assert(data_buffer.len >= size); - - ep_hard.data().data_buffer = data_buffer[0..size]; - data_buffer = data_buffer[size..]; - } - - fn endpoint_enable(ep_hard: HardwareEndpoint) void { - const EndpointType = microzig.chip.types.peripherals.USB_DPRAM.EndpointType; - var ep_ctrl = ep_hard.ep_ctrl().?; - var rmw = ep_ctrl.load(.seq_cst); - rmw.ENABLE = 1; - rmw.INTERRUPT_PER_BUFF = 1; - rmw.ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(ep_hard.data().transfer_type.as_number())); - rmw.BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data().data_buffer); - ep_ctrl.store(rmw, .seq_cst); - } - pub const UnhandledEndpointIterator = struct { initial_unhandled_mask: u32, currently_unhandled_mask: u32, pub fn init() @This() { - const mask = peripherals.USB.BUFF_STATUS.raw; + const mask = peri_usb.BUFF_STATUS.raw; return .{ .initial_unhandled_mask = mask, .currently_unhandled_mask = mask, }; } - pub fn deinit(this: @This()) void { - const handled_mask = this.initial_unhandled_mask ^ this.currently_unhandled_mask; - peripherals.USB.BUFF_STATUS.write_raw(handled_mask); - } - pub fn next(this: *@This()) ?usb.EndpointAndBuffer { - const idx = std.math.cast(u5, @ctz(this.currently_unhandled_mask)) orelse return null; - this.currently_unhandled_mask &= this.currently_unhandled_mask -% 1; // clear lowest bit - - const ep: Endpoint = .{ - // 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: - .num = .from_int(@intCast(idx >> 1)), - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - .dir = if (idx & 1 == 0) .In else .Out, - }; - - const ep_hard = HardwareEndpoint.from(ep); - const len = ep_hard.buf_ctrl().load(.seq_cst).LENGTH_0; - - return .{ - .endpoint = ep, - .buffer = ep_hard.data().data_buffer[0..len], + const idx = std.math.cast(u5, @ctz(this.currently_unhandled_mask)) orelse { + if (this.initial_unhandled_mask != 0) + peri_usb.BUFF_STATUS.write_raw(this.initial_unhandled_mask); + return null; }; + this.currently_unhandled_mask &= this.currently_unhandled_mask -% 1; // clear lowest bit + const ep: HardwareEndpoint = .from_idx(idx); + return if (ep.is_out) + .{ .Out = .{ .ep_num = ep.num, .buffer = ep.buffer_ready() } } + else + .{ .In = .{ .ep_num = ep.num, .buffer = ep.buffer_ready() } }; } }; }; From d2c8cd0ab9c365e486ee52a93c08fe6a143e4782 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 19 Aug 2025 18:53:35 +0200 Subject: [PATCH 09/33] reduce use of global variables --- core/src/core/usb.zig | 527 ++++++++---------- core/src/core/usb/cdc.zig | 103 ++-- core/src/core/usb/hid.zig | 240 ++++---- core/src/core/usb/types.zig | 159 ++---- examples/raspberrypi/rp2xxx/build.zig | 4 +- .../rp2xxx/src/rp2040_only/usb_hid.zig | 69 ++- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 78 ++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 21 +- 8 files changed, 514 insertions(+), 687 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index f24694275..3ad8ca3fb 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -29,6 +29,47 @@ const Endpoint = types.Endpoint; const SetupRequest = types.SetupRequest; const BosConfig = utils.BosConfig; +pub const Descriptors = struct { + const Lang = struct { + lo: u8, + hi: u8, + + pub const English: @This() = .{ .lo = 0x09, .hi = 0x04 }; + }; + + device: []const u8, + config: []const u8, + string: []const []const u8, + device_qualifier: []const u8, + + pub fn create(comptime device: types.DeviceDescriptor, config: []const u8, comptime lang: Lang, comptime strings: []const []const u8) @This() { + @setEvalBranchQuota(10000); + + // String 0 indicates language. + var strings_utf16: []const []const u8 = &.{&.{ 0x04, 0x03, lang.lo, lang.hi }}; + inline for (strings) |str| { + const str_utf16: []const u8 = @ptrCast(std.unicode.utf8ToUtf16LeStringLiteral(str)); + strings_utf16 = strings_utf16 ++ .{.{ str_utf16.len + 2, strings_utf16[0][1] } ++ str_utf16}; + } + return .{ + .device = &device.serialize(), + .config = config, + .string = strings_utf16, + .device_qualifier = &(types.DeviceQualifierDescriptor{ + .bcd_usb = device.bcd_usb, + .device_triple = device.device_triple, + .max_packet_size0 = device.max_packet_size0, + .num_configurations = device.num_configurations, + }).serialize(), + }; + } +}; + +pub const Config = struct { + device: type, + descriptors: Descriptors, +}; + /// Create a USB device /// /// # Arguments @@ -45,135 +86,172 @@ const BosConfig = utils.BosConfig; /// * `set_address(addr: u7) void` - Set the given address /// * `get_EPBIter(*const DeviceConfiguration) EPBIter` - Return an endpoint buffer iterator. Each call to next returns an unhandeled endpoint buffer with data. How next is implemented depends on the system. /// The functions must be grouped under the same name space and passed to the fuction at compile time. -/// The functions will be accessible to the user through the `callbacks` field. -pub fn Usb(comptime f: anytype) type { +/// The functions will be accessible to the user through the `Dev` field. +pub fn Usb(comptime config: Config) type { return struct { - /// The usb configuration set - var usb_config: ?*DeviceConfiguration = null; - var itf_to_drv: [f.cfg_max_interfaces_count]u8 = @splat(0); - var ep_to_drv: [f.cfg_max_endpoints_count][2]u8 = @splat(@splat(0)); - pub const max_packet_size = if (f.high_speed) 512 else 64; + const Controller = @This(); + + /// USB Class driver interface + pub const DriverInterface = struct { + ptr: *anyopaque, + fn_init: *const fn (ptr: *anyopaque, ctrl: *Controller) void, + fn_open: *const fn (ptr: *anyopaque, ctrl: *Controller, cfg: []const u8) anyerror!usize, + fn_class_control: *const fn (ptr: *anyopaque, ctrl: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool, + fn_send: *const fn (ptr: *anyopaque, ctrl: *Controller, ep_in: types.Endpoint.Num, data: []const u8) void, + fn_receive: *const fn (ptr: *anyopaque, ctrl: *Controller, ep_out: types.Endpoint.Num, data: []const u8) void, + + pub fn init(interface: *const @This(), ctrl: *Controller) void { + return interface.fn_init(interface.ptr, ctrl); + } + + /// Driver open (set config) operation. Must return length of consumed driver config data. + pub fn open(interface: *const @This(), ctrl: *Controller, cfg: []const u8) !usize { + return interface.fn_open(interface.ptr, ctrl, cfg); + } + + pub fn class_control(interface: *const @This(), ctrl: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + return interface.fn_class_control(interface.ptr, ctrl, stage, setup); + } + + pub fn send(interface: *const @This(), ctrl: *Controller, ep_in: types.Endpoint.Num, data: []const u8) void { + return interface.fn_send(interface.ptr, ctrl, ep_in, data); + } + + pub fn receive(interface: *const @This(), ctrl: *Controller, ep_out: types.Endpoint.Num, len: []const u8) void { + return interface.fn_receive(interface.ptr, ctrl, ep_out, len); + } + }; + + pub const max_packet_size = config.device.max_packet_size; const drvid_invalid = 0xff; - /// The callbacks passed provided by the caller - pub const callbacks = f; - - // We'll keep some state in Plain Old Static Local Variables: - const S = struct { - // When the host gives us a new address, we can't just slap it into - // registers right away, because we have to do an acknowledgement step using - // our _old_ address. - var new_address: ?u8 = null; - // 0 - no config set - var cfg_num: u16 = 0; - // Flag recording whether the host has configured us with a - // `SetConfiguration` message. - var configured = false; - // Flag recording whether we've set up buffer transfers after being - // configured. - var started = false; - // Some scratch space that we'll use for things like preparing string - // descriptors for transmission. - var tmp: [128]u8 = @splat(0); - // Keeps track of sent data from tmp buffer - var buffer_reader = BufferReader{ .buffer = &.{} }; - // Last setup packet request - var setup_packet: types.SetupPacket = undefined; - // Class driver associated with last setup request if any - var driver: ?*types.UsbClassDriver = null; + /// The usb configuration set + drivers: ?[]const DriverInterface, + itf_to_drv: [config.device.cfg_max_interfaces_count]u8, + ep_to_drv: [config.device.cfg_max_endpoints_count][2]u8, + // Class driver associated with last setup request if any + driver: ?*const DriverInterface, + // When the host gives us a new address, we can't just slap it into + // registers right away, because we have to do an acknowledgement step using + // our _old_ address. + new_address: ?u8, + // Flag recording whether the host has configured us with a + // `SetConfiguration` message. + configured: bool, + // Flag recording whether we've set up buffer transfers after being + // configured. + started: bool, + // Last setup packet request + setup_packet: types.SetupPacket, + // Keeps track of sent data from tmp buffer + now_sending: []const u8, + // 0 - no config set + cfg_num: u16, + + pub const init: @This() = .{ + .drivers = null, + .itf_to_drv = @splat(0), + .ep_to_drv = @splat(@splat(0)), + .driver = null, + .new_address = null, + .configured = false, + .started = false, + .setup_packet = undefined, + .now_sending = &.{}, + .cfg_num = 0, }; // Command endpoint utilities const CmdEndpoint = struct { /// Command response utility function that can split long data in multiple packets - fn send_cmd_response(data: []const u8, expected_max_length: u16) void { - S.buffer_reader = BufferReader{ .buffer = data[0..@min(data.len, expected_max_length)] }; - const data_chunk = S.buffer_reader.try_peek(64); + fn send_cmd_response(this2: *Controller, data: []const u8, expected_max_length: u16) void { + this2.now_sending = data[0..@min(data.len, expected_max_length)]; + const data_chunk = this2.now_sending[0..@min(this2.now_sending.len, 64)]; if (data_chunk.len > 0) { - _ = f.usb_start_tx(.ep0, &.{data_chunk}); + _ = config.device.usb_start_tx(.ep0, &.{data_chunk}); } } - fn send_cmd_ack() void { - _ = f.usb_start_tx(.ep0, &.{}); + fn send_cmd_ack(this2: *Controller) void { + _ = this2; + _ = config.device.usb_start_tx(.ep0, &.{}); } }; /// Initialize the usb device using the given configuration /// /// You have to ensure that the device is getting an appropiate clock signal. - pub fn init_device(device_config: *DeviceConfiguration) void { - f.usb_init_device(device_config); - usb_config = device_config; + pub fn init_device(this: *@This(), d: []const DriverInterface) void { + config.device.usb_init_device(); + this.drivers = d; - const device_interface = device(); - for (usb_config.?.drivers) |*driver| { - driver.init(device_interface); + for (d) |*drv| { + drv.init(this); } } - fn device() types.UsbDevice { - return .{ - .fn_ready = device_ready, - .fn_control_transfer = device_control_transfer, - .fn_control_ack = device_control_ack, - .fn_endpoint_open = device_endpoint_open, - .fn_endpoint_tx = f.usb_start_tx, - .fn_endpoint_rx = f.usb_start_rx, - }; + pub fn ready(this: *@This()) bool { + return this.started; } - fn device_ready() bool { - return S.started; + pub fn control_transfer(this: *@This(), setup: *const types.SetupPacket, data: []const u8) void { + CmdEndpoint.send_cmd_response(this, data, setup.length); } - fn device_control_transfer(setup: *const types.SetupPacket, data: []const u8) void { - CmdEndpoint.send_cmd_response(data, setup.length); + pub fn control_ack(this: *@This(), _: *const types.SetupPacket) void { + CmdEndpoint.send_cmd_ack(this); } - fn device_control_ack(_: *const types.SetupPacket) void { - CmdEndpoint.send_cmd_ack(); + pub fn endpoint_tx(this: *@This(), ep_in: Endpoint.Num, data: []const []const u8) usize { + _ = this; + return config.device.usb_start_tx(ep_in, data); } - fn device_endpoint_open(ep_desc: []const u8) void { + pub fn endpoint_rx(this: *@This(), ep_out: Endpoint.Num, len: usize) void { + _ = this; + return config.device.usb_start_rx(ep_out, len); + } + + pub fn endpoint_open(this: *@This(), ep_desc: []const u8) void { + _ = this; const ep_addr = BosConfig.get_data_u8(ep_desc, 2); const ep: Endpoint = .from_address(ep_addr); const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); - f.endpoint_open(ep, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk, ep_max_packet_size) catch unreachable; + config.device.endpoint_open(ep, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk, ep_max_packet_size) catch unreachable; } - fn get_driver(drv_idx: u8) ?*types.UsbClassDriver { - if (drv_idx == drvid_invalid) { - return null; - } - return &usb_config.?.drivers[drv_idx]; + fn get_driver(this: *@This(), drv_idx: u8) ?*const DriverInterface { + if (drv_idx == drvid_invalid) + return null + else + return &this.drivers.?[drv_idx]; } - fn get_setup_packet() types.SetupPacket { - const setup = f.get_setup_packet(); - S.setup_packet = setup; - S.driver = null; + fn get_setup_packet(this: *@This()) types.SetupPacket { + const setup = config.device.get_setup_packet(); + this.setup_packet = setup; + this.driver = null; return setup; } - fn configuration_reset() void { - @memset(&itf_to_drv, drvid_invalid); - @memset(&ep_to_drv, .{ drvid_invalid, drvid_invalid }); + fn configuration_reset(this: *@This()) void { + @memset(&this.itf_to_drv, drvid_invalid); + @memset(&this.ep_to_drv, .{ drvid_invalid, drvid_invalid }); } /// Usb task function meant to be executed in regular intervals after /// initializing the device. /// /// This function will return an error if the device hasn't been initialized. - pub fn task(debug: bool) !void { - if (usb_config == null) return error.UninitializedDevice; + pub fn task(this: *@This(), debug: bool) !void { + if (this.drivers == null) return error.UninitializedDevice; // Device Specific Request const DeviceRequestProcessor = struct { - fn process_setup_request(setup: *const types.SetupPacket, debug_mode: bool) !void { + fn process_setup_request(this2: *Controller, setup: *const types.SetupPacket, debug_mode: bool) !void { switch (setup.request_type.type) { .Class => { //const itfIndex = setup.index & 0x00ff; @@ -184,40 +262,39 @@ pub fn Usb(comptime f: anytype) type { if (req == null) return; switch (req.?) { .SetAddress => { - S.new_address = @as(u8, @intCast(setup.value & 0xff)); - CmdEndpoint.send_cmd_ack(); - if (debug_mode) std.log.info(" SetAddress: {}", .{S.new_address.?}); + this2.new_address = @as(u8, @intCast(setup.value & 0xff)); + CmdEndpoint.send_cmd_ack(this2); + if (debug_mode) std.log.info(" SetAddress: {}", .{this2.new_address.?}); }, .SetConfiguration => { if (debug_mode) std.log.info(" SetConfiguration", .{}); - const cfg_num = setup.value; - if (S.cfg_num != cfg_num) { - if (S.cfg_num > 0) { - configuration_reset(); + if (this2.cfg_num != setup.value) { + if (this2.cfg_num > 0) { + this2.configuration_reset(); } - if (cfg_num > 0) { - try process_set_config(cfg_num - 1); + if (setup.value > 0) { + try process_set_config(this2, setup.value - 1); // TODO: call mount callback if any } else { // TODO: call umount callback if any } } - S.cfg_num = cfg_num; - S.configured = true; - CmdEndpoint.send_cmd_ack(); + this2.cfg_num = setup.value; + this2.configured = true; + CmdEndpoint.send_cmd_ack(this2); }, .GetDescriptor => { const descriptor_type = DescType.from_u8(@intCast(setup.value >> 8)); if (descriptor_type) |dt| { - try process_get_descriptor(setup, dt, debug_mode); + try process_get_descriptor(this2, setup, dt, debug_mode); } }, .SetFeature => { const feature = FeatureSelector.from_u8(@intCast(setup.value >> 8)); if (feature) |feat| { switch (feat) { - .DeviceRemoteWakeup, .EndpointHalt => CmdEndpoint.send_cmd_ack(), + .DeviceRemoteWakeup, .EndpointHalt => CmdEndpoint.send_cmd_ack(this2), // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 .TestMode => {}, } @@ -229,81 +306,21 @@ pub fn Usb(comptime f: anytype) type { } } - fn process_get_descriptor(setup: *const types.SetupPacket, descriptor_type: DescType, debug_mode: bool) !void { - switch (descriptor_type) { - .Device => { - if (debug_mode) std.log.info(" Device", .{}); - - var bw = BufferWriter{ .buffer = &S.tmp }; - try bw.write(&usb_config.?.device_descriptor.serialize()); - - CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); - }, - .Config => { - if (debug_mode) std.log.info(" Config", .{}); - - var bw = BufferWriter{ .buffer = &S.tmp }; - try bw.write(usb_config.?.config_descriptor); - - CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); - }, - .String => { - if (debug_mode) std.log.info(" String", .{}); - // String descriptor index is in bottom 8 bits of - // `value`. - const i: usize = @intCast(setup.value & 0xff); - const bytes = StringBlk: { - if (i == 0) { - // Special index 0 requests the language - // descriptor. - break :StringBlk usb_config.?.lang_descriptor; - } else { - // Otherwise, set up one of our strings. - const s: []const u8 = @ptrCast(usb_config.?.descriptor_strings[i - 1]); - const len = 2 + s.len; - - var wb = BufferWriter{ .buffer = &S.tmp }; - try wb.write_int(u8, @intCast(len)); - try wb.write_int(u8, 0x03); - try wb.write(s); - - break :StringBlk wb.get_written_slice(); - } - }; - - CmdEndpoint.send_cmd_response(bytes, setup.length); - }, - .Interface => { - if (debug_mode) std.log.info(" Interface", .{}); - }, - .Endpoint => { - if (debug_mode) std.log.info(" Endpoint", .{}); - }, - .DeviceQualifier => { - if (debug_mode) std.log.info(" DeviceQualifier", .{}); - // We will just copy parts of the DeviceDescriptor because - // the DeviceQualifierDescriptor can be seen as a subset. - const dqd = types.DeviceQualifierDescriptor{ - .bcd_usb = usb_config.?.device_descriptor.bcd_usb, - .device_class = usb_config.?.device_descriptor.device_class, - .device_subclass = usb_config.?.device_descriptor.device_subclass, - .device_protocol = usb_config.?.device_descriptor.device_protocol, - .max_packet_size0 = usb_config.?.device_descriptor.max_packet_size0, - .num_configurations = usb_config.?.device_descriptor.num_configurations, - }; - - var bw = BufferWriter{ .buffer = &S.tmp }; - try bw.write(&dqd.serialize()); - - CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length); - }, - else => {}, - } + fn process_get_descriptor(this2: *Controller, setup: *const types.SetupPacket, descriptor_type: DescType, debug_mode: bool) !void { + _ = debug_mode; + const data = switch (descriptor_type) { + .Device => config.descriptors.device, + .Config => config.descriptors.config, + .String => config.descriptors.string[setup.value & 0xff], + .DeviceQualifier => config.descriptors.device_qualifier, + else => return, + }; + CmdEndpoint.send_cmd_response(this2, data, setup.length); } - fn process_set_config(_: u16) !void { + fn process_set_config(this2: *Controller, _: u16) !void { // TODO: we support just one config for now so ignore config index - const bos_cfg = usb_config.?.config_descriptor; + const bos_cfg = config.descriptors.config; var curr_bos_cfg = bos_cfg; var curr_drv_idx: u8 = 0; @@ -329,15 +346,15 @@ pub fn Usb(comptime f: anytype) type { } const desc_itf = BosConfig.get_desc_as(types.InterfaceDescriptor, curr_bos_cfg); - var driver = usb_config.?.drivers[curr_drv_idx]; - const drv_cfg_len = try driver.open(curr_bos_cfg); + var drv = this2.drivers.?[curr_drv_idx]; + const drv_cfg_len = try drv.open(this2, curr_bos_cfg); for (0..assoc_itf_count) |itf_offset| { const itf_num = desc_itf.interface_number + itf_offset; - itf_to_drv[itf_num] = curr_drv_idx; + this2.itf_to_drv[itf_num] = curr_drv_idx; } - bind_endpoints_to_driver(curr_bos_cfg[0..drv_cfg_len], curr_drv_idx); + bind_endpoints_to_driver(this2, curr_bos_cfg[0..drv_cfg_len], curr_drv_idx); curr_bos_cfg = curr_bos_cfg[drv_cfg_len..]; // TODO: TMP solution - just 1 driver so quit while loop @@ -345,7 +362,7 @@ pub fn Usb(comptime f: anytype) type { } } - fn bind_endpoints_to_driver(drv_bos_cfg: []const u8, drv_idx: u8) void { + fn bind_endpoints_to_driver(this2: *Controller, drv_bos_cfg: []const u8, drv_idx: u8) void { var curr_bos_cfg = drv_bos_cfg; while (curr_bos_cfg.len > 0) : ({ curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); @@ -355,7 +372,7 @@ pub fn Usb(comptime f: anytype) type { .Out => 0, .In => 1, }; - ep_to_drv[desc_ep.endpoint.num.to_int()][dir_as_number] = drv_idx; + this2.ep_to_drv[desc_ep.endpoint.num.to_int()][dir_as_number] = drv_idx; } } } @@ -363,13 +380,13 @@ pub fn Usb(comptime f: anytype) type { // Class/Interface Specific Request const InterfaceRequestProcessor = struct { - fn process_setup_request(setup: *const types.SetupPacket) !void { + fn process_setup_request(this2: *Controller, setup: *const types.SetupPacket) !void { const itf: u8 = @intCast(setup.index & 0xFF); - var driver = get_driver(itf_to_drv[itf]); - if (driver == null) return; - S.driver = driver; + const drv = this2.get_driver(this2.itf_to_drv[itf]); + if (drv == null) return; - if (driver.?.class_control(.Setup, setup) == false) { + this2.driver = drv; + if (this2.driver.?.class_control(this2, .Setup, setup) == false) { // TODO } } @@ -381,23 +398,23 @@ pub fn Usb(comptime f: anytype) type { }; // Check which interrupt flags are set. - const ints = f.get_interrupts(); + const ints = config.device.get_interrupts(); // Setup request received? if (ints.SetupReq) { if (debug) std.log.info("setup req", .{}); - const setup = get_setup_packet(); + const setup = this.get_setup_packet(); // Reset PID to 1 for EP0 IN. Every DATA packet we send in response // to an IN on EP0 needs to use PID DATA1, and this line will ensure // that. - // TODO - maybe it can be moved to f.get_setup_packet? - f.reset_ep0(); + // TODO - maybe it can be moved to config.device.get_setup_packet? + config.device.reset_ep0(); switch (setup.request_type.recipient) { - .Device => try DeviceRequestProcessor.process_setup_request(&setup, debug), - .Interface => try InterfaceRequestProcessor.process_setup_request(&setup), + .Device => try DeviceRequestProcessor.process_setup_request(this, &setup, debug), + .Interface => try InterfaceRequestProcessor.process_setup_request(this, &setup), .Endpoint => try EndpointRequestProcessor.process_setup_request(&setup), else => {}, } @@ -406,7 +423,7 @@ pub fn Usb(comptime f: anytype) type { // Events on one or more buffers? (In practice, always one.) if (ints.BuffStatus) { if (debug) std.log.info("buff status", .{}); - var iter: f.UnhandledEndpointIterator = .init(); + var iter: config.device.UnhandledEndpointIterator = .init(); while (iter.next()) |result| switch (result) { .In => |in| { @@ -418,25 +435,23 @@ pub fn Usb(comptime f: anytype) type { if (in.ep_num == .ep0) { if (debug) std.log.info(" EP0_IN_ADDR", .{}); - const buffer_reader = &S.buffer_reader; - // We use this opportunity to finish the delayed // SetAddress request, if there is one: - if (S.new_address) |addr| { + if (this.new_address) |addr| { // Change our address: - f.set_address(@intCast(addr)); + config.device.set_address(@intCast(addr)); } - if (in.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) { - _ = buffer_reader.try_advance(in.buffer.len); - const next_data_chunk = buffer_reader.try_peek(64); + if (in.buffer.len > 0 and this.now_sending.len > 0) { + this.now_sending = this.now_sending[in.buffer.len..]; + const next_data_chunk = this.now_sending[0..@min(this.now_sending.len, 64)]; if (next_data_chunk.len > 0) { - _ = f.usb_start_tx(.ep0, &.{next_data_chunk}); + _ = config.device.usb_start_tx(.ep0, &.{next_data_chunk}); } else { - f.usb_start_rx(.ep0, 0); + config.device.usb_start_rx(.ep0, 0); - if (S.driver) |driver| { - _ = driver.class_control(.Ack, &S.setup_packet); + if (this.driver) |drv| { + _ = drv.class_control(this, .Ack, &this.setup_packet); } } } else { @@ -445,20 +460,20 @@ pub fn Usb(comptime f: anytype) type { // status phase where the host sends us (via EP0 // OUT) a zero-byte DATA packet, so, set that // up: - f.usb_start_rx(.ep0, 0); + config.device.usb_start_rx(.ep0, 0); - if (S.driver) |driver| - _ = driver.class_control(.Ack, &S.setup_packet); + if (this.driver) |drv| + _ = drv.class_control(this, .Ack, &this.setup_packet); } - } else if (get_driver(ep_to_drv[in.ep_num.to_int()][1])) |driver| - driver.send(in.ep_num, in.buffer); + } else if (this.get_driver(this.ep_to_drv[in.ep_num.to_int()][1])) |drv| + drv.send(this, in.ep_num, in.buffer); }, .Out => |out| { if (debug) std.log.info(" data: {any}", .{out.buffer}); - if (get_driver(ep_to_drv[out.ep_num.to_int()][0])) |driver| - driver.receive(out.ep_num, out.buffer); + if (this.get_driver(this.ep_to_drv[out.ep_num.to_int()][0])) |drv| + drv.receive(this, out.ep_num, out.buffer); - f.endpoint_reset_rx(out.ep_num); + config.device.endpoint_reset_rx(out.ep_num); }, }; } // <-- END of buf status handling @@ -467,22 +482,21 @@ pub fn Usb(comptime f: anytype) type { if (ints.BusReset) { if (debug) std.log.info("bus reset", .{}); - configuration_reset(); + this.configuration_reset(); // Reset the device - f.bus_reset(); + config.device.bus_reset(); // Reset our state. - S.new_address = null; - S.configured = false; - S.started = false; - S.buffer_reader = BufferReader{ .buffer = &.{} }; + this.new_address = null; + this.configured = false; + this.started = false; + this.now_sending = &.{}; } // If we have been configured but haven't reached this point yet, set up // our custom EP OUT's to receive whatever data the host wants to send. - if (S.configured and !S.started) { - S.started = true; - } + if (this.configured and !this.started) + this.started = true; } }; } @@ -491,14 +505,6 @@ pub fn Usb(comptime f: anytype) type { // Driver support stuctures // +++++++++++++++++++++++++++++++++++++++++++++++++ -pub const DeviceConfiguration = struct { - device_descriptor: *const types.DeviceDescriptor, - config_descriptor: []const u8, - lang_descriptor: []const u8, - descriptor_strings: []const []const u16, - drivers: []types.UsbClassDriver, -}; - /// USB interrupt status /// /// __Note__: Available interrupts may change from device to device. @@ -522,93 +528,6 @@ pub const EndpointAndBuffer = union(Dir) { In: struct { ep_num: Endpoint.Num, buffer: []u8 }, }; -const BufferWriter = struct { - buffer: []u8, - pos: usize = 0, - endian: std.builtin.Endian = builtin.cpu.arch.endian(), - - pub const Error = error{EndOfBuffer}; - - /// Moves forward write cursor by the provided number of bytes. - pub fn advance(self: *@This(), bytes: usize) Error!void { - try self.bound_check(bytes); - self.advance_unsafe(bytes); - } - - /// Writes data provided as a slice to the buffer and moves write cursor forward by data size. - pub fn write(self: *@This(), data: []const u8) Error!void { - try self.bound_check(data.len); - defer self.advance_unsafe(data.len); - @memcpy(self.buffer[self.pos .. self.pos + data.len], data); - } - - /// Writes an int with respect to the buffer's endianness and moves write cursor forward by int size. - pub fn write_int(self: *@This(), comptime T: type, value: T) Error!void { - const size = @divExact(@typeInfo(T).int.bits, 8); - try self.bound_check(size); - defer self.advance_unsafe(size); - std.mem.writeInt(T, self.buffer[self.pos..][0..size], value, self.endian); - } - - /// Writes an int with respect to the buffer's endianness but skip bound check. - /// Useful in cases where the bound can be checked once for batch of ints. - pub fn write_int_unsafe(self: *@This(), comptime T: type, value: T) void { - const size = @divExact(@typeInfo(T).int.bits, 8); - defer self.advance_unsafe(size); - std.mem.writeInt(T, self.buffer[self.pos..][0..size], value, self.endian); - } - - /// Returns a slice of the internal buffer containing the written data. - pub fn get_written_slice(self: *const @This()) []const u8 { - return self.buffer[0..self.pos]; - } - - /// Performs a buffer bound check against the current cursor position and the provided number of bytes to check forward. - pub fn bound_check(self: *const @This(), bytes: usize) Error!void { - if (self.pos + bytes > self.buffer.len) return error.EndOfBuffer; - } - - fn advance_unsafe(self: *@This(), bytes: usize) void { - self.pos += bytes; - } -}; - -const BufferReader = struct { - buffer: []const u8, - pos: usize = 0, - endian: std.builtin.Endian = builtin.cpu.arch.endian(), - - /// Attempts to move read cursor forward by the specified number of bytes. - /// Returns the actual number of bytes advanced, up to the specified number. - pub fn try_advance(self: *@This(), bytes: usize) usize { - const size = @min(bytes, self.buffer.len - self.pos); - self.advance_unsafe(size); - return size; - } - - /// Attempts to read the given amount of bytes (or less if close to buffer end) and advances the read cursor. - pub fn try_read(self: *@This(), bytes: usize) []const u8 { - const size = @min(bytes, self.buffer.len - self.pos); - defer self.advance_unsafe(size); - return self.buffer[self.pos .. self.pos + size]; - } - - /// Attempts to read the given amount of bytes (or less if close to buffer end) without advancing the read cursor. - pub fn try_peek(self: *@This(), bytes: usize) []const u8 { - const size = @min(bytes, self.buffer.len - self.pos); - return self.buffer[self.pos .. self.pos + size]; - } - - /// Returns the number of bytes remaining from the current read cursor position to the end of the underlying buffer. - pub fn get_remaining_bytes_count(self: *const @This()) usize { - return self.buffer.len - self.pos; - } - - fn advance_unsafe(self: *@This(), bytes: usize) void { - self.pos += bytes; - } -}; - test "tests" { _ = hid; _ = utils; diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 8cd42a86a..2666c4fc1 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -125,11 +125,10 @@ pub const CdcLineCoding = extern struct { data_bits: u8, }; -pub fn CdcClassDriver(comptime usb: anytype) type { - const fifo = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = usb.max_packet_size }); +pub fn CdcClassDriver(comptime Controller: type) type { + const fifo = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = Controller.max_packet_size }); return struct { - device: ?types.UsbDevice = null, ep_in_notif: types.Endpoint.Num = .ep0, ep_in: types.Endpoint.Num = .ep0, ep_out: types.Endpoint.Num = .ep0, @@ -139,57 +138,51 @@ pub fn CdcClassDriver(comptime usb: anytype) type { rx: fifo = fifo.init(), tx: fifo = fifo.init(), - epin_buf: [usb.max_packet_size]u8 = undefined, + epin_buf: [Controller.max_packet_size]u8 = undefined, pub fn available(self: *@This()) usize { return self.rx.readableLength(); } - pub fn read(self: *@This(), dst: []u8) usize { + pub fn read(self: *@This(), controller: *Controller, dst: []u8) usize { const read_count = self.rx.read(dst); - self.prep_out_transaction(); + self.prep_out_transaction(controller); return read_count; } - pub fn write(self: *@This(), data: []const u8) []const u8 { + pub fn write(self: *@This(), controller: *Controller, data: []const u8) []const u8 { const write_count = @min(self.tx.writableLength(), data.len); - if (write_count > 0) { - self.tx.writeAssumeCapacity(data[0..write_count]); - } else { + if (write_count == 0) return data[0..]; - } - if (self.tx.writableLength() == 0) { - _ = self.write_flush(); - } + self.tx.writeAssumeCapacity(data[0..write_count]); + + if (self.tx.writableLength() == 0) + _ = self.write_flush(controller); return data[write_count..]; } - pub fn write_flush(self: *@This()) usize { - if (self.device.?.ready() == false) { - return 0; - } - if (self.tx.readableLength() == 0) { + pub fn write_flush(self: *@This(), controller: *Controller) usize { + if (!controller.ready() or self.tx.readableLength() == 0) return 0; - } const len = self.tx.read(&self.epin_buf); - const tx_len = self.device.?.endpoint_tx(self.ep_in, &.{self.epin_buf[0..len]}); + const tx_len = controller.endpoint_tx(self.ep_in, &.{self.epin_buf[0..len]}); if (tx_len != len) @panic("bruh"); return len; } - fn prep_out_transaction(self: *@This()) void { - if (self.rx.writableLength() >= usb.max_packet_size) { + fn prep_out_transaction(self: *@This(), controller: *Controller) void { + if (self.rx.writableLength() >= Controller.max_packet_size) { // Let endpoint know that we are ready for next packet - self.device.?.endpoint_rx(self.ep_out, usb.max_packet_size); + controller.endpoint_rx(self.ep_out, Controller.max_packet_size); } } - fn init(ptr: *anyopaque, device: types.UsbDevice) void { + fn init(ptr: *anyopaque, controller: *Controller) void { + _ = controller; var self: *@This() = @ptrCast(@alignCast(ptr)); - self.device = device; self.line_coding = .{ .bit_rate = 115200, .stop_bits = 0, @@ -198,7 +191,7 @@ pub fn CdcClassDriver(comptime usb: anytype) type { }; } - fn open(ptr: *anyopaque, cfg: []const u8) !usize { + fn open(ptr: *anyopaque, controller: *Controller, cfg: []const u8) !usize { var self: *@This() = @ptrCast(@alignCast(ptr)); var curr_cfg = cfg; @@ -230,7 +223,7 @@ pub fn CdcClassDriver(comptime usb: anytype) type { .In => self.ep_in = desc_ep.endpoint.num, .Out => self.ep_out = desc_ep.endpoint.num, } - self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); + controller.endpoint_open(curr_cfg[0..desc_ep.length]); curr_cfg = bos.get_desc_next(curr_cfg); } } @@ -240,69 +233,43 @@ pub fn CdcClassDriver(comptime usb: anytype) type { return cfg.len - curr_cfg.len; } - fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + fn class_control(ptr: *anyopaque, controller: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool { var self: *@This() = @ptrCast(@alignCast(ptr)); if (CdcManagementRequestType.from_u8(setup.request)) |request| { - switch (request) { - .SetLineCoding => { - switch (stage) { - .Setup => { - // HACK, we should handle data phase somehow to read sent line_coding - self.device.?.control_ack(setup); - }, - else => {}, - } - }, - .GetLineCoding => { - if (stage == .Setup) { - self.device.?.control_transfer(setup, std.mem.asBytes(&self.line_coding)); - } - }, - .SetControlLineState => { - switch (stage) { - .Setup => { - self.device.?.control_ack(setup); - }, - else => {}, - } - }, - .SendBreak => { - switch (stage) { - .Setup => { - self.device.?.control_ack(setup); - }, - else => {}, - } - }, - } + if (stage == .Setup) switch (request) { + .SetLineCoding => controller.control_ack(setup), + .GetLineCoding => controller.control_transfer(setup, std.mem.asBytes(&self.line_coding)), + .SetControlLineState => controller.control_ack(setup), + .SendBreak => controller.control_ack(setup), + }; } return true; } - fn send(ptr: *anyopaque, ep_in: types.Endpoint.Num, data: []const u8) void { + fn send(ptr: *anyopaque, controller: *Controller, ep_in: types.Endpoint.Num, data: []const u8) void { var self: *@This() = @ptrCast(@alignCast(ptr)); - if (ep_in == self.ep_in and self.write_flush() == 0) { + if (ep_in == self.ep_in and self.write_flush(controller) == 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.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { - _ = self.device.?.endpoint_tx(self.ep_in, &.{&.{}}); + if (self.tx.readableLength() == 0 and data.len > 0 and data.len == Controller.max_packet_size) { + _ = controller.endpoint_tx(self.ep_in, &.{&.{}}); } } } - fn receive(ptr: *anyopaque, ep_out: types.Endpoint.Num, data: []const u8) void { + fn receive(ptr: *anyopaque, controller: *Controller, ep_out: types.Endpoint.Num, data: []const u8) void { var self: *@This() = @ptrCast(@alignCast(ptr)); if (ep_out == self.ep_out) { self.rx.write(data) catch {}; - self.prep_out_transaction(); + self.prep_out_transaction(controller); } } - pub fn driver(self: *@This()) types.UsbClassDriver { + pub fn driver(self: *@This()) Controller.DriverInterface { return .{ .ptr = self, .fn_init = init, diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 1ed619e26..de7abf68f 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -522,141 +522,139 @@ pub const ReportDescriptorKeyboard = hid_usage_page(1, UsageTable.desktop) // // End ++ hid_collection_end(); -pub const HidClassDriver = struct { - device: ?types.UsbDevice = null, - ep_in: types.Endpoint.Num = .ep0, - ep_out: types.Endpoint.Num = .ep0, - hid_descriptor: []const u8 = &.{}, - report_descriptor: []const u8, - - fn init(ptr: *anyopaque, device: types.UsbDevice) void { - var self: *HidClassDriver = @ptrCast(@alignCast(ptr)); - self.device = device; - } - - fn open(ptr: *anyopaque, cfg: []const u8) !usize { - var self: *HidClassDriver = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; - - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return types.DriverErrors.UnsupportedInterfaceClassType; - } else { - return types.DriverErrors.ExpectedInterfaceDescriptor; - } +pub fn HidClassDriver(comptime Controller: type) type { + return struct { + ep_in: types.Endpoint.Num = .ep0, + ep_out: types.Endpoint.Num = .ep0, + hid_descriptor: []const u8 = &.{}, + report_descriptor: []const u8, + + fn init(_: *anyopaque, _: *Controller) void {} + + fn open(ptr: *anyopaque, controller: *Controller, cfg: []const u8) !usize { + var self: *@This() = @ptrCast(@alignCast(ptr)); + var curr_cfg = cfg; + + if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return types.DriverErrors.UnsupportedInterfaceClassType; + } else { + return types.DriverErrors.ExpectedInterfaceDescriptor; + } - curr_cfg = bos.get_desc_next(curr_cfg); - if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| { - self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)]; curr_cfg = bos.get_desc_next(curr_cfg); - } else { - return types.DriverErrors.UnexpectedDescriptor; - } + if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| { + self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)]; + curr_cfg = bos.get_desc_next(curr_cfg); + } else { + return types.DriverErrors.UnexpectedDescriptor; + } - for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (desc_ep.endpoint.dir) { - .In => self.ep_in = desc_ep.endpoint.num, - .Out => self.ep_out = desc_ep.endpoint.num, + for (0..2) |_| { + if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + switch (desc_ep.endpoint.dir) { + .In => self.ep_in = desc_ep.endpoint.num, + .Out => self.ep_out = desc_ep.endpoint.num, + } + controller.endpoint_open(curr_cfg[0..desc_ep.length]); + curr_cfg = bos.get_desc_next(curr_cfg); } - self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); } + + return cfg.len - curr_cfg.len; } - return cfg.len - curr_cfg.len; - } + fn class_control(ptr: *anyopaque, controller: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + const self: *@This() = @ptrCast(@alignCast(ptr)); - fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - var self: *HidClassDriver = @ptrCast(@alignCast(ptr)); + switch (setup.request_type.type) { + .Standard => { + if (stage == .Setup) { + const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff)); + const request_code = types.SetupRequest.from_u8(setup.request); - switch (setup.request_type.type) { - .Standard => { - if (stage == .Setup) { - const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff)); - const request_code = types.SetupRequest.from_u8(setup.request); + if (hid_desc_type == null or request_code == null) { + return false; + } - if (hid_desc_type == null or request_code == null) { - return false; + if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) { + controller.control_transfer(setup, self.hid_descriptor); + } else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) { + controller.control_transfer(setup, self.report_descriptor); + } else { + return false; + } } - - if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) { - self.device.?.control_transfer(setup, self.hid_descriptor); - } else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) { - self.device.?.control_transfer(setup, self.report_descriptor); - } else { - return false; + }, + .Class => { + const hid_request_type = HidRequestType.from_u8(setup.request); + if (hid_request_type == null) return false; + + switch (hid_request_type.?) { + .SetIdle => { + if (stage == .Setup) { + // TODO: The host is attempting to limit bandwidth by requesting that + // the device only return report data when its values actually change, + // or when the specified duration elapses. In practice, the device can + // still send reports as often as it wants, but for completeness this + // should be implemented eventually. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(setup); + } + }, + .SetProtocol => { + if (stage == .Setup) { + // TODO: The device should switch the format of its reports from the + // boot keyboard/mouse protocol to the format described in its report descriptor, + // or vice versa. + // + // For now, this request is ACKed without doing anything; in practice, + // the OS will reuqest the report protocol anyway, so usually only one format is needed. + // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), + // our device might not work in a limited BIOS environment. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(setup); + } + }, + .SetReport => { + if (stage == .Setup) { + // TODO: This request sends a feature or output report to the device, + // e.g. turning on the caps lock LED. This must be handled in an + // application-specific way, so notify the application code of the event. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(setup); + } + }, + else => { + return false; + }, } - } - }, - .Class => { - const hid_request_type = HidRequestType.from_u8(setup.request); - if (hid_request_type == null) return false; - - switch (hid_request_type.?) { - .SetIdle => { - if (stage == .Setup) { - // TODO: The host is attempting to limit bandwidth by requesting that - // the device only return report data when its values actually change, - // or when the specified duration elapses. In practice, the device can - // still send reports as often as it wants, but for completeness this - // should be implemented eventually. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - self.device.?.control_ack(setup); - } - }, - .SetProtocol => { - if (stage == .Setup) { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, - // or vice versa. - // - // For now, this request is ACKed without doing anything; in practice, - // the OS will reuqest the report protocol anyway, so usually only one format is needed. - // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), - // our device might not work in a limited BIOS environment. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - self.device.?.control_ack(setup); - } - }, - .SetReport => { - if (stage == .Setup) { - // TODO: This request sends a feature or output report to the device, - // e.g. turning on the caps lock LED. This must be handled in an - // application-specific way, so notify the application code of the event. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - self.device.?.control_ack(setup); - } - }, - else => { - return false; - }, - } - }, - else => { - return false; - }, - } + }, + else => { + return false; + }, + } - return true; - } + return true; + } - fn send(_: *anyopaque, _: types.Endpoint.Num, _: []const u8) void {} - fn receive(_: *anyopaque, _: types.Endpoint.Num, _: []const u8) void {} - - pub fn driver(self: *@This()) types.UsbClassDriver { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - .fn_send = send, - .fn_receive = receive, - }; - } -}; + fn send(_: *anyopaque, _: *Controller, _: types.Endpoint.Num, _: []const u8) void {} + fn receive(_: *anyopaque, _: *Controller, _: types.Endpoint.Num, _: []const u8) void {} + + pub fn driver(self: *@This()) Controller.DriverInterface { + return .{ + .ptr = self, + .fn_init = init, + .fn_open = open, + .fn_class_control = class_control, + .fn_send = send, + .fn_receive = receive, + }; + } + }; +} test "create hid report item" { const r = hid_report_item( diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 379b2f93c..2d075af80 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -21,11 +21,50 @@ pub const DescType = enum(u8) { }; pub const ClassCode = enum(u8) { - Unspecified = 0, - Audio = 1, - Cdc = 2, - Hid = 3, - CdcData = 10, + Unspecified = 0x00, + Audio = 0x01, + Cdc = 0x02, + Hid = 0x03, + Physical = 0x05, + Image = 0x06, + Printer = 0x07, + MassStorage = 0x08, + Hub = 0x09, + CdcData = 0x0A, + SmartCard = 0x0B, + ContentSecurity = 0x0D, + Video = 0x0E, + PersonalHealthcare = 0x0F, + AudioVideoDevice = 0x10, + BillboardDevice = 0x11, + USBTypeCBridge = 0x12, + USBBulkDisplayProtocol = 0x13, + MCTPoverUSBProtocolEndpoint = 0x14, + I3C = 0x3C, + DiagnosticDevice = 0xDC, + WirelessController = 0xE0, + Miscellaneous = 0xEF, + ApplicationSpecific = 0xFE, + VendorSpecific = 0xFF, +}; + +/// Version of the device descriptor / USB protocol, in binary-coded +/// decimal. This is typically `0x01_10` for USB 1.1. +pub const BcdUsb = extern struct { + lo: u8, + hi: u8, + + pub const v1_1: @This() = .{ .hi = 1, .lo = 1 }; + pub const v2_0: @This() = .{ .hi = 2, .lo = 0 }; +}; + +pub const DeviceTriple = extern struct { + /// Class of device, giving a broad functional area. + class: ClassCode, + /// Subclass of device, refining the class. + subclass: u8, + /// Protocol within the subclass. + protocol: u8, }; /// Types of transfer that can be indicated by the `attributes` field on `EndpointDescriptor`. @@ -354,20 +393,11 @@ pub const ConfigurationDescriptor = extern struct { /// Describes a device. This is the most broad description in USB and is /// typically the first thing the host asks for. pub const DeviceDescriptor = extern struct { - pub const const_descriptor_type = DescType.Device; - length: u8 = 18, /// Type of this descriptor, must be `Device`. - descriptor_type: DescType = const_descriptor_type, - /// Version of the device descriptor / USB protocol, in binary-coded - /// decimal. This is typically `0x01_10` for USB 1.1. - bcd_usb: u16 align(1), - /// Class of device, giving a broad functional area. - device_class: u8, - /// Subclass of device, refining the class. - device_subclass: u8, - /// Protocol within the subclass. - device_protocol: u8, + descriptor_type: DescType = .Device, + bcd_usb: BcdUsb, + device_triple: DeviceTriple, /// Maximum unit of data this device can move. max_packet_size0: u8, /// ID of product vendor. @@ -389,11 +419,11 @@ pub const DeviceDescriptor = extern struct { var out: [18]u8 = undefined; out[0] = out.len; out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.bcd_usb & 0xff); - out[3] = @intCast((self.bcd_usb >> 8) & 0xff); - out[4] = self.device_class; - out[5] = self.device_subclass; - out[6] = self.device_protocol; + out[2] = self.bcd_usb.lo; + out[3] = self.bcd_usb.hi; + out[4] = @intFromEnum(self.device_triple.class); + out[5] = self.device_triple.subclass; + out[6] = self.device_triple.protocol; out[7] = self.max_packet_size0; out[8] = @intCast(self.vendor & 0xff); out[9] = @intCast((self.vendor >> 8) & 0xff); @@ -417,15 +447,8 @@ pub const DeviceQualifierDescriptor = extern struct { length: u8 = 10, /// Type of this descriptor, must be `Device`. descriptor_type: DescType = const_descriptor_type, - /// Version of the device descriptor / USB protocol, in binary-coded - /// decimal. This is typically `0x01_10` for USB 1.1. - bcd_usb: u16 align(1), - /// Class of device, giving a broad functional area. - device_class: u8, - /// Subclass of device, refining the class. - device_subclass: u8, - /// Protocol within the subclass. - device_protocol: u8, + bcd_usb: BcdUsb, + device_triple: DeviceTriple, /// Maximum unit of data this device can move. max_packet_size0: u8, /// Number of configurations supported by this device. @@ -437,11 +460,11 @@ pub const DeviceQualifierDescriptor = extern struct { var out: [10]u8 = undefined; out[0] = out.len; out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.bcd_usb & 0xff); - out[3] = @intCast((self.bcd_usb >> 8) & 0xff); - out[4] = self.device_class; - out[5] = self.device_subclass; - out[6] = self.device_protocol; + out[2] = self.bcd_usb.lo; + out[3] = self.bcd_usb.hi; + out[4] = @intFromEnum(self.device_triple.class); + out[5] = self.device_triple.subclass; + out[6] = self.device_triple.protocol; out[7] = self.max_packet_size0; out[8] = self.num_configurations; out[9] = self.reserved; @@ -455,67 +478,3 @@ pub const DriverErrors = error{ UnsupportedInterfaceSubClassType, UnexpectedDescriptor, }; - -pub const UsbDevice = struct { - fn_ready: *const fn () bool, - fn_control_transfer: *const fn (setup: *const SetupPacket, data: []const u8) void, - fn_control_ack: *const fn (setup: *const SetupPacket) void, - fn_endpoint_open: *const fn (ep_desc: []const u8) void, - fn_endpoint_tx: *const fn (ep_in: Endpoint.Num, data: []const []const u8) usize, - fn_endpoint_rx: *const fn (ep_out: Endpoint.Num, len: usize) void, - - pub fn ready(self: *@This()) bool { - return self.fn_ready(); - } - - pub fn control_transfer(self: *@This(), setup: *const SetupPacket, data: []const u8) void { - return self.fn_control_transfer(setup, data); - } - - pub fn control_ack(self: *@This(), setup: *const SetupPacket) void { - return self.fn_control_ack(setup); - } - - pub fn endpoint_open(self: *@This(), ep_desc: []const u8) void { - return self.fn_endpoint_open(ep_desc); - } - - pub fn endpoint_tx(self: *@This(), ep_in: Endpoint.Num, data: []const []const u8) usize { - return self.fn_endpoint_tx(ep_in, data); - } - - pub fn endpoint_rx(self: *@This(), ep_out: Endpoint.Num, len: usize) void { - return self.fn_endpoint_rx(ep_out, len); - } -}; - -/// USB Class driver interface -pub const UsbClassDriver = struct { - ptr: *anyopaque, - fn_init: *const fn (ptr: *anyopaque, device: UsbDevice) void, - fn_open: *const fn (ptr: *anyopaque, cfg: []const u8) anyerror!usize, - fn_class_control: *const fn (ptr: *anyopaque, stage: ControlStage, setup: *const SetupPacket) bool, - fn_send: *const fn (ptr: *anyopaque, ep_in: Endpoint.Num, data: []const u8) void, - fn_receive: *const fn (ptr: *anyopaque, ep_out: Endpoint.Num, data: []const u8) void, - - pub fn init(self: *@This(), device: UsbDevice) void { - return self.fn_init(self.ptr, device); - } - - /// Driver open (set config) operation. Must return length of consumed driver config data. - pub fn open(self: *@This(), cfg: []const u8) !usize { - return self.fn_open(self.ptr, cfg); - } - - pub fn class_control(self: *@This(), stage: ControlStage, setup: *const SetupPacket) bool { - return self.fn_class_control(self.ptr, stage, setup); - } - - pub fn send(self: *@This(), ep_in: Endpoint.Num, data: []const u8) void { - return self.fn_send(self.ptr, ep_in, data); - } - - pub fn receive(self: *@This(), ep_out: Endpoint.Num, len: []const u8) void { - return self.fn_receive(self.ptr, ep_out, len); - } -}; diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 0c3d83617..6a904c4f4 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -12,7 +12,7 @@ pub fn build(b: *std.Build) void { const mz_dep = b.dependency("microzig", .{}); const mb = MicroBuild.init(b, mz_dep) orelse return; - // const raspberrypi = mb.ports.rp2xxx.boards.raspberrypi; + const raspberrypi = mb.ports.rp2xxx.boards.raspberrypi; const specific_examples: []const Example = &.{ // RaspberryPi Boards: @@ -20,7 +20,7 @@ pub fn build(b: *std.Build) void { // .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, // .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, // .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, // .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, // .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, // .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index f9d4a252a..7c4663cb8 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -12,8 +12,6 @@ const uart = rp2xxx.uart.instance.num(0); const baud_rate = 115200; const uart_tx_pin = gpio.num(0); -const usb_dev = rp2xxx.usb.Usb(.{}); - const usb_templates = usb.templates.DescriptorsConfigTemplates; const usb_packet_size = 64; const usb_config_len = usb_templates.config_descriptor_len + usb_templates.hid_in_out_descriptor_len; @@ -21,40 +19,41 @@ const usb_config_descriptor = usb_templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++ usb_templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, .ep1, .ep1, usb_packet_size, 0); -var driver_hid = usb.hid.HidClassDriver{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; -var drivers = [_]usb.types.UsbClassDriver{driver_hid.driver()}; +var driver_hid = usb.hid.HidClassDriver(UsbDev){ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; // This is our device configuration -pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ - .device_descriptor = &.{ - .descriptor_type = .Device, - .bcd_usb = 0x0200, - .device_class = 0, - .device_subclass = 0, - .device_protocol = 0, - .max_packet_size0 = 64, - .vendor = 0xCafe, - .product = 2, - .bcd_device = 0x0100, - // Those are indices to the descriptor strings - // Make sure to provide enough string descriptors! - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .config_descriptor = &usb_config_descriptor, - .lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409) - .descriptor_strings = blk: { - @setEvalBranchQuota(2000); - break :blk &.{ - std.unicode.utf8ToUtf16LeStringLiteral("Raspberry Pi"), - std.unicode.utf8ToUtf16LeStringLiteral("Pico Test Device"), - std.unicode.utf8ToUtf16LeStringLiteral("cafebabe"), - }; - }, - .drivers = &drivers, -}; +const UsbDev = usb.Usb(.{ + .descriptors = .create( + .{ + .descriptor_type = .Device, + .bcd_usb = .v1_1, + .device_triple = .{ + .class = .Unspecified, + .subclass = 0, + .protocol = 0, + }, + .max_packet_size0 = 64, + .vendor = 0xCafe, + .product = 2, + .bcd_device = 0x0100, + // Those are indices to the descriptor strings + // Make sure to provide enough string descriptors! + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }, + &usb_config_descriptor, + .English, + &.{ + "Raspberry Pi", + "Pico Test Device", + "cafebabe", + }, + ), + .device = rp2xxx.usb.Usb(.{}), +}); +var usb_dev: UsbDev = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -81,7 +80,7 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&DEVICE_CONFIGURATION); + usb_dev.init_device(&.{driver_hid.driver()}); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 29c838595..f78f26740 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -13,47 +13,46 @@ const baud_rate = 115200; const uart_tx_pin = gpio.num(0); const uart_rx_pin = gpio.num(1); -const usb_dev = rp2xxx.usb.Usb(.{}); - const usb_templates = usb.templates.DescriptorsConfigTemplates; const usb_config_len = usb_templates.config_descriptor_len + usb_templates.cdc_descriptor_len; const usb_config_descriptor = usb_templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ usb_templates.cdc_descriptor(0, 4, .ep1, 8, .ep2, .ep2, 64); -var driver_cdc: usb.cdc.CdcClassDriver(usb_dev) = .{}; -var drivers = [_]usb.types.UsbClassDriver{driver_cdc.driver()}; - // This is our device configuration -pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{ - .device_descriptor = &.{ - .descriptor_type = .Device, - .bcd_usb = 0x0200, - .device_class = 0xEF, - .device_subclass = 2, - .device_protocol = 1, - .max_packet_size0 = 64, - .vendor = 0x2E8A, - .product = 0x000a, - .bcd_device = 0x0100, - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 0, - .num_configurations = 1, - }, - .config_descriptor = &usb_config_descriptor, - .lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409) - .descriptor_strings = blk: { - @setEvalBranchQuota(2000); - break :blk &.{ - std.unicode.utf8ToUtf16LeStringLiteral("Raspberry Pi"), - std.unicode.utf8ToUtf16LeStringLiteral("Pico Test Device"), - std.unicode.utf8ToUtf16LeStringLiteral("someserial"), - std.unicode.utf8ToUtf16LeStringLiteral("Board CDC"), - }; - }, - .drivers = &drivers, -}; +const UsbDev = usb.Usb(.{ + .device = rp2xxx.usb.Usb(.{}), + .descriptors = .create( + .{ + .bcd_usb = .v1_1, + .device_triple = .{ + .class = .Miscellaneous, + .subclass = 2, + .protocol = 1, + }, + .max_packet_size0 = 64, + .vendor = 0x2E8A, + .product = 0x000a, + .bcd_device = 0x0100, + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 0, + .num_configurations = 1, + }, + &usb_config_descriptor, + .English, + &.{ + "Raspberry Pi", + "Pico Test Device", + "someserial", + "Board CDC", + }, + ), +}); + +var driver_cdc: usb.cdc.CdcClassDriver(UsbDev) = .{}; + +var usb_dev: UsbDev = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -71,9 +70,8 @@ pub fn main() !void { led.set_direction(.out); led.put(1); - inline for (&.{ uart_tx_pin, uart_rx_pin }) |pin| { + inline for (&.{ uart_tx_pin, uart_rx_pin }) |pin| pin.set_function(.uart); - } uart.apply(.{ .baud_rate = baud_rate, @@ -83,7 +81,7 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&DEVICE_CONFIGURATION); + usb_dev.init_device(&.{driver_cdc.driver()}); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -121,11 +119,11 @@ pub fn usb_cdc_write(comptime fmt: []const u8, args: anytype) void { var write_buff = text; while (write_buff.len > 0) { - write_buff = driver_cdc.write(write_buff); + write_buff = driver_cdc.write(&usb_dev, write_buff); usb_dev.task(false) catch unreachable; } // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them - _ = driver_cdc.write_flush(); + _ = driver_cdc.write_flush(&usb_dev); usb_dev.task(false) catch unreachable; } @@ -138,7 +136,7 @@ pub fn usb_cdc_read() []const u8 { var read_buff: []u8 = usb_rx_buff[0..]; while (true) { - const len = driver_cdc.read(read_buff); + const len = driver_cdc.read(&usb_dev, read_buff); read_buff = read_buff[len..]; total_read += len; if (len == 0) break; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 0169fc645..5823b1b91 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -1,7 +1,6 @@ //! USB device implementation //! //! Initial inspiration: cbiffle's Rust [implementation](https://github.com/cbiffle/rp2040-usb-device-in-one-file/blob/main/src/main.rs) -//! Currently progressing towards adopting the TinyUSB like API const std = @import("std"); const assert = std.debug.assert; @@ -94,9 +93,7 @@ pub const DpramAllocatorBump = struct { /// of system specific functions to Usb(). Those functions /// are used by the abstract USB impl of microzig. pub fn Usb(comptime config: UsbConfig) type { - // A set of functions required by the abstract USB impl to - // create a concrete one. - const f = struct { + return struct { comptime { if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); @@ -105,6 +102,7 @@ pub fn Usb(comptime config: UsbConfig) type { pub const cfg_max_endpoints_count = config.max_endpoints_count; pub const cfg_max_interfaces_count = config.max_interfaces_count; pub const high_speed = false; + pub const max_packet_size = 64; const HardwareEndpoint = packed struct(u7) { const ep_ctrl_all: *volatile [2 * (cfg_max_endpoints_count - 1)]EpCtrl = @@ -182,7 +180,7 @@ pub fn Usb(comptime config: UsbConfig) type { } }; - pub fn usb_init_device(_: *usb.DeviceConfiguration) void { + pub fn usb_init_device() void { if (chip == .RP2350) peri_usb.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); @@ -345,16 +343,7 @@ pub fn Usb(comptime config: UsbConfig) type { // Copy the setup packet out of its dedicated buffer at the base of // USB SRAM. The PAC models this buffer as two 32-bit registers, - // which is, like, not _wrong_ but slightly awkward since it means - // we can't just treat it as bytes. Instead, copy it out to a byte - // array. - var setup_packet: [8]u8 = @splat(0); - const spl: u32 = peri_dpram.SETUP_PACKET_LOW.raw; - const sph: u32 = peri_dpram.SETUP_PACKET_HIGH.raw; - @memcpy(setup_packet[0..4], std.mem.asBytes(&spl)); - @memcpy(setup_packet[4..8], std.mem.asBytes(&sph)); - // Reinterpret as setup packet - return std.mem.bytesToValue(usb.types.SetupPacket, &setup_packet); + return @bitCast([2]u32{ peri_dpram.SETUP_PACKET_LOW.raw, peri_dpram.SETUP_PACKET_HIGH.raw }); } /// Called on a bus reset interrupt @@ -419,6 +408,4 @@ pub fn Usb(comptime config: UsbConfig) type { } }; }; - - return usb.Usb(f); } From a875577f57a4a712bc763a4115499cfb28702fe9 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 20 Aug 2025 21:49:03 +0200 Subject: [PATCH 10/33] simplify usb controller and drivers --- core/src/core/usb.zig | 555 +++++++++--------- core/src/core/usb/cdc.zig | 307 +++++----- core/src/core/usb/hid.zig | 396 ++++++------- core/src/core/usb/templates.zig | 4 +- core/src/core/usb/types.zig | 54 +- core/src/core/usb/utils.zig | 17 +- .../rp2xxx/src/rp2040_only/usb_hid.zig | 22 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 72 +-- port/raspberrypi/rp2xxx/src/hal/usb.zig | 167 +++--- 9 files changed, 748 insertions(+), 846 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 3ad8ca3fb..3f56efe9d 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -12,6 +12,7 @@ const std = @import("std"); const builtin = @import("builtin"); +const enumFromInt = std.meta.intToEnum; /// USB primitive types pub const types = @import("usb/types.zig"); @@ -65,9 +66,110 @@ pub const Descriptors = struct { } }; +pub const ControllerInterface = struct { + const Vtable = struct { + task: *const fn (ptr: *anyopaque) void, + control_transfer: *const fn (ptr: *anyopaque, data: []const u8) void, + endpoint_open: *const fn (ptr: *anyopaque, ep_desc: []const u8) anyerror!?[]u8, + submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: Endpoint.Num, buffer_end: [*]const u8) void, + endpoint_rx: *const fn (ptr: *anyopaque, ep_out: Endpoint.Num, len: usize) void, + }; + + ptr: *anyopaque, + vtable: *const Vtable, + + pub fn task(this: @This()) void { + return this.vtable.task(this.ptr); + } + + pub fn control_transfer(this: @This(), data: []const u8) void { + if (data.len == 0) + std.log.err("data empty", .{}) + else + this.vtable.control_transfer(this.ptr, data); + } + + pub fn control_ack(this: @This()) void { + this.vtable.control_transfer(this.ptr, ""); + } + + pub fn endpoint_open(this: @This(), ep_desc: []const u8) anyerror!?[]u8 { + return this.vtable.endpoint_open(this.ptr, ep_desc); + } + + pub fn submit_tx_buffer(this: @This(), ep_in: Endpoint.Num, buffer_end: [*]const u8) void { + return this.vtable.submit_tx_buffer(this.ptr, ep_in, buffer_end); + } + + pub fn endpoint_rx(this: @This(), ep_out: Endpoint.Num, len: usize) void { + return this.vtable.endpoint_rx(this.ptr, ep_out, len); + } +}; + +/// USB Class driver interface +pub const DriverInterface = struct { + pub const Vtable = struct { + open: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, cfg: []const u8) anyerror!usize, + class_control: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool, + on_tx_ready: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, data: []u8) void, + on_data_rx: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, data: []const u8) void, + }; + + ptr: *anyopaque, + vtable: *const Vtable, + + /// Driver open (set config) operation. Must return length of consumed driver config data. + pub fn open(this: @This(), ctrl: ControllerInterface, cfg: []const u8) anyerror!usize { + return this.vtable.open(this.ptr, ctrl, cfg); + } + + pub fn class_control(this: @This(), ctrl: ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + return this.vtable.class_control(this.ptr, ctrl, stage, setup); + } + + pub fn on_tx_ready(this: @This(), ctrl: ControllerInterface, data: []u8) void { + return this.vtable.on_tx_ready(this.ptr, ctrl, data); + } + + pub fn on_data_rx(this: @This(), ctrl: ControllerInterface, len: []const u8) void { + return this.vtable.on_data_rx(this.ptr, ctrl, len); + } +}; + pub const Config = struct { - device: type, + /// TODO: Description + pub const DriverConfig = struct { + name: []const u8, + driver: type, + // endpoints_in: PerEndpointOpt([]const u8), + // endpoints_out: PerEndpointOpt([]const u8), + strings: []const struct { name: []const u8, value: []const u8 } = &.{}, + }; + + /// Describes one of the configurations the host can choose. + pub const UsbConfiguration = struct { + const Callback = struct { + name: []const u8, + func: []const u8, + }; + + endpoint_out_handlers: [16]?Callback, + + pub fn create(comptime driver_config: []const DriverConfig) @This() { + _ = driver_config; + const this: @This() = .{ + .endpoint_out_handlers = @splat(null), + }; + + return this; + } + }; + + Device: type, + Drivers: type, descriptors: Descriptors, + // Currently we only support one configuration. + usb_configurations: [1]UsbConfiguration, }; /// Create a USB device @@ -89,236 +191,120 @@ pub const Config = struct { /// The functions will be accessible to the user through the `Dev` field. pub fn Usb(comptime config: Config) type { return struct { - const Controller = @This(); - - /// USB Class driver interface - pub const DriverInterface = struct { - ptr: *anyopaque, - fn_init: *const fn (ptr: *anyopaque, ctrl: *Controller) void, - fn_open: *const fn (ptr: *anyopaque, ctrl: *Controller, cfg: []const u8) anyerror!usize, - fn_class_control: *const fn (ptr: *anyopaque, ctrl: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool, - fn_send: *const fn (ptr: *anyopaque, ctrl: *Controller, ep_in: types.Endpoint.Num, data: []const u8) void, - fn_receive: *const fn (ptr: *anyopaque, ctrl: *Controller, ep_out: types.Endpoint.Num, data: []const u8) void, - - pub fn init(interface: *const @This(), ctrl: *Controller) void { - return interface.fn_init(interface.ptr, ctrl); - } - - /// Driver open (set config) operation. Must return length of consumed driver config data. - pub fn open(interface: *const @This(), ctrl: *Controller, cfg: []const u8) !usize { - return interface.fn_open(interface.ptr, ctrl, cfg); - } - - pub fn class_control(interface: *const @This(), ctrl: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - return interface.fn_class_control(interface.ptr, ctrl, stage, setup); - } - - pub fn send(interface: *const @This(), ctrl: *Controller, ep_in: types.Endpoint.Num, data: []const u8) void { - return interface.fn_send(interface.ptr, ctrl, ep_in, data); - } - - pub fn receive(interface: *const @This(), ctrl: *Controller, ep_out: types.Endpoint.Num, len: []const u8) void { - return interface.fn_receive(interface.ptr, ctrl, ep_out, len); - } + pub const max_packet_size = config.Device.max_packet_size; + + pub const interface_vtable: ControllerInterface.Vtable = .{ + .task = &task, + .control_transfer = &control_transfer, + .endpoint_open = &endpoint_open, + .endpoint_rx = &endpoint_rx, + .submit_tx_buffer = &submit_tx_buffer, }; - pub const max_packet_size = config.device.max_packet_size; - const drvid_invalid = 0xff; - - /// The usb configuration set drivers: ?[]const DriverInterface, - itf_to_drv: [config.device.cfg_max_interfaces_count]u8, - ep_to_drv: [config.device.cfg_max_endpoints_count][2]u8, + driver_by_interface: [16]?DriverInterface, + driver_by_endpoint_in: [config.Device.max_endpoints_count]?DriverInterface, + driver_by_endpoint_out: [config.Device.max_endpoints_count]?DriverInterface, // Class driver associated with last setup request if any - driver: ?*const DriverInterface, + driver: ?DriverInterface, // When the host gives us a new address, we can't just slap it into // registers right away, because we have to do an acknowledgement step using // our _old_ address. - new_address: ?u8, - // Flag recording whether the host has configured us with a - // `SetConfiguration` message. - configured: bool, - // Flag recording whether we've set up buffer transfers after being - // configured. - started: bool, + new_address: ?u7, // Last setup packet request setup_packet: types.SetupPacket, // Keeps track of sent data from tmp buffer - now_sending: []const u8, + now_sending_ep0: ?[]const u8, // 0 - no config set cfg_num: u16, + drivers_data: config.Drivers, pub const init: @This() = .{ .drivers = null, - .itf_to_drv = @splat(0), - .ep_to_drv = @splat(@splat(0)), + .driver_by_interface = @splat(null), + .driver_by_endpoint_in = @splat(null), + .driver_by_endpoint_out = @splat(null), .driver = null, .new_address = null, - .configured = false, - .started = false, .setup_packet = undefined, - .now_sending = &.{}, + .now_sending_ep0 = null, .cfg_num = 0, + .drivers_data = undefined, }; - // Command endpoint utilities - const CmdEndpoint = struct { - /// Command response utility function that can split long data in multiple packets - fn send_cmd_response(this2: *Controller, data: []const u8, expected_max_length: u16) void { - this2.now_sending = data[0..@min(data.len, expected_max_length)]; - const data_chunk = this2.now_sending[0..@min(this2.now_sending.len, 64)]; - - if (data_chunk.len > 0) { - _ = config.device.usb_start_tx(.ep0, &.{data_chunk}); - } - } - - fn send_cmd_ack(this2: *Controller) void { - _ = this2; - _ = config.device.usb_start_tx(.ep0, &.{}); - } - }; - - /// Initialize the usb device using the given configuration - /// - /// You have to ensure that the device is getting an appropiate clock signal. - pub fn init_device(this: *@This(), d: []const DriverInterface) void { - config.device.usb_init_device(); - this.drivers = d; - - for (d) |*drv| { - drv.init(this); - } - } + /// Command response utility function that can split long data in multiple packets + fn send_cmd_response(this: *@This(), data: []const u8) void { + if (this.now_sending_ep0) |residual| + std.log.err("residual data: {any}", .{residual}); - pub fn ready(this: *@This()) bool { - return this.started; + const len = config.Device.usb_start_tx(.ep0, data); + if (len != 0) + this.now_sending_ep0 = data[len..]; } - pub fn control_transfer(this: *@This(), setup: *const types.SetupPacket, data: []const u8) void { - CmdEndpoint.send_cmd_response(this, data, setup.length); + /// Initialize the usb device using the given configuration + pub fn init_device(this: *@This(), drivers: []const DriverInterface) void { + config.Device.usb_init_device(); + this.drivers = drivers; } - pub fn control_ack(this: *@This(), _: *const types.SetupPacket) void { - CmdEndpoint.send_cmd_ack(this); + fn control_transfer(ptr: *anyopaque, data: []const u8) void { + const this: *@This() = @alignCast(@ptrCast(ptr)); + this.send_cmd_response(data); } - pub fn endpoint_tx(this: *@This(), ep_in: Endpoint.Num, data: []const []const u8) usize { - _ = this; - return config.device.usb_start_tx(ep_in, data); + fn submit_tx_buffer(ptr: *anyopaque, ep_in: Endpoint.Num, buffer_end: [*]const u8) void { + _ = ptr; + return config.Device.submit_tx_buffer(ep_in, buffer_end); } - pub fn endpoint_rx(this: *@This(), ep_out: Endpoint.Num, len: usize) void { - _ = this; - return config.device.usb_start_rx(ep_out, len); + fn endpoint_rx(ptr: *anyopaque, ep_out: Endpoint.Num, len: usize) void { + _ = ptr; + config.Device.usb_start_rx(ep_out, len); } - pub fn endpoint_open(this: *@This(), ep_desc: []const u8) void { - _ = this; + fn endpoint_open(ptr: *anyopaque, ep_desc: []const u8) anyerror!?[]u8 { + _ = ptr; const ep_addr = BosConfig.get_data_u8(ep_desc, 2); const ep: Endpoint = .from_address(ep_addr); const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); - config.device.endpoint_open(ep, types.TransferType.from_u8(ep_transfer_type) orelse types.TransferType.Bulk, ep_max_packet_size) catch unreachable; - } - - fn get_driver(this: *@This(), drv_idx: u8) ?*const DriverInterface { - if (drv_idx == drvid_invalid) - return null - else - return &this.drivers.?[drv_idx]; + return config.Device.endpoint_open(ep, enumFromInt(types.TransferType, ep_transfer_type) catch .Bulk, ep_max_packet_size); } - fn get_setup_packet(this: *@This()) types.SetupPacket { - const setup = config.device.get_setup_packet(); - this.setup_packet = setup; - this.driver = null; - return setup; - } - - fn configuration_reset(this: *@This()) void { - @memset(&this.itf_to_drv, drvid_invalid); - @memset(&this.ep_to_drv, .{ drvid_invalid, drvid_invalid }); + pub fn interface(this: *@This()) ControllerInterface { + return .{ + .ptr = this, + .vtable = &interface_vtable, + }; } - /// Usb task function meant to be executed in regular intervals after - /// initializing the device. - /// - /// This function will return an error if the device hasn't been initialized. - pub fn task(this: *@This(), debug: bool) !void { - if (this.drivers == null) return error.UninitializedDevice; - - // Device Specific Request - const DeviceRequestProcessor = struct { - fn process_setup_request(this2: *Controller, setup: *const types.SetupPacket, debug_mode: bool) !void { - switch (setup.request_type.type) { - .Class => { - //const itfIndex = setup.index & 0x00ff; - std.log.info("Device.Class", .{}); - }, - .Standard => { - const req = SetupRequest.from_u8(setup.request); - if (req == null) return; - switch (req.?) { - .SetAddress => { - this2.new_address = @as(u8, @intCast(setup.value & 0xff)); - CmdEndpoint.send_cmd_ack(this2); - if (debug_mode) std.log.info(" SetAddress: {}", .{this2.new_address.?}); - }, - .SetConfiguration => { - if (debug_mode) std.log.info(" SetConfiguration", .{}); - if (this2.cfg_num != setup.value) { - if (this2.cfg_num > 0) { - this2.configuration_reset(); - } - - if (setup.value > 0) { - try process_set_config(this2, setup.value - 1); - // TODO: call mount callback if any - } else { - // TODO: call umount callback if any - } - } - this2.cfg_num = setup.value; - this2.configured = true; - CmdEndpoint.send_cmd_ack(this2); - }, - .GetDescriptor => { - const descriptor_type = DescType.from_u8(@intCast(setup.value >> 8)); - if (descriptor_type) |dt| { - try process_get_descriptor(this2, setup, dt, debug_mode); - } - }, - .SetFeature => { - const feature = FeatureSelector.from_u8(@intCast(setup.value >> 8)); - if (feature) |feat| { - switch (feat) { - .DeviceRemoteWakeup, .EndpointHalt => CmdEndpoint.send_cmd_ack(this2), - // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 - .TestMode => {}, - } - } - }, - } - }, - else => {}, + fn process_device_setup_request(this: *@This(), setup: *const types.SetupPacket) void { + if (setup.request_type.type != .Standard) return; + switch (enumFromInt(SetupRequest, setup.request) catch return) { + .SetAddress => { + this.new_address = @intCast(setup.value); + this.send_cmd_response(""); + }, + .SetConfiguration => { + defer this.send_cmd_response(""); + if (this.cfg_num == setup.value) return; + defer this.cfg_num = setup.value; + + if (this.cfg_num > 0) { + this.driver_by_interface = @splat(null); + this.driver_by_endpoint_in = @splat(null); + this.driver_by_endpoint_out = @splat(null); + // TODO: call umount callback if any } - } - fn process_get_descriptor(this2: *Controller, setup: *const types.SetupPacket, descriptor_type: DescType, debug_mode: bool) !void { - _ = debug_mode; - const data = switch (descriptor_type) { - .Device => config.descriptors.device, - .Config => config.descriptors.config, - .String => config.descriptors.string[setup.value & 0xff], - .DeviceQualifier => config.descriptors.device_qualifier, - else => return, - }; - CmdEndpoint.send_cmd_response(this2, data, setup.length); - } + if (setup.value == 0) return; + + // TODO: only init the drivers from the current configuration + inline for (@typeInfo(config.Drivers).@"struct".fields) |field| + @field(this.drivers_data, field.name).mount(this.interface()); - fn process_set_config(this2: *Controller, _: u16) !void { // TODO: we support just one config for now so ignore config index const bos_cfg = config.descriptors.config; @@ -328,7 +314,7 @@ pub fn Usb(comptime config: Config) type { if (BosConfig.try_get_desc_as(types.ConfigurationDescriptor, curr_bos_cfg)) |_| { curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); } else { - // TODO - error + // TODO: error return; } @@ -340,163 +326,146 @@ pub fn Usb(comptime config: Config) type { curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); } - if (BosConfig.get_desc_type(curr_bos_cfg) != DescType.Interface) { - // TODO - error + if (BosConfig.get_desc_type(curr_bos_cfg) != @intFromEnum(DescType.Interface)) { + // TODO: error return; } const desc_itf = BosConfig.get_desc_as(types.InterfaceDescriptor, curr_bos_cfg); - var drv = this2.drivers.?[curr_drv_idx]; - const drv_cfg_len = try drv.open(this2, curr_bos_cfg); + var drv = this.drivers.?[curr_drv_idx]; + const drv_cfg_len = drv.open(this.interface(), curr_bos_cfg) catch unreachable; for (0..assoc_itf_count) |itf_offset| { const itf_num = desc_itf.interface_number + itf_offset; - this2.itf_to_drv[itf_num] = curr_drv_idx; + this.driver_by_interface[itf_num] = drv; + } + + var curr_bos_cfg2 = curr_bos_cfg[0..drv_cfg_len]; + while (curr_bos_cfg2.len > 0) : ({ + curr_bos_cfg2 = BosConfig.get_desc_next(curr_bos_cfg2); + }) { + if (BosConfig.try_get_desc_as(types.EndpointDescriptor, curr_bos_cfg2)) |desc_ep| { + switch (desc_ep.endpoint.dir) { + .Out => this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = drv, + .In => this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = drv, + } + } } - bind_endpoints_to_driver(this2, curr_bos_cfg[0..drv_cfg_len], curr_drv_idx); curr_bos_cfg = curr_bos_cfg[drv_cfg_len..]; // TODO: TMP solution - just 1 driver so quit while loop - return; - } - } - - fn bind_endpoints_to_driver(this2: *Controller, drv_bos_cfg: []const u8, drv_idx: u8) void { - var curr_bos_cfg = drv_bos_cfg; - while (curr_bos_cfg.len > 0) : ({ - curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); - }) { - if (BosConfig.try_get_desc_as(types.EndpointDescriptor, curr_bos_cfg)) |desc_ep| { - const dir_as_number: u1 = switch (desc_ep.endpoint.dir) { - .Out => 0, - .In => 1, - }; - this2.ep_to_drv[desc_ep.endpoint.num.to_int()][dir_as_number] = drv_idx; - } + break; } - } - }; - - // Class/Interface Specific Request - const InterfaceRequestProcessor = struct { - fn process_setup_request(this2: *Controller, setup: *const types.SetupPacket) !void { - const itf: u8 = @intCast(setup.index & 0xFF); - const drv = this2.get_driver(this2.itf_to_drv[itf]); - if (drv == null) return; - - this2.driver = drv; - if (this2.driver.?.class_control(this2, .Setup, setup) == false) { - // TODO + }, + .GetDescriptor => if (enumFromInt(DescType, setup.value >> 8)) |descriptor_type| blk: { + const data = switch (descriptor_type) { + .Device => config.descriptors.device, + .Config => config.descriptors.config, + .String => config.descriptors.string[setup.value & 0xff], + .DeviceQualifier => config.descriptors.device_qualifier, + else => break :blk, + }; + const len = @min(data.len, setup.length); + this.send_cmd_response(data[0..len]); + } else |_| {}, + .SetFeature => if (enumFromInt(FeatureSelector, setup.value >> 8)) |feature| + switch (feature) { + .DeviceRemoteWakeup, .EndpointHalt => this.send_cmd_response(""), + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 + .TestMode => {}, } - } - }; - - // Endpoint Specific Request - const EndpointRequestProcessor = struct { - fn process_setup_request(_: *const types.SetupPacket) !void {} - }; + else |_| {}, + } + } + /// Usb task function meant to be executed in regular intervals after + /// initializing the device. + /// + /// You have to ensure the device has been initialized. + pub fn task(ptr: *anyopaque) void { + const this: *@This() = @alignCast(@ptrCast(ptr)); // Check which interrupt flags are set. - const ints = config.device.get_interrupts(); + const ints = config.Device.get_interrupts(); // Setup request received? if (ints.SetupReq) { - if (debug) std.log.info("setup req", .{}); - - const setup = this.get_setup_packet(); - - // Reset PID to 1 for EP0 IN. Every DATA packet we send in response - // to an IN on EP0 needs to use PID DATA1, and this line will ensure - // that. - // TODO - maybe it can be moved to config.device.get_setup_packet? - config.device.reset_ep0(); + const setup = config.Device.get_setup_packet(); + this.setup_packet = setup; + this.driver = null; switch (setup.request_type.recipient) { - .Device => try DeviceRequestProcessor.process_setup_request(this, &setup, debug), - .Interface => try InterfaceRequestProcessor.process_setup_request(this, &setup), - .Endpoint => try EndpointRequestProcessor.process_setup_request(&setup), + .Device => this.process_device_setup_request(&setup), + .Interface => if (this.driver_by_interface[setup.index & 0xFF]) |drv| { + this.driver = drv; + if (drv.class_control(this.interface(), .Setup, &setup) == false) { + // TODO + } + }, else => {}, } } - // Events on one or more buffers? (In practice, always one.) + // Events on one or more buffers? if (ints.BuffStatus) { - if (debug) std.log.info("buff status", .{}); - var iter: config.device.UnhandledEndpointIterator = .init(); + var iter = config.Device.get_unhandled_endpoints(); + // Perform any required action on the data. For OUT, the `data` + // will be whatever was sent by the host. For IN, it's a new + // transmit buffer that has become available. while (iter.next()) |result| switch (result) { .In => |in| { - if (debug) std.log.info(" data: {any}", .{in.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. if (in.ep_num == .ep0) { - if (debug) std.log.info(" EP0_IN_ADDR", .{}); - // We use this opportunity to finish the delayed // SetAddress request, if there is one: if (this.new_address) |addr| { // Change our address: - config.device.set_address(@intCast(addr)); + config.Device.set_address(addr); + this.new_address = null; } - if (in.buffer.len > 0 and this.now_sending.len > 0) { - this.now_sending = this.now_sending[in.buffer.len..]; - const next_data_chunk = this.now_sending[0..@min(this.now_sending.len, 64)]; - if (next_data_chunk.len > 0) { - _ = config.device.usb_start_tx(.ep0, &.{next_data_chunk}); + if (this.now_sending_ep0) |data| { + if (data.len > 0) { + const len = config.Device.usb_start_tx(.ep0, data); + this.now_sending_ep0 = data[len..]; } else { - config.device.usb_start_rx(.ep0, 0); - - if (this.driver) |drv| { - _ = drv.class_control(this, .Ack, &this.setup_packet); - } + // 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: + config.Device.usb_start_rx(.ep0, 0); + if (this.driver) |drv| + _ = drv.class_control(this.interface(), .Ack, &this.setup_packet); + + this.now_sending_ep0 = null; } - } 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: - config.device.usb_start_rx(.ep0, 0); - - if (this.driver) |drv| - _ = drv.class_control(this, .Ack, &this.setup_packet); } - } else if (this.get_driver(this.ep_to_drv[in.ep_num.to_int()][1])) |drv| - drv.send(this, in.ep_num, in.buffer); + } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| + drv.on_tx_ready(this.interface(), in.buffer); }, .Out => |out| { - if (debug) std.log.info(" data: {any}", .{out.buffer}); - if (this.get_driver(this.ep_to_drv[out.ep_num.to_int()][0])) |drv| - drv.receive(this, out.ep_num, out.buffer); + if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| + drv.on_data_rx(this.interface(), out.buffer); - config.device.endpoint_reset_rx(out.ep_num); + config.Device.endpoint_reset_rx(out.ep_num); }, }; - } // <-- END of buf status handling + } // Has the host signaled a bus reset? if (ints.BusReset) { - if (debug) std.log.info("bus reset", .{}); - - this.configuration_reset(); + this.driver_by_interface = @splat(null); + this.driver_by_endpoint_in = @splat(null); + this.driver_by_endpoint_out = @splat(null); // Reset the device - config.device.bus_reset(); + config.Device.bus_reset(); // Reset our state. this.new_address = null; - this.configured = false; - this.started = false; - this.now_sending = &.{}; + this.cfg_num = 0; + this.now_sending_ep0 = null; } - - // If we have been configured but haven't reached this point yet, set up - // our custom EP OUT's to receive whatever data the host wants to send. - if (this.configured and !this.started) - this.started = true; } }; } diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 2666c4fc1..bd140696c 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -1,10 +1,10 @@ const std = @import("std"); -const types = @import("types.zig"); -const utils = @import("utils.zig"); +const usb = @import("../usb.zig"); +const types = usb.types; const DescType = types.DescType; -const bos = utils.BosConfig; +const bos = usb.utils.BosConfig; pub const DescSubType = enum(u8) { Header = 0x00, @@ -22,10 +22,6 @@ pub const CdcManagementRequestType = enum(u8) { GetLineCoding = 0x21, SetControlLineState = 0x22, SendBreak = 0x23, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } }; pub const CdcCommSubClassType = enum(u8) { @@ -42,13 +38,13 @@ pub const CdcHeaderDescriptor = extern struct { // number in binary-coded decimal. Typically 0x01_10. bcd_cdc: u16 align(1), - pub fn serialize(self: *const @This()) [5]u8 { + pub fn serialize(this: *const @This()) [5]u8 { var out: [5]u8 = undefined; out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = @intCast(self.bcd_cdc & 0xff); - out[4] = @intCast((self.bcd_cdc >> 8) & 0xff); + out[1] = @intFromEnum(this.descriptor_type); + out[2] = @intFromEnum(this.descriptor_subtype); + out[3] = @intCast(this.bcd_cdc & 0xff); + out[4] = @intCast((this.bcd_cdc >> 8) & 0xff); return out; } }; @@ -64,13 +60,13 @@ pub const CdcCallManagementDescriptor = extern struct { // Data interface number. data_interface: u8, - pub fn serialize(self: *const @This()) [5]u8 { + pub fn serialize(this: *const @This()) [5]u8 { var out: [5]u8 = undefined; out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = self.capabilities; - out[4] = self.data_interface; + out[1] = @intFromEnum(this.descriptor_type); + out[2] = @intFromEnum(this.descriptor_subtype); + out[3] = this.capabilities; + out[4] = this.data_interface; return out; } }; @@ -84,12 +80,12 @@ pub const CdcAcmDescriptor = extern struct { // Capabilities. Should be 0x02 for use as a serial device. capabilities: u8, - pub fn serialize(self: *const @This()) [4]u8 { + pub fn serialize(this: *const @This()) [4]u8 { var out: [4]u8 = undefined; out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = self.capabilities; + out[1] = @intFromEnum(this.descriptor_type); + out[2] = @intFromEnum(this.descriptor_subtype); + out[3] = this.capabilities; return out; } }; @@ -107,13 +103,13 @@ pub const CdcUnionDescriptor = extern struct { // union. slave_interface_0: u8, - pub fn serialize(self: *const @This()) [5]u8 { + pub fn serialize(this: *const @This()) [5]u8 { var out: [5]u8 = undefined; out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intFromEnum(self.descriptor_subtype); - out[3] = self.master_interface; - out[4] = self.slave_interface_0; + out[1] = @intFromEnum(this.descriptor_type); + out[2] = @intFromEnum(this.descriptor_subtype); + out[3] = this.master_interface; + out[4] = this.slave_interface_0; return out; } }; @@ -123,161 +119,164 @@ pub const CdcLineCoding = extern struct { stop_bits: u8, parity: u8, data_bits: u8, -}; -pub fn CdcClassDriver(comptime Controller: type) type { - const fifo = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = Controller.max_packet_size }); + pub const init: @This() = .{ + .bit_rate = 115200, + .stop_bits = 0, + .parity = 0, + .data_bits = 8, + }; +}; - return struct { - ep_in_notif: types.Endpoint.Num = .ep0, - ep_in: types.Endpoint.Num = .ep0, - ep_out: types.Endpoint.Num = .ep0, +pub const CdcClassDriver = struct { + const max_packet_size = 64; + const fifo = std.fifo.LinearFifo( + u8, + std.fifo.LinearFifoBufferType{ .Static = max_packet_size }, + ); + + ep_in_notif: types.Endpoint.Num = .ep0, + ep_in: types.Endpoint.Num = .ep0, + ep_out: types.Endpoint.Num = .ep0, + awaiting_data: bool = false, + + line_coding: CdcLineCoding = .init, + + rx_buf: ?[]const u8 = null, + tx_buf: ?[]u8 = null, + + /// This function is called when the host chooses a configuration that contains this driver. + pub fn mount(ptr: *@This(), controller: usb.ControllerInterface) void { + _ = controller; + var this: *@This() = @ptrCast(@alignCast(ptr)); + this.line_coding = .init; + this.awaiting_data = false; + this.rx_buf = null; + this.tx_buf = null; + } - line_coding: CdcLineCoding = undefined, + pub fn available(this: *@This()) usize { + return if (this.rx_buf) |rx| rx.len else 0; + } - rx: fifo = fifo.init(), - tx: fifo = fifo.init(), + pub fn read(this: *@This(), controller: usb.ControllerInterface, dst: []u8) usize { + if (this.rx_buf) |rx| { + const len = @min(rx.len, dst.len); + // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 + std.mem.copyForwards(u8, dst, rx[0..len]); + if (len < rx.len) + this.rx_buf = rx[len..] + else { + controller.endpoint_rx(this.ep_out, max_packet_size); + this.rx_buf = null; + } + return len; + } else return 0; + } - epin_buf: [Controller.max_packet_size]u8 = undefined, + pub fn write(this: *@This(), src: []const u8) usize { + if (this.tx_buf) |tx| { + const len = @min(tx.len, src.len); + // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 + std.mem.copyForwards(u8, tx, src[0..len]); + this.tx_buf = tx[len..]; + return len; + } else return 0; + } - pub fn available(self: *@This()) usize { - return self.rx.readableLength(); + pub fn flush(this: *@This(), controller: usb.ControllerInterface) void { + if (this.tx_buf) |tx| { + defer this.tx_buf = null; + controller.submit_tx_buffer(this.ep_in, tx.ptr); } + } - pub fn read(self: *@This(), controller: *Controller, dst: []u8) usize { - const read_count = self.rx.read(dst); - self.prep_out_transaction(controller); - return read_count; + pub fn writeAll(this: *@This(), controller: usb.ControllerInterface, data: []const u8) void { + var offset: usize = 0; + while (offset < data.len) { + offset += this.write(data[offset..]); + // TODO: Interrupt-safe. + this.flush(controller); + controller.task(); } + } - pub fn write(self: *@This(), controller: *Controller, data: []const u8) []const u8 { - const write_count = @min(self.tx.writableLength(), data.len); - - if (write_count == 0) - return data[0..]; + fn open(ptr: *anyopaque, controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { + var this: *@This() = @ptrCast(@alignCast(ptr)); + var curr_cfg = cfg; - self.tx.writeAssumeCapacity(data[0..write_count]); + if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) + return error.UnsupportedInterfaceClassType; + if (desc_itf.interface_subclass != @intFromEnum(CdcCommSubClassType.AbstractControlModel)) + return error.UnsupportedInterfaceSubClassType; + } else return error.ExpectedInterfaceDescriptor; - if (self.tx.writableLength() == 0) - _ = self.write_flush(controller); + curr_cfg = bos.get_desc_next(curr_cfg); - return data[write_count..]; - } - - pub fn write_flush(self: *@This(), controller: *Controller) usize { - if (!controller.ready() or self.tx.readableLength() == 0) - return 0; - const len = self.tx.read(&self.epin_buf); - const tx_len = controller.endpoint_tx(self.ep_in, &.{self.epin_buf[0..len]}); - if (tx_len != len) @panic("bruh"); - return len; - } - - fn prep_out_transaction(self: *@This(), controller: *Controller) void { - if (self.rx.writableLength() >= Controller.max_packet_size) { - // Let endpoint know that we are ready for next packet - controller.endpoint_rx(self.ep_out, Controller.max_packet_size); - } - } - - fn init(ptr: *anyopaque, controller: *Controller) void { - _ = controller; - var self: *@This() = @ptrCast(@alignCast(ptr)); - self.line_coding = .{ - .bit_rate = 115200, - .stop_bits = 0, - .parity = 0, - .data_bits = 8, - }; + while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == @intFromEnum(DescType.CsInterface)) { + curr_cfg = bos.get_desc_next(curr_cfg); } - fn open(ptr: *anyopaque, controller: *Controller, cfg: []const u8) !usize { - var self: *@This() = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; - - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) return types.DriverErrors.UnsupportedInterfaceClassType; - if (desc_itf.interface_subclass != @intFromEnum(CdcCommSubClassType.AbstractControlModel)) return types.DriverErrors.UnsupportedInterfaceSubClassType; - } else { - return types.DriverErrors.ExpectedInterfaceDescriptor; - } - + if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + std.debug.assert(desc_ep.endpoint.dir == .In); + this.ep_in_notif = desc_ep.endpoint.num; curr_cfg = bos.get_desc_next(curr_cfg); + } - while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == DescType.CsInterface) { - curr_cfg = bos.get_desc_next(curr_cfg); - } - - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - std.debug.assert(desc_ep.endpoint.dir == .In); - self.ep_in_notif = desc_ep.endpoint.num; + if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { curr_cfg = bos.get_desc_next(curr_cfg); - } - - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { - curr_cfg = bos.get_desc_next(curr_cfg); - for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (desc_ep.endpoint.dir) { - .In => self.ep_in = desc_ep.endpoint.num, - .Out => self.ep_out = desc_ep.endpoint.num, - } - controller.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); + for (0..2) |_| { + if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + switch (desc_ep.endpoint.dir) { + .In => this.ep_in = desc_ep.endpoint.num, + .Out => this.ep_out = desc_ep.endpoint.num, } + this.tx_buf = try controller.endpoint_open(curr_cfg[0..desc_ep.length]); + curr_cfg = bos.get_desc_next(curr_cfg); } } } - - return cfg.len - curr_cfg.len; } + controller.endpoint_rx(this.ep_out, max_packet_size); - fn class_control(ptr: *anyopaque, controller: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - var self: *@This() = @ptrCast(@alignCast(ptr)); - - if (CdcManagementRequestType.from_u8(setup.request)) |request| { - if (stage == .Setup) switch (request) { - .SetLineCoding => controller.control_ack(setup), - .GetLineCoding => controller.control_transfer(setup, std.mem.asBytes(&self.line_coding)), - .SetControlLineState => controller.control_ack(setup), - .SendBreak => controller.control_ack(setup), - }; - } + return cfg.len - curr_cfg.len; + } - return true; - } + fn class_control(ptr: *anyopaque, controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + var this: *@This() = @ptrCast(@alignCast(ptr)); + if (stage != .Setup) return true; - fn send(ptr: *anyopaque, controller: *Controller, ep_in: types.Endpoint.Num, data: []const u8) void { - var self: *@This() = @ptrCast(@alignCast(ptr)); + if (std.meta.intToEnum(CdcManagementRequestType, setup.request)) |request| switch (request) { + .SetLineCoding => controller.control_ack(), + .GetLineCoding => controller.control_transfer(std.mem.asBytes(&this.line_coding)[0..@min(@sizeOf(CdcLineCoding), setup.length)]), + .SetControlLineState => controller.control_ack(), + .SendBreak => controller.control_ack(), + } else |_| {} - if (ep_in == self.ep_in and self.write_flush(controller) == 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.readableLength() == 0 and data.len > 0 and data.len == Controller.max_packet_size) { - _ = controller.endpoint_tx(self.ep_in, &.{&.{}}); - } - } - } + return true; + } - fn receive(ptr: *anyopaque, controller: *Controller, ep_out: types.Endpoint.Num, data: []const u8) void { - var self: *@This() = @ptrCast(@alignCast(ptr)); + fn on_tx_ready(ptr: *anyopaque, _: usb.ControllerInterface, data: []u8) void { + var this: *@This() = @ptrCast(@alignCast(ptr)); + this.tx_buf = data; + } - if (ep_out == self.ep_out) { - self.rx.write(data) catch {}; - self.prep_out_transaction(controller); - } - } + fn on_data_rx(ptr: *anyopaque, _: usb.ControllerInterface, data: []const u8) void { + var this: *@This() = @ptrCast(@alignCast(ptr)); + this.rx_buf = data; + } - pub fn driver(self: *@This()) Controller.DriverInterface { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - .fn_send = send, - .fn_receive = receive, - }; - } - }; -} + pub fn driver(this: *@This()) usb.DriverInterface { + return .{ + .ptr = this, + .vtable = comptime &.{ + .open = &open, + .class_control = &class_control, + .on_tx_ready = &on_tx_ready, + .on_data_rx = &on_data_rx, + }, + }; + } +}; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index de7abf68f..345916d58 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -42,12 +42,12 @@ //! The HID descriptor identifies the length and type of subordinate descriptors for device. const std = @import("std"); +const assert = std.debug.assert; -const types = @import("types.zig"); -const utils = @import("utils.zig"); - +const usb = @import("../usb.zig"); +const types = usb.types; const DescType = types.DescType; -const bos = utils.BosConfig; +const bos = usb.utils.BosConfig; // +++++++++++++++++++++++++++++++++++++++++++++++++ // Common Data Types @@ -302,21 +302,18 @@ pub const HID_BUFFERED_BYTES = 1 << 8; // +++++++++++++++++++++++++++++++++++++++++++++++++ pub fn hid_report_item( - comptime n: u2, typ: u2, tag: u4, - data: [n]u8, -) [n + 1]u8 { - var out: [n + 1]u8 = undefined; - - out[0] = (@as(u8, @intCast(tag)) << 4) | (@as(u8, @intCast(typ)) << 2) | n; - - var i: usize = 0; - while (i < n) : (i += 1) { - out[i + 1] = data[i]; - } - - return out; + data: anytype, +) [data.len + 1]u8 { + comptime if (data.len != 0) assert(@TypeOf(data[0]) == u8); + const first = (@as(u8, tag) << 4) | (@as(u8, typ) << 2) | @as(u2, data.len + 1); + + return switch (@typeInfo(@TypeOf(data))) { + .array, .@"struct" => .{first} ++ data, + .pointer => .{first} ++ data.*, + else => @compileLog(data), + }; } // Main Items @@ -324,34 +321,30 @@ pub fn hid_report_item( pub fn hid_collection(data: CollectionItem) [2]u8 { return hid_report_item( - 1, @intFromEnum(ReportItemTypes.Main), @intFromEnum(ReportItemMainGroup.Collection), - std.mem.toBytes(@intFromEnum(data)), + .{@intFromEnum(data)}, ); } pub fn hid_input(data: u8) [2]u8 { return hid_report_item( - 1, @intFromEnum(ReportItemTypes.Main), @intFromEnum(ReportItemMainGroup.Input), - std.mem.toBytes(data), + .{data}, ); } pub fn hid_output(data: u8) [2]u8 { return hid_report_item( - 1, @intFromEnum(ReportItemTypes.Main), @intFromEnum(ReportItemMainGroup.Output), - std.mem.toBytes(data), + .{data}, ); } pub fn hid_collection_end() [1]u8 { return hid_report_item( - 0, @intFromEnum(ReportItemTypes.Main), @intFromEnum(ReportItemMainGroup.CollectionEnd), .{}, @@ -361,45 +354,45 @@ pub fn hid_collection_end() [1]u8 { // Global Items // ------------------------------------------------- -pub fn hid_usage_page(comptime n: u2, usage: [n]u8) [n + 1]u8 { +pub fn hid_usage_page(usage: anytype) [usage.len + 1]u8 { + comptime assert(@TypeOf(usage[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Global), @intFromEnum(GlobalItem.UsagePage), usage, ); } -pub fn hid_logical_min(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_logical_min(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Global), @intFromEnum(GlobalItem.LogicalMin), data, ); } -pub fn hid_logical_max(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_logical_max(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Global), @intFromEnum(GlobalItem.LogicalMax), data, ); } -pub fn hid_report_size(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_report_size(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Global), @intFromEnum(GlobalItem.ReportSize), data, ); } -pub fn hid_report_count(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_report_count(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Global), @intFromEnum(GlobalItem.ReportCount), data, @@ -409,27 +402,27 @@ pub fn hid_report_count(comptime n: u2, data: [n]u8) [n + 1]u8 { // Local Items // ------------------------------------------------- -pub fn hid_usage(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_usage(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Local), @intFromEnum(LocalItem.Usage), data, ); } -pub fn hid_usage_min(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_usage_min(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Local), @intFromEnum(LocalItem.UsageMin), data, ); } -pub fn hid_usage_max(comptime n: u2, data: [n]u8) [n + 1]u8 { +pub fn hid_usage_max(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); return hid_report_item( - n, @intFromEnum(ReportItemTypes.Local), @intFromEnum(LocalItem.UsageMax), data, @@ -440,42 +433,42 @@ pub fn hid_usage_max(comptime n: u2, data: [n]u8) [n + 1]u8 { // Report Descriptors // +++++++++++++++++++++++++++++++++++++++++++++++++ -pub const ReportDescriptorFidoU2f = hid_usage_page(2, UsageTable.fido) // - ++ hid_usage(1, FidoAllianceUsage.u2fhid) // +pub const ReportDescriptorFidoU2f = hid_usage_page(UsageTable.fido) // + ++ hid_usage(FidoAllianceUsage.u2fhid) // ++ hid_collection(CollectionItem.Application) // // Usage Data In - ++ hid_usage(1, FidoAllianceUsage.data_in) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // + ++ hid_usage(FidoAllianceUsage.data_in) // + ++ hid_logical_min("\x00") // + ++ hid_logical_max("\xff\x00") // + ++ hid_report_size("\x08") // + ++ hid_report_count("\x40") // ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // // Usage Data Out - ++ hid_usage(1, FidoAllianceUsage.data_out) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // + ++ hid_usage(FidoAllianceUsage.data_out) // + ++ hid_logical_min("\x00") // + ++ hid_logical_max("\xff\x00") // + ++ hid_report_size("\x08") // + ++ hid_report_count("\x40") // ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // // End ++ hid_collection_end(); -pub const ReportDescriptorGenericInOut = hid_usage_page(2, UsageTable.vendor) // - ++ hid_usage(1, "\x01".*) // +pub const ReportDescriptorGenericInOut = hid_usage_page(UsageTable.vendor) // + ++ hid_usage("\x01") // ++ hid_collection(CollectionItem.Application) // // Usage Data In - ++ hid_usage(1, "\x02".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // + ++ hid_usage("\x02") // + ++ hid_logical_min("\x00") // + ++ hid_logical_max("\xff\x00") // + ++ hid_report_size("\x08") // + ++ hid_report_count("\x40") // ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // // Usage Data Out - ++ hid_usage(1, "\x03".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_report_count(1, "\x40".*) // + ++ hid_usage("\x03") // + ++ hid_logical_min("\x00") // + ++ hid_logical_max("\xff\x00") // + ++ hid_report_size("\x08") // + ++ hid_report_count("\x40") // ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // // End ++ hid_collection_end(); @@ -483,185 +476,160 @@ pub const ReportDescriptorGenericInOut = hid_usage_page(2, UsageTable.vendor) // /// Common keyboard report format, conforming to the boot protocol. /// See Appendix B.1 of the USB HID specification: /// https://usb.org/sites/default/files/hid1_11.pdf -pub const ReportDescriptorKeyboard = hid_usage_page(1, UsageTable.desktop) // - ++ hid_usage(1, DesktopUsage.keyboard) // - ++ hid_collection(.Application) // +pub const ReportDescriptorKeyboard = hid_usage_page(UsageTable.desktop) ++ hid_usage(DesktopUsage.keyboard) ++ hid_collection(.Application) // Input: modifier key bitmap - ++ hid_usage_page(1, UsageTable.keyboard) // - ++ hid_usage_min(1, "\xe0".*) // - ++ hid_usage_max(1, "\xe7".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(1, "\x01".*) // - ++ hid_report_count(1, "\x08".*) // - ++ hid_report_size(1, "\x01".*) // - ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // +++ hid_usage_page(UsageTable.keyboard) ++ hid_usage_min("\xe0") ++ hid_usage_max("\xe7") ++ hid_logical_min("\x00") ++ hid_logical_max("\x01") ++ hid_report_count("\x08") ++ hid_report_size("\x01") ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // // Reserved 8 bits - ++ hid_report_count(1, "\x01".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_input(HID_CONSTANT) // +++ hid_report_count("\x01") ++ hid_report_size("\x08") ++ hid_input(HID_CONSTANT) // Output: indicator LEDs - ++ hid_usage_page(1, UsageTable.led) // - ++ hid_usage_min(1, "\x01".*) // - ++ hid_usage_max(1, "\x05".*) // - ++ hid_report_count(1, "\x05".*) // - ++ hid_report_size(1, "\x01".*) // - ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // +++ hid_usage_page(UsageTable.led) ++ hid_usage_min("\x01") ++ hid_usage_max("\x05") ++ hid_report_count("\x05") ++ hid_report_size("\x01") ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // Padding - ++ hid_report_count(1, "\x01".*) // - ++ hid_report_size(1, "\x03".*) // - ++ hid_output(HID_CONSTANT) // +++ hid_report_count("\x01") ++ hid_report_size("\x03") ++ hid_output(HID_CONSTANT) // Input: up to 6 pressed key codes - ++ hid_usage_page(1, UsageTable.keyboard) // - ++ hid_usage_min(1, "\x00".*) // - ++ hid_usage_max(2, "\xff\x00".*) // - ++ hid_logical_min(1, "\x00".*) // - ++ hid_logical_max(2, "\xff\x00".*) // - ++ hid_report_count(1, "\x06".*) // - ++ hid_report_size(1, "\x08".*) // - ++ hid_input(HID_DATA | HID_ARRAY | HID_ABSOLUTE) // +++ hid_usage_page(UsageTable.keyboard) ++ hid_usage_min("\x00") ++ hid_usage_max("\xff\x00") ++ hid_logical_min("\x00") ++ hid_logical_max("\xff\x00") ++ hid_report_count("\x06") ++ hid_report_size("\x08") ++ hid_input(HID_DATA | HID_ARRAY | HID_ABSOLUTE) // End - ++ hid_collection_end(); +++ hid_collection_end(); -pub fn HidClassDriver(comptime Controller: type) type { - return struct { - ep_in: types.Endpoint.Num = .ep0, - ep_out: types.Endpoint.Num = .ep0, - hid_descriptor: []const u8 = &.{}, - report_descriptor: []const u8, +pub const HidClassDriver = struct { + ep_in: types.Endpoint.Num = .ep0, + ep_out: types.Endpoint.Num = .ep0, + hid_descriptor: []const u8 = &.{}, + report_descriptor: []const u8, - fn init(_: *anyopaque, _: *Controller) void {} + /// This function is called when the host chooses a configuration that contains this driver. + pub fn mount(_: *@This(), _: usb.ControllerInterface) void {} - fn open(ptr: *anyopaque, controller: *Controller, cfg: []const u8) !usize { - var self: *@This() = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; + fn open(ptr: *anyopaque, controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { + var self: *@This() = @ptrCast(@alignCast(ptr)); + var curr_cfg = cfg; - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return types.DriverErrors.UnsupportedInterfaceClassType; - } else { - return types.DriverErrors.ExpectedInterfaceDescriptor; - } + if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return error.UnsupportedInterfaceClassType; + } else { + return error.ExpectedInterfaceDescriptor; + } + curr_cfg = bos.get_desc_next(curr_cfg); + if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| { + self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)]; curr_cfg = bos.get_desc_next(curr_cfg); - if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| { - self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)]; - curr_cfg = bos.get_desc_next(curr_cfg); - } else { - return types.DriverErrors.UnexpectedDescriptor; - } + } else { + return error.UnexpectedDescriptor; + } - for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (desc_ep.endpoint.dir) { - .In => self.ep_in = desc_ep.endpoint.num, - .Out => self.ep_out = desc_ep.endpoint.num, - } - controller.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); + for (0..2) |_| { + if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + switch (desc_ep.endpoint.dir) { + .In => self.ep_in = desc_ep.endpoint.num, + .Out => self.ep_out = desc_ep.endpoint.num, } + _ = try controller.endpoint_open(curr_cfg[0..desc_ep.length]); + curr_cfg = bos.get_desc_next(curr_cfg); } - - return cfg.len - curr_cfg.len; } - fn class_control(ptr: *anyopaque, controller: *Controller, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - const self: *@This() = @ptrCast(@alignCast(ptr)); + return cfg.len - curr_cfg.len; + } - switch (setup.request_type.type) { - .Standard => { - if (stage == .Setup) { - const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff)); - const request_code = types.SetupRequest.from_u8(setup.request); + fn class_control(ptr: *anyopaque, controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + const self: *@This() = @ptrCast(@alignCast(ptr)); - if (hid_desc_type == null or request_code == null) { - return false; - } + switch (setup.request_type.type) { + .Standard => { + if (stage == .Setup) { + const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff)); + const request_code = types.SetupRequest.from_u8(setup.request); - if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) { - controller.control_transfer(setup, self.hid_descriptor); - } else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) { - controller.control_transfer(setup, self.report_descriptor); - } else { - return false; - } + if (hid_desc_type == null or request_code == null) { + return false; } - }, - .Class => { - const hid_request_type = HidRequestType.from_u8(setup.request); - if (hid_request_type == null) return false; - - switch (hid_request_type.?) { - .SetIdle => { - if (stage == .Setup) { - // TODO: The host is attempting to limit bandwidth by requesting that - // the device only return report data when its values actually change, - // or when the specified duration elapses. In practice, the device can - // still send reports as often as it wants, but for completeness this - // should be implemented eventually. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(setup); - } - }, - .SetProtocol => { - if (stage == .Setup) { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, - // or vice versa. - // - // For now, this request is ACKed without doing anything; in practice, - // the OS will reuqest the report protocol anyway, so usually only one format is needed. - // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), - // our device might not work in a limited BIOS environment. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(setup); - } - }, - .SetReport => { - if (stage == .Setup) { - // TODO: This request sends a feature or output report to the device, - // e.g. turning on the caps lock LED. This must be handled in an - // application-specific way, so notify the application code of the event. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(setup); - } - }, - else => { - return false; - }, - } - }, - else => { - return false; - }, - } - return true; + if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) { + controller.control_transfer(self.hid_descriptor[0..@min(self.hid_descriptor.len, setup.length)]); + } else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) { + controller.control_transfer(self.report_descriptor[0..@min(self.report_descriptor.len, setup.length)]); + } else { + return false; + } + } + }, + .Class => { + const hid_request_type = HidRequestType.from_u8(setup.request); + if (hid_request_type == null) return false; + + switch (hid_request_type.?) { + .SetIdle => { + if (stage == .Setup) { + // TODO: The host is attempting to limit bandwidth by requesting that + // the device only return report data when its values actually change, + // or when the specified duration elapses. In practice, the device can + // still send reports as often as it wants, but for completeness this + // should be implemented eventually. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(); + } + }, + .SetProtocol => { + if (stage == .Setup) { + // TODO: The device should switch the format of its reports from the + // boot keyboard/mouse protocol to the format described in its report descriptor, + // or vice versa. + // + // For now, this request is ACKed without doing anything; in practice, + // the OS will reuqest the report protocol anyway, so usually only one format is needed. + // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), + // our device might not work in a limited BIOS environment. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(); + } + }, + .SetReport => { + if (stage == .Setup) { + // TODO: This request sends a feature or output report to the device, + // e.g. turning on the caps lock LED. This must be handled in an + // application-specific way, so notify the application code of the event. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(); + } + }, + else => { + return false; + }, + } + }, + else => { + return false; + }, } - fn send(_: *anyopaque, _: *Controller, _: types.Endpoint.Num, _: []const u8) void {} - fn receive(_: *anyopaque, _: *Controller, _: types.Endpoint.Num, _: []const u8) void {} - - pub fn driver(self: *@This()) Controller.DriverInterface { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - .fn_send = send, - .fn_receive = receive, - }; - } - }; -} + return true; + } + + fn on_tx_ready(_: *anyopaque, _: usb.ControllerInterface, _: []u8) void {} + fn on_data_rx(_: *anyopaque, _: usb.ControllerInterface, _: []const u8) void {} + + pub fn driver(this: *@This()) usb.DriverInterface { + return .{ + .ptr = this, + .vtable = comptime &.{ + .open = open, + .class_control = class_control, + .on_tx_ready = on_tx_ready, + .on_data_rx = on_data_rx, + }, + }; + } +}; test "create hid report item" { const r = hid_report_item( 2, 0, 3, - "\x22\x11".*, + "\x22\x11", ); try std.testing.expectEqual(@as(usize, @intCast(3)), r.len); @@ -671,7 +639,7 @@ test "create hid report item" { } test "create hid fido usage page" { - const f = hid_usage_page(2, UsageTable.fido); + const f = hid_usage_page(UsageTable.fido); try std.testing.expectEqual(@as(usize, @intCast(3)), f.len); try std.testing.expectEqual(@as(u8, @intCast(6)), f[0]); diff --git a/core/src/core/usb/templates.zig b/core/src/core/usb/templates.zig index fd92c04b9..86b756d5e 100644 --- a/core/src/core/usb/templates.zig +++ b/core/src/core/usb/templates.zig @@ -6,8 +6,8 @@ const Ep = types.Endpoint; pub const DescriptorsConfigTemplates = struct { pub const config_descriptor_len = 9; - pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: u8, max_power_ma: u9) [9]u8 { - const desc1 = types.ConfigurationDescriptor{ .total_length = total_len, .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = 0b01000000 | attributes, .max_power = max_power_ma / 2 }; + pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: u8, max_current: types.ConfigurationDescriptor.MaxCurrent) [9]u8 { + const desc1 = types.ConfigurationDescriptor{ .total_length = total_len, .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = 0b01000000 | attributes, .max_current = max_current }; return desc1.serialize(); } diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 2d075af80..028a4a644 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -14,10 +14,7 @@ pub const DescType = enum(u8) { CsString = 0x23, CsInterface = 0x24, CsEndpoint = 0x25, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } + _, }; pub const ClassCode = enum(u8) { @@ -46,16 +43,20 @@ pub const ClassCode = enum(u8) { Miscellaneous = 0xEF, ApplicationSpecific = 0xFE, VendorSpecific = 0xFF, + _, }; -/// Version of the device descriptor / USB protocol, in binary-coded -/// decimal. This is typically `0x01_10` for USB 1.1. pub const BcdUsb = extern struct { - lo: u8, - hi: u8, + major: u8, + minor: u8, - pub const v1_1: @This() = .{ .hi = 1, .lo = 1 }; - pub const v2_0: @This() = .{ .hi = 2, .lo = 0 }; + pub const v1_1: @This() = .{ .major = 1, .minor = 1 }; + pub const v2_0: @This() = .{ .major = 2, .minor = 0 }; +}; + +pub const BcdDevice = extern struct { + major: u8, + minor: u8, }; pub const DeviceTriple = extern struct { @@ -74,10 +75,6 @@ pub const TransferType = enum(u2) { Bulk = 2, Interrupt = 3, - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } - pub inline fn as_number(self: @This()) u2 { return @intFromEnum(self); } @@ -349,6 +346,15 @@ pub const InterfaceAssociationDescriptor = extern struct { pub const ConfigurationDescriptor = extern struct { pub const const_descriptor_type = DescType.Config; + /// Maximum device power consumption in units of 2mA. + pub const MaxCurrent = extern struct { + multiple_of_2ma: u8, + + pub fn from_ma(ma: u9) @This() { + return .{ .multiple_of_2ma = @intCast((ma +| 1) >> 1) }; + } + }; + length: u8 = 9, /// Type of this descriptor, must be `Config`. descriptor_type: DescType = const_descriptor_type, @@ -372,8 +378,7 @@ pub const ConfigurationDescriptor = extern struct { /// (like a keyboard). /// - The rest are reserved and should be zero. attributes: u8, - /// Maximum device power consumption in units of 2mA. - max_power: u8, + max_current: MaxCurrent, pub fn serialize(self: *const @This()) [9]u8 { var out: [9]u8 = undefined; @@ -385,7 +390,7 @@ pub const ConfigurationDescriptor = extern struct { out[5] = self.configuration_value; out[6] = self.configuration_s; out[7] = self.attributes; - out[8] = self.max_power; + out[8] = self.max_current.multiple_of_2ma; return out; } }; @@ -419,8 +424,8 @@ pub const DeviceDescriptor = extern struct { var out: [18]u8 = undefined; out[0] = out.len; out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.bcd_usb.lo; - out[3] = self.bcd_usb.hi; + out[2] = self.bcd_usb.minor; + out[3] = self.bcd_usb.major; out[4] = @intFromEnum(self.device_triple.class); out[5] = self.device_triple.subclass; out[6] = self.device_triple.protocol; @@ -460,8 +465,8 @@ pub const DeviceQualifierDescriptor = extern struct { var out: [10]u8 = undefined; out[0] = out.len; out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.bcd_usb.lo; - out[3] = self.bcd_usb.hi; + out[2] = self.bcd_usb.minor; + out[3] = self.bcd_usb.major; out[4] = @intFromEnum(self.device_triple.class); out[5] = self.device_triple.subclass; out[6] = self.device_triple.protocol; @@ -471,10 +476,3 @@ pub const DeviceQualifierDescriptor = extern struct { return out; } }; - -pub const DriverErrors = error{ - ExpectedInterfaceDescriptor, - UnsupportedInterfaceClassType, - UnsupportedInterfaceSubClassType, - UnexpectedDescriptor, -}; diff --git a/core/src/core/usb/utils.zig b/core/src/core/usb/utils.zig index a9b79106c..e2a1db895 100644 --- a/core/src/core/usb/utils.zig +++ b/core/src/core/usb/utils.zig @@ -2,15 +2,12 @@ const std = @import("std"); const types = @import("types.zig"); pub const BosConfig = struct { - const DESC_OFFSET_LEN = 0; - const DESC_OFFSET_TYPE = 1; - - pub fn get_desc_len(bos_cfg: []const u8) u8 { - return bos_cfg[DESC_OFFSET_LEN]; + pub inline fn get_desc_len(bos_cfg: []const u8) u8 { + return bos_cfg[0]; } - pub fn get_desc_type(bos_cfg: []const u8) ?types.DescType { - return types.DescType.from_u8(bos_cfg[DESC_OFFSET_TYPE]); + pub fn get_desc_type(bos_cfg: []const u8) u8 { + return bos_cfg[1]; } pub fn get_data_u8(bos_cfg: []const u8, offset: u16) u8 { @@ -32,8 +29,7 @@ pub const BosConfig = struct { pub fn try_get_desc_as(comptime T: type, bos_cfg: []const u8) ?*const T { if (bos_cfg.len == 0) return null; const exp_desc_type = @field(T, "const_descriptor_type"); - const cfg_desc_type = bos_cfg[DESC_OFFSET_TYPE]; - if (cfg_desc_type != @intFromEnum(exp_desc_type)) { + if (get_desc_type(bos_cfg) != @intFromEnum(exp_desc_type)) { return null; } else { return @constCast(@ptrCast(bos_cfg.ptr)); @@ -41,8 +37,7 @@ pub const BosConfig = struct { } pub fn get_desc_next(bos_cfg: []const u8) []const u8 { - const len = bos_cfg[DESC_OFFSET_LEN]; - return bos_cfg[len..]; + return bos_cfg[get_desc_len(bos_cfg)..]; } }; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 7c4663cb8..5fcf2eb77 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -16,16 +16,17 @@ const usb_templates = usb.templates.DescriptorsConfigTemplates; const usb_packet_size = 64; const usb_config_len = usb_templates.config_descriptor_len + usb_templates.hid_in_out_descriptor_len; const usb_config_descriptor = - usb_templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++ + usb_templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, .from_ma(100)) ++ usb_templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, .ep1, .ep1, usb_packet_size, 0); -var driver_hid = usb.hid.HidClassDriver(UsbDev){ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; - // This is our device configuration const UsbDev = usb.Usb(.{ + .Device = rp2xxx.usb.Usb(.{}), + .Drivers = struct { + hid: usb.hid.HidClassDriver, + }, .descriptors = .create( .{ - .descriptor_type = .Device, .bcd_usb = .v1_1, .device_triple = .{ .class = .Unspecified, @@ -51,7 +52,10 @@ const UsbDev = usb.Usb(.{ "cafebabe", }, ), - .device = rp2xxx.usb.Usb(.{}), + .usb_configurations = .{.create(&.{.{ + .name = "hid", + .driver = usb.hid.HidClassDriver, + }})}, }); var usb_dev: UsbDev = .init; @@ -67,6 +71,8 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { + usb_dev.drivers_data.hid = .{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; + // init uart logging uart_tx_pin.set_function(.uart); uart.apply(.{ @@ -80,14 +86,12 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&.{driver_hid.driver()}); + usb_dev.init_device(&.{usb_dev.drivers_data.hid.driver()}); 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.task( - false, // debug output over UART [Y/n] - ) catch unreachable; + usb_dev.interface().task(); new = time.get_time_since_boot().to_us(); if (new - old > 500000) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index f78f26740..5f4a1c210 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -16,12 +16,15 @@ const uart_rx_pin = gpio.num(1); const usb_templates = usb.templates.DescriptorsConfigTemplates; const usb_config_len = usb_templates.config_descriptor_len + usb_templates.cdc_descriptor_len; const usb_config_descriptor = - usb_templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ + usb_templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, .from_ma(100)) ++ usb_templates.cdc_descriptor(0, 4, .ep1, 8, .ep2, .ep2, 64); // This is our device configuration const UsbDev = usb.Usb(.{ - .device = rp2xxx.usb.Usb(.{}), + .Device = rp2xxx.usb.Usb(.{}), + .Drivers = struct { + serial: usb.cdc.CdcClassDriver, + }, .descriptors = .create( .{ .bcd_usb = .v1_1, @@ -48,10 +51,15 @@ const UsbDev = usb.Usb(.{ "Board CDC", }, ), + .usb_configurations = .{.create(&.{.{ + .name = "serial", + .driver = microzig.core.usb.cdc.CdcClassDriver, + // .endpoints_in = .{ .ep1 = "notifi", .ep2 = "data" }, + // .endpoints_out = .{ .ep2 = "data" }, + .strings = &.{.{ .name = "cdc", .value = "Board CDC" }}, + }})}, }); -var driver_cdc: usb.cdc.CdcClassDriver(UsbDev) = .{}; - var usb_dev: UsbDev = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { @@ -66,6 +74,8 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { + usb_dev.drivers_data.serial = .{}; + led.set_function(.sio); led.set_direction(.out); led.put(1); @@ -81,16 +91,14 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&.{driver_cdc.driver()}); + usb_dev.init_device(&.{usb_dev.drivers_data.serial.driver()}); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; var i: u32 = 0; while (true) { // You can now poll for USB events - usb_dev.task( - false, // debug output over UART [Y/n] - ) catch unreachable; + usb_dev.interface().task(); new = time.get_time_since_boot().to_us(); if (new - old > 500000) { @@ -99,48 +107,18 @@ pub fn main() !void { i += 1; std.log.info("cdc test: {}\r\n", .{i}); - usb_cdc_write("This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); + var tx_buf: [1024]u8 = undefined; + const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); + usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), text); } // read and print host command if present - const message = usb_cdc_read(); - if (message.len > 0) { - usb_cdc_write("Your message to me was: {s}\r\n", .{message}); + var rx_buf: [64]u8 = undefined; + const len = usb_dev.drivers_data.serial.read(usb_dev.interface(), &rx_buf); + if (len > 0) { + usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), "Your message to me was: '"); + usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), rx_buf[0..len]); + usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), "'\r\n"); } } } - -var usb_tx_buff: [1024]u8 = undefined; - -// Transfer data to host -// NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled -pub fn usb_cdc_write(comptime fmt: []const u8, args: anytype) void { - const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; - - var write_buff = text; - while (write_buff.len > 0) { - write_buff = driver_cdc.write(&usb_dev, write_buff); - usb_dev.task(false) catch unreachable; - } - // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them - _ = driver_cdc.write_flush(&usb_dev); - usb_dev.task(false) catch unreachable; -} - -var usb_rx_buff: [1024]u8 = undefined; - -// Receive data from host -// NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation -pub fn usb_cdc_read() []const u8 { - var total_read: usize = 0; - var read_buff: []u8 = usb_rx_buff[0..]; - - while (true) { - const len = driver_cdc.read(&usb_dev, read_buff); - read_buff = read_buff[len..]; - total_read += len; - if (len == 0) break; - } - - return usb_rx_buff[0..total_read]; -} diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 5823b1b91..4270ab0fe 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -16,12 +16,9 @@ const resets = @import("resets.zig"); pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; pub const UsbConfig = struct { - // Comptime defined supported max endpoints number, can be reduced to save RAM space - max_endpoints_count: comptime_int = RP2XXX_MAX_ENDPOINTS_COUNT, - max_interfaces_count: comptime_int = 16, synchronization_nops: comptime_int = 3, dpram_allocator: type = DpramAllocatorBump, - swap_dpdm: bool = false, + // swap_dpdm: bool = false, }; const Endpoint = usb.types.Endpoint; @@ -94,21 +91,15 @@ pub const DpramAllocatorBump = struct { /// are used by the abstract USB impl of microzig. pub fn Usb(comptime config: UsbConfig) type { return struct { - comptime { - if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) - @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); - } - - pub const cfg_max_endpoints_count = config.max_endpoints_count; - pub const cfg_max_interfaces_count = config.max_interfaces_count; - pub const high_speed = false; - pub const max_packet_size = 64; + pub const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; + // TODO: Support other buffer sizes. + const max_transfer_size = 64; const HardwareEndpoint = packed struct(u7) { - const ep_ctrl_all: *volatile [2 * (cfg_max_endpoints_count - 1)]EpCtrl = + const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = @ptrCast(&peri_dpram.EP1_IN_CONTROL); - const buf_ctrl_all: *volatile [2 * (cfg_max_endpoints_count)]BufCtrl = + const buf_ctrl_all: *volatile [2 * (max_endpoints_count)]BufCtrl = @ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL); var awaiting_rx: u32 = 0; @@ -145,11 +136,6 @@ pub fn Usb(comptime config: UsbConfig) type { return .{ .num = ep.num, .is_out = ep.dir == .Out }; } - fn into(this: @This()) Endpoint { - assert(this._padding == 0); - return .{ .num = this.num, .dir = if (this.is_out) .Out else .In }; - } - fn in(num: Endpoint.Num) @This() { return .{ .num = num, .is_out = false }; } @@ -174,10 +160,6 @@ pub fn Usb(comptime config: UsbConfig) type { .ep0buf0; return buf.start()[0..DpramBuffer.chunk_len]; } - - fn buffer_ready(this: @This()) []u8 { - return this.buffer()[0..this.buf_ctrl().read().LENGTH_0]; - } }; pub fn usb_init_device() void { @@ -233,8 +215,8 @@ pub fn Usb(comptime config: UsbConfig) type { .SETUP_REQ = 1, }); - endpoint_open(.in(.ep0), .Control, 0) catch unreachable; - endpoint_open(.out(.ep0), .Control, 0) catch unreachable; + _ = endpoint_open(.in(.ep0), .Control, 0) catch unreachable; + _ = endpoint_open(.out(.ep0), .Control, 0) catch unreachable; // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. @@ -247,58 +229,60 @@ pub fn Usb(comptime config: UsbConfig) type { /// The contents of each of the slices in `data` will be _copied_ into USB SRAM, /// so you can reuse them immediately after this returns. /// No need to wait for the packet to be sent. - pub fn usb_start_tx(ep_in: Endpoint.Num, data: []const []const u8) usize { + pub fn usb_start_tx(ep_in: Endpoint.Num, data: []const u8) usize { const ep_hard: HardwareEndpoint = .in(ep_in); const buf = ep_hard.buffer(); - // It is technically possible to support longer buffers but this demo doesn't bother. - var n: usize = 0; - for (data) |buffer| { - const space_left = buf.len - n; - if (space_left >= buffer.len) { - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, buf[n .. n + buffer.len], buffer); - n += buffer.len; - } else { - std.mem.copyForwards(u8, buf[n..DpramBuffer.chunk_len], buffer[0..space_left]); - n = DpramBuffer.chunk_len; - } - } + const len = @min(buf.len, data.len); + // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 + std.mem.copyForwards(u8, buf[0..len], data[0..len]); - // The AVAILABLE bit in the buffer control register should be set - // separately to the rest of the data in the buffer control register, - // so that the rest of the data in the buffer control register is - // accurate when the AVAILABLE bit is set. + submit_tx_buffer(ep_in, buf.ptr + len); + + return len; + } + + pub fn submit_tx_buffer(ep_in: Endpoint.Num, buffer_end: [*]const u8) void { + const ep_hard: HardwareEndpoint = .in(ep_in); + const buf = ep_hard.buffer(); + + // It is technically possible to support longer buffers but this demo doesn't bother. + const len = buffer_end - buf.ptr; + if (len > max_transfer_size) + std.log.err("wrong buffer submitted", .{}); // Write the buffer information to the buffer control register const buf_ctrl = ep_hard.buf_ctrl(); var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 1; // We have put data in - rmw.LENGTH_0 = @as(u10, @intCast(n)); // There are this many bytes - buf_ctrl.write(rmw); + rmw.LENGTH_0 = @intCast(len); // There are this many bytes + + // If the CPU is running at a higher clock speed than USB, + // the AVAILABLE bit in the buffer control register should be set + // separately to the rest of the data in the buffer control register, + // so that the rest of the data in the buffer control register is + // accurate when the AVAILABLE bit is set. - // Nop for some clock cycles - // use volatile so the compiler doesn't optimize the nops away - inline for (0..config.synchronization_nops) |_| - asm volatile ("nop"); + if (config.synchronization_nops != 0) { + buf_ctrl.write(rmw); + asm volatile ("nop\n" ** config.synchronization_nops); + } - // Set available bit - rmw.AVAILABLE_0 = 1; // The data is for the computer to use now + rmw.AVAILABLE_0 = 1; buf_ctrl.write(rmw); - - return n; } pub fn usb_start_rx(ep_out: Endpoint.Num, len: usize) void { const ep_hard: HardwareEndpoint = .out(ep_out); - if (ep_hard.awaiting_rx_get()) - return; - // Configure the OUT: const buf_ctrl = ep_hard.buf_ctrl(); var rmw = buf_ctrl.read(); + if (ep_hard.awaiting_rx_get()) { + std.log.err("should not be called twice {} {}", .{ rmw.FULL_0, rmw.AVAILABLE_0 }); + return; + } rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it rmw.AVAILABLE_0 = 1; // It is, however, available to be filled @@ -316,14 +300,13 @@ pub fn Usb(comptime config: UsbConfig) type { /// Check which interrupt flags are set pub fn get_interrupts() usb.InterruptStatus { const ints = peri_usb.INTS.read(); - return .{ - .BuffStatus = if (ints.BUFF_STATUS == 1) true else false, - .BusReset = if (ints.BUS_RESET == 1) true else false, - .DevConnDis = if (ints.DEV_CONN_DIS == 1) true else false, - .DevSuspend = if (ints.DEV_SUSPEND == 1) true else false, - .DevResumeFromHost = if (ints.DEV_RESUME_FROM_HOST == 1) true else false, - .SetupReq = if (ints.SETUP_REQ == 1) true else false, + .BuffStatus = ints.BUFF_STATUS == 1, + .BusReset = ints.BUS_RESET == 1, + .DevConnDis = ints.DEV_CONN_DIS == 1, + .DevSuspend = ints.DEV_SUSPEND == 1, + .DevResumeFromHost = ints.DEV_RESUME_FROM_HOST == 1, + .SetupReq = ints.SETUP_REQ == 1, }; } @@ -335,7 +318,14 @@ pub fn Usb(comptime config: UsbConfig) type { /// setup request falg is set. pub fn get_setup_packet() usb.types.SetupPacket { // Clear the status flag (write-one-to-clear) - peri_usb.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); + var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); + sie_status.SETUP_REC = 1; + peri_usb.SIE_STATUS.write(sie_status); + + // 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. + defer peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); // This assumes that the setup packet is arriving on EP0, our // control endpoint. Which it should be. We don't have any other @@ -349,7 +339,9 @@ pub fn Usb(comptime config: UsbConfig) type { /// Called on a bus reset interrupt pub fn bus_reset() void { // Acknowledge by writing the write-one-to-clear status bit. - peri_usb.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); + var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); + sie_status.BUS_RESET = 1; + peri_usb.SIE_STATUS.write(sie_status); peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); } @@ -357,42 +349,40 @@ pub fn Usb(comptime config: UsbConfig) type { peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); } - pub fn reset_ep0() void { - // Next packet ID will be DATA1 - peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); - } - - pub fn endpoint_open(ep: Endpoint, transfer_type: usb.types.TransferType, buf_size_hint: usize) error{OutOfBufferMemory}!void { + pub fn endpoint_open(ep: Endpoint, transfer_type: usb.types.TransferType, buf_size_hint: usize) error{OutOfBufferMemory}!?[]u8 { _ = buf_size_hint; const ep_hard: HardwareEndpoint = .from(ep); - assert(ep.num.to_int() < cfg_max_endpoints_count); + assert(ep.num.to_int() < max_endpoints_count); ep_hard.awaiting_rx_clr(); - if (ep.num != .ep0) { + const start = if (ep.num != .ep0) blk: { + const buf = try config.dpram_allocator.alloc(1); var ep_ctrl = ep_hard.ep_ctrl().?; var rmw = ep_ctrl.read(); rmw.ENABLE = 1; rmw.INTERRUPT_PER_BUFF = 1; rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); - rmw.BUFFER_ADDRESS = (try config.dpram_allocator.alloc(1)).to_u16(); + rmw.BUFFER_ADDRESS = buf.to_u16(); ep_ctrl.write(rmw); - } + break :blk buf.start(); + } else DpramBuffer.Index.ep0buf0.start(); + return start[0..max_transfer_size]; + } + + pub fn get_unhandled_endpoints() UnhandledEndpointIterator { + const mask = peri_usb.BUFF_STATUS.raw; + return .{ + .initial_unhandled_mask = mask, + .currently_unhandled_mask = mask, + }; } pub const UnhandledEndpointIterator = struct { initial_unhandled_mask: u32, currently_unhandled_mask: u32, - pub fn init() @This() { - const mask = peri_usb.BUFF_STATUS.raw; - return .{ - .initial_unhandled_mask = mask, - .currently_unhandled_mask = mask, - }; - } - pub fn next(this: *@This()) ?usb.EndpointAndBuffer { const idx = std.math.cast(u5, @ctz(this.currently_unhandled_mask)) orelse { if (this.initial_unhandled_mask != 0) @@ -401,10 +391,11 @@ pub fn Usb(comptime config: UsbConfig) type { }; this.currently_unhandled_mask &= this.currently_unhandled_mask -% 1; // clear lowest bit const ep: HardwareEndpoint = .from_idx(idx); - return if (ep.is_out) - .{ .Out = .{ .ep_num = ep.num, .buffer = ep.buffer_ready() } } - else - .{ .In = .{ .ep_num = ep.num, .buffer = ep.buffer_ready() } }; + const buf = ep.buffer(); + if (ep.is_out) { + const len = ep.buf_ctrl().read().LENGTH_0; + return .{ .Out = .{ .ep_num = ep.num, .buffer = buf[0..len] } }; + } else return .{ .In = .{ .ep_num = ep.num, .buffer = buf } }; } }; }; From 8871ecd93b2371dd989894eea03f416d470e8b45 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 21 Aug 2025 06:08:06 +0200 Subject: [PATCH 11/33] add more type safety and change usb controller config --- core/src/core/usb.zig | 217 ++++++++----- core/src/core/usb/cdc.zig | 149 ++------- core/src/core/usb/descriptor.zig | 283 +++++++++++++++++ core/src/core/usb/descriptor/cdc.zig | 196 ++++++++++++ core/src/core/usb/descriptor/hid.zig | 0 core/src/core/usb/hid.zig | 20 +- core/src/core/usb/templates.zig | 48 +-- core/src/core/usb/types.zig | 285 +----------------- .../rp2xxx/src/rp2040_only/usb_hid.zig | 63 ++-- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 65 +--- port/raspberrypi/rp2xxx/src/hal/usb.zig | 13 +- 11 files changed, 744 insertions(+), 595 deletions(-) create mode 100644 core/src/core/usb/descriptor.zig create mode 100644 core/src/core/usb/descriptor/cdc.zig create mode 100644 core/src/core/usb/descriptor/hid.zig diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 3f56efe9d..0ea0815ee 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -22,50 +22,14 @@ pub const cdc = @import("usb/cdc.zig"); pub const vendor = @import("usb/vendor.zig"); pub const utils = @import("usb/utils.zig"); pub const templates = @import("usb/templates.zig"); +pub const descriptor = @import("usb/descriptor.zig"); -const DescType = types.DescType; const FeatureSelector = types.FeatureSelector; const Dir = types.Dir; const Endpoint = types.Endpoint; const SetupRequest = types.SetupRequest; const BosConfig = utils.BosConfig; -pub const Descriptors = struct { - const Lang = struct { - lo: u8, - hi: u8, - - pub const English: @This() = .{ .lo = 0x09, .hi = 0x04 }; - }; - - device: []const u8, - config: []const u8, - string: []const []const u8, - device_qualifier: []const u8, - - pub fn create(comptime device: types.DeviceDescriptor, config: []const u8, comptime lang: Lang, comptime strings: []const []const u8) @This() { - @setEvalBranchQuota(10000); - - // String 0 indicates language. - var strings_utf16: []const []const u8 = &.{&.{ 0x04, 0x03, lang.lo, lang.hi }}; - inline for (strings) |str| { - const str_utf16: []const u8 = @ptrCast(std.unicode.utf8ToUtf16LeStringLiteral(str)); - strings_utf16 = strings_utf16 ++ .{.{ str_utf16.len + 2, strings_utf16[0][1] } ++ str_utf16}; - } - return .{ - .device = &device.serialize(), - .config = config, - .string = strings_utf16, - .device_qualifier = &(types.DeviceQualifierDescriptor{ - .bcd_usb = device.bcd_usb, - .device_triple = device.device_triple, - .max_packet_size0 = device.max_packet_size0, - .num_configurations = device.num_configurations, - }).serialize(), - }; - } -}; - pub const ControllerInterface = struct { const Vtable = struct { task: *const fn (ptr: *anyopaque) void, @@ -137,39 +101,45 @@ pub const DriverInterface = struct { }; pub const Config = struct { - /// TODO: Description - pub const DriverConfig = struct { - name: []const u8, - driver: type, - // endpoints_in: PerEndpointOpt([]const u8), - // endpoints_out: PerEndpointOpt([]const u8), - strings: []const struct { name: []const u8, value: []const u8 } = &.{}, - }; - - /// Describes one of the configurations the host can choose. - pub const UsbConfiguration = struct { - const Callback = struct { - name: []const u8, - func: []const u8, + pub fn NameValue(T: type) type { + return struct { + name: [:0]const u8, + value: T, }; + } - endpoint_out_handlers: [16]?Callback, + /// Vendor id and product id combo. + pub const VidPid = struct { + product: u16, + vendor: u16, + }; - pub fn create(comptime driver_config: []const DriverConfig) @This() { - _ = driver_config; - const this: @This() = .{ - .endpoint_out_handlers = @splat(null), - }; + pub const Strings = struct { + manufacturer: []const u8, + product: []const u8, + serial: []const u8, + }; - return this; - } + pub const Language = enum(u16) { + English = 0x0409, }; Device: type, - Drivers: type, - descriptors: Descriptors, - // Currently we only support one configuration. - usb_configurations: [1]UsbConfiguration, + device_triple: descriptor.Device.DeviceTriple = .{ + .class = .Unspecified, + .subclass = 0, + .protocol = 0, + }, + id: ?VidPid = null, + bcd_device: u16 = 0x01_00, + strings: ?Strings = null, + attributes: descriptor.Configuration.Attributes, + max_current: descriptor.Configuration.MaxCurrent = .from_ma(100), + language: Language = .English, + // Eventually the fields below could be in an array to support multiple configurations. + Driver: type, + driver_endpoints: []const NameValue(types.Endpoint.Num), + driver_strings: []const NameValue([]const u8), }; /// Create a USB device @@ -201,6 +171,90 @@ pub fn Usb(comptime config: Config) type { .submit_tx_buffer = &submit_tx_buffer, }; + const device_descriptor: descriptor.Device = .{ + .bcd_usb = .from(config.Device.bcd_usb), + .device_triple = config.device_triple, + .max_packet_size0 = config.Device.max_transfer_size, + .vendor = .from(if (config.id) |id| id.vendor else config.Device.default_vidpid.vendor), + .product = .from(if (config.id) |id| id.product else config.Device.default_vidpid.product), + .bcd_device = .from(config.bcd_device), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }; + + const config_descriptor: []const u8 = blk: { + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.driver_endpoints) |fld| { + fields = fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(fld.value), + .default_value_ptr = @ptrCast(&fld.value), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(fld.value)), + }}; + } + const endpoints_struct = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); + + const driver_desc = config.Driver.config_descriptor(string.ids{}, endpoints_struct{}); + break :blk &(descriptor.Configuration{ + .total_length = .from(@sizeOf(descriptor.Configuration) + driver_desc.len), + .num_interfaces = config.Driver.num_interfaces, + .configuration_value = 1, + .configuration_s = 0, + .attributes = config.attributes, + .max_current = config.max_current, + }).serialize() ++ driver_desc; + }; + + const string = blk: { + const Dev = config.Device; + + // String 0 indicates language. First byte is length. + const lang: types.U16Le = .from(@intFromEnum(config.language)); + var desc: []const []const u8 = &.{&.{ + 0x04, + @intFromEnum(descriptor.Type.String), + lang.lo, + lang.hi, + }}; + + const device_strings = config.strings orelse Dev.default_strings; + desc = desc ++ .{descriptor.string(device_strings.manufacturer)}; + desc = desc ++ .{descriptor.string(device_strings.product)}; + desc = desc ++ .{descriptor.string(device_strings.serial)}; + + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.driver_strings) |fld| { + const id: u8 = desc.len; + fields = fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(id), + .default_value_ptr = @ptrCast(&id), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(id)), + }}; + desc = desc ++ .{descriptor.string(fld.value)}; + } + break :blk .{ + .descriptors = desc, + .ids = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }), + }; + }; + drivers: ?[]const DriverInterface, driver_by_interface: [16]?DriverInterface, driver_by_endpoint_in: [config.Device.max_endpoints_count]?DriverInterface, @@ -217,7 +271,7 @@ pub fn Usb(comptime config: Config) type { now_sending_ep0: ?[]const u8, // 0 - no config set cfg_num: u16, - drivers_data: config.Drivers, + driver_data: config.Driver, pub const init: @This() = .{ .drivers = null, @@ -229,7 +283,7 @@ pub fn Usb(comptime config: Config) type { .setup_packet = undefined, .now_sending_ep0 = null, .cfg_num = 0, - .drivers_data = undefined, + .driver_data = undefined, }; /// Command response utility function that can split long data in multiple packets @@ -301,17 +355,15 @@ pub fn Usb(comptime config: Config) type { if (setup.value == 0) return; - // TODO: only init the drivers from the current configuration - inline for (@typeInfo(config.Drivers).@"struct".fields) |field| - @field(this.drivers_data, field.name).mount(this.interface()); + this.driver_data.mount(this.interface()); // TODO: we support just one config for now so ignore config index - const bos_cfg = config.descriptors.config; + const bos_cfg = config_descriptor; var curr_bos_cfg = bos_cfg; var curr_drv_idx: u8 = 0; - if (BosConfig.try_get_desc_as(types.ConfigurationDescriptor, curr_bos_cfg)) |_| { + if (BosConfig.try_get_desc_as(descriptor.Configuration, curr_bos_cfg)) |_| { curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); } else { // TODO: error @@ -321,16 +373,16 @@ pub fn Usb(comptime config: Config) type { while (curr_bos_cfg.len > 0) : (curr_drv_idx += 1) { var assoc_itf_count: u8 = 1; // New class starts optionally from InterfaceAssociation followed by mandatory Interface - if (BosConfig.try_get_desc_as(types.InterfaceAssociationDescriptor, curr_bos_cfg)) |desc_assoc_itf| { + if (BosConfig.try_get_desc_as(descriptor.InterfaceAssociation, curr_bos_cfg)) |desc_assoc_itf| { assoc_itf_count = desc_assoc_itf.interface_count; curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); } - if (BosConfig.get_desc_type(curr_bos_cfg) != @intFromEnum(DescType.Interface)) { + if (BosConfig.get_desc_type(curr_bos_cfg) != @intFromEnum(descriptor.Type.Interface)) { // TODO: error return; } - const desc_itf = BosConfig.get_desc_as(types.InterfaceDescriptor, curr_bos_cfg); + const desc_itf = BosConfig.get_desc_as(descriptor.Interface, curr_bos_cfg); var drv = this.drivers.?[curr_drv_idx]; const drv_cfg_len = drv.open(this.interface(), curr_bos_cfg) catch unreachable; @@ -344,7 +396,7 @@ pub fn Usb(comptime config: Config) type { while (curr_bos_cfg2.len > 0) : ({ curr_bos_cfg2 = BosConfig.get_desc_next(curr_bos_cfg2); }) { - if (BosConfig.try_get_desc_as(types.EndpointDescriptor, curr_bos_cfg2)) |desc_ep| { + if (BosConfig.try_get_desc_as(descriptor.Endpoint, curr_bos_cfg2)) |desc_ep| { switch (desc_ep.endpoint.dir) { .Out => this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = drv, .In => this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = drv, @@ -358,12 +410,15 @@ pub fn Usb(comptime config: Config) type { break; } }, - .GetDescriptor => if (enumFromInt(DescType, setup.value >> 8)) |descriptor_type| blk: { - const data = switch (descriptor_type) { - .Device => config.descriptors.device, - .Config => config.descriptors.config, - .String => config.descriptors.string[setup.value & 0xff], - .DeviceQualifier => config.descriptors.device_qualifier, + .GetDescriptor => if (enumFromInt(descriptor.Type, setup.value >> 8)) |descriptor_type| blk: { + const data: []const u8 = switch (descriptor_type) { + .Device => comptime &device_descriptor.serialize(), + .Configuration => config_descriptor, + .String => if (setup.value & 0xff < string.descriptors.len) + string.descriptors[setup.value & 0xff] + else + comptime descriptor.string(""), + .DeviceQualifier => comptime &device_descriptor.qualifier().serialize(), else => break :blk, }; const len = @min(data.len, setup.length); @@ -441,10 +496,12 @@ pub fn Usb(comptime config: Config) type { this.now_sending_ep0 = null; } } + // TODO: Route different endpoints to different functions. } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| drv.on_tx_ready(this.interface(), in.buffer); }, .Out => |out| { + // TODO: Route different endpoints to different functions. if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| drv.on_data_rx(this.interface(), out.buffer); diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index bd140696c..b32909416 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -1,120 +1,18 @@ const std = @import("std"); const usb = @import("../usb.zig"); -const types = usb.types; - -const DescType = types.DescType; const bos = usb.utils.BosConfig; +const descriptor = usb.descriptor; +const types = usb.types; -pub const DescSubType = enum(u8) { - Header = 0x00, - CallManagement = 0x01, - ACM = 0x02, - Union = 0x06, - - pub fn from_u16(v: u16) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } -}; - -pub const CdcManagementRequestType = enum(u8) { +pub const ManagementRequestType = enum(u8) { SetLineCoding = 0x20, GetLineCoding = 0x21, SetControlLineState = 0x22, SendBreak = 0x23, }; -pub const CdcCommSubClassType = enum(u8) { - AbstractControlModel = 2, -}; - -pub const CdcHeaderDescriptor = extern struct { - length: u8 = 5, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `Header`. - descriptor_subtype: DescSubType = DescSubType.Header, - // USB Class Definitions for Communication Devices Specification release - // number in binary-coded decimal. Typically 0x01_10. - bcd_cdc: u16 align(1), - - pub fn serialize(this: *const @This()) [5]u8 { - var out: [5]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(this.descriptor_type); - out[2] = @intFromEnum(this.descriptor_subtype); - out[3] = @intCast(this.bcd_cdc & 0xff); - out[4] = @intCast((this.bcd_cdc >> 8) & 0xff); - return out; - } -}; - -pub const CdcCallManagementDescriptor = extern struct { - length: u8 = 5, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `CallManagement`. - descriptor_subtype: DescSubType = DescSubType.CallManagement, - // Capabilities. Should be 0x00 for use as a serial device. - capabilities: u8, - // Data interface number. - data_interface: u8, - - pub fn serialize(this: *const @This()) [5]u8 { - var out: [5]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(this.descriptor_type); - out[2] = @intFromEnum(this.descriptor_subtype); - out[3] = this.capabilities; - out[4] = this.data_interface; - return out; - } -}; - -pub const CdcAcmDescriptor = extern struct { - length: u8 = 4, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `ACM`. - descriptor_subtype: DescSubType = DescSubType.ACM, - // Capabilities. Should be 0x02 for use as a serial device. - capabilities: u8, - - pub fn serialize(this: *const @This()) [4]u8 { - var out: [4]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(this.descriptor_type); - out[2] = @intFromEnum(this.descriptor_subtype); - out[3] = this.capabilities; - return out; - } -}; - -pub const CdcUnionDescriptor = extern struct { - length: u8 = 5, - // Type of this descriptor, must be `ClassSpecific`. - descriptor_type: DescType = DescType.CsInterface, - // Subtype of this descriptor, must be `Union`. - descriptor_subtype: DescSubType = DescSubType.Union, - // The interface number of the communication or data class interface - // designated as the master or controlling interface for the union. - master_interface: u8, - // The interface number of the first slave or associated interface in the - // union. - slave_interface_0: u8, - - pub fn serialize(this: *const @This()) [5]u8 { - var out: [5]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(this.descriptor_type); - out[2] = @intFromEnum(this.descriptor_subtype); - out[3] = this.master_interface; - out[4] = this.slave_interface_0; - return out; - } -}; - -pub const CdcLineCoding = extern struct { +pub const LineCoding = extern struct { bit_rate: u32 align(1), stop_bits: u8, parity: u8, @@ -126,25 +24,38 @@ pub const CdcLineCoding = extern struct { .parity = 0, .data_bits = 8, }; + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } }; pub const CdcClassDriver = struct { + pub const num_interfaces = 2; const max_packet_size = 64; - const fifo = std.fifo.LinearFifo( - u8, - std.fifo.LinearFifoBufferType{ .Static = max_packet_size }, - ); ep_in_notif: types.Endpoint.Num = .ep0, ep_in: types.Endpoint.Num = .ep0, ep_out: types.Endpoint.Num = .ep0, awaiting_data: bool = false, - line_coding: CdcLineCoding = .init, + line_coding: LineCoding = .init, rx_buf: ?[]const u8 = null, tx_buf: ?[]u8 = null, + pub fn config_descriptor(string_ids: anytype, endpoints: anytype) []const u8 { + return &usb.templates.DescriptorsConfigTemplates.cdc_descriptor( + 0, + string_ids.name, + endpoints.notifi, + 8, + endpoints.data, + endpoints.data, + 64, + ); + } + /// This function is called when the host chooses a configuration that contains this driver. pub fn mount(ptr: *@This(), controller: usb.ControllerInterface) void { _ = controller; @@ -205,30 +116,30 @@ pub const CdcClassDriver = struct { var this: *@This() = @ptrCast(@alignCast(ptr)); var curr_cfg = cfg; - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (bos.try_get_desc_as(descriptor.Interface, curr_cfg)) |desc_itf| { if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) return error.UnsupportedInterfaceClassType; - if (desc_itf.interface_subclass != @intFromEnum(CdcCommSubClassType.AbstractControlModel)) + if (desc_itf.interface_subclass != @intFromEnum(descriptor.cdc.SubType.AbstractControlModel)) return error.UnsupportedInterfaceSubClassType; } else return error.ExpectedInterfaceDescriptor; curr_cfg = bos.get_desc_next(curr_cfg); - while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == @intFromEnum(DescType.CsInterface)) { + while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == @intFromEnum(descriptor.Type.CsInterface)) { curr_cfg = bos.get_desc_next(curr_cfg); } - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + if (bos.try_get_desc_as(descriptor.Endpoint, curr_cfg)) |desc_ep| { std.debug.assert(desc_ep.endpoint.dir == .In); this.ep_in_notif = desc_ep.endpoint.num; curr_cfg = bos.get_desc_next(curr_cfg); } - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (bos.try_get_desc_as(descriptor.Interface, curr_cfg)) |desc_itf| { if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { curr_cfg = bos.get_desc_next(curr_cfg); for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + if (bos.try_get_desc_as(descriptor.Endpoint, curr_cfg)) |desc_ep| { switch (desc_ep.endpoint.dir) { .In => this.ep_in = desc_ep.endpoint.num, .Out => this.ep_out = desc_ep.endpoint.num, @@ -248,9 +159,9 @@ pub const CdcClassDriver = struct { var this: *@This() = @ptrCast(@alignCast(ptr)); if (stage != .Setup) return true; - if (std.meta.intToEnum(CdcManagementRequestType, setup.request)) |request| switch (request) { + if (std.meta.intToEnum(ManagementRequestType, setup.request)) |request| switch (request) { .SetLineCoding => controller.control_ack(), - .GetLineCoding => controller.control_transfer(std.mem.asBytes(&this.line_coding)[0..@min(@sizeOf(CdcLineCoding), setup.length)]), + .GetLineCoding => controller.control_transfer(std.mem.asBytes(&this.line_coding)[0..@min(@sizeOf(LineCoding), setup.length)]), .SetControlLineState => controller.control_ack(), .SendBreak => controller.control_ack(), } else |_| {} diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig new file mode 100644 index 000000000..f1e3f58bc --- /dev/null +++ b/core/src/core/usb/descriptor.zig @@ -0,0 +1,283 @@ +pub const cdc = @import("descriptor/cdc.zig"); +pub const hid = @import("descriptor/hid.zig"); + +const std = @import("std"); +const types = @import("types.zig"); +const assert = std.debug.assert; + +pub const Type = enum(u8) { + Device = 0x01, + Configuration = 0x02, + String = 0x03, + Interface = 0x04, + Endpoint = 0x05, + DeviceQualifier = 0x06, + InterfaceAssociation = 0x0b, + CsDevice = 0x21, + CsConfig = 0x22, + CsString = 0x23, + CsInterface = 0x24, + CsEndpoint = 0x25, + _, +}; + +// pub const Any = union(Type) { +// Device: Device, +// Configuration: Configuration, +// String, +// Interface: Interface, +// Endpoint: Endpoint, +// DeviceQualifier: Device.Qualifier, +// InterfaceAssociation: InterfaceAssociation, +// CsDevice, +// CsConfig, +// CsString, +// CsInterface, +// CsEndpoint, + +// pub fn serialize(comptime this: @This()) []const u8 { +// return comptime switch (this) { +// .Interface => |d| d.serialize(), +// }; +// } +// }; + +/// 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, + }; + + /// USB Device Qualifier Descriptor + /// This descriptor is a subset of the DeviceDescriptor + pub const Qualifier = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 10); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `DeviceQualifier`. + descriptor_type: Type = .DeviceQualifier, + /// Specification version as Binary Coded Decimal + bcd_usb: types.U16Le, + /// Class, subclass and protocol of device. + device_triple: DeviceTriple, + /// Maximum unit of data this device can move. + max_packet_size0: u8, + /// Number of configurations supported by this device. + num_configurations: u8, + /// Reserved for future use; must be 0 + reserved: u8 = 0, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } + }; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 18); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Device`. + descriptor_type: Type = .Device, + /// Specification version as Binary Coded Decimal + bcd_usb: types.U16Le, + /// Class, subclass and protocol of device. + device_triple: DeviceTriple, + /// Maximum length of data this device can move. + max_packet_size0: u8, + /// ID of product vendor. + vendor: types.U16Le, + /// ID of product. + product: types.U16Le, + /// Device version number as Binary Coded Decimal. + bcd_device: types.U16Le, + /// Index of manufacturer name in string descriptor table. + manufacturer_s: u8, + /// Index of product name in string descriptor table. + product_s: u8, + /// Index of serial number in string descriptor table. + serial_s: u8, + /// Number of configurations supported by this device. + num_configurations: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } + + pub fn qualifier(this: @This()) Qualifier { + return .{ + .bcd_usb = this.bcd_usb, + .device_triple = this.device_triple, + .max_packet_size0 = this.max_packet_size0, + .num_configurations = this.num_configurations, + }; + } +}; + +/// Description of a single available device configuration. +pub const Configuration = extern struct { + pub const const_descriptor_type: Type = .Configuration; + + /// Maximum device power consumption in units of 2mA. + pub const MaxCurrent = extern struct { + multiple_of_2ma: u8, + + pub fn from_ma(ma: u9) @This() { + return .{ .multiple_of_2ma = @intCast((ma +| 1) >> 1) }; + } + }; + + /// Bit set of device attributes: + /// + /// - Bit 7 should be set (indicates that device can be bus powered in USB + /// 1.0). + /// - Bit 6 indicates that the device can be self-powered. + /// - Bit 5 indicates that the device can signal remote wakeup of the host + /// (like a keyboard). + /// - The rest are reserved and should be zero. + pub const Attributes = packed struct(u8) { + reserved0: u5 = 0, + can_remote_wakeup: bool = false, + self_powered: bool, + usb1_bus_powered: bool = true, + }; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 9); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Configuration`. + descriptor_type: Type = .Configuration, + /// Total length of all descriptors in this configuration, concatenated. + /// This will include this descriptor, plus at least one interface + /// descriptor, plus each interface descriptor's endpoint descriptors. + total_length: types.U16Le, + /// Number of interface descriptors in this configuration. + num_interfaces: u8, + /// Number to use when requesting this configuration via a + /// `SetConfiguration` request. + configuration_value: u8, + /// Index of this configuration's name in the string descriptor table. + configuration_s: u8, + /// Bit set of device attributes. + attributes: Attributes, + /// Maximum device power consumption in units of 2mA. + max_current: MaxCurrent, + + pub fn serialize(this: @This()) [9]u8 { + return @bitCast(this); + } +}; + +pub fn string(comptime value: []const u8) []const u8 { + const encoded: []const u8 = @ptrCast(std.unicode.utf8ToUtf16LeStringLiteral(value)); + return &[2]u8{ encoded.len + 2, @intFromEnum(Type.String) } ++ encoded; +} + +/// Describes an endpoint within an interface +pub const Endpoint = extern struct { + pub const const_descriptor_type: Type = .Endpoint; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 7); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Endpoint`. + descriptor_type: Type = .Endpoint, + /// Address of this endpoint, where the bottom 4 bits give the endpoint + /// number (0..15) and the top bit distinguishes IN (1) from OUT (0). + endpoint: types.Endpoint, + /// Endpoint attributes; the most relevant part is the bottom 2 bits, which + /// control the transfer type using the values from `TransferType`. + attributes: u8, + /// Maximum packet size this endpoint can accept/produce. + max_packet_size: types.U16Le, + /// Interval for polling interrupt/isochronous endpoints (which we don't + /// currently support) in milliseconds. + interval: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +/// Description of an interface within a configuration. +pub const Interface = extern struct { + pub const const_descriptor_type: Type = .Interface; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 9); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `Interface`. + descriptor_type: Type = .Interface, + /// ID of this interface. + interface_number: u8, + /// Allows a single `interface_number` to have several alternate interface + /// settings, where each alternate increments this field. Normally there's + /// only one, and `alternate_setting` is zero. + alternate_setting: u8, + /// Number of endpoint descriptors in this interface. + num_endpoints: u8, + /// Interface class code, distinguishing the type of interface. + interface_class: u8, + /// Interface subclass code, refining the class of interface. + interface_subclass: u8, + /// Protocol within the interface class/subclass. + interface_protocol: u8, + /// Index of interface name within string descriptor table. + interface_s: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +/// USB interface association descriptor (IAD) allows the device to group interfaces that belong to a function. +pub const InterfaceAssociation = extern struct { + pub const const_descriptor_type: Type = .InterfaceAssociation; + + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 8); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `InterfaceAssociation`. + descriptor_type: Type = .InterfaceAssociation, + // First interface number of the set of interfaces that follow this + // descriptor. + first_interface: u8, + // The number of interfaces that follow this descriptor that are considered + // associated. + interface_count: u8, + // The interface class used for associated interfaces. + function_class: u8, + // The interface subclass used for associated interfaces. + function_subclass: u8, + // The interface protocol used for associated interfaces. + function_protocol: u8, + // Index of the string descriptor describing the associated interfaces. + function: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig new file mode 100644 index 000000000..c8c6d5b25 --- /dev/null +++ b/core/src/core/usb/descriptor/cdc.zig @@ -0,0 +1,196 @@ +const std = @import("std"); +const usb = @import("../../usb.zig"); + +const assert = std.debug.assert; +const descriptor = usb.descriptor; +const Type = descriptor.Type; +const types = usb.types; + +pub const SubType = enum(u8) { + Header = 0x00, + CallManagement = 0x01, + AbstractControlModel = 0x02, + Union = 0x06, +}; + +pub const Header = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `Header`. + descriptor_subtype: SubType = .Header, + // USB Class Definitions for Communication Devices Specification release + // number in binary-coded decimal. Typically 0x01_10. + bcd_cdc: types.U16Le, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +pub const CallManagement = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + length: u8 = 5, + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `CallManagement`. + descriptor_subtype: SubType = .CallManagement, + // Capabilities. Should be 0x00 for use as a serial device. + capabilities: u8, + // Data interface number. + data_interface: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +pub const AbstractControlModel = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 4); + } + + length: u8 = @sizeOf(@This()), + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `ACM`. + descriptor_subtype: SubType = .AbstractControlModel, + // Capabilities. Should be 0x02 for use as a serial device. + capabilities: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +pub const Union = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + length: u8 = 5, + // Type of this descriptor, must be `ClassSpecific`. + descriptor_type: Type = .CsInterface, + // Subtype of this descriptor, must be `Union`. + descriptor_subtype: SubType = .Union, + // The interface number of the communication or data class interface + // designated as the master or controlling interface for the union. + master_interface: u8, + // The interface number of the first slave or associated interface in the + // union. + slave_interface_0: u8, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + +pub const Template = extern struct { + desc1: descriptor.InterfaceAssociation, + desc2: descriptor.Interface, + desc3: Header, + desc4: CallManagement, + desc5: AbstractControlModel, + desc6: Union, + desc7: descriptor.Endpoint, + desc8: descriptor.Interface, + desc9: descriptor.Endpoint, + desc10: descriptor.Endpoint, + + pub fn create( + interface_number: u8, + string_index: u8, + endpoint_in_notifi: types.Endpoint.Num, + endpoint_notifi_size: u16, + endpoint_out: types.Endpoint.Num, + endpoint_in: types.Endpoint.Num, + endpoint_size: u16, + ) @This() { + const ret: @This() = .{ + .desc1 = .{ + .first_interface = interface_number, + .interface_count = 2, + .function_class = 2, + .function_subclass = 2, + .function_protocol = 0, + .function = 0, + }, + .desc2 = .{ + .interface_number = interface_number, + .alternate_setting = 0, + .num_endpoints = 1, + .interface_class = 2, + .interface_subclass = 2, + .interface_protocol = 0, + .interface_s = string_index, + }, + .desc3 = .{ + .descriptor_type = .CsInterface, + .descriptor_subtype = .Header, + .bcd_cdc = .from(0x0120), + }, + .desc4 = .{ + .descriptor_type = .CsInterface, + .descriptor_subtype = .CallManagement, + .capabilities = 0, + .data_interface = interface_number + 1, + }, + .desc5 = .{ + .descriptor_type = .CsInterface, + .descriptor_subtype = .AbstractControlModel, + .capabilities = 6, + }, + .desc6 = .{ + .descriptor_type = .CsInterface, + .descriptor_subtype = .Union, + .master_interface = interface_number, + .slave_interface_0 = interface_number + 1, + }, + .desc7 = .{ + .endpoint = .in(endpoint_in_notifi), + .attributes = @intFromEnum(types.TransferType.Interrupt), + .max_packet_size = .from(endpoint_notifi_size), + .interval = 16, + }, + .desc8 = .{ + .interface_number = interface_number + 1, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 10, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = 0, + }, + .desc9 = .{ + .endpoint = .out(endpoint_out), + .attributes = @intFromEnum(types.TransferType.Bulk), + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + .desc10 = .{ + .endpoint = .in(endpoint_in), + .attributes = @intFromEnum(types.TransferType.Bulk), + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + }; + @compileLog(ret.serialize()); + @compileLog(ret.serialize(usb.templates.DescriptorsConfigTemplates.cdc_descriptor(interface_number, string_index, endpoint_in_notifi, endpoint_notifi_size, endpoint_out, endpoint_in, endpoint_size))); + return ret; + } + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig new file mode 100644 index 000000000..e69de29bb diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 345916d58..544246a27 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -45,6 +45,7 @@ const std = @import("std"); const assert = std.debug.assert; const usb = @import("../usb.zig"); +const descriptor = usb.descriptor; const types = usb.types; const DescType = types.DescType; const bos = usb.utils.BosConfig; @@ -491,11 +492,26 @@ pub const ReportDescriptorKeyboard = hid_usage_page(UsageTable.desktop) ++ hid_u ++ hid_collection_end(); pub const HidClassDriver = struct { + pub const num_interfaces = 1; + ep_in: types.Endpoint.Num = .ep0, ep_out: types.Endpoint.Num = .ep0, hid_descriptor: []const u8 = &.{}, report_descriptor: []const u8, + pub fn config_descriptor(string_ids: anytype, endpoints: anytype) []const u8 { + return &usb.templates.DescriptorsConfigTemplates.hid_in_out_descriptor( + 0, + string_ids.name, + 0, + ReportDescriptorGenericInOut.len, + endpoints.main, + endpoints.main, + 64, + 0, + ); + } + /// This function is called when the host chooses a configuration that contains this driver. pub fn mount(_: *@This(), _: usb.ControllerInterface) void {} @@ -503,7 +519,7 @@ pub const HidClassDriver = struct { var self: *@This() = @ptrCast(@alignCast(ptr)); var curr_cfg = cfg; - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (bos.try_get_desc_as(descriptor.Interface, curr_cfg)) |desc_itf| { if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return error.UnsupportedInterfaceClassType; } else { return error.ExpectedInterfaceDescriptor; @@ -518,7 +534,7 @@ pub const HidClassDriver = struct { } for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + if (bos.try_get_desc_as(descriptor.Endpoint, curr_cfg)) |desc_ep| { switch (desc_ep.endpoint.dir) { .In => self.ep_in = desc_ep.endpoint.num, .Out => self.ep_out = desc_ep.endpoint.num, diff --git a/core/src/core/usb/templates.zig b/core/src/core/usb/templates.zig index 86b756d5e..add268b65 100644 --- a/core/src/core/usb/templates.zig +++ b/core/src/core/usb/templates.zig @@ -1,57 +1,59 @@ -const types = @import("types.zig"); -const hid = @import("hid.zig"); -const cdc = @import("cdc.zig"); +const usb = @import("../usb.zig"); +const cdc = usb.cdc; +const descriptor = usb.descriptor; +const hid = usb.hid; +const types = usb.types; const Ep = types.Endpoint; pub const DescriptorsConfigTemplates = struct { pub const config_descriptor_len = 9; - pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: u8, max_current: types.ConfigurationDescriptor.MaxCurrent) [9]u8 { - const desc1 = types.ConfigurationDescriptor{ .total_length = total_len, .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = 0b01000000 | attributes, .max_current = max_current }; + pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: descriptor.Configuration.Attributes, max_current: descriptor.Configuration.MaxCurrent) [9]u8 { + const desc1 = descriptor.Configuration{ .total_length = .from(total_len), .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = attributes, .max_current = max_current }; return desc1.serialize(); } pub const cdc_descriptor_len = 8 + 9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7; pub fn cdc_descriptor(interface_number: u8, string_index: u8, endpoint_in_notifi: Ep.Num, endpoint_notifi_size: u16, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16) [cdc_descriptor_len]u8 { - const desc1 = types.InterfaceAssociationDescriptor{ .first_interface = interface_number, .interface_count = 2, .function_class = 2, .function_subclass = 2, .function_protocol = 0, .function = 0 }; - const desc2 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, .interface_subclass = 2, .interface_protocol = 0, .interface_s = string_index }; - const desc3 = cdc.CdcHeaderDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .Header, .bcd_cdc = 0x0120 }; - const desc4 = cdc.CdcCallManagementDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1 }; - const desc5 = cdc.CdcAcmDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .ACM, .capabilities = 6 }; - const desc6 = cdc.CdcUnionDescriptor{ .descriptor_type = .CsInterface, .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1 }; - const desc7 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in_notifi), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_notifi_size, .interval = 16 }; - const desc8 = types.InterfaceDescriptor{ .interface_number = interface_number + 1, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, .interface_subclass = 0, .interface_protocol = 0, .interface_s = 0 }; - const desc9 = types.EndpointDescriptor{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - const desc10 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; + const desc1 = descriptor.InterfaceAssociation{ .first_interface = interface_number, .interface_count = 2, .function_class = 2, .function_subclass = 2, .function_protocol = 0, .function = 0 }; + const desc2 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, .interface_subclass = 2, .interface_protocol = 0, .interface_s = string_index }; + const desc3 = descriptor.cdc.Header{ .descriptor_type = .CsInterface, .descriptor_subtype = .Header, .bcd_cdc = .from(0x0120) }; + const desc4 = descriptor.cdc.CallManagement{ .descriptor_type = .CsInterface, .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1 }; + const desc5 = descriptor.cdc.AbstractControlModel{ .descriptor_type = .CsInterface, .descriptor_subtype = .AbstractControlModel, .capabilities = 6 }; + const desc6 = descriptor.cdc.Union{ .descriptor_type = .CsInterface, .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1 }; + const desc7 = descriptor.Endpoint{ .endpoint = .in(endpoint_in_notifi), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = .from(endpoint_notifi_size), .interval = 16 }; + const desc8 = descriptor.Interface{ .interface_number = interface_number + 1, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, .interface_subclass = 0, .interface_protocol = 0, .interface_s = 0 }; + const desc9 = descriptor.Endpoint{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; + const desc10 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize() ++ desc5.serialize() ++ desc6.serialize() ++ desc7.serialize() ++ desc8.serialize() ++ desc9.serialize() ++ desc10.serialize(); } pub const hid_in_descriptor_len = 9 + 9 + 7; pub fn hid_in_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in: Ep.Num, endpoint_size: u16, endpoint_interval: u16) [hid_in_descriptor_len]u8 { - const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; + const desc1 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; + const desc3 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); } pub const hid_in_out_descriptor_len = 9 + 9 + 7 + 7; pub fn hid_in_out_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16, endpoint_interval: u16) [hid_in_out_descriptor_len]u8 { - const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; + const desc1 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = types.EndpointDescriptor{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; - const desc4 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; + const desc3 = descriptor.Endpoint{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = .from(endpoint_size), .interval = endpoint_interval }; + const desc4 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = .from(endpoint_size), .interval = endpoint_interval }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize(); } pub const vendor_descriptor_len = 9 + 7 + 7; pub fn vendor_descriptor(interface_number: u8, string_index: u8, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16) [vendor_descriptor_len]u8 { - const desc1 = types.InterfaceDescriptor{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xff, .interface_subclass = 0, .interface_protocol = 0, .interface_s = string_index }; - const desc2 = types.EndpointDescriptor{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; - const desc3 = types.EndpointDescriptor{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 }; + const desc1 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xff, .interface_subclass = 0, .interface_protocol = 0, .interface_s = string_index }; + const desc2 = descriptor.Endpoint{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; + const desc3 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); } }; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 028a4a644..a1c0e276a 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -1,22 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; -pub const DescType = enum(u8) { - Device = 0x01, - Config = 0x02, - String = 0x03, - Interface = 0x04, - Endpoint = 0x05, - DeviceQualifier = 0x06, - InterfaceAssociation = 0x0b, - CsDevice = 0x21, - CsConfig = 0x22, - CsString = 0x23, - CsInterface = 0x24, - CsEndpoint = 0x25, - _, -}; - +/// Class of device, giving a broad functional area. pub const ClassCode = enum(u8) { Unspecified = 0x00, Audio = 0x01, @@ -46,28 +31,6 @@ pub const ClassCode = enum(u8) { _, }; -pub const BcdUsb = extern struct { - major: u8, - minor: u8, - - pub const v1_1: @This() = .{ .major = 1, .minor = 1 }; - pub const v2_0: @This() = .{ .major = 2, .minor = 0 }; -}; - -pub const BcdDevice = extern struct { - major: u8, - minor: u8, -}; - -pub const DeviceTriple = extern struct { - /// Class of device, giving a broad functional area. - class: ClassCode, - /// Subclass of device, refining the class. - subclass: u8, - /// Protocol within the subclass. - protocol: u8, -}; - /// Types of transfer that can be indicated by the `attributes` field on `EndpointDescriptor`. pub const TransferType = enum(u2) { Control = 0, @@ -235,244 +198,18 @@ pub const SetupPacket = extern struct { length: u16, }; -/// Describes an endpoint within an interface -pub const EndpointDescriptor = extern struct { - pub const const_descriptor_type = DescType.Endpoint; +pub const U16Le = extern struct { + lo: u8, + hi: u8, - length: u8 = 7, - /// Type of this descriptor, must be `Endpoint`. - descriptor_type: DescType = const_descriptor_type, - /// Address of this endpoint, where the bottom 4 bits give the endpoint - /// number (0..15) and the top bit distinguishes IN (1) from OUT (0). - endpoint: Endpoint, - /// Endpoint attributes; the most relevant part is the bottom 2 bits, which - /// control the transfer type using the values from `TransferType`. - attributes: u8, - /// Maximum packet size this endpoint can accept/produce. - max_packet_size: u16 align(1), - /// Interval for polling interrupt/isochronous endpoints (which we don't - /// currently support) in milliseconds. - interval: u8, - - pub fn serialize(self: *const @This()) [7]u8 { - var out: [7]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.endpoint.to_address(); - out[3] = self.attributes; - out[4] = @intCast(self.max_packet_size & 0xff); - out[5] = @intCast((self.max_packet_size >> 8) & 0xff); - out[6] = self.interval; - return out; + pub fn from(val: u16) @This() { + return .{ + .lo = @truncate(val), + .hi = @intCast(val >> 8), + }; } -}; - -/// Description of an interface within a configuration. -pub const InterfaceDescriptor = extern struct { - pub const const_descriptor_type = DescType.Interface; - - length: u8 = 9, - /// Type of this descriptor, must be `Interface`. - descriptor_type: DescType = const_descriptor_type, - /// ID of this interface. - interface_number: u8, - /// Allows a single `interface_number` to have several alternate interface - /// settings, where each alternate increments this field. Normally there's - /// only one, and `alternate_setting` is zero. - alternate_setting: u8, - /// Number of endpoint descriptors in this interface. - num_endpoints: u8, - /// Interface class code, distinguishing the type of interface. - interface_class: u8, - /// Interface subclass code, refining the class of interface. - interface_subclass: u8, - /// Protocol within the interface class/subclass. - interface_protocol: u8, - /// Index of interface name within string descriptor table. - interface_s: u8, - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.interface_number; - out[3] = self.alternate_setting; - out[4] = self.num_endpoints; - out[5] = self.interface_class; - out[6] = self.interface_subclass; - out[7] = self.interface_protocol; - out[8] = self.interface_s; - return out; - } -}; - -/// USB interface association descriptor (IAD) allows the device to group interfaces that belong to a function. -pub const InterfaceAssociationDescriptor = extern struct { - pub const const_descriptor_type = DescType.InterfaceAssociation; - - length: u8 = 8, - // Type of this descriptor, must be `InterfaceAssociation`. - descriptor_type: DescType = const_descriptor_type, - // First interface number of the set of interfaces that follow this - // descriptor. - first_interface: u8, - // The number of interfaces that follow this descriptor that are considered - // associated. - interface_count: u8, - // The interface class used for associated interfaces. - function_class: u8, - // The interface subclass used for associated interfaces. - function_subclass: u8, - // The interface protocol used for associated interfaces. - function_protocol: u8, - // Index of the string descriptor describing the associated interfaces. - function: u8, - - pub fn serialize(self: *const @This()) [8]u8 { - var out: [8]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.first_interface; - out[3] = self.interface_count; - out[4] = self.function_class; - out[5] = self.function_subclass; - out[6] = self.function_protocol; - out[7] = self.function; - return out; - } -}; - -/// Description of a single available device configuration. -pub const ConfigurationDescriptor = extern struct { - pub const const_descriptor_type = DescType.Config; - - /// Maximum device power consumption in units of 2mA. - pub const MaxCurrent = extern struct { - multiple_of_2ma: u8, - - pub fn from_ma(ma: u9) @This() { - return .{ .multiple_of_2ma = @intCast((ma +| 1) >> 1) }; - } - }; - - length: u8 = 9, - /// Type of this descriptor, must be `Config`. - descriptor_type: DescType = const_descriptor_type, - /// Total length of all descriptors in this configuration, concatenated. - /// This will include this descriptor, plus at least one interface - /// descriptor, plus each interface descriptor's endpoint descriptors. - total_length: u16 align(1), - /// Number of interface descriptors in this configuration. - num_interfaces: u8, - /// Number to use when requesting this configuration via a - /// `SetConfiguration` request. - configuration_value: u8, - /// Index of this configuration's name in the string descriptor table. - configuration_s: u8, - /// Bit set of device attributes: - /// - /// - Bit 7 should be set (indicates that device can be bus powered in USB - /// 1.0). - /// - Bit 6 indicates that the device can be self-powered. - /// - Bit 5 indicates that the device can signal remote wakeup of the host - /// (like a keyboard). - /// - The rest are reserved and should be zero. - attributes: u8, - max_current: MaxCurrent, - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.total_length & 0xff); - out[3] = @intCast((self.total_length >> 8) & 0xff); - out[4] = self.num_interfaces; - out[5] = self.configuration_value; - out[6] = self.configuration_s; - out[7] = self.attributes; - out[8] = self.max_current.multiple_of_2ma; - return out; - } -}; - -/// Describes a device. This is the most broad description in USB and is -/// typically the first thing the host asks for. -pub const DeviceDescriptor = extern struct { - length: u8 = 18, - /// Type of this descriptor, must be `Device`. - descriptor_type: DescType = .Device, - bcd_usb: BcdUsb, - device_triple: DeviceTriple, - /// Maximum unit of data this device can move. - max_packet_size0: u8, - /// ID of product vendor. - vendor: u16 align(1), - /// ID of product. - product: u16 align(1), - /// Device version number, as BCD again. - bcd_device: u16 align(1), - /// Index of manufacturer name in string descriptor table. - manufacturer_s: u8, - /// Index of product name in string descriptor table. - product_s: u8, - /// Index of serial number in string descriptor table. - serial_s: u8, - /// Number of configurations supported by this device. - num_configurations: u8, - - pub fn serialize(self: *const @This()) [18]u8 { - var out: [18]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.bcd_usb.minor; - out[3] = self.bcd_usb.major; - out[4] = @intFromEnum(self.device_triple.class); - out[5] = self.device_triple.subclass; - out[6] = self.device_triple.protocol; - out[7] = self.max_packet_size0; - out[8] = @intCast(self.vendor & 0xff); - out[9] = @intCast((self.vendor >> 8) & 0xff); - out[10] = @intCast(self.product & 0xff); - out[11] = @intCast((self.product >> 8) & 0xff); - out[12] = @intCast(self.bcd_device & 0xff); - out[13] = @intCast((self.bcd_device >> 8) & 0xff); - out[14] = self.manufacturer_s; - out[15] = self.product_s; - out[16] = self.serial_s; - out[17] = self.num_configurations; - return out; - } -}; - -/// USB Device Qualifier Descriptor -/// This descriptor is mostly the same as the DeviceDescriptor -pub const DeviceQualifierDescriptor = extern struct { - pub const const_descriptor_type = DescType.DeviceQualifier; - - length: u8 = 10, - /// Type of this descriptor, must be `Device`. - descriptor_type: DescType = const_descriptor_type, - bcd_usb: BcdUsb, - device_triple: DeviceTriple, - /// Maximum unit of data this device can move. - max_packet_size0: u8, - /// Number of configurations supported by this device. - num_configurations: u8, - /// Reserved for future use; must be 0 - reserved: u8 = 0, - pub fn serialize(self: *const @This()) [10]u8 { - var out: [10]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.bcd_usb.minor; - out[3] = self.bcd_usb.major; - out[4] = @intFromEnum(self.device_triple.class); - out[5] = self.device_triple.subclass; - out[6] = self.device_triple.protocol; - out[7] = self.max_packet_size0; - out[8] = self.num_configurations; - out[9] = self.reserved; - return out; + pub fn into(this: @This()) u16 { + return (@as(u16, this.hi) << 8) | @as(u16, this.lo); } }; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 5fcf2eb77..e89903335 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -5,59 +5,30 @@ const rp2xxx = microzig.hal; const flash = rp2xxx.flash; 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 baud_rate = 115200; const uart_tx_pin = gpio.num(0); -const usb_templates = usb.templates.DescriptorsConfigTemplates; -const usb_packet_size = 64; -const usb_config_len = usb_templates.config_descriptor_len + usb_templates.hid_in_out_descriptor_len; -const usb_config_descriptor = - usb_templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, .from_ma(100)) ++ - usb_templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, .ep1, .ep1, usb_packet_size, 0); - // This is our device configuration -const UsbDev = usb.Usb(.{ +const UsbDev = microzig.core.usb.Usb(.{ .Device = rp2xxx.usb.Usb(.{}), - .Drivers = struct { - hid: usb.hid.HidClassDriver, + .attributes = .{ .self_powered = true }, + .device_triple = .{ + .class = .Miscellaneous, + .subclass = 2, + .protocol = 1, + }, + .Driver = microzig.core.usb.hid.HidClassDriver, + .driver_endpoints = &.{ + .{ .name = "main", .value = .ep1 }, + }, + .driver_strings = &.{ + .{ .name = "name", .value = "Board HID" }, }, - .descriptors = .create( - .{ - .bcd_usb = .v1_1, - .device_triple = .{ - .class = .Unspecified, - .subclass = 0, - .protocol = 0, - }, - .max_packet_size0 = 64, - .vendor = 0xCafe, - .product = 2, - .bcd_device = 0x0100, - // Those are indices to the descriptor strings - // Make sure to provide enough string descriptors! - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - &usb_config_descriptor, - .English, - &.{ - "Raspberry Pi", - "Pico Test Device", - "cafebabe", - }, - ), - .usb_configurations = .{.create(&.{.{ - .name = "hid", - .driver = usb.hid.HidClassDriver, - }})}, }); -var usb_dev: UsbDev = .init; +var usb: UsbDev = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -71,7 +42,7 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { - usb_dev.drivers_data.hid = .{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut }; + usb.driver_data = .{ .report_descriptor = µzig.core.usb.hid.ReportDescriptorGenericInOut }; // init uart logging uart_tx_pin.set_function(.uart); @@ -86,12 +57,12 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&.{usb_dev.drivers_data.hid.driver()}); + usb.init_device(&.{usb.driver_data.driver()}); 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.interface().task(); + usb.interface().task(); new = time.get_time_since_boot().to_us(); if (new - old > 500000) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 5f4a1c210..54d34038a 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -10,54 +10,21 @@ const usb = microzig.core.usb; const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); const baud_rate = 115200; -const uart_tx_pin = gpio.num(0); +const uart_tx_pin = gpio.num(12); const uart_rx_pin = gpio.num(1); -const usb_templates = usb.templates.DescriptorsConfigTemplates; -const usb_config_len = usb_templates.config_descriptor_len + usb_templates.cdc_descriptor_len; -const usb_config_descriptor = - usb_templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, .from_ma(100)) ++ - usb_templates.cdc_descriptor(0, 4, .ep1, 8, .ep2, .ep2, 64); - // This is our device configuration const UsbDev = usb.Usb(.{ .Device = rp2xxx.usb.Usb(.{}), - .Drivers = struct { - serial: usb.cdc.CdcClassDriver, + .attributes = .{ .self_powered = true }, + .Driver = microzig.core.usb.cdc.CdcClassDriver, + .driver_endpoints = &.{ + .{ .name = "notifi", .value = .ep1 }, + .{ .name = "data", .value = .ep2 }, + }, + .driver_strings = &.{ + .{ .name = "name", .value = "Board CDC" }, }, - .descriptors = .create( - .{ - .bcd_usb = .v1_1, - .device_triple = .{ - .class = .Miscellaneous, - .subclass = 2, - .protocol = 1, - }, - .max_packet_size0 = 64, - .vendor = 0x2E8A, - .product = 0x000a, - .bcd_device = 0x0100, - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 0, - .num_configurations = 1, - }, - &usb_config_descriptor, - .English, - &.{ - "Raspberry Pi", - "Pico Test Device", - "someserial", - "Board CDC", - }, - ), - .usb_configurations = .{.create(&.{.{ - .name = "serial", - .driver = microzig.core.usb.cdc.CdcClassDriver, - // .endpoints_in = .{ .ep1 = "notifi", .ep2 = "data" }, - // .endpoints_out = .{ .ep2 = "data" }, - .strings = &.{.{ .name = "cdc", .value = "Board CDC" }}, - }})}, }); var usb_dev: UsbDev = .init; @@ -74,7 +41,7 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { - usb_dev.drivers_data.serial = .{}; + usb_dev.driver_data = .{}; led.set_function(.sio); led.set_direction(.out); @@ -91,7 +58,7 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&.{usb_dev.drivers_data.serial.driver()}); + usb_dev.init_device(&.{usb_dev.driver_data.driver()}); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -109,16 +76,16 @@ pub fn main() !void { var tx_buf: [1024]u8 = undefined; const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); - usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), text); + usb_dev.driver_data.writeAll(usb_dev.interface(), text); } // read and print host command if present var rx_buf: [64]u8 = undefined; - const len = usb_dev.drivers_data.serial.read(usb_dev.interface(), &rx_buf); + const len = usb_dev.driver_data.read(usb_dev.interface(), &rx_buf); if (len > 0) { - usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), "Your message to me was: '"); - usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), rx_buf[0..len]); - usb_dev.drivers_data.serial.writeAll(usb_dev.interface(), "'\r\n"); + usb_dev.driver_data.writeAll(usb_dev.interface(), "Your message to me was: '"); + usb_dev.driver_data.writeAll(usb_dev.interface(), rx_buf[0..len]); + usb_dev.driver_data.writeAll(usb_dev.interface(), "'\r\n"); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 4270ab0fe..a0d97b482 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -92,8 +92,17 @@ pub const DpramAllocatorBump = struct { pub fn Usb(comptime config: UsbConfig) type { return struct { pub const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; - // TODO: Support other buffer sizes. - const max_transfer_size = 64; + pub const max_transfer_size = 64; // TODO: Support other buffer sizes. + pub const default_strings: usb.Config.Strings = .{ + .manufacturer = "Raspberry Pi", + .product = "Pico Test Device", + .serial = "someserial", + }; + pub const bcd_usb = 0x02_00; + pub const default_vidpid: usb.Config.VidPid = .{ + .vendor = 0x2E8A, + .product = 0x000a, + }; const HardwareEndpoint = packed struct(u7) { const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = From bd25ca3224343425bbb00a966322e256a0b5f07e Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 21 Aug 2025 17:06:18 +0200 Subject: [PATCH 12/33] remove usb driver interface --- core/src/core/usb.zig | 47 +----- core/src/core/usb/cdc.zig | 20 +-- core/src/core/usb/descriptor/cdc.zig | 18 +-- core/src/core/usb/hid.zig | 152 +++++++----------- .../rp2xxx/src/rp2040_only/usb_hid.zig | 4 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 4 +- 6 files changed, 74 insertions(+), 171 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 0ea0815ee..d49f26926 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -70,36 +70,6 @@ pub const ControllerInterface = struct { } }; -/// USB Class driver interface -pub const DriverInterface = struct { - pub const Vtable = struct { - open: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, cfg: []const u8) anyerror!usize, - class_control: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool, - on_tx_ready: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, data: []u8) void, - on_data_rx: *const fn (ptr: *anyopaque, ctrl: ControllerInterface, data: []const u8) void, - }; - - ptr: *anyopaque, - vtable: *const Vtable, - - /// Driver open (set config) operation. Must return length of consumed driver config data. - pub fn open(this: @This(), ctrl: ControllerInterface, cfg: []const u8) anyerror!usize { - return this.vtable.open(this.ptr, ctrl, cfg); - } - - pub fn class_control(this: @This(), ctrl: ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - return this.vtable.class_control(this.ptr, ctrl, stage, setup); - } - - pub fn on_tx_ready(this: @This(), ctrl: ControllerInterface, data: []u8) void { - return this.vtable.on_tx_ready(this.ptr, ctrl, data); - } - - pub fn on_data_rx(this: @This(), ctrl: ControllerInterface, len: []const u8) void { - return this.vtable.on_data_rx(this.ptr, ctrl, len); - } -}; - pub const Config = struct { pub fn NameValue(T: type) type { return struct { @@ -159,7 +129,7 @@ pub const Config = struct { /// * `get_EPBIter(*const DeviceConfiguration) EPBIter` - Return an endpoint buffer iterator. Each call to next returns an unhandeled endpoint buffer with data. How next is implemented depends on the system. /// The functions must be grouped under the same name space and passed to the fuction at compile time. /// The functions will be accessible to the user through the `Dev` field. -pub fn Usb(comptime config: Config) type { +pub fn Controller(comptime config: Config) type { return struct { pub const max_packet_size = config.Device.max_packet_size; @@ -255,12 +225,11 @@ pub fn Usb(comptime config: Config) type { }; }; - drivers: ?[]const DriverInterface, - driver_by_interface: [16]?DriverInterface, - driver_by_endpoint_in: [config.Device.max_endpoints_count]?DriverInterface, - driver_by_endpoint_out: [config.Device.max_endpoints_count]?DriverInterface, + driver_by_interface: [16]?*config.Driver, + driver_by_endpoint_in: [config.Device.max_endpoints_count]?*config.Driver, + driver_by_endpoint_out: [config.Device.max_endpoints_count]?*config.Driver, // Class driver associated with last setup request if any - driver: ?DriverInterface, + driver: ?*config.Driver, // When the host gives us a new address, we can't just slap it into // registers right away, because we have to do an acknowledgement step using // our _old_ address. @@ -274,7 +243,6 @@ pub fn Usb(comptime config: Config) type { driver_data: config.Driver, pub const init: @This() = .{ - .drivers = null, .driver_by_interface = @splat(null), .driver_by_endpoint_in = @splat(null), .driver_by_endpoint_out = @splat(null), @@ -297,9 +265,8 @@ pub fn Usb(comptime config: Config) type { } /// Initialize the usb device using the given configuration - pub fn init_device(this: *@This(), drivers: []const DriverInterface) void { + pub fn init_device(_: *@This()) void { config.Device.usb_init_device(); - this.drivers = drivers; } fn control_transfer(ptr: *anyopaque, data: []const u8) void { @@ -384,7 +351,7 @@ pub fn Usb(comptime config: Config) type { } const desc_itf = BosConfig.get_desc_as(descriptor.Interface, curr_bos_cfg); - var drv = this.drivers.?[curr_drv_idx]; + var drv = &this.driver_data; const drv_cfg_len = drv.open(this.interface(), curr_bos_cfg) catch unreachable; for (0..assoc_itf_count) |itf_offset| { diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index b32909416..6cf04af9b 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -112,7 +112,7 @@ pub const CdcClassDriver = struct { } } - fn open(ptr: *anyopaque, controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { + pub fn open(ptr: *@This(), controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { var this: *@This() = @ptrCast(@alignCast(ptr)); var curr_cfg = cfg; @@ -155,7 +155,7 @@ pub const CdcClassDriver = struct { return cfg.len - curr_cfg.len; } - fn class_control(ptr: *anyopaque, controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + pub fn class_control(ptr: *@This(), controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { var this: *@This() = @ptrCast(@alignCast(ptr)); if (stage != .Setup) return true; @@ -169,25 +169,13 @@ pub const CdcClassDriver = struct { return true; } - fn on_tx_ready(ptr: *anyopaque, _: usb.ControllerInterface, data: []u8) void { + pub fn on_tx_ready(ptr: *@This(), _: usb.ControllerInterface, data: []u8) void { var this: *@This() = @ptrCast(@alignCast(ptr)); this.tx_buf = data; } - fn on_data_rx(ptr: *anyopaque, _: usb.ControllerInterface, data: []const u8) void { + pub fn on_data_rx(ptr: *@This(), _: usb.ControllerInterface, data: []const u8) void { var this: *@This() = @ptrCast(@alignCast(ptr)); this.rx_buf = data; } - - pub fn driver(this: *@This()) usb.DriverInterface { - return .{ - .ptr = this, - .vtable = comptime &.{ - .open = &open, - .class_control = &class_control, - .on_tx_ready = &on_tx_ready, - .on_data_rx = &on_data_rx, - }, - }; - } }; diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index c8c6d5b25..145fe30ea 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -63,7 +63,7 @@ pub const AbstractControlModel = extern struct { length: u8 = @sizeOf(@This()), // Type of this descriptor, must be `ClassSpecific`. descriptor_type: Type = .CsInterface, - // Subtype of this descriptor, must be `ACM`. + // Subtype of this descriptor, must be `AbstractControlModel`. descriptor_subtype: SubType = .AbstractControlModel, // Capabilities. Should be 0x02 for use as a serial device. capabilities: u8, @@ -135,25 +135,13 @@ pub const Template = extern struct { .interface_protocol = 0, .interface_s = string_index, }, - .desc3 = .{ - .descriptor_type = .CsInterface, - .descriptor_subtype = .Header, - .bcd_cdc = .from(0x0120), - }, + .desc3 = .{ .bcd_cdc = .from(0x0120) }, .desc4 = .{ - .descriptor_type = .CsInterface, - .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1, }, - .desc5 = .{ - .descriptor_type = .CsInterface, - .descriptor_subtype = .AbstractControlModel, - .capabilities = 6, - }, + .desc5 = .{ .capabilities = 6 }, .desc6 = .{ - .descriptor_type = .CsInterface, - .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1, }, diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 544246a27..07b3dddac 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -43,11 +43,11 @@ const std = @import("std"); const assert = std.debug.assert; +const enumFromInt = std.meta.intToEnum; const usb = @import("../usb.zig"); const descriptor = usb.descriptor; const types = usb.types; -const DescType = types.DescType; const bos = usb.utils.BosConfig; // +++++++++++++++++++++++++++++++++++++++++++++++++ @@ -75,14 +75,10 @@ const bos = usb.utils.BosConfig; // | ReportDescriptor | | PhysicalDesc | // ----------------------- --------------------- -pub const HidDescType = enum(u8) { +pub const SubType = enum(u8) { Hid = 0x21, Report = 0x22, Physical = 0x23, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } }; pub const HidRequestType = enum(u8) { @@ -92,19 +88,15 @@ pub const HidRequestType = enum(u8) { SetReport = 0x09, SetIdle = 0x0a, SetProtocol = 0x0b, - - pub fn from_u8(v: u8) ?@This() { - return std.meta.intToEnum(@This(), v) catch null; - } }; /// USB HID descriptor pub const HidDescriptor = struct { - pub const const_descriptor_type = HidDescType.Hid; + pub const const_descriptor_type = SubType.Hid; length: u8 = 9, /// Type of this descriptor - descriptor_type: HidDescType = const_descriptor_type, + descriptor_type: SubType = const_descriptor_type, /// Numeric expression identifying the HID Class Specification release bcd_hid: u16 align(1), /// Numeric expression identifying country code of the localized hardware @@ -112,7 +104,7 @@ pub const HidDescriptor = struct { /// Numeric expression specifying the number of class descriptors num_descriptors: u8, /// Type of HID class report - report_type: HidDescType = HidDescType.Report, + report_type: SubType = SubType.Report, /// The total size of the Report descriptor report_length: u16 align(1), @@ -515,7 +507,7 @@ pub const HidClassDriver = struct { /// This function is called when the host chooses a configuration that contains this driver. pub fn mount(_: *@This(), _: usb.ControllerInterface) void {} - fn open(ptr: *anyopaque, controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { + pub fn open(ptr: *@This(), controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { var self: *@This() = @ptrCast(@alignCast(ptr)); var curr_cfg = cfg; @@ -547,97 +539,65 @@ pub const HidClassDriver = struct { return cfg.len - curr_cfg.len; } - fn class_control(ptr: *anyopaque, controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + pub fn class_control(ptr: *@This(), controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { const self: *@This() = @ptrCast(@alignCast(ptr)); switch (setup.request_type.type) { - .Standard => { - if (stage == .Setup) { - const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff)); - const request_code = types.SetupRequest.from_u8(setup.request); - - if (hid_desc_type == null or request_code == null) { - return false; - } - - if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) { - controller.control_transfer(self.hid_descriptor[0..@min(self.hid_descriptor.len, setup.length)]); - } else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) { - controller.control_transfer(self.report_descriptor[0..@min(self.report_descriptor.len, setup.length)]); - } else { - return false; - } - } - }, - .Class => { - const hid_request_type = HidRequestType.from_u8(setup.request); - if (hid_request_type == null) return false; - - switch (hid_request_type.?) { - .SetIdle => { - if (stage == .Setup) { - // TODO: The host is attempting to limit bandwidth by requesting that - // the device only return report data when its values actually change, - // or when the specified duration elapses. In practice, the device can - // still send reports as often as it wants, but for completeness this - // should be implemented eventually. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(); - } - }, - .SetProtocol => { - if (stage == .Setup) { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, - // or vice versa. - // - // For now, this request is ACKed without doing anything; in practice, - // the OS will reuqest the report protocol anyway, so usually only one format is needed. - // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), - // our device might not work in a limited BIOS environment. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(); - } - }, - .SetReport => { - if (stage == .Setup) { - // TODO: This request sends a feature or output report to the device, - // e.g. turning on the caps lock LED. This must be handled in an - // application-specific way, so notify the application code of the event. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(); - } - }, - else => { - return false; - }, - } + .Standard => if (stage == .Setup) { + const hid_desc_type = enumFromInt(SubType, (setup.value >> 8) & 0xff) catch return false; + const request_code = enumFromInt(types.SetupRequest, setup.request) catch return false; + + if (request_code != .GetDescriptor) return false; + + const data = switch (hid_desc_type) { + .Hid => self.hid_descriptor, + .Report => self.report_descriptor, + else => return false, + }; + + controller.control_transfer(data[0..@min(data.len, setup.length)]); }, - else => { - return false; + .Class => switch (enumFromInt(HidRequestType, setup.request) catch return false) { + .SetIdle => if (stage == .Setup) { + // TODO: The host is attempting to limit bandwidth by requesting that + // the device only return report data when its values actually change, + // or when the specified duration elapses. In practice, the device can + // still send reports as often as it wants, but for completeness this + // should be implemented eventually. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(); + }, + .SetProtocol => if (stage == .Setup) { + // TODO: The device should switch the format of its reports from the + // boot keyboard/mouse protocol to the format described in its report descriptor, + // or vice versa. + // + // For now, this request is ACKed without doing anything; in practice, + // the OS will reuqest the report protocol anyway, so usually only one format is needed. + // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), + // our device might not work in a limited BIOS environment. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(); + }, + .SetReport => if (stage == .Setup) { + // TODO: This request sends a feature or output report to the device, + // e.g. turning on the caps lock LED. This must be handled in an + // application-specific way, so notify the application code of the event. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + controller.control_ack(); + }, + else => return false, }, + else => return false, } - return true; } - fn on_tx_ready(_: *anyopaque, _: usb.ControllerInterface, _: []u8) void {} - fn on_data_rx(_: *anyopaque, _: usb.ControllerInterface, _: []const u8) void {} - - pub fn driver(this: *@This()) usb.DriverInterface { - return .{ - .ptr = this, - .vtable = comptime &.{ - .open = open, - .class_control = class_control, - .on_tx_ready = on_tx_ready, - .on_data_rx = on_data_rx, - }, - }; - } + pub fn on_tx_ready(_: *@This(), _: usb.ControllerInterface, _: []u8) void {} + pub fn on_data_rx(_: *@This(), _: usb.ControllerInterface, _: []const u8) void {} }; test "create hid report item" { diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index e89903335..5da4ea354 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -12,7 +12,7 @@ const baud_rate = 115200; const uart_tx_pin = gpio.num(0); // This is our device configuration -const UsbDev = microzig.core.usb.Usb(.{ +const UsbDev = microzig.core.usb.Controller(.{ .Device = rp2xxx.usb.Usb(.{}), .attributes = .{ .self_powered = true }, .device_triple = .{ @@ -57,7 +57,7 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb.init_device(&.{usb.driver_data.driver()}); + usb.init_device(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 54d34038a..932a6b1d4 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -14,7 +14,7 @@ const uart_tx_pin = gpio.num(12); const uart_rx_pin = gpio.num(1); // This is our device configuration -const UsbDev = usb.Usb(.{ +const UsbDev = usb.Controller(.{ .Device = rp2xxx.usb.Usb(.{}), .attributes = .{ .self_powered = true }, .Driver = microzig.core.usb.cdc.CdcClassDriver, @@ -58,7 +58,7 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(&.{usb_dev.driver_data.driver()}); + usb_dev.init_device(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; From 61b5291771e735bb8fc03b2ac7823e4f8b8f8b06 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 22 Aug 2025 01:41:30 +0200 Subject: [PATCH 13/33] use unserialized descriptors instead of parsing after serializing --- core/src/core/usb.zig | 447 ++++++-------- core/src/core/usb/cdc.zig | 140 +++-- core/src/core/usb/descriptor.zig | 40 +- core/src/core/usb/descriptor/cdc.zig | 105 +--- core/src/core/usb/descriptor/hid.zig | 461 ++++++++++++++ core/src/core/usb/descriptor/vendor.zig | 51 ++ core/src/core/usb/hid.zig | 570 +++--------------- core/src/core/usb/templates.zig | 59 -- core/src/core/usb/utils.zig | 47 -- core/src/core/usb/vendor.zig | 24 - .../rp2xxx/src/rp2040_only/usb_hid.zig | 6 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 22 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 158 +++-- 13 files changed, 1000 insertions(+), 1130 deletions(-) create mode 100644 core/src/core/usb/descriptor/vendor.zig delete mode 100644 core/src/core/usb/templates.zig delete mode 100644 core/src/core/usb/utils.zig delete mode 100644 core/src/core/usb/vendor.zig diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index d49f26926..fcfc86a85 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -1,134 +1,115 @@ -//! Abstract USB device implementation -//! -//! This can be used to setup a USB device. -//! -//! ## Usage -//! -//! 1. Define the functions (`pub const F = struct { ... }`) required by `Usb()` (see below) -//! 2. Call `pub const device = Usb(F)` -//! 3. Define the device configuration (DeviceConfiguration) -//! 4. Initialize the device in main by calling `usb.init_device(device_conf)` -//! 5. Call `usb.task()` within the main loop - const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; const enumFromInt = std.meta.intToEnum; -/// USB primitive types -pub const types = @import("usb/types.zig"); -/// USB Human Interface Device (HID) -pub const hid = @import("usb/hid.zig"); pub const cdc = @import("usb/cdc.zig"); -pub const vendor = @import("usb/vendor.zig"); -pub const utils = @import("usb/utils.zig"); -pub const templates = @import("usb/templates.zig"); pub const descriptor = @import("usb/descriptor.zig"); +pub const hid = @import("usb/hid.zig"); +pub const types = @import("usb/types.zig"); -const FeatureSelector = types.FeatureSelector; -const Dir = types.Dir; -const Endpoint = types.Endpoint; -const SetupRequest = types.SetupRequest; -const BosConfig = utils.BosConfig; +test "tests" { + _ = cdc; + _ = descriptor; + _ = hid; + _ = types; +} + +const EpNum = types.Endpoint.Num; pub const ControllerInterface = struct { const Vtable = struct { task: *const fn (ptr: *anyopaque) void, control_transfer: *const fn (ptr: *anyopaque, data: []const u8) void, - endpoint_open: *const fn (ptr: *anyopaque, ep_desc: []const u8) anyerror!?[]u8, - submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: Endpoint.Num, buffer_end: [*]const u8) void, - endpoint_rx: *const fn (ptr: *anyopaque, ep_out: Endpoint.Num, len: usize) void, + submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, + signal_rx_ready: *const fn (ptr: *anyopaque, ep_out: EpNum, max_len: usize) void, }; - ptr: *anyopaque, vtable: *const Vtable, pub fn task(this: @This()) void { return this.vtable.task(this.ptr); } - pub fn control_transfer(this: @This(), data: []const u8) void { - if (data.len == 0) - std.log.err("data empty", .{}) - else - this.vtable.control_transfer(this.ptr, data); + assert(data.len != 0); + this.vtable.control_transfer(this.ptr, data); } - pub fn control_ack(this: @This()) void { this.vtable.control_transfer(this.ptr, ""); } - - pub fn endpoint_open(this: @This(), ep_desc: []const u8) anyerror!?[]u8 { - return this.vtable.endpoint_open(this.ptr, ep_desc); - } - - pub fn submit_tx_buffer(this: @This(), ep_in: Endpoint.Num, buffer_end: [*]const u8) void { + pub fn submit_tx_buffer(this: @This(), ep_in: EpNum, buffer_end: [*]const u8) void { return this.vtable.submit_tx_buffer(this.ptr, ep_in, buffer_end); } - - pub fn endpoint_rx(this: @This(), ep_out: Endpoint.Num, len: usize) void { - return this.vtable.endpoint_rx(this.ptr, ep_out, len); + pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { + return this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); } }; pub const Config = struct { pub fn NameValue(T: type) type { - return struct { - name: [:0]const u8, - value: T, - }; + return struct { name: [:0]const u8, value: T }; } + /// Vendor ID and product ID combo. + pub const VidPid = struct { product: u16, vendor: u16 }; - /// Vendor id and product id combo. - pub const VidPid = struct { - product: u16, - vendor: u16, - }; - + /// Manufacturer, product and serial number strings. pub const Strings = struct { manufacturer: []const u8, product: []const u8, serial: []const u8, }; - + /// String descriptor language. pub const Language = enum(u16) { English = 0x0409, }; + /// Underlying USB device. Device: type, - device_triple: descriptor.Device.DeviceTriple = .{ - .class = .Unspecified, - .subclass = 0, - .protocol = 0, - }, + /// Class, subclass and protocol of device. + device_triple: descriptor.Device.DeviceTriple = .unspecified, + /// Vendor ID and product ID combo. id: ?VidPid = null, + /// Device version number as Binary Coded Decimal. bcd_device: u16 = 0x01_00, + /// Manufacturer, product and serial number strings. strings: ?Strings = null, + /// Bit set of device attributes. attributes: descriptor.Configuration.Attributes, + /// Maximum device current consumption.. max_current: descriptor.Configuration.MaxCurrent = .from_ma(100), + /// String descriptor language. language: Language = .English, - // Eventually the fields below could be in an array to support multiple configurations. + // Eventually the fields below could be in an array to support multiple drivers. Driver: type, - driver_endpoints: []const NameValue(types.Endpoint.Num), + driver_endpoints: []const NameValue(EpNum), driver_strings: []const NameValue([]const u8), }; -/// Create a USB device +/// And endpoint and its corresponding buffer. +pub const EndpointAndBuffer = union(types.Dir) { + Out: struct { ep_num: EpNum, buffer: []const u8 }, + In: struct { ep_num: EpNum, buffer: []u8 }, +}; + +/// Create a USB device controller. +/// +/// This is an abstract USB device controller implementation that requires +/// the USB device driver to implement a handful of functions to work correctly: /// -/// # Arguments +/// * `usb_init_device() ?[]u8` - Initialize the USB device controller (e.g. enable interrupts, etc.). Returns the ep0 tx buffer, if any. +/// * `set_address(addr: u7) void` - Set device address. +/// * `get_events() anytype` - Returns what events need to be handled (rx/tx completed, bus reset etc., specific to device). +/// * `submit_tx_buffer(ep_in: EpNum, buffer_end: [*]const u8) void` - Send the specified buffer to the host. +/// * `signal_rx_ready(ep_out: EpNum, len: usize) void` - Receive n bytes over the specified endpoint. +/// * `bus_reset_clear() void` - Called after handling a bus reset. /// -/// This is a abstract USB device implementation that requires a handful of functions -/// to work correctly: +/// As well as some declarations: /// -/// * `usb_init_device(*DeviceConfiguration) - Initialize the USB device controller (e.g. enable interrupts, etc.) -/// * `usb_start_tx(*EndpointConfiguration, []const u8)` - Transmit the given bytes over the specified endpoint -/// * `usb_start_rx(*usb.EndpointConfiguration, n: usize)` - Receive n bytes over the specified endpoint -/// * `get_interrupts() InterruptStatus` - Return which interrupts haven't been handled yet -/// * `get_setup_packet() SetupPacket` - Return the USB setup packet received (called if SetupReq received). Make sure to clear the status flag yourself! -/// * `bus_reset() void` - Called on a bus reset interrupt -/// * `set_address(addr: u7) void` - Set the given address -/// * `get_EPBIter(*const DeviceConfiguration) EPBIter` - Return an endpoint buffer iterator. Each call to next returns an unhandeled endpoint buffer with data. How next is implemented depends on the system. -/// The functions must be grouped under the same name space and passed to the fuction at compile time. -/// The functions will be accessible to the user through the `Dev` field. +/// * `max_endpoints_count` - How many endpoints are supported, up to 16. +/// * `max_transfer_size` - How many bytes can be transferred in one packet. +/// * `bcd_usb` - Version of USB specification supported by device. +/// * `default_strings` - Default manufacturer, product and serial (optional). +/// * `default_vid_pid` - Default VID and PID (optional). pub fn Controller(comptime config: Config) type { return struct { pub const max_packet_size = config.Device.max_packet_size; @@ -136,8 +117,7 @@ pub fn Controller(comptime config: Config) type { pub const interface_vtable: ControllerInterface.Vtable = .{ .task = &task, .control_transfer = &control_transfer, - .endpoint_open = &endpoint_open, - .endpoint_rx = &endpoint_rx, + .signal_rx_ready = &signal_rx_ready, .submit_tx_buffer = &submit_tx_buffer, }; @@ -145,8 +125,8 @@ pub fn Controller(comptime config: Config) type { .bcd_usb = .from(config.Device.bcd_usb), .device_triple = config.device_triple, .max_packet_size0 = config.Device.max_transfer_size, - .vendor = .from(if (config.id) |id| id.vendor else config.Device.default_vidpid.vendor), - .product = .from(if (config.id) |id| id.product else config.Device.default_vidpid.product), + .vendor = .from(if (config.id) |id| id.vendor else config.Device.default_vid_pid.vendor), + .product = .from(if (config.id) |id| id.product else config.Device.default_vid_pid.product), .bcd_device = .from(config.bcd_device), .manufacturer_s = 1, .product_s = 2, @@ -154,7 +134,7 @@ pub fn Controller(comptime config: Config) type { .num_configurations = 1, }; - const config_descriptor: []const u8 = blk: { + const config_descriptor = blk: { const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; for (config.driver_endpoints) |fld| { @@ -173,15 +153,30 @@ pub fn Controller(comptime config: Config) type { .is_tuple = false, } }); - const driver_desc = config.Driver.config_descriptor(string.ids{}, endpoints_struct{}); - break :blk &(descriptor.Configuration{ - .total_length = .from(@sizeOf(descriptor.Configuration) + driver_desc.len), - .num_interfaces = config.Driver.num_interfaces, - .configuration_value = 1, - .configuration_s = 0, - .attributes = config.attributes, - .max_current = config.max_current, - }).serialize() ++ driver_desc; + const driver = config.Driver.config_descriptor(0, string.ids{}, endpoints_struct{}); + assert(@alignOf(@TypeOf(driver)) == 1); + assert(@typeInfo(@TypeOf(driver)).@"struct".layout == .@"extern"); + + const Ret = extern struct { + first: descriptor.Configuration, + driver: @TypeOf(driver), + + fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } + }; + + break :blk Ret{ + .first = .{ + .total_length = .from(@sizeOf(Ret)), + .num_interfaces = config.Driver.num_interfaces, + .configuration_value = 1, + .configuration_s = 0, + .attributes = config.attributes, + .max_current = config.max_current, + }, + .driver = driver, + }; }; const string = blk: { @@ -236,8 +231,10 @@ pub fn Controller(comptime config: Config) type { new_address: ?u7, // Last setup packet request setup_packet: types.SetupPacket, - // Keeps track of sent data from tmp buffer + // Configuration data still pending to be sent over endpoint 0. now_sending_ep0: ?[]const u8, + // Empty endpoint 0 tx buffer, if available. + ep0_tx: ?[]u8, // 0 - no config set cfg_num: u16, driver_data: config.Driver, @@ -250,11 +247,22 @@ pub fn Controller(comptime config: Config) type { .new_address = null, .setup_packet = undefined, .now_sending_ep0 = null, + .ep0_tx = null, .cfg_num = 0, .driver_data = undefined, }; - /// Command response utility function that can split long data in multiple packets + pub fn init_device(this: *@This()) void { + this.ep0_tx = config.Device.usb_init_device(); + } + + pub fn interface(this: *@This()) ControllerInterface { + return .{ + .ptr = this, + .vtable = &interface_vtable, + }; + } + fn send_cmd_response(this: *@This(), data: []const u8) void { if (this.now_sending_ep0) |residual| std.log.err("residual data: {any}", .{residual}); @@ -264,52 +272,39 @@ pub fn Controller(comptime config: Config) type { this.now_sending_ep0 = data[len..]; } - /// Initialize the usb device using the given configuration - pub fn init_device(_: *@This()) void { - config.Device.usb_init_device(); - } - fn control_transfer(ptr: *anyopaque, data: []const u8) void { const this: *@This() = @alignCast(@ptrCast(ptr)); this.send_cmd_response(data); } - fn submit_tx_buffer(ptr: *anyopaque, ep_in: Endpoint.Num, buffer_end: [*]const u8) void { - _ = ptr; - return config.Device.submit_tx_buffer(ep_in, buffer_end); + fn ep0_ack(this: *@This()) void { + if (this.now_sending_ep0) |residual| + std.log.err("residual data!: {any}", .{residual}); + if (this.ep0_tx) |tx| { + config.Device.submit_tx_buffer(.ep0, tx.ptr); + this.ep0_tx = null; + } else this.now_sending_ep0 = ""; } - fn endpoint_rx(ptr: *anyopaque, ep_out: Endpoint.Num, len: usize) void { + fn submit_tx_buffer(ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void { _ = ptr; - config.Device.usb_start_rx(ep_out, len); + return config.Device.submit_tx_buffer(ep_in, buffer_end); } - fn endpoint_open(ptr: *anyopaque, ep_desc: []const u8) anyerror!?[]u8 { + fn signal_rx_ready(ptr: *anyopaque, ep_out: EpNum, max_len: usize) void { _ = ptr; - const ep_addr = BosConfig.get_data_u8(ep_desc, 2); - const ep: Endpoint = .from_address(ep_addr); - const ep_transfer_type = BosConfig.get_data_u8(ep_desc, 3); - const ep_max_packet_size = @as(u11, @intCast(BosConfig.get_data_u16(ep_desc, 4) & 0x7FF)); - - return config.Device.endpoint_open(ep, enumFromInt(types.TransferType, ep_transfer_type) catch .Bulk, ep_max_packet_size); - } - - pub fn interface(this: *@This()) ControllerInterface { - return .{ - .ptr = this, - .vtable = &interface_vtable, - }; + config.Device.signal_rx_ready(ep_out, max_len); } - fn process_device_setup_request(this: *@This(), setup: *const types.SetupPacket) void { + fn process_device_setup_request(this: *@This(), setup: *const types.SetupPacket) anyerror!void { if (setup.request_type.type != .Standard) return; - switch (enumFromInt(SetupRequest, setup.request) catch return) { + switch (enumFromInt(types.SetupRequest, setup.request) catch return) { .SetAddress => { this.new_address = @intCast(setup.value); - this.send_cmd_response(""); + this.ep0_ack(); }, .SetConfiguration => { - defer this.send_cmd_response(""); + defer this.ep0_ack(); if (this.cfg_num == setup.value) return; defer this.cfg_num = setup.value; @@ -322,65 +317,59 @@ pub fn Controller(comptime config: Config) type { if (setup.value == 0) return; - this.driver_data.mount(this.interface()); - - // TODO: we support just one config for now so ignore config index - const bos_cfg = config_descriptor; - - var curr_bos_cfg = bos_cfg; - var curr_drv_idx: u8 = 0; + { // Eventually do this for every driver + const driver = config_descriptor.driver; + comptime var fields = @typeInfo(@TypeOf(driver)).@"struct".fields; - if (BosConfig.try_get_desc_as(descriptor.Configuration, curr_bos_cfg)) |_| { - curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); - } else { - // TODO: error - return; - } - - while (curr_bos_cfg.len > 0) : (curr_drv_idx += 1) { var assoc_itf_count: u8 = 1; // New class starts optionally from InterfaceAssociation followed by mandatory Interface - if (BosConfig.try_get_desc_as(descriptor.InterfaceAssociation, curr_bos_cfg)) |desc_assoc_itf| { - assoc_itf_count = desc_assoc_itf.interface_count; - curr_bos_cfg = BosConfig.get_desc_next(curr_bos_cfg); + if (fields[0].type == descriptor.InterfaceAssociation) { + assoc_itf_count = @field(driver, fields[0].name).interface_count; + fields = fields[1..]; } - if (BosConfig.get_desc_type(curr_bos_cfg) != @intFromEnum(descriptor.Type.Interface)) { - // TODO: error - return; - } - const desc_itf = BosConfig.get_desc_as(descriptor.Interface, curr_bos_cfg); - - var drv = &this.driver_data; - const drv_cfg_len = drv.open(this.interface(), curr_bos_cfg) catch unreachable; - + assert(fields[0].type == descriptor.Interface); + const desc_itf = @field(driver, fields[0].name); for (0..assoc_itf_count) |itf_offset| { const itf_num = desc_itf.interface_number + itf_offset; - this.driver_by_interface[itf_num] = drv; + this.driver_by_interface[itf_num] = &this.driver_data; } - - var curr_bos_cfg2 = curr_bos_cfg[0..drv_cfg_len]; - while (curr_bos_cfg2.len > 0) : ({ - curr_bos_cfg2 = BosConfig.get_desc_next(curr_bos_cfg2); - }) { - if (BosConfig.try_get_desc_as(descriptor.Endpoint, curr_bos_cfg2)) |desc_ep| { - switch (desc_ep.endpoint.dir) { - .Out => this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = drv, - .In => this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = drv, - } - } + fields = fields[1..]; + + inline for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc_ep = @field(driver, fld.name); + if (desc_ep.endpoint.dir != .Out) continue; + + this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = &this.driver_data; + _ = try config.Device.endpoint_open( + desc_ep.endpoint, + desc_ep.attributes.transfer_type, + desc_ep.max_packet_size.into(), + ); } - curr_bos_cfg = curr_bos_cfg[drv_cfg_len..]; + try this.driver_data.mount(this.interface(), &driver); + + inline for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc_ep = @field(driver, fld.name); + if (desc_ep.endpoint.dir != .In) continue; - // TODO: TMP solution - just 1 driver so quit while loop - break; + this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = &this.driver_data; + if (try config.Device.endpoint_open( + desc_ep.endpoint, + desc_ep.attributes.transfer_type, + desc_ep.max_packet_size.into(), + )) |buf| + this.driver_data.on_tx_ready(this.interface(), buf); + } } }, .GetDescriptor => if (enumFromInt(descriptor.Type, setup.value >> 8)) |descriptor_type| blk: { const data: []const u8 = switch (descriptor_type) { .Device => comptime &device_descriptor.serialize(), - .Configuration => config_descriptor, + .Configuration => comptime &config_descriptor.serialize(), .String => if (setup.value & 0xff < string.descriptors.len) string.descriptors[setup.value & 0xff] else @@ -391,9 +380,9 @@ pub fn Controller(comptime config: Config) type { const len = @min(data.len, setup.length); this.send_cmd_response(data[0..len]); } else |_| {}, - .SetFeature => if (enumFromInt(FeatureSelector, setup.value >> 8)) |feature| + .SetFeature => if (enumFromInt(types.FeatureSelector, setup.value >> 8)) |feature| switch (feature) { - .DeviceRemoteWakeup, .EndpointHalt => this.send_cmd_response(""), + .DeviceRemoteWakeup, .EndpointHalt => this.ep0_ack(), // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 .TestMode => {}, } @@ -405,19 +394,16 @@ pub fn Controller(comptime config: Config) type { /// initializing the device. /// /// You have to ensure the device has been initialized. - pub fn task(ptr: *anyopaque) void { + fn task(ptr: *anyopaque) void { const this: *@This() = @alignCast(@ptrCast(ptr)); - // Check which interrupt flags are set. - const ints = config.Device.get_interrupts(); + const events = config.Device.get_events(); - // Setup request received? - if (ints.SetupReq) { - const setup = config.Device.get_setup_packet(); + if (events.setup_packet) |setup| { this.setup_packet = setup; this.driver = null; switch (setup.request_type.recipient) { - .Device => this.process_device_setup_request(&setup), + .Device => this.process_device_setup_request(&setup) catch unreachable, .Interface => if (this.driver_by_interface[setup.index & 0xFF]) |drv| { this.driver = drv; if (drv.class_control(this.interface(), .Setup, &setup) == false) { @@ -428,45 +414,46 @@ pub fn Controller(comptime config: Config) type { } } - // Events on one or more buffers? - if (ints.BuffStatus) { - var iter = config.Device.get_unhandled_endpoints(); - + if (events.unhandled_buffers) |iter_const| { + var iter = iter_const; // Perform any required action on the data. For OUT, the `data` // will be whatever was sent by the host. For IN, it's a new // transmit buffer that has become available. while (iter.next()) |result| switch (result) { - .In => |in| { - if (in.ep_num == .ep0) { - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (this.new_address) |addr| { - // Change our address: - config.Device.set_address(addr); - this.new_address = null; - } - - if (this.now_sending_ep0) |data| { - if (data.len > 0) { - const len = config.Device.usb_start_tx(.ep0, data); - this.now_sending_ep0 = data[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: - config.Device.usb_start_rx(.ep0, 0); - if (this.driver) |drv| - _ = drv.class_control(this.interface(), .Ack, &this.setup_packet); - - this.now_sending_ep0 = null; - } - } - // TODO: Route different endpoints to different functions. - } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| - drv.on_tx_ready(this.interface(), in.buffer); - }, + .In => |in| if (in.ep_num == .ep0) { + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (this.new_address) |addr| { + // Change our address: + config.Device.set_address(addr); + this.new_address = null; + } + + const data = this.now_sending_ep0 orelse { + // Save the buffer for the next transfer. + this.ep0_tx = in.buffer; + continue; + }; + + if (data.len > 0) { + const len = @min(in.buffer.len, data.len); + std.mem.copyForwards(u8, in.buffer[0..len], data[0..len]); + config.Device.submit_tx_buffer(.ep0, in.buffer.ptr + len); + this.now_sending_ep0 = data[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. + config.Device.signal_rx_ready(.ep0, 0); + if (this.driver) |drv| + _ = drv.class_control(this.interface(), .Ack, &this.setup_packet); + + this.now_sending_ep0 = null; + } + // TODO: Route different endpoints to different functions. + } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| + drv.on_tx_ready(this.interface(), in.buffer), .Out => |out| { // TODO: Route different endpoints to different functions. if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| @@ -477,51 +464,11 @@ pub fn Controller(comptime config: Config) type { }; } - // Has the host signaled a bus reset? - if (ints.BusReset) { - this.driver_by_interface = @splat(null); - this.driver_by_endpoint_in = @splat(null); - this.driver_by_endpoint_out = @splat(null); - // Reset the device - config.Device.bus_reset(); - - // Reset our state. - this.new_address = null; - this.cfg_num = 0; - this.now_sending_ep0 = null; + if (events.bus_reset) { + // TODO: call umount callback if any + this.* = .init; + config.Device.bus_reset_clear(); } } }; } - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Driver support stuctures -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -/// USB interrupt status -/// -/// __Note__: Available interrupts may change from device to device. -pub const InterruptStatus = struct { - /// Host: raised every time the host sends a SOF (Start of Frame) - BuffStatus: bool = false, - BusReset: bool = false, - /// Set when the device connection state changes - DevConnDis: bool = false, - /// Set when the device suspend state changes - DevSuspend: bool = false, - /// Set when the device receives a resume from the host - DevResumeFromHost: bool = false, - /// Setup Request - SetupReq: bool = false, -}; - -/// And endpoint and its corresponding buffer. -pub const EndpointAndBuffer = union(Dir) { - Out: struct { ep_num: Endpoint.Num, buffer: []const u8 }, - In: struct { ep_num: Endpoint.Num, buffer: []u8 }, -}; - -test "tests" { - _ = hid; - _ = utils; -} diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 6cf04af9b..29515d0f2 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -1,7 +1,6 @@ const std = @import("std"); const usb = @import("../usb.zig"); -const bos = usb.utils.BosConfig; const descriptor = usb.descriptor; const types = usb.types; @@ -30,6 +29,23 @@ pub const LineCoding = extern struct { } }; +pub const Descriptor = extern struct { + desc1: descriptor.InterfaceAssociation, + desc2: descriptor.Interface, + desc3: descriptor.cdc.Header, + desc4: descriptor.cdc.CallManagement, + desc5: descriptor.cdc.AbstractControlModel, + desc6: descriptor.cdc.Union, + ep_notifi: descriptor.Endpoint, + desc8: descriptor.Interface, + ep_out: descriptor.Endpoint, + ep_in: descriptor.Endpoint, + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + pub const CdcClassDriver = struct { pub const num_interfaces = 2; const max_packet_size = 64; @@ -44,26 +60,77 @@ pub const CdcClassDriver = struct { rx_buf: ?[]const u8 = null, tx_buf: ?[]u8 = null, - pub fn config_descriptor(string_ids: anytype, endpoints: anytype) []const u8 { - return &usb.templates.DescriptorsConfigTemplates.cdc_descriptor( - 0, - string_ids.name, - endpoints.notifi, - 8, - endpoints.data, - endpoints.data, - 64, - ); + pub fn config_descriptor(first_interface: u8, string_ids: anytype, endpoints: anytype) Descriptor { + const endpoint_notifi_size = 8; + const endpoint_size = 64; + return .{ + .desc1 = .{ + .first_interface = first_interface, + .interface_count = 2, + .function_class = 2, + .function_subclass = 2, + .function_protocol = 0, + .function = 0, + }, + .desc2 = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 1, + .interface_class = 2, + .interface_subclass = 2, + .interface_protocol = 0, + .interface_s = string_ids.name, + }, + .desc3 = .{ .bcd_cdc = .from(0x0120) }, + .desc4 = .{ + .capabilities = 0, + .data_interface = first_interface + 1, + }, + .desc5 = .{ .capabilities = 6 }, + .desc6 = .{ + .master_interface = first_interface, + .slave_interface_0 = first_interface + 1, + }, + .ep_notifi = .{ + .endpoint = .in(endpoints.notifi), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_notifi_size), + .interval = 16, + }, + .desc8 = .{ + .interface_number = first_interface + 1, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 10, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = 0, + }, + .ep_out = .{ + .endpoint = .out(endpoints.data), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + .ep_in = .{ + .endpoint = .in(endpoints.data), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + }; } /// This function is called when the host chooses a configuration that contains this driver. - pub fn mount(ptr: *@This(), controller: usb.ControllerInterface) void { - _ = controller; - var this: *@This() = @ptrCast(@alignCast(ptr)); + pub fn mount(this: *@This(), controller: usb.ControllerInterface, desc: *const Descriptor) anyerror!void { this.line_coding = .init; this.awaiting_data = false; this.rx_buf = null; this.tx_buf = null; + this.ep_in_notif = desc.ep_notifi.endpoint.num; + this.ep_out = desc.ep_out.endpoint.num; + this.ep_in = desc.ep_in.endpoint.num; + controller.signal_rx_ready(this.ep_out, std.math.maxInt(usize)); } pub fn available(this: *@This()) usize { @@ -78,7 +145,7 @@ pub const CdcClassDriver = struct { if (len < rx.len) this.rx_buf = rx[len..] else { - controller.endpoint_rx(this.ep_out, max_packet_size); + controller.signal_rx_ready(this.ep_out, std.math.maxInt(usize)); this.rx_buf = null; } return len; @@ -112,49 +179,6 @@ pub const CdcClassDriver = struct { } } - pub fn open(ptr: *@This(), controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { - var this: *@This() = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; - - if (bos.try_get_desc_as(descriptor.Interface, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) - return error.UnsupportedInterfaceClassType; - if (desc_itf.interface_subclass != @intFromEnum(descriptor.cdc.SubType.AbstractControlModel)) - return error.UnsupportedInterfaceSubClassType; - } else return error.ExpectedInterfaceDescriptor; - - curr_cfg = bos.get_desc_next(curr_cfg); - - while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == @intFromEnum(descriptor.Type.CsInterface)) { - curr_cfg = bos.get_desc_next(curr_cfg); - } - - if (bos.try_get_desc_as(descriptor.Endpoint, curr_cfg)) |desc_ep| { - std.debug.assert(desc_ep.endpoint.dir == .In); - this.ep_in_notif = desc_ep.endpoint.num; - curr_cfg = bos.get_desc_next(curr_cfg); - } - - if (bos.try_get_desc_as(descriptor.Interface, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { - curr_cfg = bos.get_desc_next(curr_cfg); - for (0..2) |_| { - if (bos.try_get_desc_as(descriptor.Endpoint, curr_cfg)) |desc_ep| { - switch (desc_ep.endpoint.dir) { - .In => this.ep_in = desc_ep.endpoint.num, - .Out => this.ep_out = desc_ep.endpoint.num, - } - this.tx_buf = try controller.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); - } - } - } - } - controller.endpoint_rx(this.ep_out, max_packet_size); - - return cfg.len - curr_cfg.len; - } - pub fn class_control(ptr: *@This(), controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { var this: *@This() = @ptrCast(@alignCast(ptr)); if (stage != .Setup) return true; diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index f1e3f58bc..97ed0cd37 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -1,5 +1,12 @@ pub const cdc = @import("descriptor/cdc.zig"); pub const hid = @import("descriptor/hid.zig"); +pub const vendor = @import("descriptor/vendor.zig"); + +test "descriptor tests" { + _ = cdc; + _ = hid; + _ = vendor; +} const std = @import("std"); const types = @import("types.zig"); @@ -53,6 +60,12 @@ pub const Device = extern struct { subclass: u8, /// Protocol within the subclass. protocol: u8, + + pub const unspecified: @This() = .{ + .class = .Unspecified, + .subclass = 0, + .protocol = 0, + }; }; /// USB Device Qualifier Descriptor @@ -129,7 +142,7 @@ pub const Device = extern struct { pub const Configuration = extern struct { pub const const_descriptor_type: Type = .Configuration; - /// Maximum device power consumption in units of 2mA. + /// Maximum device current consumption. pub const MaxCurrent = extern struct { multiple_of_2ma: u8, @@ -147,7 +160,7 @@ pub const Configuration = extern struct { /// (like a keyboard). /// - The rest are reserved and should be zero. pub const Attributes = packed struct(u8) { - reserved0: u5 = 0, + reserved: u5 = 0, can_remote_wakeup: bool = false, self_powered: bool, usb1_bus_powered: bool = true, @@ -191,6 +204,27 @@ pub fn string(comptime value: []const u8) []const u8 { pub const Endpoint = extern struct { pub const const_descriptor_type: Type = .Endpoint; + pub const Attributes = packed struct(u8) { + pub const Synchronisation = enum(u2) { + none = 0, + asynchronous = 1, + adaptive = 2, + synchronous = 3, + }; + + pub const Usage = enum(u2) { + data = 0, + feedback = 1, + implicit_feedback = 2, + reserved = 3, + }; + + transfer_type: types.TransferType, + synchronisation: Synchronisation = .none, + usage: Usage, + reserved: u2 = 0, + }; + comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 7); @@ -204,7 +238,7 @@ pub const Endpoint = extern struct { endpoint: types.Endpoint, /// Endpoint attributes; the most relevant part is the bottom 2 bits, which /// control the transfer type using the values from `TransferType`. - attributes: u8, + attributes: Attributes, /// Maximum packet size this endpoint can accept/produce. max_packet_size: types.U16Le, /// Interval for polling interrupt/isochronous endpoints (which we don't diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index 145fe30ea..64daa5eeb 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -1,10 +1,6 @@ -const std = @import("std"); -const usb = @import("../../usb.zig"); - -const assert = std.debug.assert; -const descriptor = usb.descriptor; -const Type = descriptor.Type; -const types = usb.types; +const assert = @import("std").debug.assert; +const Type = @import("../descriptor.zig").Type; +const types = @import("../types.zig"); pub const SubType = enum(u8) { Header = 0x00, @@ -28,7 +24,7 @@ pub const Header = extern struct { // number in binary-coded decimal. Typically 0x01_10. bcd_cdc: types.U16Le, - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { return @bitCast(this); } }; @@ -49,7 +45,7 @@ pub const CallManagement = extern struct { // Data interface number. data_interface: u8, - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { return @bitCast(this); } }; @@ -68,7 +64,7 @@ pub const AbstractControlModel = extern struct { // Capabilities. Should be 0x02 for use as a serial device. capabilities: u8, - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { return @bitCast(this); } }; @@ -91,94 +87,7 @@ pub const Union = extern struct { // union. slave_interface_0: u8, - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { - return @bitCast(this); - } -}; - -pub const Template = extern struct { - desc1: descriptor.InterfaceAssociation, - desc2: descriptor.Interface, - desc3: Header, - desc4: CallManagement, - desc5: AbstractControlModel, - desc6: Union, - desc7: descriptor.Endpoint, - desc8: descriptor.Interface, - desc9: descriptor.Endpoint, - desc10: descriptor.Endpoint, - - pub fn create( - interface_number: u8, - string_index: u8, - endpoint_in_notifi: types.Endpoint.Num, - endpoint_notifi_size: u16, - endpoint_out: types.Endpoint.Num, - endpoint_in: types.Endpoint.Num, - endpoint_size: u16, - ) @This() { - const ret: @This() = .{ - .desc1 = .{ - .first_interface = interface_number, - .interface_count = 2, - .function_class = 2, - .function_subclass = 2, - .function_protocol = 0, - .function = 0, - }, - .desc2 = .{ - .interface_number = interface_number, - .alternate_setting = 0, - .num_endpoints = 1, - .interface_class = 2, - .interface_subclass = 2, - .interface_protocol = 0, - .interface_s = string_index, - }, - .desc3 = .{ .bcd_cdc = .from(0x0120) }, - .desc4 = .{ - .capabilities = 0, - .data_interface = interface_number + 1, - }, - .desc5 = .{ .capabilities = 6 }, - .desc6 = .{ - .master_interface = interface_number, - .slave_interface_0 = interface_number + 1, - }, - .desc7 = .{ - .endpoint = .in(endpoint_in_notifi), - .attributes = @intFromEnum(types.TransferType.Interrupt), - .max_packet_size = .from(endpoint_notifi_size), - .interval = 16, - }, - .desc8 = .{ - .interface_number = interface_number + 1, - .alternate_setting = 0, - .num_endpoints = 2, - .interface_class = 10, - .interface_subclass = 0, - .interface_protocol = 0, - .interface_s = 0, - }, - .desc9 = .{ - .endpoint = .out(endpoint_out), - .attributes = @intFromEnum(types.TransferType.Bulk), - .max_packet_size = .from(endpoint_size), - .interval = 0, - }, - .desc10 = .{ - .endpoint = .in(endpoint_in), - .attributes = @intFromEnum(types.TransferType.Bulk), - .max_packet_size = .from(endpoint_size), - .interval = 0, - }, - }; - @compileLog(ret.serialize()); - @compileLog(ret.serialize(usb.templates.DescriptorsConfigTemplates.cdc_descriptor(interface_number, string_index, endpoint_in_notifi, endpoint_notifi_size, endpoint_out, endpoint_in, endpoint_size))); - return ret; - } - - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { return @bitCast(this); } }; diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index e69de29bb..08908b530 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -0,0 +1,461 @@ +const std = @import("std"); +const types = @import("../types.zig"); +const assert = std.debug.assert; + +// ... +// | +// v +// ------------------------- +// | InterfaceDescriptor | +// ------------------------- +// | | +// | ----------------- +// | | +// v v +// ... -------------------------- +// | HidDescriptor | +// -------------------------- +// | | +// ------ -------- +// | | +// v v +// ----------------------- --------------------- +// | ReportDescriptor | | PhysicalDesc | +// ----------------------- --------------------- + +pub const SubType = enum(u8) { + Hid = 0x21, + Report = 0x22, + Physical = 0x23, +}; + +pub const RequestType = enum(u8) { + GetReport = 0x01, + GetIdle = 0x02, + GetProtocol = 0x03, + SetReport = 0x09, + SetIdle = 0x0a, + SetProtocol = 0x0b, +}; + +/// USB HID descriptor +pub const Hid = extern struct { + pub const const_descriptor_type = .Hid; + + length: u8 = 9, + /// Type of this descriptor + descriptor_type: SubType = const_descriptor_type, + /// Numeric expression identifying the HID Class Specification release + bcd_hid: types.U16Le, + /// Numeric expression identifying country code of the localized hardware + country_code: u8, + /// Numeric expression specifying the number of class descriptors + num_descriptors: u8, + /// Type of HID class report + report_type: SubType = .Report, + /// The total size of the Report descriptor + report_length: types.U16Le, + + pub fn serialize(self: *const @This()) [9]u8 { + var out: [9]u8 = undefined; + out[0] = out.len; + out[1] = @intFromEnum(self.descriptor_type); + out[2] = self.bcd_hid.lo; + out[3] = self.bcd_hid.hi; + out[4] = self.country_code; + out[5] = self.num_descriptors; + out[6] = @intFromEnum(self.report_type); + out[7] = self.report_length.lo; + out[8] = self.report_length.hi; + return out; + } +}; + +/// HID interface Subclass (for UsbInterfaceDescriptor) +pub const Subclass = enum(u8) { + /// No Subclass + None = 0, + /// Boot Interface Subclass + Boot = 1, +}; + +/// HID interface protocol +pub const Protocol = enum(u8) { + /// No protocol + None = 0, + /// Keyboard (only if boot interface) + Keyboard = 1, + /// Mouse (only if boot interface) + Mouse = 2, +}; + +/// HID request report type +pub const ReportType = enum(u8) { + Invalid = 0, + Input = 1, + Output = 2, + Feature = 3, +}; + +/// HID country codes +pub const CountryCode = enum(u8) { + NotSupported = 0, + Arabic, + Belgian, + CanadianBilingual, + CanadianFrench, + CzechRepublic, + Danish, + Finnish, + French, + German, + Greek, + Hebrew, + Hungary, + International, + Italian, + JapanKatakana, + Korean, + LatinAmerica, + NetherlandsDutch, + Norwegian, + PersianFarsi, + Poland, + Portuguese, + Russia, + Slovakia, + Spanish, + Swedish, + SwissFrench, + SwissGerman, + Switzerland, + Taiwan, + TurkishQ, + Uk, + Us, + Yugoslavia, + TurkishF, +}; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptor Data Types +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const ReportItemTypes = enum(u2) { + Main = 0, + Global = 1, + Local = 2, +}; + +pub const ReportItemMainGroup = enum(u4) { + Input = 8, + Output = 9, + Collection = 10, + Feature = 11, + CollectionEnd = 12, +}; + +pub const CollectionItem = enum(u8) { + Physical = 0, + Application, + Logical, + Report, + NamedArray, + UsageSwitch, + UsageModifier, +}; + +pub const GlobalItem = enum(u4) { + UsagePage = 0, + LogicalMin = 1, + LogicalMax = 2, + PhysicalMin = 3, + PhysicalMax = 4, + UnitExponent = 5, + Unit = 6, + ReportSize = 7, + ReportId = 8, + ReportCount = 9, + Push = 10, + Pop = 11, +}; + +pub const LocalItem = enum(u4) { + Usage = 0, + UsageMin = 1, + UsageMax = 2, + DesignatorIndex = 3, + DesignatorMin = 4, + DesignatorMax = 5, + StringIndex = 7, + StringMin = 8, + StringMax = 9, + Delimiter = 10, +}; + +pub const UsageTable = struct { + pub const desktop: [1]u8 = "\x01".*; + pub const keyboard: [1]u8 = "\x07".*; + pub const led: [1]u8 = "\x08".*; + pub const fido: [2]u8 = "\xD0\xF1".*; + pub const vendor: [2]u8 = "\x00\xFF".*; +}; + +pub const FidoAllianceUsage = struct { + pub const u2fhid: [1]u8 = "\x01".*; + pub const data_in: [1]u8 = "\x20".*; + pub const data_out: [1]u8 = "\x21".*; +}; + +pub const DesktopUsage = struct { + pub const keyboard: [1]u8 = "\x06".*; +}; + +pub const HID_DATA: u8 = 0 << 0; +pub const HID_CONSTANT: u8 = 1 << 0; + +pub const HID_ARRAY = 0 << 1; +pub const HID_VARIABLE = 1 << 1; + +pub const HID_ABSOLUTE = 0 << 2; +pub const HID_RELATIVE = 1 << 2; + +pub const HID_WRAP_NO = 0 << 3; +pub const HID_WRAP = 1 << 3; + +pub const HID_LINEAR = 0 << 4; +pub const HID_NONLINEAR = 1 << 4; + +pub const HID_PREFERRED_STATE = 0 << 5; +pub const HID_PREFERRED_NO = 1 << 5; + +pub const HID_NO_NULL_POSITION = 0 << 6; +pub const HID_NULL_STATE = 1 << 6; + +pub const HID_NON_VOLATILE = 0 << 7; +pub const HID_VOLATILE = 1 << 7; + +pub const HID_BITFIELD = 0 << 8; +pub const HID_BUFFERED_BYTES = 1 << 8; + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptor Functions +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub fn report_item( + typ: u2, + tag: u4, + data: anytype, +) [data.len + 1]u8 { + comptime if (data.len != 0) assert(@TypeOf(data[0]) == u8); + const first = (@as(u8, tag) << 4) | (@as(u8, typ) << 2) | @as(u2, data.len + 1); + + return switch (@typeInfo(@TypeOf(data))) { + .array, .@"struct" => .{first} ++ data, + .pointer => .{first} ++ data.*, + else => @compileLog(data), + }; +} + +// Main Items +// ------------------------------------------------- + +pub fn collection(data: CollectionItem) [2]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Collection), + .{@intFromEnum(data)}, + ); +} + +pub fn input(data: u8) [2]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Input), + .{data}, + ); +} + +pub fn output(data: u8) [2]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.Output), + .{data}, + ); +} + +pub fn collection_end() [1]u8 { + return report_item( + @intFromEnum(ReportItemTypes.Main), + @intFromEnum(ReportItemMainGroup.CollectionEnd), + .{}, + ); +} + +// Global Items +// ------------------------------------------------- + +pub fn usage_page(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.UsagePage), + data, + ); +} + +pub fn logical_min(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.LogicalMin), + data, + ); +} + +pub fn logical_max(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.LogicalMax), + data, + ); +} + +pub fn report_size(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.ReportSize), + data, + ); +} + +pub fn report_count(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Global), + @intFromEnum(GlobalItem.ReportCount), + data, + ); +} + +// Local Items +// ------------------------------------------------- + +pub fn usage(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.Usage), + data, + ); +} + +pub fn usage_min(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.UsageMin), + data, + ); +} + +pub fn usage_max(data: anytype) [data.len + 1]u8 { + comptime assert(@TypeOf(data[0]) == u8); + return report_item( + @intFromEnum(ReportItemTypes.Local), + @intFromEnum(LocalItem.UsageMax), + data, + ); +} + +// +++++++++++++++++++++++++++++++++++++++++++++++++ +// Report Descriptors +// +++++++++++++++++++++++++++++++++++++++++++++++++ + +pub const report = struct { + pub const FidoU2f = usage_page(UsageTable.fido) // + ++ usage(FidoAllianceUsage.u2fhid) // + ++ collection(CollectionItem.Application) // + // Usage Data In + ++ usage(FidoAllianceUsage.data_in) // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // Usage Data Out + ++ usage(FidoAllianceUsage.data_out) // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // End + ++ collection_end(); + + pub const GenericInOut = usage_page(UsageTable.vendor) // + ++ usage("\x01") // + ++ collection(CollectionItem.Application) // + // Usage Data In + ++ usage("\x02") // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // Usage Data Out + ++ usage("\x03") // + ++ logical_min("\x00") // + ++ logical_max("\xff\x00") // + ++ report_size("\x08") // + ++ report_count("\x40") // + ++ output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // End + ++ collection_end(); + + /// Common keyboard report format, conforming to the boot protocol. + /// See Appendix B.1 of the USB HID specification: + /// https://usb.org/sites/default/files/hid1_11.pdf + pub const Keyboard = usage_page(UsageTable.desktop) ++ usage(DesktopUsage.keyboard) ++ collection(.Application) + // Input: modifier key bitmap + ++ usage_page(UsageTable.keyboard) ++ usage_min("\xe0") ++ usage_max("\xe7") ++ logical_min("\x00") ++ logical_max("\x01") ++ report_count("\x08") ++ report_size("\x01") ++ input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // + // Reserved 8 bits + ++ report_count("\x01") ++ report_size("\x08") ++ input(HID_CONSTANT) + // Output: indicator LEDs + ++ usage_page(UsageTable.led) ++ usage_min("\x01") ++ usage_max("\x05") ++ report_count("\x05") ++ report_size("\x01") ++ output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + // Padding + ++ report_count("\x01") ++ report_size("\x03") ++ output(HID_CONSTANT) + // Input: up to 6 pressed key codes + ++ usage_page(UsageTable.keyboard) ++ usage_min("\x00") ++ usage_max("\xff\x00") ++ logical_min("\x00") ++ logical_max("\xff\x00") ++ report_count("\x06") ++ report_size("\x08") ++ input(HID_DATA | HID_ARRAY | HID_ABSOLUTE) + // End + ++ collection_end(); + + test "create hid report item" { + const r = report_item( + 2, + 0, + 3, + "\x22\x11", + ); + + try std.testing.expectEqual(@as(usize, @intCast(3)), r.len); + try std.testing.expectEqual(@as(u8, @intCast(50)), r[0]); + try std.testing.expectEqual(@as(u8, @intCast(0x22)), r[1]); + try std.testing.expectEqual(@as(u8, @intCast(0x11)), r[2]); + } + + test "create hid fido usage page" { + const f = usage_page(UsageTable.fido); + + try std.testing.expectEqual(@as(usize, @intCast(3)), f.len); + try std.testing.expectEqual(@as(u8, @intCast(6)), f[0]); + try std.testing.expectEqual(@as(u8, @intCast(0xd0)), f[1]); + try std.testing.expectEqual(@as(u8, @intCast(0xf1)), f[2]); + } + + test "report descriptor fido" { + _ = FidoU2f; + } +}; diff --git a/core/src/core/usb/descriptor/vendor.zig b/core/src/core/usb/descriptor/vendor.zig new file mode 100644 index 000000000..6ad46240d --- /dev/null +++ b/core/src/core/usb/descriptor/vendor.zig @@ -0,0 +1,51 @@ +const assert = @import("std").debug.assert; +const descriptor = @import("../descriptor.zig"); +const types = @import("../types.zig"); + +/// Not sure what this was supposed to be, but I am keeping it for now. +pub const Vendor = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 5); + } + + desc1: descriptor.Interface, + desc2: descriptor.Endpoint, + desc3: descriptor.Endpoint, + + pub fn create( + interface_number: u8, + string_index: u8, + endpoint_out: types.Endpoint.Num, + endpoint_in: types.Endpoint.Num, + endpoint_size: u16, + ) @This() { + return .{ + .desc1 = .{ + .interface_number = interface_number, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 0xff, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = string_index, + }, + .desc2 = .{ + .endpoint = .out(endpoint_out), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + .desc3 = .{ + .endpoint = .in(endpoint_in), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + }; + } + + pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 07b3dddac..81deb89a7 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -42,7 +42,6 @@ //! The HID descriptor identifies the length and type of subordinate descriptors for device. const std = @import("std"); -const assert = std.debug.assert; const enumFromInt = std.meta.intToEnum; const usb = @import("../usb.zig"); @@ -50,439 +49,77 @@ const descriptor = usb.descriptor; const types = usb.types; const bos = usb.utils.BosConfig; -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Common Data Types -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -// ... -// | -// v -// ------------------------- -// | InterfaceDescriptor | -// ------------------------- -// | | -// | ----------------- -// | | -// v v -// ... -------------------------- -// | HidDescriptor | -// -------------------------- -// | | -// ------ -------- -// | | -// v v -// ----------------------- --------------------- -// | ReportDescriptor | | PhysicalDesc | -// ----------------------- --------------------- - -pub const SubType = enum(u8) { - Hid = 0x21, - Report = 0x22, - Physical = 0x23, -}; - -pub const HidRequestType = enum(u8) { - GetReport = 0x01, - GetIdle = 0x02, - GetProtocol = 0x03, - SetReport = 0x09, - SetIdle = 0x0a, - SetProtocol = 0x0b, -}; - -/// USB HID descriptor -pub const HidDescriptor = struct { - pub const const_descriptor_type = SubType.Hid; - - length: u8 = 9, - /// Type of this descriptor - descriptor_type: SubType = const_descriptor_type, - /// Numeric expression identifying the HID Class Specification release - bcd_hid: u16 align(1), - /// Numeric expression identifying country code of the localized hardware - country_code: u8, - /// Numeric expression specifying the number of class descriptors - num_descriptors: u8, - /// Type of HID class report - report_type: SubType = SubType.Report, - /// The total size of the Report descriptor - report_length: u16 align(1), - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = @intCast(self.bcd_hid & 0xff); - out[3] = @intCast((self.bcd_hid >> 8) & 0xff); - out[4] = self.country_code; - out[5] = self.num_descriptors; - out[6] = @intFromEnum(self.report_type); - out[7] = @intCast(self.report_length & 0xff); - out[8] = @intCast((self.report_length >> 8) & 0xff); - return out; +pub const InDescriptor = extern struct { + desc1: descriptor.Interface, + desc2: descriptor.hid.Hid, + desc3: descriptor.Endpoint, + + fn create(first_interface: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in: types.Endpoint.Num, endpoint_size: u16, endpoint_interval: u16) @This() { + return .{ + .desc1 = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 1, + .interface_class = 3, + .interface_subclass = if (boot_protocol > 0) 1 else 0, + .interface_protocol = boot_protocol, + .interface_s = string_index, + }, + .desc2 = .{ + .bcd_hid = .from(0x0111), + .country_code = 0, + .num_descriptors = 1, + .report_length = .from(report_desc_len), + }, + .desc3 = .{ + .endpoint = .in(endpoint_in), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = endpoint_size, + .interval = endpoint_interval, + }, + }; } }; -/// HID interface Subclass (for UsbInterfaceDescriptor) -pub const Subclass = enum(u8) { - /// No Subclass - None = 0, - /// Boot Interface Subclass - Boot = 1, -}; - -/// HID interface protocol -pub const Protocol = enum(u8) { - /// No protocol - None = 0, - /// Keyboard (only if boot interface) - Keyboard = 1, - /// Mouse (only if boot interface) - Mouse = 2, -}; - -/// HID request report type -pub const ReportType = enum(u8) { - Invalid = 0, - Input = 1, - Output = 2, - Feature = 3, -}; - -/// HID country codes -pub const CountryCode = enum(u8) { - NotSupported = 0, - Arabic, - Belgian, - CanadianBilingual, - CanadianFrench, - CzechRepublic, - Danish, - Finnish, - French, - German, - Greek, - Hebrew, - Hungary, - International, - Italian, - JapanKatakana, - Korean, - LatinAmerica, - NetherlandsDutch, - Norwegian, - PersianFarsi, - Poland, - Portuguese, - Russia, - Slovakia, - Spanish, - Swedish, - SwissFrench, - SwissGerman, - Switzerland, - Taiwan, - TurkishQ, - Uk, - Us, - Yugoslavia, - TurkishF, -}; - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Report Descriptor Data Types -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub const ReportItemTypes = enum(u2) { - Main = 0, - Global = 1, - Local = 2, -}; - -pub const ReportItemMainGroup = enum(u4) { - Input = 8, - Output = 9, - Collection = 10, - Feature = 11, - CollectionEnd = 12, -}; - -pub const CollectionItem = enum(u8) { - Physical = 0, - Application, - Logical, - Report, - NamedArray, - UsageSwitch, - UsageModifier, -}; - -pub const GlobalItem = enum(u4) { - UsagePage = 0, - LogicalMin = 1, - LogicalMax = 2, - PhysicalMin = 3, - PhysicalMax = 4, - UnitExponent = 5, - Unit = 6, - ReportSize = 7, - ReportId = 8, - ReportCount = 9, - Push = 10, - Pop = 11, -}; - -pub const LocalItem = enum(u4) { - Usage = 0, - UsageMin = 1, - UsageMax = 2, - DesignatorIndex = 3, - DesignatorMin = 4, - DesignatorMax = 5, - StringIndex = 7, - StringMin = 8, - StringMax = 9, - Delimiter = 10, -}; - -pub const UsageTable = struct { - pub const desktop: [1]u8 = "\x01".*; - pub const keyboard: [1]u8 = "\x07".*; - pub const led: [1]u8 = "\x08".*; - pub const fido: [2]u8 = "\xD0\xF1".*; - pub const vendor: [2]u8 = "\x00\xFF".*; -}; - -pub const FidoAllianceUsage = struct { - pub const u2fhid: [1]u8 = "\x01".*; - pub const data_in: [1]u8 = "\x20".*; - pub const data_out: [1]u8 = "\x21".*; -}; - -pub const DesktopUsage = struct { - pub const keyboard: [1]u8 = "\x06".*; +pub const InOutDescriptor = extern struct { + interface: descriptor.Interface, + hid: descriptor.hid.Hid, + ep_out: descriptor.Endpoint, + ep_in: descriptor.Endpoint, + + fn create(first_interface: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out: types.Endpoint.Num, endpoint_in: types.Endpoint.Num, endpoint_size: u16, endpoint_interval: u16) @This() { + return .{ + .interface = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 3, + .interface_subclass = if (boot_protocol > 0) 1 else 0, + .interface_protocol = boot_protocol, + .interface_s = string_index, + }, + .hid = .{ + .bcd_hid = .from(0x0111), + .country_code = 0, + .num_descriptors = 1, + .report_length = .from(report_desc_len), + }, + .ep_out = .{ + .endpoint = .out(endpoint_out), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = endpoint_interval, + }, + .ep_in = .{ + .endpoint = .in(endpoint_in), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = endpoint_interval, + }, + }; + } }; -pub const HID_DATA: u8 = 0 << 0; -pub const HID_CONSTANT: u8 = 1 << 0; - -pub const HID_ARRAY = 0 << 1; -pub const HID_VARIABLE = 1 << 1; - -pub const HID_ABSOLUTE = 0 << 2; -pub const HID_RELATIVE = 1 << 2; - -pub const HID_WRAP_NO = 0 << 3; -pub const HID_WRAP = 1 << 3; - -pub const HID_LINEAR = 0 << 4; -pub const HID_NONLINEAR = 1 << 4; - -pub const HID_PREFERRED_STATE = 0 << 5; -pub const HID_PREFERRED_NO = 1 << 5; - -pub const HID_NO_NULL_POSITION = 0 << 6; -pub const HID_NULL_STATE = 1 << 6; - -pub const HID_NON_VOLATILE = 0 << 7; -pub const HID_VOLATILE = 1 << 7; - -pub const HID_BITFIELD = 0 << 8; -pub const HID_BUFFERED_BYTES = 1 << 8; - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Report Descriptor Functions -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub fn hid_report_item( - typ: u2, - tag: u4, - data: anytype, -) [data.len + 1]u8 { - comptime if (data.len != 0) assert(@TypeOf(data[0]) == u8); - const first = (@as(u8, tag) << 4) | (@as(u8, typ) << 2) | @as(u2, data.len + 1); - - return switch (@typeInfo(@TypeOf(data))) { - .array, .@"struct" => .{first} ++ data, - .pointer => .{first} ++ data.*, - else => @compileLog(data), - }; -} - -// Main Items -// ------------------------------------------------- - -pub fn hid_collection(data: CollectionItem) [2]u8 { - return hid_report_item( - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.Collection), - .{@intFromEnum(data)}, - ); -} - -pub fn hid_input(data: u8) [2]u8 { - return hid_report_item( - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.Input), - .{data}, - ); -} - -pub fn hid_output(data: u8) [2]u8 { - return hid_report_item( - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.Output), - .{data}, - ); -} - -pub fn hid_collection_end() [1]u8 { - return hid_report_item( - @intFromEnum(ReportItemTypes.Main), - @intFromEnum(ReportItemMainGroup.CollectionEnd), - .{}, - ); -} - -// Global Items -// ------------------------------------------------- - -pub fn hid_usage_page(usage: anytype) [usage.len + 1]u8 { - comptime assert(@TypeOf(usage[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.UsagePage), - usage, - ); -} - -pub fn hid_logical_min(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.LogicalMin), - data, - ); -} - -pub fn hid_logical_max(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.LogicalMax), - data, - ); -} - -pub fn hid_report_size(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.ReportSize), - data, - ); -} - -pub fn hid_report_count(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Global), - @intFromEnum(GlobalItem.ReportCount), - data, - ); -} - -// Local Items -// ------------------------------------------------- - -pub fn hid_usage(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Local), - @intFromEnum(LocalItem.Usage), - data, - ); -} - -pub fn hid_usage_min(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Local), - @intFromEnum(LocalItem.UsageMin), - data, - ); -} - -pub fn hid_usage_max(data: anytype) [data.len + 1]u8 { - comptime assert(@TypeOf(data[0]) == u8); - return hid_report_item( - @intFromEnum(ReportItemTypes.Local), - @intFromEnum(LocalItem.UsageMax), - data, - ); -} - -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Report Descriptors -// +++++++++++++++++++++++++++++++++++++++++++++++++ - -pub const ReportDescriptorFidoU2f = hid_usage_page(UsageTable.fido) // - ++ hid_usage(FidoAllianceUsage.u2fhid) // - ++ hid_collection(CollectionItem.Application) // - // Usage Data In - ++ hid_usage(FidoAllianceUsage.data_in) // - ++ hid_logical_min("\x00") // - ++ hid_logical_max("\xff\x00") // - ++ hid_report_size("\x08") // - ++ hid_report_count("\x40") // - ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Usage Data Out - ++ hid_usage(FidoAllianceUsage.data_out) // - ++ hid_logical_min("\x00") // - ++ hid_logical_max("\xff\x00") // - ++ hid_report_size("\x08") // - ++ hid_report_count("\x40") // - ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // End - ++ hid_collection_end(); - -pub const ReportDescriptorGenericInOut = hid_usage_page(UsageTable.vendor) // - ++ hid_usage("\x01") // - ++ hid_collection(CollectionItem.Application) // - // Usage Data In - ++ hid_usage("\x02") // - ++ hid_logical_min("\x00") // - ++ hid_logical_max("\xff\x00") // - ++ hid_report_size("\x08") // - ++ hid_report_count("\x40") // - ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Usage Data Out - ++ hid_usage("\x03") // - ++ hid_logical_min("\x00") // - ++ hid_logical_max("\xff\x00") // - ++ hid_report_size("\x08") // - ++ hid_report_count("\x40") // - ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // End - ++ hid_collection_end(); - -/// Common keyboard report format, conforming to the boot protocol. -/// See Appendix B.1 of the USB HID specification: -/// https://usb.org/sites/default/files/hid1_11.pdf -pub const ReportDescriptorKeyboard = hid_usage_page(UsageTable.desktop) ++ hid_usage(DesktopUsage.keyboard) ++ hid_collection(.Application) - // Input: modifier key bitmap -++ hid_usage_page(UsageTable.keyboard) ++ hid_usage_min("\xe0") ++ hid_usage_max("\xe7") ++ hid_logical_min("\x00") ++ hid_logical_max("\x01") ++ hid_report_count("\x08") ++ hid_report_size("\x01") ++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) // - // Reserved 8 bits -++ hid_report_count("\x01") ++ hid_report_size("\x08") ++ hid_input(HID_CONSTANT) - // Output: indicator LEDs -++ hid_usage_page(UsageTable.led) ++ hid_usage_min("\x01") ++ hid_usage_max("\x05") ++ hid_report_count("\x05") ++ hid_report_size("\x01") ++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) - // Padding -++ hid_report_count("\x01") ++ hid_report_size("\x03") ++ hid_output(HID_CONSTANT) - // Input: up to 6 pressed key codes -++ hid_usage_page(UsageTable.keyboard) ++ hid_usage_min("\x00") ++ hid_usage_max("\xff\x00") ++ hid_logical_min("\x00") ++ hid_logical_max("\xff\x00") ++ hid_report_count("\x06") ++ hid_report_size("\x08") ++ hid_input(HID_DATA | HID_ARRAY | HID_ABSOLUTE) - // End -++ hid_collection_end(); - pub const HidClassDriver = struct { pub const num_interfaces = 1; @@ -491,12 +128,12 @@ pub const HidClassDriver = struct { hid_descriptor: []const u8 = &.{}, report_descriptor: []const u8, - pub fn config_descriptor(string_ids: anytype, endpoints: anytype) []const u8 { - return &usb.templates.DescriptorsConfigTemplates.hid_in_out_descriptor( - 0, + pub fn config_descriptor(first_interface: u8, string_ids: anytype, endpoints: anytype) InOutDescriptor { + return .create( + first_interface, string_ids.name, 0, - ReportDescriptorGenericInOut.len, + descriptor.hid.report.GenericInOut.len, endpoints.main, endpoints.main, 64, @@ -505,38 +142,10 @@ pub const HidClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn mount(_: *@This(), _: usb.ControllerInterface) void {} - - pub fn open(ptr: *@This(), controller: usb.ControllerInterface, cfg: []const u8) anyerror!usize { - var self: *@This() = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; - - if (bos.try_get_desc_as(descriptor.Interface, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return error.UnsupportedInterfaceClassType; - } else { - return error.ExpectedInterfaceDescriptor; - } - - curr_cfg = bos.get_desc_next(curr_cfg); - if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| { - self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)]; - curr_cfg = bos.get_desc_next(curr_cfg); - } else { - return error.UnexpectedDescriptor; - } - - for (0..2) |_| { - if (bos.try_get_desc_as(descriptor.Endpoint, curr_cfg)) |desc_ep| { - switch (desc_ep.endpoint.dir) { - .In => self.ep_in = desc_ep.endpoint.num, - .Out => self.ep_out = desc_ep.endpoint.num, - } - _ = try controller.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); - } - } - - return cfg.len - curr_cfg.len; + pub fn mount(this: *@This(), _: usb.ControllerInterface, desc: *const InOutDescriptor) anyerror!void { + this.hid_descriptor = std.mem.asBytes(&desc.hid); + this.ep_in = desc.ep_in.endpoint.num; + this.ep_out = desc.ep_out.endpoint.num; } pub fn class_control(ptr: *@This(), controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { @@ -544,7 +153,7 @@ pub const HidClassDriver = struct { switch (setup.request_type.type) { .Standard => if (stage == .Setup) { - const hid_desc_type = enumFromInt(SubType, (setup.value >> 8) & 0xff) catch return false; + const hid_desc_type = enumFromInt(descriptor.hid.SubType, (setup.value >> 8) & 0xff) catch return false; const request_code = enumFromInt(types.SetupRequest, setup.request) catch return false; if (request_code != .GetDescriptor) return false; @@ -557,7 +166,7 @@ pub const HidClassDriver = struct { controller.control_transfer(data[0..@min(data.len, setup.length)]); }, - .Class => switch (enumFromInt(HidRequestType, setup.request) catch return false) { + .Class => switch (enumFromInt(descriptor.hid.RequestType, setup.request) catch return false) { .SetIdle => if (stage == .Setup) { // TODO: The host is attempting to limit bandwidth by requesting that // the device only return report data when its values actually change, @@ -599,30 +208,3 @@ pub const HidClassDriver = struct { pub fn on_tx_ready(_: *@This(), _: usb.ControllerInterface, _: []u8) void {} pub fn on_data_rx(_: *@This(), _: usb.ControllerInterface, _: []const u8) void {} }; - -test "create hid report item" { - const r = hid_report_item( - 2, - 0, - 3, - "\x22\x11", - ); - - try std.testing.expectEqual(@as(usize, @intCast(3)), r.len); - try std.testing.expectEqual(@as(u8, @intCast(50)), r[0]); - try std.testing.expectEqual(@as(u8, @intCast(0x22)), r[1]); - try std.testing.expectEqual(@as(u8, @intCast(0x11)), r[2]); -} - -test "create hid fido usage page" { - const f = hid_usage_page(UsageTable.fido); - - try std.testing.expectEqual(@as(usize, @intCast(3)), f.len); - try std.testing.expectEqual(@as(u8, @intCast(6)), f[0]); - try std.testing.expectEqual(@as(u8, @intCast(0xd0)), f[1]); - try std.testing.expectEqual(@as(u8, @intCast(0xf1)), f[2]); -} - -test "report descriptor fido" { - _ = ReportDescriptorFidoU2f; -} diff --git a/core/src/core/usb/templates.zig b/core/src/core/usb/templates.zig deleted file mode 100644 index add268b65..000000000 --- a/core/src/core/usb/templates.zig +++ /dev/null @@ -1,59 +0,0 @@ -const usb = @import("../usb.zig"); -const cdc = usb.cdc; -const descriptor = usb.descriptor; -const hid = usb.hid; -const types = usb.types; -const Ep = types.Endpoint; - -pub const DescriptorsConfigTemplates = struct { - pub const config_descriptor_len = 9; - - pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: descriptor.Configuration.Attributes, max_current: descriptor.Configuration.MaxCurrent) [9]u8 { - const desc1 = descriptor.Configuration{ .total_length = .from(total_len), .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = attributes, .max_current = max_current }; - return desc1.serialize(); - } - - pub const cdc_descriptor_len = 8 + 9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7; - - pub fn cdc_descriptor(interface_number: u8, string_index: u8, endpoint_in_notifi: Ep.Num, endpoint_notifi_size: u16, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16) [cdc_descriptor_len]u8 { - const desc1 = descriptor.InterfaceAssociation{ .first_interface = interface_number, .interface_count = 2, .function_class = 2, .function_subclass = 2, .function_protocol = 0, .function = 0 }; - const desc2 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, .interface_subclass = 2, .interface_protocol = 0, .interface_s = string_index }; - const desc3 = descriptor.cdc.Header{ .descriptor_type = .CsInterface, .descriptor_subtype = .Header, .bcd_cdc = .from(0x0120) }; - const desc4 = descriptor.cdc.CallManagement{ .descriptor_type = .CsInterface, .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1 }; - const desc5 = descriptor.cdc.AbstractControlModel{ .descriptor_type = .CsInterface, .descriptor_subtype = .AbstractControlModel, .capabilities = 6 }; - const desc6 = descriptor.cdc.Union{ .descriptor_type = .CsInterface, .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1 }; - const desc7 = descriptor.Endpoint{ .endpoint = .in(endpoint_in_notifi), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = .from(endpoint_notifi_size), .interval = 16 }; - const desc8 = descriptor.Interface{ .interface_number = interface_number + 1, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, .interface_subclass = 0, .interface_protocol = 0, .interface_s = 0 }; - const desc9 = descriptor.Endpoint{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; - const desc10 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize() ++ desc5.serialize() ++ desc6.serialize() ++ desc7.serialize() ++ desc8.serialize() ++ desc9.serialize() ++ desc10.serialize(); - } - - pub const hid_in_descriptor_len = 9 + 9 + 7; - - pub fn hid_in_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in: Ep.Num, endpoint_size: u16, endpoint_interval: u16) [hid_in_descriptor_len]u8 { - const desc1 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; - const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); - } - - pub const hid_in_out_descriptor_len = 9 + 9 + 7 + 7; - - pub fn hid_in_out_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16, endpoint_interval: u16) [hid_in_out_descriptor_len]u8 { - const desc1 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index }; - const desc2 = hid.HidDescriptor{ .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len }; - const desc3 = descriptor.Endpoint{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = .from(endpoint_size), .interval = endpoint_interval }; - const desc4 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = .from(endpoint_size), .interval = endpoint_interval }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize(); - } - - pub const vendor_descriptor_len = 9 + 7 + 7; - - pub fn vendor_descriptor(interface_number: u8, string_index: u8, endpoint_out: Ep.Num, endpoint_in: Ep.Num, endpoint_size: u16) [vendor_descriptor_len]u8 { - const desc1 = descriptor.Interface{ .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xff, .interface_subclass = 0, .interface_protocol = 0, .interface_s = string_index }; - const desc2 = descriptor.Endpoint{ .endpoint = .out(endpoint_out), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; - const desc3 = descriptor.Endpoint{ .endpoint = .in(endpoint_in), .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = .from(endpoint_size), .interval = 0 }; - return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize(); - } -}; diff --git a/core/src/core/usb/utils.zig b/core/src/core/usb/utils.zig deleted file mode 100644 index e2a1db895..000000000 --- a/core/src/core/usb/utils.zig +++ /dev/null @@ -1,47 +0,0 @@ -const std = @import("std"); -const types = @import("types.zig"); - -pub const BosConfig = struct { - pub inline fn get_desc_len(bos_cfg: []const u8) u8 { - return bos_cfg[0]; - } - - pub fn get_desc_type(bos_cfg: []const u8) u8 { - return bos_cfg[1]; - } - - pub fn get_data_u8(bos_cfg: []const u8, offset: u16) u8 { - return bos_cfg[offset]; - } - - pub fn get_data_u16(bos_cfg: []const u8, offset: u16) u16 { - const low_byte: u16 = bos_cfg[offset]; - const high_byte: u16 = bos_cfg[offset + 1]; - return (high_byte << 8) | low_byte; - } - - /// Only for temporal u8 fields use as u16 fields will have wrong values because BOS endianness - pub fn get_desc_as(comptime T: type, bos_cfg: []const u8) *const T { - return @ptrCast(@constCast(bos_cfg.ptr)); - } - - /// Only for temporal u8 fields use as u16 fields will have wrong values because BOS endianness - pub fn try_get_desc_as(comptime T: type, bos_cfg: []const u8) ?*const T { - if (bos_cfg.len == 0) return null; - const exp_desc_type = @field(T, "const_descriptor_type"); - if (get_desc_type(bos_cfg) != @intFromEnum(exp_desc_type)) { - return null; - } else { - return @constCast(@ptrCast(bos_cfg.ptr)); - } - } - - pub fn get_desc_next(bos_cfg: []const u8) []const u8 { - return bos_cfg[get_desc_len(bos_cfg)..]; - } -}; - -test "Test try_get_desc_as" { - const cfg = [_]u8{ 7, 5, 129, 3, 8, 0, 16 }; - try std.testing.expect(BosConfig.try_get_desc_as(types.EndpointDescriptor, cfg[0..]) != null); -} diff --git a/core/src/core/usb/vendor.zig b/core/src/core/usb/vendor.zig deleted file mode 100644 index 7edd2eb34..000000000 --- a/core/src/core/usb/vendor.zig +++ /dev/null @@ -1,24 +0,0 @@ -const std = @import("std"); - -const types = @import("types.zig"); - -pub const VendorClassDriver = struct { - fn init(_: *anyopaque, _: types.UsbDevice) void {} - - fn open(_: *anyopaque, _: []const u8) !usize { - return 0; - } - - pub fn class_control(_: *anyopaque, _: types.ControlStage, _: *const types.SetupPacket) bool { - return true; - } - - pub fn driver(self: *@This()) types.UsbClassDriver { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control, - }; - } -}; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 5da4ea354..149c6987a 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -12,7 +12,7 @@ const baud_rate = 115200; const uart_tx_pin = gpio.num(0); // This is our device configuration -const UsbDev = microzig.core.usb.Controller(.{ +const Usb = microzig.core.usb.Controller(.{ .Device = rp2xxx.usb.Usb(.{}), .attributes = .{ .self_powered = true }, .device_triple = .{ @@ -28,7 +28,7 @@ const UsbDev = microzig.core.usb.Controller(.{ .{ .name = "name", .value = "Board HID" }, }, }); -var usb: UsbDev = .init; +var usb: Usb = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -42,7 +42,7 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { - usb.driver_data = .{ .report_descriptor = µzig.core.usb.hid.ReportDescriptorGenericInOut }; + usb.driver_data = .{ .report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut }; // init uart logging uart_tx_pin.set_function(.uart); diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 932a6b1d4..1dcf1f562 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -5,7 +5,6 @@ const rp2xxx = microzig.hal; const flash = rp2xxx.flash; 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); @@ -14,7 +13,7 @@ const uart_tx_pin = gpio.num(12); const uart_rx_pin = gpio.num(1); // This is our device configuration -const UsbDev = usb.Controller(.{ +const Usb = microzig.core.usb.Controller(.{ .Device = rp2xxx.usb.Usb(.{}), .attributes = .{ .self_powered = true }, .Driver = microzig.core.usb.cdc.CdcClassDriver, @@ -26,8 +25,7 @@ const UsbDev = usb.Controller(.{ .{ .name = "name", .value = "Board CDC" }, }, }); - -var usb_dev: UsbDev = .init; +var usb: Usb = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -41,7 +39,7 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { - usb_dev.driver_data = .{}; + usb.driver_data = .{}; led.set_function(.sio); led.set_direction(.out); @@ -58,14 +56,14 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above - usb_dev.init_device(); + usb.init_device(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; var i: u32 = 0; while (true) { // You can now poll for USB events - usb_dev.interface().task(); + usb.interface().task(); new = time.get_time_since_boot().to_us(); if (new - old > 500000) { @@ -76,16 +74,16 @@ pub fn main() !void { var tx_buf: [1024]u8 = undefined; const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); - usb_dev.driver_data.writeAll(usb_dev.interface(), text); + usb.driver_data.writeAll(usb.interface(), text); } // read and print host command if present var rx_buf: [64]u8 = undefined; - const len = usb_dev.driver_data.read(usb_dev.interface(), &rx_buf); + const len = usb.driver_data.read(usb.interface(), &rx_buf); if (len > 0) { - usb_dev.driver_data.writeAll(usb_dev.interface(), "Your message to me was: '"); - usb_dev.driver_data.writeAll(usb_dev.interface(), rx_buf[0..len]); - usb_dev.driver_data.writeAll(usb_dev.interface(), "'\r\n"); + usb.driver_data.writeAll(usb.interface(), "Your message to me was: '"); + usb.driver_data.writeAll(usb.interface(), rx_buf[0..len]); + usb.driver_data.writeAll(usb.interface(), "'\r\n"); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index a0d97b482..0f752bedb 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -15,7 +15,7 @@ const resets = @import("resets.zig"); pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; -pub const UsbConfig = struct { +pub const Config = struct { synchronization_nops: comptime_int = 3, dpram_allocator: type = DpramAllocatorBump, // swap_dpdm: bool = false, @@ -89,21 +89,50 @@ pub const DpramAllocatorBump = struct { /// We create a concrete implementaion by passing a handful /// of system specific functions to Usb(). Those functions /// are used by the abstract USB impl of microzig. -pub fn Usb(comptime config: UsbConfig) type { +pub fn Usb(comptime config: Config) type { return struct { pub const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; pub const max_transfer_size = 64; // TODO: Support other buffer sizes. + pub const bcd_usb = 0x02_00; pub const default_strings: usb.Config.Strings = .{ .manufacturer = "Raspberry Pi", .product = "Pico Test Device", .serial = "someserial", }; - pub const bcd_usb = 0x02_00; - pub const default_vidpid: usb.Config.VidPid = .{ + pub const default_vid_pid: usb.Config.VidPid = .{ .vendor = 0x2E8A, .product = 0x000a, }; + pub const Events = struct { + pub const BufferIterator = struct { + // One bit per endpoint. + initial: u32, + pending: u32, + + pub fn next(this: *@This()) ?usb.EndpointAndBuffer { + const idx = std.math.cast(u5, @ctz(this.pending)) orelse { + if (this.initial != 0) + peri_usb.BUFF_STATUS.write_raw(this.initial); + return null; + }; + this.pending &= this.pending -% 1; // Clear lowest bit. + const ep: HardwareEndpoint = .from_idx(idx); + const buf = ep.buffer(); + if (ep.is_out) { + const len = ep.buf_ctrl().read().LENGTH_0; + return .{ .Out = .{ .ep_num = ep.num, .buffer = buf[0..len] } }; + } else return .{ .In = .{ .ep_num = ep.num, .buffer = buf } }; + } + }; + + unhandled_buffers: ?BufferIterator, + bus_reset: bool, + device_suspend: bool, + host_resume: bool, + setup_packet: ?usb.types.SetupPacket, + }; + const HardwareEndpoint = packed struct(u7) { const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = @ptrCast(&peri_dpram.EP1_IN_CONTROL); @@ -171,7 +200,7 @@ pub fn Usb(comptime config: UsbConfig) type { } }; - pub fn usb_init_device() void { + pub fn usb_init_device() ?[]u8 { if (chip == .RP2350) peri_usb.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); @@ -224,12 +253,48 @@ pub fn Usb(comptime config: UsbConfig) type { .SETUP_REQ = 1, }); - _ = endpoint_open(.in(.ep0), .Control, 0) catch unreachable; + const ret = endpoint_open(.in(.ep0), .Control, 0) catch unreachable; _ = endpoint_open(.out(.ep0), .Control, 0) catch unreachable; // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. peri_usb.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); + return ret; + } + + pub fn set_address(addr: u7) void { + peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + } + + /// Check which interrupt flags are set + pub fn get_events() Events { + const ints = peri_usb.INTS.read(); + return .{ + .unhandled_buffers = if (ints.BUFF_STATUS != 0) blk: { + const mask = peri_usb.BUFF_STATUS.raw; + break :blk .{ .initial = mask, .pending = mask }; + } else null, + .bus_reset = ints.BUS_RESET == 1, + .device_suspend = ints.DEV_SUSPEND == 1, + .host_resume = ints.DEV_RESUME_FROM_HOST == 1, + .setup_packet = if (ints.SETUP_REQ != 0) blk: { + // Clear the status flag (write-one-to-clear) + var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); + sie_status.SETUP_REC = 1; + peri_usb.SIE_STATUS.write(sie_status); + + // Reset PID to 1 for EP0 IN. Every DATA packet we send in response + // to an IN on EP0 needs to use PID DATA1. + defer peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); + + // Copy the setup packet out of its dedicated buffer at the base of + // USB SRAM. The PAC models this buffer as two 32-bit registers. + break :blk @bitCast([2]u32{ + peri_dpram.SETUP_PACKET_LOW.raw, + peri_dpram.SETUP_PACKET_HIGH.raw, + }); + } else null, + }; } /// Configures a given endpoint to send data (device-to-host, IN) when the host @@ -282,7 +347,7 @@ pub fn Usb(comptime config: UsbConfig) type { buf_ctrl.write(rmw); } - pub fn usb_start_rx(ep_out: Endpoint.Num, len: usize) void { + pub fn signal_rx_ready(ep_out: Endpoint.Num, len: usize) void { const ep_hard: HardwareEndpoint = .out(ep_out); // Configure the OUT: @@ -293,9 +358,9 @@ pub fn Usb(comptime config: UsbConfig) type { return; } rmw.PID_0 ^= 1; // Flip DATA0/1 - rmw.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it - rmw.AVAILABLE_0 = 1; // It is, however, available to be filled - rmw.LENGTH_0 = @intCast(len); // Up tho this many bytes + rmw.FULL_0 = 0; // Buffer is empty + rmw.AVAILABLE_0 = 1; // And ready to be filled + rmw.LENGTH_0 = @intCast(@min(len, max_transfer_size)); buf_ctrl.write(rmw); ep_hard.awaiting_rx_set(); @@ -306,47 +371,8 @@ pub fn Usb(comptime config: UsbConfig) type { ep_hard.awaiting_rx_clr(); } - /// Check which interrupt flags are set - pub fn get_interrupts() usb.InterruptStatus { - const ints = peri_usb.INTS.read(); - return .{ - .BuffStatus = ints.BUFF_STATUS == 1, - .BusReset = ints.BUS_RESET == 1, - .DevConnDis = ints.DEV_CONN_DIS == 1, - .DevSuspend = ints.DEV_SUSPEND == 1, - .DevResumeFromHost = ints.DEV_RESUME_FROM_HOST == 1, - .SetupReq = ints.SETUP_REQ == 1, - }; - } - - /// Returns a received USB setup packet - /// - /// Side effect: The setup request status flag will be cleared - /// - /// One can assume that this function is only called if the - /// setup request falg is set. - pub fn get_setup_packet() usb.types.SetupPacket { - // Clear the status flag (write-one-to-clear) - var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); - sie_status.SETUP_REC = 1; - peri_usb.SIE_STATUS.write(sie_status); - - // 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. - defer peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); - - // This assumes that the setup packet is arriving on EP0, our - // control endpoint. Which it should be. We don't have any other - // Control endpoints. - - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers, - return @bitCast([2]u32{ peri_dpram.SETUP_PACKET_LOW.raw, peri_dpram.SETUP_PACKET_HIGH.raw }); - } - /// Called on a bus reset interrupt - pub fn bus_reset() void { + pub fn bus_reset_clear() void { // Acknowledge by writing the write-one-to-clear status bit. var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); sie_status.BUS_RESET = 1; @@ -354,10 +380,6 @@ pub fn Usb(comptime config: UsbConfig) type { peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); } - pub fn set_address(addr: u7) void { - peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); - } - pub fn endpoint_open(ep: Endpoint, transfer_type: usb.types.TransferType, buf_size_hint: usize) error{OutOfBufferMemory}!?[]u8 { _ = buf_size_hint; @@ -379,33 +401,5 @@ pub fn Usb(comptime config: UsbConfig) type { } else DpramBuffer.Index.ep0buf0.start(); return start[0..max_transfer_size]; } - - pub fn get_unhandled_endpoints() UnhandledEndpointIterator { - const mask = peri_usb.BUFF_STATUS.raw; - return .{ - .initial_unhandled_mask = mask, - .currently_unhandled_mask = mask, - }; - } - - pub const UnhandledEndpointIterator = struct { - initial_unhandled_mask: u32, - currently_unhandled_mask: u32, - - pub fn next(this: *@This()) ?usb.EndpointAndBuffer { - const idx = std.math.cast(u5, @ctz(this.currently_unhandled_mask)) orelse { - if (this.initial_unhandled_mask != 0) - peri_usb.BUFF_STATUS.write_raw(this.initial_unhandled_mask); - return null; - }; - this.currently_unhandled_mask &= this.currently_unhandled_mask -% 1; // clear lowest bit - const ep: HardwareEndpoint = .from_idx(idx); - const buf = ep.buffer(); - if (ep.is_out) { - const len = ep.buf_ctrl().read().LENGTH_0; - return .{ .Out = .{ .ep_num = ep.num, .buffer = buf[0..len] } }; - } else return .{ .In = .{ .ep_num = ep.num, .buffer = buf } }; - } - }; }; } From d82651539869cfd02c775fa4a2bea4a80e718d2b Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 22 Aug 2025 05:25:22 +0200 Subject: [PATCH 14/33] redo endpoint 0 control and remove unused functions --- core/src/core/usb.zig | 157 +++++++++++++----------- port/raspberrypi/rp2xxx/src/hal/usb.zig | 63 ++-------- 2 files changed, 95 insertions(+), 125 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index fcfc86a85..48b658e7d 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -28,7 +28,7 @@ pub const ControllerInterface = struct { vtable: *const Vtable, pub fn task(this: @This()) void { - return this.vtable.task(this.ptr); + this.vtable.task(this.ptr); } pub fn control_transfer(this: @This(), data: []const u8) void { assert(data.len != 0); @@ -38,10 +38,10 @@ pub const ControllerInterface = struct { this.vtable.control_transfer(this.ptr, ""); } pub fn submit_tx_buffer(this: @This(), ep_in: EpNum, buffer_end: [*]const u8) void { - return this.vtable.submit_tx_buffer(this.ptr, ep_in, buffer_end); + this.vtable.submit_tx_buffer(this.ptr, ep_in, buffer_end); } pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { - return this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); + this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); } }; @@ -220,6 +220,49 @@ pub fn Controller(comptime config: Config) type { }; }; + const ACK = ""; + + pub const ControlEndpointDriver = union(enum) { + sending: []const u8, // Slice of data left to be sent. + no_buffer, + ready: []u8, // Buffer for next transaction. Always empty if available. + + fn submit(this: *@This(), tx_buf: []u8, data: []const u8) void { + const len = @min(tx_buf.len, data.len); + if (len == 0) + this.* = .no_buffer + else { + std.mem.copyForwards(u8, tx_buf, data[0..len]); + this.* = .{ .sending = data[len..] }; + } + config.Device.submit_tx_buffer(.ep0, tx_buf.ptr + len); + } + + pub fn send(this: *@This(), data: []const u8) void { + switch (this.*) { + .sending => |residual| { + std.log.err("residual data: {any}", .{residual}); + this.* = .{ .sending = data }; + }, + .no_buffer => this.* = .{ .sending = data }, + .ready => |tx_buf| this.submit(tx_buf, data), + } + } + + pub fn on_tx_ready(this: *@This(), tx_buf: []u8) bool { + switch (this.*) { + .sending => |data| { + const ret = data.len == 0; + this.submit(tx_buf, data); + return ret; + }, + .no_buffer => this.* = .{ .ready = tx_buf }, + .ready => |_| std.log.err("Got buffer twice!", .{}), + } + return false; + } + }; + driver_by_interface: [16]?*config.Driver, driver_by_endpoint_in: [config.Device.max_endpoints_count]?*config.Driver, driver_by_endpoint_out: [config.Device.max_endpoints_count]?*config.Driver, @@ -231,10 +274,8 @@ pub fn Controller(comptime config: Config) type { new_address: ?u7, // Last setup packet request setup_packet: types.SetupPacket, - // Configuration data still pending to be sent over endpoint 0. - now_sending_ep0: ?[]const u8, - // Empty endpoint 0 tx buffer, if available. - ep0_tx: ?[]u8, + // + ep0_driver: ControlEndpointDriver, // 0 - no config set cfg_num: u16, driver_data: config.Driver, @@ -246,14 +287,14 @@ pub fn Controller(comptime config: Config) type { .driver = null, .new_address = null, .setup_packet = undefined, - .now_sending_ep0 = null, - .ep0_tx = null, + .ep0_driver = .no_buffer, .cfg_num = 0, .driver_data = undefined, }; pub fn init_device(this: *@This()) void { - this.ep0_tx = config.Device.usb_init_device(); + if (config.Device.usb_init_device()) |tx_buf| + this.ep0_driver = .{ .ready = tx_buf }; } pub fn interface(this: *@This()) ControllerInterface { @@ -263,32 +304,14 @@ pub fn Controller(comptime config: Config) type { }; } - fn send_cmd_response(this: *@This(), data: []const u8) void { - if (this.now_sending_ep0) |residual| - std.log.err("residual data: {any}", .{residual}); - - const len = config.Device.usb_start_tx(.ep0, data); - if (len != 0) - this.now_sending_ep0 = data[len..]; - } - fn control_transfer(ptr: *anyopaque, data: []const u8) void { const this: *@This() = @alignCast(@ptrCast(ptr)); - this.send_cmd_response(data); - } - - fn ep0_ack(this: *@This()) void { - if (this.now_sending_ep0) |residual| - std.log.err("residual data!: {any}", .{residual}); - if (this.ep0_tx) |tx| { - config.Device.submit_tx_buffer(.ep0, tx.ptr); - this.ep0_tx = null; - } else this.now_sending_ep0 = ""; + this.ep0_driver.send(data); } fn submit_tx_buffer(ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void { _ = ptr; - return config.Device.submit_tx_buffer(ep_in, buffer_end); + config.Device.submit_tx_buffer(ep_in, buffer_end); } fn signal_rx_ready(ptr: *anyopaque, ep_out: EpNum, max_len: usize) void { @@ -301,10 +324,10 @@ pub fn Controller(comptime config: Config) type { switch (enumFromInt(types.SetupRequest, setup.request) catch return) { .SetAddress => { this.new_address = @intCast(setup.value); - this.ep0_ack(); + this.ep0_driver.send(ACK); }, .SetConfiguration => { - defer this.ep0_ack(); + defer this.ep0_driver.send(ACK); if (this.cfg_num == setup.value) return; defer this.cfg_num = setup.value; @@ -378,11 +401,11 @@ pub fn Controller(comptime config: Config) type { else => break :blk, }; const len = @min(data.len, setup.length); - this.send_cmd_response(data[0..len]); + this.ep0_driver.send(data[0..len]); } else |_| {}, .SetFeature => if (enumFromInt(types.FeatureSelector, setup.value >> 8)) |feature| switch (feature) { - .DeviceRemoteWakeup, .EndpointHalt => this.ep0_ack(), + .DeviceRemoteWakeup, .EndpointHalt => this.ep0_driver.send(ACK), // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 .TestMode => {}, } @@ -420,53 +443,47 @@ pub fn Controller(comptime config: Config) type { // will be whatever was sent by the host. For IN, it's a new // transmit buffer that has become available. while (iter.next()) |result| switch (result) { - .In => |in| if (in.ep_num == .ep0) { - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (this.new_address) |addr| { - // Change our address: - config.Device.set_address(addr); - this.new_address = null; - } - - const data = this.now_sending_ep0 orelse { - // Save the buffer for the next transfer. - this.ep0_tx = in.buffer; - continue; - }; - - if (data.len > 0) { - const len = @min(in.buffer.len, data.len); - std.mem.copyForwards(u8, in.buffer[0..len], data[0..len]); - config.Device.submit_tx_buffer(.ep0, in.buffer.ptr + len); - this.now_sending_ep0 = data[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. - config.Device.signal_rx_ready(.ep0, 0); - if (this.driver) |drv| - _ = drv.class_control(this.interface(), .Ack, &this.setup_packet); - - this.now_sending_ep0 = null; - } - // TODO: Route different endpoints to different functions. - } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| - drv.on_tx_ready(this.interface(), in.buffer), + .In => |in| { + if (in.ep_num == .ep0) { + + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (this.new_address) |addr| { + // Change our address: + config.Device.set_address(addr); + this.new_address = null; + } + + if (this.ep0_driver.on_tx_ready(in.buffer)) { + // 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: + config.Device.signal_rx_ready(.ep0, 0); + if (this.driver) |drv| + _ = drv.class_control(this.interface(), .Ack, &this.setup_packet); + + // I believe this is incorrect but reality disagrees. + this.ep0_driver = .{ .ready = in.buffer }; + } + // TODO: Route different endpoints to different functions. + } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| + drv.on_tx_ready(this.interface(), in.buffer); + }, .Out => |out| { // TODO: Route different endpoints to different functions. if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| drv.on_data_rx(this.interface(), out.buffer); - - config.Device.endpoint_reset_rx(out.ep_num); }, }; } if (events.bus_reset) { // TODO: call umount callback if any + const tmp = this.ep0_driver; this.* = .init; + this.ep0_driver = tmp; config.Device.bus_reset_clear(); } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 0f752bedb..789f914ab 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -117,7 +117,10 @@ pub fn Usb(comptime config: Config) type { return null; }; this.pending &= this.pending -% 1; // Clear lowest bit. - const ep: HardwareEndpoint = .from_idx(idx); + const ep: HardwareEndpoint = .{ + .num = @enumFromInt(idx >> 1), + .is_out = (idx & 1) == 1, + }; const buf = ep.buffer(); if (ep.is_out) { const len = ep.buf_ctrl().read().LENGTH_0; @@ -133,7 +136,7 @@ pub fn Usb(comptime config: Config) type { setup_packet: ?usb.types.SetupPacket, }; - const HardwareEndpoint = packed struct(u7) { + pub const HardwareEndpoint = packed struct(u7) { const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = @ptrCast(&peri_dpram.EP1_IN_CONTROL); @@ -150,31 +153,11 @@ pub fn Usb(comptime config: Config) type { return @intCast(@shrExact(@as(u7, @bitCast(this)), 2)); } - inline fn from_idx(idx: u5) @This() { - return @bitCast(@shlExact(@as(u7, idx), 2)); - } - - inline fn mask(this: @This()) u32 { - return @as(u32, 1) << this.to_idx(); - } - - inline fn awaiting_rx_get(this: @This()) bool { - return (this.mask() & @atomicLoad(u32, &awaiting_rx, .seq_cst)) != 0; - } - - inline fn awaiting_rx_set(this: @This()) void { - _ = @atomicRmw(u32, &awaiting_rx, .Or, this.mask(), .seq_cst); - } - - inline fn awaiting_rx_clr(this: @This()) void { - _ = @atomicRmw(u32, &awaiting_rx, .And, ~this.mask(), .seq_cst); - } - fn from(ep: Endpoint) @This() { return .{ .num = ep.num, .is_out = ep.dir == .Out }; } - fn in(num: Endpoint.Num) @This() { + pub fn in(num: Endpoint.Num) @This() { return .{ .num = num, .is_out = false }; } @@ -191,7 +174,7 @@ pub fn Usb(comptime config: Config) type { return &buf_ctrl_all[this.to_idx()]; } - fn buffer(this: @This()) []u8 { + pub fn buffer(this: @This()) []u8 { const buf: DpramBuffer.Index = if (this.ep_ctrl()) |reg| .from_reg(reg.read()) else @@ -297,25 +280,6 @@ pub fn Usb(comptime config: Config) type { }; } - /// Configures a given endpoint to send data (device-to-host, IN) when the host - /// next asks for it. - /// - /// The contents of each of the slices in `data` will be _copied_ into USB SRAM, - /// so you can reuse them immediately after this returns. - /// No need to wait for the packet to be sent. - pub fn usb_start_tx(ep_in: Endpoint.Num, data: []const u8) usize { - const ep_hard: HardwareEndpoint = .in(ep_in); - const buf = ep_hard.buffer(); - - const len = @min(buf.len, data.len); - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, buf[0..len], data[0..len]); - - submit_tx_buffer(ep_in, buf.ptr + len); - - return len; - } - pub fn submit_tx_buffer(ep_in: Endpoint.Num, buffer_end: [*]const u8) void { const ep_hard: HardwareEndpoint = .in(ep_in); const buf = ep_hard.buffer(); @@ -353,22 +317,12 @@ pub fn Usb(comptime config: Config) type { // Configure the OUT: const buf_ctrl = ep_hard.buf_ctrl(); var rmw = buf_ctrl.read(); - if (ep_hard.awaiting_rx_get()) { - std.log.err("should not be called twice {} {}", .{ rmw.FULL_0, rmw.AVAILABLE_0 }); - return; - } + rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is empty rmw.AVAILABLE_0 = 1; // And ready to be filled rmw.LENGTH_0 = @intCast(@min(len, max_transfer_size)); buf_ctrl.write(rmw); - - ep_hard.awaiting_rx_set(); - } - - pub fn endpoint_reset_rx(ep_out: Endpoint.Num) void { - const ep_hard: HardwareEndpoint = .out(ep_out); - ep_hard.awaiting_rx_clr(); } /// Called on a bus reset interrupt @@ -386,7 +340,6 @@ pub fn Usb(comptime config: Config) type { const ep_hard: HardwareEndpoint = .from(ep); assert(ep.num.to_int() < max_endpoints_count); - ep_hard.awaiting_rx_clr(); const start = if (ep.num != .ep0) blk: { const buf = try config.dpram_allocator.alloc(1); From fb803ef54cbd6eb7e7a3277afcf0c6e57d8a8581 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 22 Aug 2025 18:07:53 +0200 Subject: [PATCH 15/33] make usb device own the controller, not vice versa --- core/src/core/usb.zig | 398 +++++++----------- core/src/core/usb/cdc.zig | 14 +- core/src/core/usb/hid.zig | 8 +- core/src/core/usb/types.zig | 30 +- .../rp2xxx/src/rp2040_only/usb_hid.zig | 15 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 23 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 220 +++++++--- 7 files changed, 336 insertions(+), 372 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 48b658e7d..da1b049ea 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -17,8 +17,8 @@ test "tests" { const EpNum = types.Endpoint.Num; -pub const ControllerInterface = struct { - const Vtable = struct { +pub const DeviceInterface = struct { + pub const Vtable = struct { task: *const fn (ptr: *anyopaque) void, control_transfer: *const fn (ptr: *anyopaque, data: []const u8) void, submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, @@ -31,7 +31,7 @@ pub const ControllerInterface = struct { this.vtable.task(this.ptr); } pub fn control_transfer(this: @This(), data: []const u8) void { - assert(data.len != 0); + // assert(data.len != 0); this.vtable.control_transfer(this.ptr, data); } pub fn control_ack(this: @This()) void { @@ -45,40 +45,36 @@ pub const ControllerInterface = struct { } }; +/// Vendor ID and product ID combo. +pub const VidPid = struct { product: u16, vendor: u16 }; + +/// Manufacturer, product and serial number strings. +pub const Strings = struct { + manufacturer: []const u8, + product: []const u8, + serial: []const u8, +}; + pub const Config = struct { pub fn NameValue(T: type) type { return struct { name: [:0]const u8, value: T }; } - /// Vendor ID and product ID combo. - pub const VidPid = struct { product: u16, vendor: u16 }; - - /// Manufacturer, product and serial number strings. - pub const Strings = struct { - manufacturer: []const u8, - product: []const u8, - serial: []const u8, - }; + /// String descriptor language. pub const Language = enum(u16) { English = 0x0409, }; - /// Underlying USB device. - Device: type, - /// Class, subclass and protocol of device. - device_triple: descriptor.Device.DeviceTriple = .unspecified, - /// Vendor ID and product ID combo. - id: ?VidPid = null, - /// Device version number as Binary Coded Decimal. - bcd_device: u16 = 0x01_00, - /// Manufacturer, product and serial number strings. - strings: ?Strings = null, /// Bit set of device attributes. attributes: descriptor.Configuration.Attributes, /// Maximum device current consumption.. max_current: descriptor.Configuration.MaxCurrent = .from_ma(100), /// String descriptor language. language: Language = .English, + /// Device version number as Binary Coded Decimal. + bcd_device: u16 = 0x01_00, + /// Class, subclass and protocol of device. + device_triple: descriptor.Device.DeviceTriple = .unspecified, // Eventually the fields below could be in an array to support multiple drivers. Driver: type, driver_endpoints: []const NameValue(EpNum), @@ -91,6 +87,12 @@ pub const EndpointAndBuffer = union(types.Dir) { In: struct { ep_num: EpNum, buffer: []u8 }, }; +pub const ControlEndpointState = union(enum) { + sending: []const u8, // Slice of data left to be sent. + no_buffer: ?u7, // Optionally a new address. + ready: []u8, // Buffer for next transaction. Always empty if available. +}; + /// Create a USB device controller. /// /// This is an abstract USB device controller implementation that requires @@ -113,221 +115,141 @@ pub const EndpointAndBuffer = union(types.Dir) { pub fn Controller(comptime config: Config) type { return struct { pub const max_packet_size = config.Device.max_packet_size; - - pub const interface_vtable: ControllerInterface.Vtable = .{ - .task = &task, - .control_transfer = &control_transfer, - .signal_rx_ready = &signal_rx_ready, - .submit_tx_buffer = &submit_tx_buffer, - }; - - const device_descriptor: descriptor.Device = .{ - .bcd_usb = .from(config.Device.bcd_usb), - .device_triple = config.device_triple, - .max_packet_size0 = config.Device.max_transfer_size, - .vendor = .from(if (config.id) |id| id.vendor else config.Device.default_vid_pid.vendor), - .product = .from(if (config.id) |id| id.product else config.Device.default_vid_pid.product), - .bcd_device = .from(config.bcd_device), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }; - - const config_descriptor = blk: { - const Field = std.builtin.Type.StructField; - var fields: []const Field = &.{}; - for (config.driver_endpoints) |fld| { - fields = fields ++ .{Field{ - .name = fld.name, - .type = @TypeOf(fld.value), - .default_value_ptr = @ptrCast(&fld.value), - .is_comptime = true, - .alignment = @alignOf(@TypeOf(fld.value)), - }}; - } - const endpoints_struct = @Type(.{ .@"struct" = .{ - .layout = .auto, - .fields = fields, - .decls = &.{}, - .is_tuple = false, - } }); - - const driver = config.Driver.config_descriptor(0, string.ids{}, endpoints_struct{}); - assert(@alignOf(@TypeOf(driver)) == 1); - assert(@typeInfo(@TypeOf(driver)).@"struct".layout == .@"extern"); - - const Ret = extern struct { - first: descriptor.Configuration, - driver: @TypeOf(driver), - - fn serialize(this: @This()) [@sizeOf(@This())]u8 { - return @bitCast(this); - } - }; - - break :blk Ret{ - .first = .{ - .total_length = .from(@sizeOf(Ret)), - .num_interfaces = config.Driver.num_interfaces, - .configuration_value = 1, - .configuration_s = 0, - .attributes = config.attributes, - .max_current = config.max_current, - }, - .driver = driver, - }; - }; - - const string = blk: { - const Dev = config.Device; - - // String 0 indicates language. First byte is length. - const lang: types.U16Le = .from(@intFromEnum(config.language)); - var desc: []const []const u8 = &.{&.{ - 0x04, - @intFromEnum(descriptor.Type.String), - lang.lo, - lang.hi, - }}; - - const device_strings = config.strings orelse Dev.default_strings; - desc = desc ++ .{descriptor.string(device_strings.manufacturer)}; - desc = desc ++ .{descriptor.string(device_strings.product)}; - desc = desc ++ .{descriptor.string(device_strings.serial)}; - - const Field = std.builtin.Type.StructField; - var fields: []const Field = &.{}; - for (config.driver_strings) |fld| { - const id: u8 = desc.len; - fields = fields ++ .{Field{ - .name = fld.name, - .type = @TypeOf(id), - .default_value_ptr = @ptrCast(&id), - .is_comptime = true, - .alignment = @alignOf(@TypeOf(id)), - }}; - desc = desc ++ .{descriptor.string(fld.value)}; - } - break :blk .{ - .descriptors = desc, - .ids = @Type(.{ .@"struct" = .{ - .layout = .auto, - .fields = fields, - .decls = &.{}, - .is_tuple = false, - } }), - }; - }; + const max_endpoints_count = 16; const ACK = ""; - pub const ControlEndpointDriver = union(enum) { - sending: []const u8, // Slice of data left to be sent. - no_buffer, - ready: []u8, // Buffer for next transaction. Always empty if available. - - fn submit(this: *@This(), tx_buf: []u8, data: []const u8) void { - const len = @min(tx_buf.len, data.len); - if (len == 0) - this.* = .no_buffer - else { - std.mem.copyForwards(u8, tx_buf, data[0..len]); - this.* = .{ .sending = data[len..] }; - } - config.Device.submit_tx_buffer(.ep0, tx_buf.ptr + len); - } - - pub fn send(this: *@This(), data: []const u8) void { - switch (this.*) { - .sending => |residual| { - std.log.err("residual data: {any}", .{residual}); - this.* = .{ .sending = data }; - }, - .no_buffer => this.* = .{ .sending = data }, - .ready => |tx_buf| this.submit(tx_buf, data), - } - } - - pub fn on_tx_ready(this: *@This(), tx_buf: []u8) bool { - switch (this.*) { - .sending => |data| { - const ret = data.len == 0; - this.submit(tx_buf, data); - return ret; - }, - .no_buffer => this.* = .{ .ready = tx_buf }, - .ready => |_| std.log.err("Got buffer twice!", .{}), - } - return false; - } - }; - driver_by_interface: [16]?*config.Driver, - driver_by_endpoint_in: [config.Device.max_endpoints_count]?*config.Driver, - driver_by_endpoint_out: [config.Device.max_endpoints_count]?*config.Driver, + driver_by_endpoint_in: [max_endpoints_count]?*config.Driver, + driver_by_endpoint_out: [max_endpoints_count]?*config.Driver, // Class driver associated with last setup request if any driver: ?*config.Driver, - // When the host gives us a new address, we can't just slap it into - // registers right away, because we have to do an acknowledgement step using - // our _old_ address. - new_address: ?u7, // Last setup packet request setup_packet: types.SetupPacket, - // - ep0_driver: ControlEndpointDriver, // 0 - no config set cfg_num: u16, driver_data: config.Driver, - pub const init: @This() = .{ + pub const default: @This() = .{ .driver_by_interface = @splat(null), .driver_by_endpoint_in = @splat(null), .driver_by_endpoint_out = @splat(null), .driver = null, - .new_address = null, .setup_packet = undefined, - .ep0_driver = .no_buffer, .cfg_num = 0, .driver_data = undefined, }; - pub fn init_device(this: *@This()) void { - if (config.Device.usb_init_device()) |tx_buf| - this.ep0_driver = .{ .ready = tx_buf }; + pub fn init() @This() { + return .default; } - pub fn interface(this: *@This()) ControllerInterface { - return .{ - .ptr = this, - .vtable = &interface_vtable, + pub fn deinit(_: *@This()) void {} + + pub fn process_device_setup_request(this: *@This(), setup: *const types.SetupPacket, device: anytype) anyerror!void { + const device_descriptor: descriptor.Device = comptime .{ + .bcd_usb = .from(@TypeOf(device.*).bcd_usb), + .device_triple = config.device_triple, + .max_packet_size0 = @TypeOf(device.*).max_transfer_size, + .vendor = .from(@TypeOf(device.*).id.vendor), + .product = .from(@TypeOf(device.*).id.product), + .bcd_device = .from(config.bcd_device), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, }; - } - fn control_transfer(ptr: *anyopaque, data: []const u8) void { - const this: *@This() = @alignCast(@ptrCast(ptr)); - this.ep0_driver.send(data); - } + const string = comptime blk: { + const Dev = @TypeOf(device.*); - fn submit_tx_buffer(ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void { - _ = ptr; - config.Device.submit_tx_buffer(ep_in, buffer_end); - } + // String 0 indicates language. First byte is length. + const lang: types.U16Le = .from(@intFromEnum(config.language)); + var desc: []const []const u8 = &.{&.{ + 0x04, + @intFromEnum(descriptor.Type.String), + lang.lo, + lang.hi, + }}; - fn signal_rx_ready(ptr: *anyopaque, ep_out: EpNum, max_len: usize) void { - _ = ptr; - config.Device.signal_rx_ready(ep_out, max_len); - } + desc = desc ++ .{descriptor.string(Dev.strings.manufacturer)}; + desc = desc ++ .{descriptor.string(Dev.strings.product)}; + desc = desc ++ .{descriptor.string(Dev.strings.serial)}; + + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.driver_strings) |fld| { + const id: u8 = desc.len; + fields = fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(id), + .default_value_ptr = @ptrCast(&id), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(id)), + }}; + desc = desc ++ .{descriptor.string(fld.value)}; + } + break :blk .{ + .descriptors = desc, + .ids = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }), + }; + }; + + const config_descriptor = comptime blk: { + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.driver_endpoints) |fld| { + fields = fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(fld.value), + .default_value_ptr = @ptrCast(&fld.value), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(fld.value)), + }}; + } + const endpoints_struct = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); + + const driver = config.Driver.config_descriptor(0, string.ids{}, endpoints_struct{}); + assert(@alignOf(@TypeOf(driver)) == 1); + assert(@typeInfo(@TypeOf(driver)).@"struct".layout == .@"extern"); + + const Ret = extern struct { + first: descriptor.Configuration, + driver: @TypeOf(driver), + + fn serialize(x: @This()) [@sizeOf(@This())]u8 { + return @bitCast(x); + } + }; + + break :blk Ret{ + .first = .{ + .total_length = .from(@sizeOf(Ret)), + .num_interfaces = config.Driver.num_interfaces, + .configuration_value = 1, + .configuration_s = 0, + .attributes = config.attributes, + .max_current = config.max_current, + }, + .driver = driver, + }; + }; - fn process_device_setup_request(this: *@This(), setup: *const types.SetupPacket) anyerror!void { if (setup.request_type.type != .Standard) return; switch (enumFromInt(types.SetupRequest, setup.request) catch return) { - .SetAddress => { - this.new_address = @intCast(setup.value); - this.ep0_driver.send(ACK); - }, + .SetAddress => device.set_address(@intCast(setup.value)), .SetConfiguration => { - defer this.ep0_driver.send(ACK); + defer device.interface().control_transfer(ACK); if (this.cfg_num == setup.value) return; defer this.cfg_num = setup.value; @@ -365,14 +287,14 @@ pub fn Controller(comptime config: Config) type { if (desc_ep.endpoint.dir != .Out) continue; this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = &this.driver_data; - _ = try config.Device.endpoint_open( + try device.endpoint_open( desc_ep.endpoint, desc_ep.attributes.transfer_type, desc_ep.max_packet_size.into(), ); } - try this.driver_data.mount(this.interface(), &driver); + try this.driver_data.mount(device.interface(), &driver); inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; @@ -380,12 +302,12 @@ pub fn Controller(comptime config: Config) type { if (desc_ep.endpoint.dir != .In) continue; this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = &this.driver_data; - if (try config.Device.endpoint_open( + if (try device.endpoint_open( desc_ep.endpoint, desc_ep.attributes.transfer_type, desc_ep.max_packet_size.into(), )) |buf| - this.driver_data.on_tx_ready(this.interface(), buf); + this.driver_data.on_tx_ready(device.interface(), buf); } } }, @@ -401,11 +323,11 @@ pub fn Controller(comptime config: Config) type { else => break :blk, }; const len = @min(data.len, setup.length); - this.ep0_driver.send(data[0..len]); + device.interface().control_transfer(data[0..len]); } else |_| {}, .SetFeature => if (enumFromInt(types.FeatureSelector, setup.value >> 8)) |feature| switch (feature) { - .DeviceRemoteWakeup, .EndpointHalt => this.ep0_driver.send(ACK), + .DeviceRemoteWakeup, .EndpointHalt => device.interface().control_transfer(ACK), // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 .TestMode => {}, } @@ -417,19 +339,16 @@ pub fn Controller(comptime config: Config) type { /// initializing the device. /// /// You have to ensure the device has been initialized. - fn task(ptr: *anyopaque) void { - const this: *@This() = @alignCast(@ptrCast(ptr)); - const events = config.Device.get_events(); - + pub fn task(this: *@This(), events: anytype, device: anytype) void { if (events.setup_packet) |setup| { this.setup_packet = setup; this.driver = null; switch (setup.request_type.recipient) { - .Device => this.process_device_setup_request(&setup) catch unreachable, + .Device => this.process_device_setup_request(&setup, device) catch unreachable, .Interface => if (this.driver_by_interface[setup.index & 0xFF]) |drv| { this.driver = drv; - if (drv.class_control(this.interface(), .Setup, &setup) == false) { + if (drv.class_control(device.interface(), .Setup, &setup) == false) { // TODO } }, @@ -444,48 +363,19 @@ pub fn Controller(comptime config: Config) type { // transmit buffer that has become available. while (iter.next()) |result| switch (result) { .In => |in| { - if (in.ep_num == .ep0) { - - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (this.new_address) |addr| { - // Change our address: - config.Device.set_address(addr); - this.new_address = null; - } - - if (this.ep0_driver.on_tx_ready(in.buffer)) { - // 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: - config.Device.signal_rx_ready(.ep0, 0); - if (this.driver) |drv| - _ = drv.class_control(this.interface(), .Ack, &this.setup_packet); - - // I believe this is incorrect but reality disagrees. - this.ep0_driver = .{ .ready = in.buffer }; - } + if (in.ep_num == .ep0) + unreachable + else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| // TODO: Route different endpoints to different functions. - } else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| - drv.on_tx_ready(this.interface(), in.buffer); + drv.on_tx_ready(device.interface(), in.buffer); }, .Out => |out| { - // TODO: Route different endpoints to different functions. - if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| - drv.on_data_rx(this.interface(), out.buffer); + if (out.ep_num == .ep0) {} else if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| + // TODO: Route different endpoints to different functions. + drv.on_data_rx(device.interface(), out.buffer); }, }; } - - if (events.bus_reset) { - // TODO: call umount callback if any - const tmp = this.ep0_driver; - this.* = .init; - this.ep0_driver = tmp; - config.Device.bus_reset_clear(); - } } }; } diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 29515d0f2..fa613f8c5 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -122,7 +122,7 @@ pub const CdcClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn mount(this: *@This(), controller: usb.ControllerInterface, desc: *const Descriptor) anyerror!void { + pub fn mount(this: *@This(), controller: usb.DeviceInterface, desc: *const Descriptor) anyerror!void { this.line_coding = .init; this.awaiting_data = false; this.rx_buf = null; @@ -137,7 +137,7 @@ pub const CdcClassDriver = struct { return if (this.rx_buf) |rx| rx.len else 0; } - pub fn read(this: *@This(), controller: usb.ControllerInterface, dst: []u8) usize { + pub fn read(this: *@This(), controller: usb.DeviceInterface, dst: []u8) usize { if (this.rx_buf) |rx| { const len = @min(rx.len, dst.len); // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 @@ -162,14 +162,14 @@ pub const CdcClassDriver = struct { } else return 0; } - pub fn flush(this: *@This(), controller: usb.ControllerInterface) void { + pub fn flush(this: *@This(), controller: usb.DeviceInterface) void { if (this.tx_buf) |tx| { defer this.tx_buf = null; controller.submit_tx_buffer(this.ep_in, tx.ptr); } } - pub fn writeAll(this: *@This(), controller: usb.ControllerInterface, data: []const u8) void { + pub fn writeAll(this: *@This(), controller: usb.DeviceInterface, data: []const u8) void { var offset: usize = 0; while (offset < data.len) { offset += this.write(data[offset..]); @@ -179,7 +179,7 @@ pub const CdcClassDriver = struct { } } - pub fn class_control(ptr: *@This(), controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + pub fn class_control(ptr: *@This(), controller: usb.DeviceInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { var this: *@This() = @ptrCast(@alignCast(ptr)); if (stage != .Setup) return true; @@ -193,12 +193,12 @@ pub const CdcClassDriver = struct { return true; } - pub fn on_tx_ready(ptr: *@This(), _: usb.ControllerInterface, data: []u8) void { + pub fn on_tx_ready(ptr: *@This(), _: usb.DeviceInterface, data: []u8) void { var this: *@This() = @ptrCast(@alignCast(ptr)); this.tx_buf = data; } - pub fn on_data_rx(ptr: *@This(), _: usb.ControllerInterface, data: []const u8) void { + pub fn on_data_rx(ptr: *@This(), _: usb.DeviceInterface, data: []const u8) void { var this: *@This() = @ptrCast(@alignCast(ptr)); this.rx_buf = data; } diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 81deb89a7..f3b25bbc7 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -142,13 +142,13 @@ pub const HidClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn mount(this: *@This(), _: usb.ControllerInterface, desc: *const InOutDescriptor) anyerror!void { + pub fn mount(this: *@This(), _: usb.DeviceInterface, desc: *const InOutDescriptor) anyerror!void { this.hid_descriptor = std.mem.asBytes(&desc.hid); this.ep_in = desc.ep_in.endpoint.num; this.ep_out = desc.ep_out.endpoint.num; } - pub fn class_control(ptr: *@This(), controller: usb.ControllerInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + pub fn class_control(ptr: *@This(), controller: usb.DeviceInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { const self: *@This() = @ptrCast(@alignCast(ptr)); switch (setup.request_type.type) { @@ -205,6 +205,6 @@ pub const HidClassDriver = struct { return true; } - pub fn on_tx_ready(_: *@This(), _: usb.ControllerInterface, _: []u8) void {} - pub fn on_data_rx(_: *@This(), _: usb.ControllerInterface, _: []const u8) void {} + pub fn on_tx_ready(_: *@This(), _: usb.DeviceInterface, _: []u8) void {} + pub fn on_data_rx(_: *@This(), _: usb.DeviceInterface, _: []const u8) void {} }; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index a1c0e276a..0a2d5af78 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -143,40 +143,12 @@ pub const RequestType = packed struct(u8) { Other, }; - /// RequestType is created from raw bytes using std.mem.bytesToValue, we need to support all potential values if we don't want to crash such conversion const Recipient = enum(u5) { Device, Interface, Endpoint, Other, - Reserved1, - Reserved2, - Reserved3, - Reserved4, - Reserved5, - Reserved6, - Reserved7, - Reserved8, - Reserved9, - Reserved10, - Reserved11, - Reserved12, - Reserved13, - Reserved14, - Reserved15, - Reserved16, - Reserved17, - Reserved18, - Reserved19, - Reserved20, - Reserved21, - Reserved22, - Reserved23, - Reserved24, - Reserved25, - Reserved26, - Reserved27, - Reserved28, + _, // Reserved }; }; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 149c6987a..bcc1a8167 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -12,14 +12,13 @@ const baud_rate = 115200; const uart_tx_pin = gpio.num(0); // This is our device configuration -const Usb = microzig.core.usb.Controller(.{ - .Device = rp2xxx.usb.Usb(.{}), - .attributes = .{ .self_powered = true }, +const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ .device_triple = .{ .class = .Miscellaneous, .subclass = 2, .protocol = 1, }, + .attributes = .{ .self_powered = true }, .Driver = microzig.core.usb.hid.HidClassDriver, .driver_endpoints = &.{ .{ .name = "main", .value = .ep1 }, @@ -27,8 +26,8 @@ const Usb = microzig.core.usb.Controller(.{ .driver_strings = &.{ .{ .name = "name", .value = "Board HID" }, }, -}); -var usb: Usb = .init; +}) }); +var usb: Usb = undefined; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -42,8 +41,6 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { - usb.driver_data = .{ .report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut }; - // init uart logging uart_tx_pin.set_function(.uart); uart.apply(.{ @@ -57,7 +54,9 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb.init_device(); + usb = .init(); + usb.controller.driver_data = .{ .report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut }; + var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 1dcf1f562..54ed96d83 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -13,8 +13,7 @@ const uart_tx_pin = gpio.num(12); const uart_rx_pin = gpio.num(1); // This is our device configuration -const Usb = microzig.core.usb.Controller(.{ - .Device = rp2xxx.usb.Usb(.{}), +const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ .attributes = .{ .self_powered = true }, .Driver = microzig.core.usb.cdc.CdcClassDriver, .driver_endpoints = &.{ @@ -24,8 +23,8 @@ const Usb = microzig.core.usb.Controller(.{ .driver_strings = &.{ .{ .name = "name", .value = "Board CDC" }, }, -}); -var usb: Usb = .init; +}) }); +var usb: Usb = undefined; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -39,8 +38,6 @@ pub const microzig_options = microzig.Options{ }; pub fn main() !void { - usb.driver_data = .{}; - led.set_function(.sio); led.set_direction(.out); led.put(1); @@ -56,7 +53,9 @@ pub fn main() !void { rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above - usb.init_device(); + usb = .init(); + usb.controller.driver_data = .{}; + var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -74,16 +73,16 @@ pub fn main() !void { var tx_buf: [1024]u8 = undefined; const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); - usb.driver_data.writeAll(usb.interface(), text); + usb.controller.driver_data.writeAll(usb.interface(), text); } // read and print host command if present var rx_buf: [64]u8 = undefined; - const len = usb.driver_data.read(usb.interface(), &rx_buf); + const len = usb.controller.driver_data.read(usb.interface(), &rx_buf); if (len > 0) { - usb.driver_data.writeAll(usb.interface(), "Your message to me was: '"); - usb.driver_data.writeAll(usb.interface(), rx_buf[0..len]); - usb.driver_data.writeAll(usb.interface(), "'\r\n"); + usb.controller.driver_data.writeAll(usb.interface(), "Your message to me was: '"); + usb.controller.driver_data.writeAll(usb.interface(), rx_buf[0..len]); + usb.controller.driver_data.writeAll(usb.interface(), "'\r\n"); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 789f914ab..f6ac571ef 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -18,7 +18,19 @@ pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; pub const Config = struct { synchronization_nops: comptime_int = 3, dpram_allocator: type = DpramAllocatorBump, + Controller: type, // swap_dpdm: bool = false, + /// Vendor ID and product ID combo. + id: usb.VidPid = .{ + .vendor = 0x2E8A, + .product = 0x000a, + }, + /// Manufacturer, product and serial number strings. + strings: usb.Strings = .{ + .manufacturer = "Raspberry Pi", + .product = "Pico Test Device", + .serial = "someserial", + }, }; const Endpoint = usb.types.Endpoint; @@ -91,18 +103,18 @@ pub const DpramAllocatorBump = struct { /// are used by the abstract USB impl of microzig. pub fn Usb(comptime config: Config) type { return struct { + pub const interface_vtable: usb.DeviceInterface.Vtable = .{ + .task = &task, + .control_transfer = &control_transfer, + .signal_rx_ready = &signal_rx_ready, + .submit_tx_buffer = &submit_tx_buffer, + }; + pub const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; pub const max_transfer_size = 64; // TODO: Support other buffer sizes. pub const bcd_usb = 0x02_00; - pub const default_strings: usb.Config.Strings = .{ - .manufacturer = "Raspberry Pi", - .product = "Pico Test Device", - .serial = "someserial", - }; - pub const default_vid_pid: usb.Config.VidPid = .{ - .vendor = 0x2E8A, - .product = 0x000a, - }; + pub const id = config.id; + pub const strings = config.strings; pub const Events = struct { pub const BufferIterator = struct { @@ -143,8 +155,6 @@ pub fn Usb(comptime config: Config) type { const buf_ctrl_all: *volatile [2 * (max_endpoints_count)]BufCtrl = @ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL); - var awaiting_rx: u32 = 0; - _padding: u2 = 0, // 2 bits of padding so that address generation is a nop. is_out: bool, num: Endpoint.Num, @@ -153,10 +163,6 @@ pub fn Usb(comptime config: Config) type { return @intCast(@shrExact(@as(u7, @bitCast(this)), 2)); } - fn from(ep: Endpoint) @This() { - return .{ .num = ep.num, .is_out = ep.dir == .Out }; - } - pub fn in(num: Endpoint.Num) @This() { return .{ .num = num, .is_out = false }; } @@ -183,7 +189,18 @@ pub fn Usb(comptime config: Config) type { } }; - pub fn usb_init_device() ?[]u8 { + // + ep0_state: usb.ControlEndpointState, + controller: config.Controller, + + pub fn interface(this: *@This()) usb.DeviceInterface { + return .{ + .ptr = this, + .vtable = &interface_vtable, + }; + } + + pub fn init() @This() { if (chip == .RP2350) peri_usb.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); @@ -236,51 +253,135 @@ pub fn Usb(comptime config: Config) type { .SETUP_REQ = 1, }); - const ret = endpoint_open(.in(.ep0), .Control, 0) catch unreachable; - _ = endpoint_open(.out(.ep0), .Control, 0) catch unreachable; + var this: @This() = .{ + .ep0_state = .{ .no_buffer = null }, + .controller = .init(), + }; + + if (this.endpoint_open(.in(.ep0), .Control, 0) catch unreachable) |tx| + this.ep0_state = .{ .ready = tx }; + + this.endpoint_open(.out(.ep0), .Control, 0) catch unreachable; // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. peri_usb.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); - return ret; + return this; } - pub fn set_address(addr: u7) void { - peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + fn ep0_send(this: *@This(), tx_buf: []u8, data: []const u8) void { + const len = @min(tx_buf.len, data.len); + if (len == 0) + this.ep0_state = .{ .no_buffer = null } + else { + std.mem.copyForwards(u8, tx_buf, data[0..len]); + this.ep0_state = .{ .sending = data[len..] }; + } + this.interface().submit_tx_buffer(.ep0, tx_buf.ptr + len); + } + + pub fn set_address(this: *@This(), new_address: u7) void { + switch (this.ep0_state) { + .ready => |tx| { + this.interface().submit_tx_buffer(.ep0, tx.ptr); + this.ep0_state = .{ .no_buffer = new_address }; + }, + else => unreachable, + } + } + + fn control_transfer(ptr: *anyopaque, data: []const u8) void { + const this: *@This() = @alignCast(@ptrCast(ptr)); + switch (this.ep0_state) { + .sending => |residual| { + std.log.err("residual data: {any}", .{residual}); + this.ep0_state = .{ .sending = data }; + }, + .no_buffer => |new_address| { + if (new_address) |_| + std.log.err("missed address change!", .{}); + this.ep0_state = .{ .sending = data }; + }, + .ready => |tx_buf| this.ep0_send(tx_buf, data), + } } /// Check which interrupt flags are set - pub fn get_events() Events { + pub fn task(ptr: *anyopaque) void { + const this: *@This() = @alignCast(@ptrCast(ptr)); const ints = peri_usb.INTS.read(); - return .{ - .unhandled_buffers = if (ints.BUFF_STATUS != 0) blk: { - const mask = peri_usb.BUFF_STATUS.raw; - break :blk .{ .initial = mask, .pending = mask }; - } else null, + + var events: Events = .{ + .unhandled_buffers = null, .bus_reset = ints.BUS_RESET == 1, .device_suspend = ints.DEV_SUSPEND == 1, .host_resume = ints.DEV_RESUME_FROM_HOST == 1, - .setup_packet = if (ints.SETUP_REQ != 0) blk: { - // Clear the status flag (write-one-to-clear) - var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); - sie_status.SETUP_REC = 1; - peri_usb.SIE_STATUS.write(sie_status); - - // Reset PID to 1 for EP0 IN. Every DATA packet we send in response - // to an IN on EP0 needs to use PID DATA1. - defer peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); - - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers. - break :blk @bitCast([2]u32{ - peri_dpram.SETUP_PACKET_LOW.raw, - peri_dpram.SETUP_PACKET_HIGH.raw, - }); - } else null, + .setup_packet = null, }; + + if (ints.BUFF_STATUS != 0) blk: { + const initial = peri_usb.BUFF_STATUS.raw; + const pending = initial & ~(@as(u32, 1)); // ep0 in is handled here. + events.unhandled_buffers = .{ .initial = initial, .pending = pending }; + if (initial == pending) break :blk; + + const tx_buf = HardwareEndpoint.in(.ep0).buffer(); + switch (this.ep0_state) { + .sending => |data| { + this.ep0_send(tx_buf, data); + + if (data.len == 0) { + this.interface().signal_rx_ready(.ep0, 0); + // I believe this is incorrect but reality disagrees. + this.ep0_state = .{ .ready = tx_buf }; + } + }, + .no_buffer => |new_address| { + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (new_address) |addr| { + // Change our address: + peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + } + this.ep0_state = .{ .ready = tx_buf }; + }, + .ready => |_| std.log.err("Got buffer twice!", .{}), + } + } + if (ints.SETUP_REQ != 0) { + // Clear the status flag (write-one-to-clear) + var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); + sie_status.SETUP_REC = 1; + peri_usb.SIE_STATUS.write(sie_status); + + // Reset PID to 1 for EP0 IN. Every DATA packet we send in response + // to an IN on EP0 needs to use PID DATA1. + defer peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); + + // Copy the setup packet out of its dedicated buffer at the base of + // USB SRAM. The PAC models this buffer as two 32-bit registers. + const setup: usb.types.SetupPacket = @bitCast([2]u32{ + peri_dpram.SETUP_PACKET_LOW.raw, + peri_dpram.SETUP_PACKET_HIGH.raw, + }); + + events.setup_packet = setup; + } + + this.controller.task(events, this); + + if (events.bus_reset) { + this.controller.deinit(); + this.controller = .init(); + + var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); + sie_status.BUS_RESET = 1; + peri_usb.SIE_STATUS.write(sie_status); + peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); + } } - pub fn submit_tx_buffer(ep_in: Endpoint.Num, buffer_end: [*]const u8) void { + pub fn submit_tx_buffer(_: *anyopaque, ep_in: Endpoint.Num, buffer_end: [*]const u8) void { const ep_hard: HardwareEndpoint = .in(ep_in); const buf = ep_hard.buffer(); @@ -311,7 +412,7 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } - pub fn signal_rx_ready(ep_out: Endpoint.Num, len: usize) void { + pub fn signal_rx_ready(_: *anyopaque, ep_out: Endpoint.Num, len: usize) void { const ep_hard: HardwareEndpoint = .out(ep_out); // Configure the OUT: @@ -325,19 +426,21 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } - /// Called on a bus reset interrupt - pub fn bus_reset_clear() void { - // Acknowledge by writing the write-one-to-clear status bit. - var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); - sie_status.BUS_RESET = 1; - peri_usb.SIE_STATUS.write(sie_status); - peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); - } - - pub fn endpoint_open(ep: Endpoint, transfer_type: usb.types.TransferType, buf_size_hint: usize) error{OutOfBufferMemory}!?[]u8 { + pub fn endpoint_open( + _: *@This(), + comptime ep: Endpoint, + transfer_type: usb.types.TransferType, + buf_size_hint: usize, + ) error{OutOfBufferMemory}!switch (ep.dir) { + .In => ?[]u8, + .Out => void, + } { _ = buf_size_hint; - const ep_hard: HardwareEndpoint = .from(ep); + const ep_hard: HardwareEndpoint = .{ + .num = ep.num, + .is_out = ep.dir == .Out, + }; assert(ep.num.to_int() < max_endpoints_count); @@ -352,7 +455,8 @@ pub fn Usb(comptime config: Config) type { ep_ctrl.write(rmw); break :blk buf.start(); } else DpramBuffer.Index.ep0buf0.start(); - return start[0..max_transfer_size]; + if (ep.dir == .In) + return start[0..max_transfer_size]; } }; } From 850e7c8a7b55b01ffe35dfc367cf8be50821a8c7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 22 Aug 2025 23:11:39 +0200 Subject: [PATCH 16/33] rework usb device task --- core/src/core/usb.zig | 448 +++++++++--------- core/src/core/usb/cdc.zig | 44 +- core/src/core/usb/hid.zig | 94 ++-- .../rp2xxx/src/rp2040_only/usb_hid.zig | 55 +-- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 58 ++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 288 +++++------ 6 files changed, 489 insertions(+), 498 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index da1b049ea..bcb8f33d8 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -23,6 +23,7 @@ pub const DeviceInterface = struct { control_transfer: *const fn (ptr: *anyopaque, data: []const u8) void, submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, signal_rx_ready: *const fn (ptr: *anyopaque, ep_out: EpNum, max_len: usize) void, + endpoint_open: *const fn (ptr: *anyopaque, ep: types.Endpoint, transfer_type: types.TransferType, buf_size_hint: usize) anyerror!?[]u8, }; ptr: *anyopaque, vtable: *const Vtable, @@ -31,7 +32,7 @@ pub const DeviceInterface = struct { this.vtable.task(this.ptr); } pub fn control_transfer(this: @This(), data: []const u8) void { - // assert(data.len != 0); + assert(data.len != 0); this.vtable.control_transfer(this.ptr, data); } pub fn control_ack(this: @This()) void { @@ -43,11 +44,11 @@ pub const DeviceInterface = struct { pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); } + pub fn endpoint_open(this: @This(), ep: types.Endpoint, transfer_type: types.TransferType, buf_size_hint: usize) anyerror!?[]u8 { + return this.vtable.endpoint_open(this.ptr, ep, transfer_type, buf_size_hint); + } }; -/// Vendor ID and product ID combo. -pub const VidPid = struct { product: u16, vendor: u16 }; - /// Manufacturer, product and serial number strings. pub const Strings = struct { manufacturer: []const u8, @@ -75,6 +76,16 @@ pub const Config = struct { bcd_device: u16 = 0x01_00, /// Class, subclass and protocol of device. device_triple: descriptor.Device.DeviceTriple = .unspecified, + /// Manufacturer, product and serial number strings. + strings: Strings, + /// Vendor ID. + vid: u16, + /// Product ID. + pid: u16, + /// Device version number as Binary Coded Decimal. + bcd_usb: u16 = 0x02_00, + /// Maximum endpoint size. + max_transfer_size: comptime_int, // Eventually the fields below could be in an array to support multiple drivers. Driver: type, driver_endpoints: []const NameValue(EpNum), @@ -87,11 +98,8 @@ pub const EndpointAndBuffer = union(types.Dir) { In: struct { ep_num: EpNum, buffer: []u8 }, }; -pub const ControlEndpointState = union(enum) { - sending: []const u8, // Slice of data left to be sent. - no_buffer: ?u7, // Optionally a new address. - ready: []u8, // Buffer for next transaction. Always empty if available. -}; +pub const PacketUnhandled = error{UsbPacketUnhandled}; +pub const ACK = ""; /// Create a USB device controller. /// @@ -114,18 +122,9 @@ pub const ControlEndpointState = union(enum) { /// * `default_vid_pid` - Default VID and PID (optional). pub fn Controller(comptime config: Config) type { return struct { - pub const max_packet_size = config.Device.max_packet_size; - const max_endpoints_count = 16; - - const ACK = ""; - driver_by_interface: [16]?*config.Driver, - driver_by_endpoint_in: [max_endpoints_count]?*config.Driver, - driver_by_endpoint_out: [max_endpoints_count]?*config.Driver, - // Class driver associated with last setup request if any - driver: ?*config.Driver, - // Last setup packet request - setup_packet: types.SetupPacket, + driver_by_endpoint_in: [16]?*config.Driver, + driver_by_endpoint_out: [16]?*config.Driver, // 0 - no config set cfg_num: u16, driver_data: config.Driver, @@ -134,248 +133,223 @@ pub fn Controller(comptime config: Config) type { .driver_by_interface = @splat(null), .driver_by_endpoint_in = @splat(null), .driver_by_endpoint_out = @splat(null), - .driver = null, - .setup_packet = undefined, .cfg_num = 0, .driver_data = undefined, }; - pub fn init() @This() { - return .default; - } - - pub fn deinit(_: *@This()) void {} - - pub fn process_device_setup_request(this: *@This(), setup: *const types.SetupPacket, device: anytype) anyerror!void { - const device_descriptor: descriptor.Device = comptime .{ - .bcd_usb = .from(@TypeOf(device.*).bcd_usb), - .device_triple = config.device_triple, - .max_packet_size0 = @TypeOf(device.*).max_transfer_size, - .vendor = .from(@TypeOf(device.*).id.vendor), - .product = .from(@TypeOf(device.*).id.product), - .bcd_device = .from(config.bcd_device), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }; - - const string = comptime blk: { - const Dev = @TypeOf(device.*); + const device_descriptor: descriptor.Device = .{ + .bcd_usb = .from(config.bcd_usb), + .device_triple = config.device_triple, + .max_packet_size0 = config.max_transfer_size, + .vendor = .from(config.vid), + .product = .from(config.pid), + .bcd_device = .from(config.bcd_device), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }; - // String 0 indicates language. First byte is length. - const lang: types.U16Le = .from(@intFromEnum(config.language)); - var desc: []const []const u8 = &.{&.{ - 0x04, - @intFromEnum(descriptor.Type.String), - lang.lo, - lang.hi, + const config_descriptor = blk: { + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.driver_endpoints) |fld| { + fields = fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(fld.value), + .default_value_ptr = @ptrCast(&fld.value), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(fld.value)), }}; - - desc = desc ++ .{descriptor.string(Dev.strings.manufacturer)}; - desc = desc ++ .{descriptor.string(Dev.strings.product)}; - desc = desc ++ .{descriptor.string(Dev.strings.serial)}; - - const Field = std.builtin.Type.StructField; - var fields: []const Field = &.{}; - for (config.driver_strings) |fld| { - const id: u8 = desc.len; - fields = fields ++ .{Field{ - .name = fld.name, - .type = @TypeOf(id), - .default_value_ptr = @ptrCast(&id), - .is_comptime = true, - .alignment = @alignOf(@TypeOf(id)), - }}; - desc = desc ++ .{descriptor.string(fld.value)}; + } + const endpoints_struct = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); + + const driver = config.Driver.config_descriptor(0, string.ids{}, endpoints_struct{}); + assert(@alignOf(@TypeOf(driver)) == 1); + assert(@typeInfo(@TypeOf(driver)).@"struct".layout == .@"extern"); + + const Ret = extern struct { + first: descriptor.Configuration, + driver: @TypeOf(driver), + + fn serialize(x: @This()) [@sizeOf(@This())]u8 { + return @bitCast(x); } - break :blk .{ - .descriptors = desc, - .ids = @Type(.{ .@"struct" = .{ - .layout = .auto, - .fields = fields, - .decls = &.{}, - .is_tuple = false, - } }), - }; }; - const config_descriptor = comptime blk: { - const Field = std.builtin.Type.StructField; - var fields: []const Field = &.{}; - for (config.driver_endpoints) |fld| { - fields = fields ++ .{Field{ - .name = fld.name, - .type = @TypeOf(fld.value), - .default_value_ptr = @ptrCast(&fld.value), - .is_comptime = true, - .alignment = @alignOf(@TypeOf(fld.value)), - }}; - } - const endpoints_struct = @Type(.{ .@"struct" = .{ + break :blk Ret{ + .first = .{ + .total_length = .from(@sizeOf(Ret)), + .num_interfaces = config.Driver.num_interfaces, + .configuration_value = 1, + .configuration_s = 0, + .attributes = config.attributes, + .max_current = config.max_current, + }, + .driver = driver, + }; + }; + + const string = blk: { + // String 0 indicates language. First byte is length. + const lang: types.U16Le = .from(@intFromEnum(config.language)); + var desc: []const []const u8 = &.{&.{ + 0x04, + @intFromEnum(descriptor.Type.String), + lang.lo, + lang.hi, + }}; + + desc = desc ++ .{descriptor.string(config.strings.manufacturer)}; + desc = desc ++ .{descriptor.string(config.strings.product)}; + desc = desc ++ .{descriptor.string(config.strings.serial)}; + + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + for (config.driver_strings) |fld| { + const id: u8 = desc.len; + fields = fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(id), + .default_value_ptr = @ptrCast(&id), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(id)), + }}; + desc = desc ++ .{descriptor.string(fld.value)}; + } + break :blk .{ + .descriptors = desc, + .ids = @Type(.{ .@"struct" = .{ .layout = .auto, .fields = fields, .decls = &.{}, .is_tuple = false, - } }); + } }), + }; + }; - const driver = config.Driver.config_descriptor(0, string.ids{}, endpoints_struct{}); - assert(@alignOf(@TypeOf(driver)) == 1); - assert(@typeInfo(@TypeOf(driver)).@"struct".layout == .@"extern"); + pub fn init() @This() { + return .default; + } - const Ret = extern struct { - first: descriptor.Configuration, - driver: @TypeOf(driver), + pub fn deinit(_: *@This()) void {} - fn serialize(x: @This()) [@sizeOf(@This())]u8 { - return @bitCast(x); - } + pub fn get_descriptor(setup: *const types.SetupPacket) ?[]const u8 { + if (enumFromInt(descriptor.Type, setup.value >> 8)) |descriptor_type| { + const data: []const u8 = switch (descriptor_type) { + .Device => comptime &device_descriptor.serialize(), + .Configuration => comptime &config_descriptor.serialize(), + .String => if (setup.value & 0xff < string.descriptors.len) + string.descriptors[setup.value & 0xff] + else + comptime descriptor.string(""), + .DeviceQualifier => comptime &device_descriptor.qualifier().serialize(), + else => return null, }; + return data[0..@min(data.len, setup.length)]; + } else |_| return null; + } - break :blk Ret{ - .first = .{ - .total_length = .from(@sizeOf(Ret)), - .num_interfaces = config.Driver.num_interfaces, - .configuration_value = 1, - .configuration_s = 0, - .attributes = config.attributes, - .max_current = config.max_current, - }, - .driver = driver, - }; - }; + pub fn set_configuration(this: *@This(), device: DeviceInterface, setup: *const types.SetupPacket) void { + defer device.control_ack(); + if (this.cfg_num == setup.value) return; + defer this.cfg_num = setup.value; - if (setup.request_type.type != .Standard) return; - switch (enumFromInt(types.SetupRequest, setup.request) catch return) { - .SetAddress => device.set_address(@intCast(setup.value)), - .SetConfiguration => { - defer device.interface().control_transfer(ACK); - if (this.cfg_num == setup.value) return; - defer this.cfg_num = setup.value; - - if (this.cfg_num > 0) { - this.driver_by_interface = @splat(null); - this.driver_by_endpoint_in = @splat(null); - this.driver_by_endpoint_out = @splat(null); - // TODO: call umount callback if any - } - - if (setup.value == 0) return; - - { // Eventually do this for every driver - const driver = config_descriptor.driver; - comptime var fields = @typeInfo(@TypeOf(driver)).@"struct".fields; - - var assoc_itf_count: u8 = 1; - // New class starts optionally from InterfaceAssociation followed by mandatory Interface - if (fields[0].type == descriptor.InterfaceAssociation) { - assoc_itf_count = @field(driver, fields[0].name).interface_count; - fields = fields[1..]; - } - - assert(fields[0].type == descriptor.Interface); - const desc_itf = @field(driver, fields[0].name); - for (0..assoc_itf_count) |itf_offset| { - const itf_num = desc_itf.interface_number + itf_offset; - this.driver_by_interface[itf_num] = &this.driver_data; - } - fields = fields[1..]; - - inline for (fields) |fld| { - if (fld.type != descriptor.Endpoint) continue; - const desc_ep = @field(driver, fld.name); - if (desc_ep.endpoint.dir != .Out) continue; - - this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = &this.driver_data; - try device.endpoint_open( - desc_ep.endpoint, - desc_ep.attributes.transfer_type, - desc_ep.max_packet_size.into(), - ); - } - - try this.driver_data.mount(device.interface(), &driver); - - inline for (fields) |fld| { - if (fld.type != descriptor.Endpoint) continue; - const desc_ep = @field(driver, fld.name); - if (desc_ep.endpoint.dir != .In) continue; - - this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = &this.driver_data; - if (try device.endpoint_open( - desc_ep.endpoint, - desc_ep.attributes.transfer_type, - desc_ep.max_packet_size.into(), - )) |buf| - this.driver_data.on_tx_ready(device.interface(), buf); - } - } - }, - .GetDescriptor => if (enumFromInt(descriptor.Type, setup.value >> 8)) |descriptor_type| blk: { - const data: []const u8 = switch (descriptor_type) { - .Device => comptime &device_descriptor.serialize(), - .Configuration => comptime &config_descriptor.serialize(), - .String => if (setup.value & 0xff < string.descriptors.len) - string.descriptors[setup.value & 0xff] - else - comptime descriptor.string(""), - .DeviceQualifier => comptime &device_descriptor.qualifier().serialize(), - else => break :blk, - }; - const len = @min(data.len, setup.length); - device.interface().control_transfer(data[0..len]); - } else |_| {}, - .SetFeature => if (enumFromInt(types.FeatureSelector, setup.value >> 8)) |feature| - switch (feature) { - .DeviceRemoteWakeup, .EndpointHalt => device.interface().control_transfer(ACK), - // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 - .TestMode => {}, - } - else |_| {}, + if (this.cfg_num > 0) { + this.driver_by_interface = @splat(null); + this.driver_by_endpoint_in = @splat(null); + this.driver_by_endpoint_out = @splat(null); + // TODO: call umount callback if any } - } - /// Usb task function meant to be executed in regular intervals after - /// initializing the device. - /// - /// You have to ensure the device has been initialized. - pub fn task(this: *@This(), events: anytype, device: anytype) void { - if (events.setup_packet) |setup| { - this.setup_packet = setup; - this.driver = null; - - switch (setup.request_type.recipient) { - .Device => this.process_device_setup_request(&setup, device) catch unreachable, - .Interface => if (this.driver_by_interface[setup.index & 0xFF]) |drv| { - this.driver = drv; - if (drv.class_control(device.interface(), .Setup, &setup) == false) { - // TODO - } - }, - else => {}, + if (setup.value == 0) return; + + { // Eventually do this for every driver + const driver = config_descriptor.driver; + comptime var fields = @typeInfo(@TypeOf(driver)).@"struct".fields; + + var assoc_itf_count: u8 = 1; + // New class starts optionally from InterfaceAssociation followed by mandatory Interface + if (fields[0].type == descriptor.InterfaceAssociation) { + assoc_itf_count = @field(driver, fields[0].name).interface_count; + fields = fields[1..]; + } + + assert(fields[0].type == descriptor.Interface); + const desc_itf = @field(driver, fields[0].name); + for (0..assoc_itf_count) |itf_offset| { + const itf_num = desc_itf.interface_number + itf_offset; + this.driver_by_interface[itf_num] = &this.driver_data; + } + fields = fields[1..]; + + inline for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc_ep = @field(driver, fld.name); + if (desc_ep.endpoint.dir != .Out) continue; + + this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = &this.driver_data; + _ = device.endpoint_open( + desc_ep.endpoint, + desc_ep.attributes.transfer_type, + desc_ep.max_packet_size.into(), + ) catch unreachable; + } + + this.driver_data = .init(device, &driver); + + inline for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc_ep = @field(driver, fld.name); + if (desc_ep.endpoint.dir != .In) continue; + + this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = &this.driver_data; + if (device.endpoint_open( + desc_ep.endpoint, + desc_ep.attributes.transfer_type, + desc_ep.max_packet_size.into(), + ) catch unreachable) |buf| + this.driver_data.on_tx_ready(device, buf); } } + } - if (events.unhandled_buffers) |iter_const| { - var iter = iter_const; - // Perform any required action on the data. For OUT, the `data` - // will be whatever was sent by the host. For IN, it's a new - // transmit buffer that has become available. - while (iter.next()) |result| switch (result) { - .In => |in| { - if (in.ep_num == .ep0) - unreachable - else if (this.driver_by_endpoint_in[in.ep_num.to_int()]) |drv| - // TODO: Route different endpoints to different functions. - drv.on_tx_ready(device.interface(), in.buffer); - }, - .Out => |out| { - if (out.ep_num == .ep0) {} else if (this.driver_by_endpoint_out[out.ep_num.to_int()]) |drv| - // TODO: Route different endpoints to different functions. - drv.on_data_rx(device.interface(), out.buffer); - }, - }; + pub fn set_feature(this: *@This(), feature_selector: u8, index: u16, value: bool) bool { + _ = this; + _ = index; + _ = value; + switch (enumFromInt(types.FeatureSelector, feature_selector) catch return false) { + .DeviceRemoteWakeup, .EndpointHalt => return true, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 + .TestMode => return false, } } + + pub fn interface_setup(this: @This(), setup: *const types.SetupPacket) ?[]const u8 { + if (this.driver_by_interface[setup.index & 0xFF]) |drv| + return drv.interface_setup(setup); + return null; + } + + pub fn on_tx_ready(this: *@This(), device: DeviceInterface, ep_num: EpNum, buf: []u8) PacketUnhandled!void { + if (ep_num == .ep0) + return error.UsbPacketUnhandled + else if (this.driver_by_endpoint_in[ep_num.to_int()]) |drv| + // TODO: Route different endpoints to different functions. + return drv.on_tx_ready(device, buf); + } + + pub fn on_data_rx(this: *@This(), device: DeviceInterface, ep_num: EpNum, buf: []const u8) PacketUnhandled!void { + if (ep_num == .ep0) + return error.UsbPacketUnhandled + else if (this.driver_by_endpoint_out[ep_num.to_int()]) |drv| + // TODO: Route different endpoints to different functions. + return drv.on_data_rx(device, buf); + } }; } diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index fa613f8c5..dee78e520 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const enumFromInt = std.meta.intToEnum; const usb = @import("../usb.zig"); const descriptor = usb.descriptor; @@ -122,15 +123,17 @@ pub const CdcClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn mount(this: *@This(), controller: usb.DeviceInterface, desc: *const Descriptor) anyerror!void { - this.line_coding = .init; - this.awaiting_data = false; - this.rx_buf = null; - this.tx_buf = null; - this.ep_in_notif = desc.ep_notifi.endpoint.num; - this.ep_out = desc.ep_out.endpoint.num; - this.ep_in = desc.ep_in.endpoint.num; - controller.signal_rx_ready(this.ep_out, std.math.maxInt(usize)); + pub fn init(controller: usb.DeviceInterface, desc: *const Descriptor) @This() { + controller.signal_rx_ready(desc.ep_out.endpoint.num, std.math.maxInt(usize)); + return .{ + .line_coding = .init, + .awaiting_data = false, + .rx_buf = null, + .tx_buf = null, + .ep_in_notif = desc.ep_notifi.endpoint.num, + .ep_out = desc.ep_out.endpoint.num, + .ep_in = desc.ep_in.endpoint.num, + }; } pub fn available(this: *@This()) usize { @@ -179,18 +182,21 @@ pub const CdcClassDriver = struct { } } - pub fn class_control(ptr: *@This(), controller: usb.DeviceInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + pub fn interface_setup(ptr: *@This(), setup: *const types.SetupPacket) ?[]const u8 { var this: *@This() = @ptrCast(@alignCast(ptr)); - if (stage != .Setup) return true; - if (std.meta.intToEnum(ManagementRequestType, setup.request)) |request| switch (request) { - .SetLineCoding => controller.control_ack(), - .GetLineCoding => controller.control_transfer(std.mem.asBytes(&this.line_coding)[0..@min(@sizeOf(LineCoding), setup.length)]), - .SetControlLineState => controller.control_ack(), - .SendBreak => controller.control_ack(), - } else |_| {} - - return true; + return if (enumFromInt( + ManagementRequestType, + setup.request, + )) |request| switch (request) { + .SetLineCoding => usb.ACK, + .GetLineCoding => { + const data = std.mem.asBytes(&this.line_coding); + return data[0..@min(@sizeOf(LineCoding), setup.length)]; + }, + .SetControlLineState => usb.ACK, + .SendBreak => usb.ACK, + } else |_| usb.ACK; } pub fn on_tx_ready(ptr: *@This(), _: usb.DeviceInterface, data: []u8) void { diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index f3b25bbc7..3d9fc1448 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -142,67 +142,69 @@ pub const HidClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn mount(this: *@This(), _: usb.DeviceInterface, desc: *const InOutDescriptor) anyerror!void { - this.hid_descriptor = std.mem.asBytes(&desc.hid); - this.ep_in = desc.ep_in.endpoint.num; - this.ep_out = desc.ep_out.endpoint.num; + pub fn init(_: usb.DeviceInterface, desc: *const InOutDescriptor) @This() { + return .{ + .hid_descriptor = std.mem.asBytes(&desc.hid), + .report_descriptor = undefined, + .ep_in = desc.ep_in.endpoint.num, + .ep_out = desc.ep_out.endpoint.num, + }; } - pub fn class_control(ptr: *@This(), controller: usb.DeviceInterface, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + pub fn interface_setup(ptr: *@This(), setup: *const types.SetupPacket) ?[]const u8 { const self: *@This() = @ptrCast(@alignCast(ptr)); switch (setup.request_type.type) { - .Standard => if (stage == .Setup) { - const hid_desc_type = enumFromInt(descriptor.hid.SubType, (setup.value >> 8) & 0xff) catch return false; - const request_code = enumFromInt(types.SetupRequest, setup.request) catch return false; + .Standard => { + const hid_desc_type = enumFromInt(descriptor.hid.SubType, (setup.value >> 8) & 0xff) catch return null; + const request_code = enumFromInt(types.SetupRequest, setup.request) catch return null; - if (request_code != .GetDescriptor) return false; + if (request_code != .GetDescriptor) return usb.ACK; const data = switch (hid_desc_type) { .Hid => self.hid_descriptor, .Report => self.report_descriptor, - else => return false, + else => return null, }; - controller.control_transfer(data[0..@min(data.len, setup.length)]); + return data[0..@min(data.len, setup.length)]; }, - .Class => switch (enumFromInt(descriptor.hid.RequestType, setup.request) catch return false) { - .SetIdle => if (stage == .Setup) { - // TODO: The host is attempting to limit bandwidth by requesting that - // the device only return report data when its values actually change, - // or when the specified duration elapses. In practice, the device can - // still send reports as often as it wants, but for completeness this - // should be implemented eventually. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(); - }, - .SetProtocol => if (stage == .Setup) { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, - // or vice versa. - // - // For now, this request is ACKed without doing anything; in practice, - // the OS will reuqest the report protocol anyway, so usually only one format is needed. - // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), - // our device might not work in a limited BIOS environment. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(); - }, - .SetReport => if (stage == .Setup) { - // TODO: This request sends a feature or output report to the device, - // e.g. turning on the caps lock LED. This must be handled in an - // application-specific way, so notify the application code of the event. - // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 - controller.control_ack(); - }, - else => return false, + .Class => return switch (enumFromInt( + descriptor.hid.RequestType, + setup.request, + ) catch return null) { + // TODO: The host is attempting to limit bandwidth by requesting that + // the device only return report data when its values actually change, + // or when the specified duration elapses. In practice, the device can + // still send reports as often as it wants, but for completeness this + // should be implemented eventually. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + .SetIdle => usb.ACK, + + // TODO: The device should switch the format of its reports from the + // boot keyboard/mouse protocol to the format described in its report descriptor, + // or vice versa. + // + // For now, this request is ACKed without doing anything; in practice, + // the OS will reuqest the report protocol anyway, so usually only one format is needed. + // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), + // our device might not work in a limited BIOS environment. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + .SetProtocol => usb.ACK, + + // TODO: This request sends a feature or output report to the device, + // e.g. turning on the caps lock LED. This must be handled in an + // application-specific way, so notify the application code of the event. + // + // https://github.com/ZigEmbeddedGroup/microzig/issues/454 + .SetReport => usb.ACK, + else => null, }, - else => return false, + else => return null, } - return true; + return usb.ACK; } pub fn on_tx_ready(_: *@This(), _: usb.DeviceInterface, _: []u8) void {} diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index bcc1a8167..2d58c2f06 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -6,13 +6,26 @@ const flash = rp2xxx.flash; const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); -const baud_rate = 115200; -const uart_tx_pin = gpio.num(0); +const logger_baud_rate = 1_000_000; + +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, +}; + +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO12 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; +const pins = pin_config.pins(); // This is our device configuration const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ + .strings = rp2xxx.usb.default.strings, + .vid = rp2xxx.usb.default.vid, + .pid = rp2xxx.usb.default.pid, + .max_transfer_size = rp2xxx.usb.default.transfer_size, .device_triple = .{ .class = .Miscellaneous, .subclass = 2, @@ -29,44 +42,32 @@ const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ }) }); var usb: Usb = undefined; -pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { - std.log.err("panic: {s}", .{message}); - @breakpoint(); - while (true) {} -} - -pub const microzig_options = microzig.Options{ - .log_level = .debug, - .logFn = rp2xxx.uart.log, -}; - pub fn main() !void { + pin_config.apply(); + // init uart logging - uart_tx_pin.set_function(.uart); uart.apply(.{ - .baud_rate = baud_rate, + .baud_rate = logger_baud_rate, .clock_config = rp2xxx.clock_config, }); rp2xxx.uart.init_logger(uart); - led.set_function(.sio); - led.set_direction(.out); - led.put(1); - // Then initialize the USB device using the configuration defined above usb = .init(); - usb.controller.driver_data = .{ .report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut }; - var old: u64 = time.get_time_since_boot().to_us(); - var new: u64 = 0; + pins.led.put(1); + var last_led_toggle: u64 = time.get_time_since_boot().to_us(); + const delay_us = 500_000; + while (true) { + usb.controller.driver_data.report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut; // You can now poll for USB events usb.interface().task(); - new = time.get_time_since_boot().to_us(); - if (new - old > 500000) { - old = new; - led.toggle(); + const now = time.get_time_since_boot().to_us(); + if (now - last_led_toggle > delay_us) { + last_led_toggle += delay_us; + pins.led.toggle(); } } } diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 54ed96d83..1330cd8a9 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -6,14 +6,26 @@ const flash = rp2xxx.flash; const time = rp2xxx.time; const gpio = rp2xxx.gpio; -const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); -const baud_rate = 115200; -const uart_tx_pin = gpio.num(12); -const uart_rx_pin = gpio.num(1); +const logger_baud_rate = 1_000_000; + +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = rp2xxx.uart.log, +}; + +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO12 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; +const pins = pin_config.pins(); // This is our device configuration const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ + .strings = rp2xxx.usb.default.strings, + .vid = rp2xxx.usb.default.vid, + .pid = rp2xxx.usb.default.pid, + .max_transfer_size = rp2xxx.usb.default.transfer_size, .attributes = .{ .self_powered = true }, .Driver = microzig.core.usb.cdc.CdcClassDriver, .driver_endpoints = &.{ @@ -26,49 +38,33 @@ const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ }) }); var usb: Usb = undefined; -pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { - std.log.err("panic: {s}", .{message}); - @breakpoint(); - while (true) {} -} - -pub const microzig_options = microzig.Options{ - .log_level = .debug, - .logFn = rp2xxx.uart.log, -}; - pub fn main() !void { - led.set_function(.sio); - led.set_direction(.out); - led.put(1); - - inline for (&.{ uart_tx_pin, uart_rx_pin }) |pin| - pin.set_function(.uart); + pin_config.apply(); uart.apply(.{ - .baud_rate = baud_rate, + .baud_rate = logger_baud_rate, .clock_config = rp2xxx.clock_config, }); - rp2xxx.uart.init_logger(uart); // Then initialize the USB device using the configuration defined above usb = .init(); - usb.controller.driver_data = .{}; - var old: u64 = time.get_time_since_boot().to_us(); - var new: u64 = 0; + pins.led.put(1); + var last_led_toggle: u64 = time.get_time_since_boot().to_us(); + const delay_us = 500_000; var i: u32 = 0; while (true) { // You can now poll for USB events usb.interface().task(); - new = time.get_time_since_boot().to_us(); - if (new - old > 500000) { - old = new; - led.toggle(); - i += 1; + const now = time.get_time_since_boot().to_us(); + if (now - last_led_toggle > delay_us) { + last_led_toggle += delay_us; + pins.led.toggle(); + + i +%= 1; std.log.info("cdc test: {}\r\n", .{i}); var tx_buf: [1024]u8 = undefined; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index f6ac571ef..2c17731f2 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -4,6 +4,7 @@ const std = @import("std"); const assert = std.debug.assert; +const enumFromInt = std.meta.intToEnum; const microzig = @import("microzig"); const chip = microzig.hal.compatibility.chip; @@ -20,17 +21,17 @@ pub const Config = struct { dpram_allocator: type = DpramAllocatorBump, Controller: type, // swap_dpdm: bool = false, - /// Vendor ID and product ID combo. - id: usb.VidPid = .{ - .vendor = 0x2E8A, - .product = 0x000a, - }, - /// Manufacturer, product and serial number strings. - strings: usb.Strings = .{ +}; + +pub const default = struct { + pub const strings: usb.Strings = .{ .manufacturer = "Raspberry Pi", .product = "Pico Test Device", .serial = "someserial", - }, + }; + pub const vid: u16 = 0x2E8A; + pub const pid: u16 = 0x000a; + pub const transfer_size = 64; }; const Endpoint = usb.types.Endpoint; @@ -73,13 +74,11 @@ pub const DpramBuffer = struct { }; }; -pub const DpramAllocatorError = error{OutOfBufferMemory}; - pub const DpramAllocatorBump = struct { // First 0x100 bytes contain control registers and first 2 buffers are for endpoint 0. var top: DpramBuffer.Index = .data_start; - fn alloc(len: DpramBuffer.Len) DpramAllocatorError!DpramBuffer.Index { + fn alloc(len: DpramBuffer.Len) error{OutOfBufferMemory}!DpramBuffer.Index { if (top == .invalid) return error.OutOfBufferMemory; const next, const ovf = @addWithOverflow(len, @intFromEnum(top)); @@ -108,62 +107,28 @@ pub fn Usb(comptime config: Config) type { .control_transfer = &control_transfer, .signal_rx_ready = &signal_rx_ready, .submit_tx_buffer = &submit_tx_buffer, + .endpoint_open = &endpoint_open, }; pub const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; - pub const max_transfer_size = 64; // TODO: Support other buffer sizes. pub const bcd_usb = 0x02_00; pub const id = config.id; - pub const strings = config.strings; - - pub const Events = struct { - pub const BufferIterator = struct { - // One bit per endpoint. - initial: u32, - pending: u32, - - pub fn next(this: *@This()) ?usb.EndpointAndBuffer { - const idx = std.math.cast(u5, @ctz(this.pending)) orelse { - if (this.initial != 0) - peri_usb.BUFF_STATUS.write_raw(this.initial); - return null; - }; - this.pending &= this.pending -% 1; // Clear lowest bit. - const ep: HardwareEndpoint = .{ - .num = @enumFromInt(idx >> 1), - .is_out = (idx & 1) == 1, - }; - const buf = ep.buffer(); - if (ep.is_out) { - const len = ep.buf_ctrl().read().LENGTH_0; - return .{ .Out = .{ .ep_num = ep.num, .buffer = buf[0..len] } }; - } else return .{ .In = .{ .ep_num = ep.num, .buffer = buf } }; - } - }; - unhandled_buffers: ?BufferIterator, - bus_reset: bool, - device_suspend: bool, - host_resume: bool, - setup_packet: ?usb.types.SetupPacket, - }; - - pub const HardwareEndpoint = packed struct(u7) { + pub const HardwareEndpoint = packed struct(u5) { const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = @ptrCast(&peri_dpram.EP1_IN_CONTROL); const buf_ctrl_all: *volatile [2 * (max_endpoints_count)]BufCtrl = @ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL); - _padding: u2 = 0, // 2 bits of padding so that address generation is a nop. is_out: bool, num: Endpoint.Num, inline fn to_idx(this: @This()) u5 { - return @intCast(@shrExact(@as(u7, @bitCast(this)), 2)); + return @bitCast(this); } - pub fn in(num: Endpoint.Num) @This() { + fn in(num: Endpoint.Num) @This() { return .{ .num = num, .is_out = false }; } @@ -180,17 +145,27 @@ pub fn Usb(comptime config: Config) type { return &buf_ctrl_all[this.to_idx()]; } - pub fn buffer(this: @This()) []u8 { + fn buffer(this: @This()) []u8 { const buf: DpramBuffer.Index = if (this.ep_ctrl()) |reg| .from_reg(reg.read()) else .ep0buf0; return buf.start()[0..DpramBuffer.chunk_len]; } + + fn len(this: @This()) u16 { + return this.buf_ctrl().read().LENGTH_0; + } + }; + + const State = union(enum) { + sending: []const u8, // Slice of data left to be sent. + no_buffer: ?u7, // Optionally a new address. + ready: []u8, // Buffer for next transaction. Always empty if available. + waiting_ack, // Host is expected to send an ACK. }; - // - ep0_state: usb.ControlEndpointState, + state: State, controller: config.Controller, pub fn interface(this: *@This()) usb.DeviceInterface { @@ -254,14 +229,14 @@ pub fn Usb(comptime config: Config) type { }); var this: @This() = .{ - .ep0_state = .{ .no_buffer = null }, + .state = .{ .no_buffer = null }, .controller = .init(), }; - if (this.endpoint_open(.in(.ep0), .Control, 0) catch unreachable) |tx| - this.ep0_state = .{ .ready = tx }; + if (this.interface().endpoint_open(.in(.ep0), .Control, 0) catch unreachable) |tx| + this.state = .{ .ready = tx }; - this.endpoint_open(.out(.ep0), .Control, 0) catch unreachable; + _ = this.interface().endpoint_open(.out(.ep0), .Control, 0) catch unreachable; // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. @@ -272,109 +247,149 @@ pub fn Usb(comptime config: Config) type { fn ep0_send(this: *@This(), tx_buf: []u8, data: []const u8) void { const len = @min(tx_buf.len, data.len); if (len == 0) - this.ep0_state = .{ .no_buffer = null } + this.state = .{ .no_buffer = null } else { std.mem.copyForwards(u8, tx_buf, data[0..len]); - this.ep0_state = .{ .sending = data[len..] }; + this.state = .{ .sending = data[len..] }; } this.interface().submit_tx_buffer(.ep0, tx_buf.ptr + len); } - pub fn set_address(this: *@This(), new_address: u7) void { - switch (this.ep0_state) { - .ready => |tx| { - this.interface().submit_tx_buffer(.ep0, tx.ptr); - this.ep0_state = .{ .no_buffer = new_address }; - }, - else => unreachable, - } - } - fn control_transfer(ptr: *anyopaque, data: []const u8) void { const this: *@This() = @alignCast(@ptrCast(ptr)); - switch (this.ep0_state) { + switch (this.state) { .sending => |residual| { std.log.err("residual data: {any}", .{residual}); - this.ep0_state = .{ .sending = data }; + this.state = .{ .sending = data }; }, .no_buffer => |new_address| { if (new_address) |_| std.log.err("missed address change!", .{}); - this.ep0_state = .{ .sending = data }; + this.state = .{ .sending = data }; }, .ready => |tx_buf| this.ep0_send(tx_buf, data), + .waiting_ack => unreachable, + } + } + + fn process_setup(this: *@This(), tx_buf: []u8) void { + // Copy the setup packet out of its dedicated buffer at the base of + // USB SRAM. The PAC models this buffer as two 32-bit registers. + const setup: usb.types.SetupPacket = @bitCast([2]u32{ + peri_dpram.SETUP_PACKET_LOW.raw, + peri_dpram.SETUP_PACKET_HIGH.raw, + }); + + // Reset PID to 1 for EP0 IN. Every DATA packet we send in response + // to an IN on EP0 needs to use PID DATA1. + peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); + + switch (setup.request_type.recipient) { + .Device => blk: { + if (setup.request_type.type != .Standard) break :blk; + switch (enumFromInt( + usb.types.SetupRequest, + setup.request, + ) catch break :blk) { + .SetAddress => { + this.ep0_send(tx_buf, usb.ACK); + this.state = .{ .no_buffer = @intCast(setup.value) }; + }, + .SetConfiguration => this.controller.set_configuration(this.interface(), &setup), + .GetDescriptor => if (config.Controller.get_descriptor(&setup)) |desc| + this.ep0_send(tx_buf, desc), + .SetFeature => if (this.controller.set_feature( + @intCast(setup.value >> 8), + setup.index, + true, + )) this.ep0_send(tx_buf, usb.ACK), + } + }, + .Interface => if (this.controller.interface_setup(&setup)) |data| + this.ep0_send(tx_buf, data), + else => {}, } } - /// Check which interrupt flags are set + // fn ep0_handler(this: *@This(), ep: HardwareEndpoint, buf: []u8) usb.PacketUnhandled!void {} + + /// Usb task function meant to be executed in regular intervals after + /// initializing the device. pub fn task(ptr: *anyopaque) void { const this: *@This() = @alignCast(@ptrCast(ptr)); const ints = peri_usb.INTS.read(); + const SieStatus = @TypeOf(peri_usb.SIE_STATUS).underlying_type; + + switch (this.state) { + .ready => |tx_buf| if (ints.SETUP_REQ != 0) { + // Clear the status flag (write one to clear) + var sie_status: SieStatus = @bitCast(@as(u32, 0)); + sie_status.SETUP_REC = 1; + peri_usb.SIE_STATUS.write(sie_status); + this.process_setup(tx_buf); + }, + else => {}, + } + if (ints.BUFF_STATUS != 0) { + const unhandled_initial = peri_usb.BUFF_STATUS.raw; + var unhandled_pending = unhandled_initial; - var events: Events = .{ - .unhandled_buffers = null, - .bus_reset = ints.BUS_RESET == 1, - .device_suspend = ints.DEV_SUSPEND == 1, - .host_resume = ints.DEV_RESUME_FROM_HOST == 1, - .setup_packet = null, - }; + while (std.math.cast(u5, @ctz(unhandled_pending))) |idx| { + unhandled_pending &= unhandled_pending -% 1; // Clear lowest bit. + const ep: HardwareEndpoint = .{ + .num = @enumFromInt(idx >> 1), + .is_out = (idx & 1) == 1, + }; + const buf = ep.buffer(); - if (ints.BUFF_STATUS != 0) blk: { - const initial = peri_usb.BUFF_STATUS.raw; - const pending = initial & ~(@as(u32, 1)); // ep0 in is handled here. - events.unhandled_buffers = .{ .initial = initial, .pending = pending }; - if (initial == pending) break :blk; - - const tx_buf = HardwareEndpoint.in(.ep0).buffer(); - switch (this.ep0_state) { - .sending => |data| { - this.ep0_send(tx_buf, data); - - if (data.len == 0) { - this.interface().signal_rx_ready(.ep0, 0); - // I believe this is incorrect but reality disagrees. - this.ep0_state = .{ .ready = tx_buf }; - } - }, - .no_buffer => |new_address| { - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (new_address) |addr| { - // Change our address: - peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + const result = if (ep.num == .ep0) blk: { + switch (this.state) { + .sending => |data| { + if (ep.is_out) break :blk error.UsbPacketUnhandled; + this.ep0_send(buf, data); + + if (data.len == 0) { + this.interface().signal_rx_ready(.ep0, 0); + this.state = .waiting_ack; + } + }, + .no_buffer => |new_address| { + if (ep.is_out) break :blk error.UsbPacketUnhandled; + // Finish the delayed SetAddress request, if there is one: + if (new_address) |addr| + peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + + this.state = .{ .ready = buf }; + }, + .ready => |_| { + if (ep.is_out) + std.log.err("Got buffer twice!", .{}); + break :blk error.UsbPacketUnhandled; + }, + .waiting_ack => { + if (ep.is_out) assert(ep.len() == 0); + this.state = .{ .ready = buf }; + }, } - this.ep0_state = .{ .ready = tx_buf }; - }, - .ready => |_| std.log.err("Got buffer twice!", .{}), + } else if (ep.is_out) + this.controller.on_data_rx(this.interface(), ep.num, buf[0..ep.len()]) + else + this.controller.on_tx_ready(this.interface(), ep.num, buf); + + result catch { + std.log.warn("unhandled usb packet: ep{}{s}", .{ ep.num, if (ep.is_out) "out" else "in" }); + if (ep.is_out) + std.log.warn("{any}", .{buf[0..ep.len()]}); + }; } + peri_usb.BUFF_STATUS.write_raw(unhandled_initial); } - if (ints.SETUP_REQ != 0) { - // Clear the status flag (write-one-to-clear) - var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); - sie_status.SETUP_REC = 1; - peri_usb.SIE_STATUS.write(sie_status); - - // Reset PID to 1 for EP0 IN. Every DATA packet we send in response - // to an IN on EP0 needs to use PID DATA1. - defer peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers. - const setup: usb.types.SetupPacket = @bitCast([2]u32{ - peri_dpram.SETUP_PACKET_LOW.raw, - peri_dpram.SETUP_PACKET_HIGH.raw, - }); - - events.setup_packet = setup; - } - - this.controller.task(events, this); - - if (events.bus_reset) { + if (ints.BUS_RESET != 0) { this.controller.deinit(); this.controller = .init(); - var sie_status: @TypeOf(peri_usb.SIE_STATUS).underlying_type = @bitCast(@as(u32, 0)); + var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.BUS_RESET = 1; peri_usb.SIE_STATUS.write(sie_status); peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); @@ -387,7 +402,7 @@ pub fn Usb(comptime config: Config) type { // It is technically possible to support longer buffers but this demo doesn't bother. const len = buffer_end - buf.ptr; - if (len > max_transfer_size) + if (len > default.transfer_size) std.log.err("wrong buffer submitted", .{}); // Write the buffer information to the buffer control register @@ -422,19 +437,16 @@ pub fn Usb(comptime config: Config) type { rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is empty rmw.AVAILABLE_0 = 1; // And ready to be filled - rmw.LENGTH_0 = @intCast(@min(len, max_transfer_size)); + rmw.LENGTH_0 = @intCast(@min(len, default.transfer_size)); buf_ctrl.write(rmw); } pub fn endpoint_open( - _: *@This(), - comptime ep: Endpoint, + _: *anyopaque, + ep: Endpoint, transfer_type: usb.types.TransferType, buf_size_hint: usize, - ) error{OutOfBufferMemory}!switch (ep.dir) { - .In => ?[]u8, - .Out => void, - } { + ) error{OutOfBufferMemory}!?[]u8 { _ = buf_size_hint; const ep_hard: HardwareEndpoint = .{ @@ -455,8 +467,8 @@ pub fn Usb(comptime config: Config) type { ep_ctrl.write(rmw); break :blk buf.start(); } else DpramBuffer.Index.ep0buf0.start(); - if (ep.dir == .In) - return start[0..max_transfer_size]; + + return start[0..default.transfer_size]; } }; } From 7fe09ba6e206381d083a969711b65d40e180e0bc Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 26 Aug 2025 01:32:42 +0200 Subject: [PATCH 17/33] support multiple functions --- core/src/core/usb.zig | 316 +++++++++++------- core/src/core/usb/cdc.zig | 126 +++---- core/src/core/usb/hid.zig | 59 ++-- .../rp2xxx/src/rp2040_only/usb_hid.zig | 24 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 34 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 8 +- 6 files changed, 339 insertions(+), 228 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index bcb8f33d8..68da520ae 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -48,7 +48,6 @@ pub const DeviceInterface = struct { return this.vtable.endpoint_open(this.ptr, ep, transfer_type, buf_size_hint); } }; - /// Manufacturer, product and serial number strings. pub const Strings = struct { manufacturer: []const u8, @@ -66,6 +65,13 @@ pub const Config = struct { English = 0x0409, }; + pub const Driver = struct { + name: [:0]const u8, + Type: type, + endpoints: []const NameValue(EpNum), + strings: []const NameValue([]const u8), + }; + /// Bit set of device attributes. attributes: descriptor.Configuration.Attributes, /// Maximum device current consumption.. @@ -87,11 +93,18 @@ pub const Config = struct { /// Maximum endpoint size. max_transfer_size: comptime_int, // Eventually the fields below could be in an array to support multiple drivers. - Driver: type, - driver_endpoints: []const NameValue(EpNum), - driver_strings: []const NameValue([]const u8), + drivers: []const Driver, }; +pub fn DriverInfo(T: type, Descriptors: type) type { + return struct { + descriptors: Descriptors, + interface_handlers: []const struct { itf: u8, func: fn (*T, *const types.SetupPacket) ?[]const u8 }, + endpoint_in_handlers: []const struct { ep_num: EpNum, func: fn (*T, []u8) void }, + endpoint_out_handlers: []const struct { ep_num: EpNum, func: fn (*T, []const u8) void }, + }; +} + /// And endpoint and its corresponding buffer. pub const EndpointAndBuffer = union(types.Dir) { Out: struct { ep_num: EpNum, buffer: []const u8 }, @@ -100,6 +113,7 @@ pub const EndpointAndBuffer = union(types.Dir) { pub const PacketUnhandled = error{UsbPacketUnhandled}; pub const ACK = ""; +pub const NAK = null; /// Create a USB device controller. /// @@ -122,79 +136,28 @@ pub const ACK = ""; /// * `default_vid_pid` - Default VID and PID (optional). pub fn Controller(comptime config: Config) type { return struct { - driver_by_interface: [16]?*config.Driver, - driver_by_endpoint_in: [16]?*config.Driver, - driver_by_endpoint_out: [16]?*config.Driver, - // 0 - no config set - cfg_num: u16, - driver_data: config.Driver, - - pub const default: @This() = .{ - .driver_by_interface = @splat(null), - .driver_by_endpoint_in = @splat(null), - .driver_by_endpoint_out = @splat(null), - .cfg_num = 0, - .driver_data = undefined, - }; - - const device_descriptor: descriptor.Device = .{ - .bcd_usb = .from(config.bcd_usb), - .device_triple = config.device_triple, - .max_packet_size0 = config.max_transfer_size, - .vendor = .from(config.vid), - .product = .from(config.pid), - .bcd_device = .from(config.bcd_device), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }; - - const config_descriptor = blk: { + const DriverData = blk: { const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; - for (config.driver_endpoints) |fld| { + for (config.drivers) |drv| { fields = fields ++ .{Field{ - .name = fld.name, - .type = @TypeOf(fld.value), - .default_value_ptr = @ptrCast(&fld.value), - .is_comptime = true, - .alignment = @alignOf(@TypeOf(fld.value)), + .name = drv.name, + .type = drv.Type, + .default_value_ptr = null, + .is_comptime = false, + .alignment = @alignOf(drv.Type), }}; } - const endpoints_struct = @Type(.{ .@"struct" = .{ + break :blk @Type(.{ .@"struct" = .{ .layout = .auto, .fields = fields, .decls = &.{}, .is_tuple = false, } }); - - const driver = config.Driver.config_descriptor(0, string.ids{}, endpoints_struct{}); - assert(@alignOf(@TypeOf(driver)) == 1); - assert(@typeInfo(@TypeOf(driver)).@"struct".layout == .@"extern"); - - const Ret = extern struct { - first: descriptor.Configuration, - driver: @TypeOf(driver), - - fn serialize(x: @This()) [@sizeOf(@This())]u8 { - return @bitCast(x); - } - }; - - break :blk Ret{ - .first = .{ - .total_length = .from(@sizeOf(Ret)), - .num_interfaces = config.Driver.num_interfaces, - .configuration_value = 1, - .configuration_s = 0, - .attributes = config.attributes, - .max_current = config.max_current, - }, - .driver = driver, - }; }; + drivers: ?DriverData, + const string = blk: { // String 0 indicates language. First byte is length. const lang: types.U16Le = .from(@intFromEnum(config.language)); @@ -211,16 +174,35 @@ pub fn Controller(comptime config: Config) type { const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; - for (config.driver_strings) |fld| { - const id: u8 = desc.len; + for (config.drivers) |drv| { + var fields_s: []const Field = &.{}; + + for (config.drivers[0].strings) |fld| { + const id: u8 = desc.len; + fields_s = fields_s ++ .{Field{ + .name = fld.name, + .type = @TypeOf(id), + .default_value_ptr = @ptrCast(&id), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(id)), + }}; + desc = desc ++ .{descriptor.string(fld.value)}; + } + + const DrvStrings = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields_s, + .decls = &.{}, + .is_tuple = false, + } }); + fields = fields ++ .{Field{ - .name = fld.name, - .type = @TypeOf(id), - .default_value_ptr = @ptrCast(&id), + .name = drv.name, + .type = DrvStrings, + .default_value_ptr = @ptrCast(&DrvStrings{}), .is_comptime = true, - .alignment = @alignOf(@TypeOf(id)), + .alignment = @alignOf(DrvStrings), }}; - desc = desc ++ .{descriptor.string(fld.value)}; } break :blk .{ .descriptors = desc, @@ -233,17 +215,100 @@ pub fn Controller(comptime config: Config) type { }; }; - pub fn init() @This() { - return .default; - } + const driver_info = blk: { + const Field = std.builtin.Type.StructField; + var fields: []const Field = &.{}; + + var interface = 0; - pub fn deinit(_: *@This()) void {} + for (config.drivers) |drv| { + var ep_fields: []const Field = &.{}; + + for (drv.endpoints) |fld| { + ep_fields = ep_fields ++ .{Field{ + .name = fld.name, + .type = @TypeOf(fld.value), + .default_value_ptr = @ptrCast(&fld.value), + .is_comptime = true, + .alignment = @alignOf(@TypeOf(fld.value)), + }}; + } + + const ep_struct = @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = ep_fields, + .decls = &.{}, + .is_tuple = false, + } }); + + const string_ids = @field(string.ids{}, drv.name); + const info = drv.Type.info(interface, string_ids, ep_struct{}); + fields = fields ++ .{Field{ + .name = drv.name, + .type = @TypeOf(info), + .default_value_ptr = @ptrCast(&info), + .is_comptime = false, + .alignment = @alignOf(@TypeOf(info)), + }}; + interface += drv.Type.num_interfaces; + } + + break :blk @Type(.{ .@"struct" = .{ + .layout = .auto, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }){}; + }; + + pub const init: @This() = .{ .drivers = null }; + + pub fn deinit(this: *@This()) void { + if (this.drivers) |*drivers| { + inline for (config.drivers) |drv| + @field(drivers, drv.name).deinit(); + } + this.* = .init; + } pub fn get_descriptor(setup: *const types.SetupPacket) ?[]const u8 { + const device_descriptor: descriptor.Device = comptime .{ + .bcd_usb = .from(config.bcd_usb), + .device_triple = config.device_triple, + .max_packet_size0 = config.max_transfer_size, + .vendor = .from(config.vid), + .product = .from(config.pid), + .bcd_device = .from(config.bcd_device), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }; + + const config_descriptor = comptime blk: { + var ret: [:0]const u8 = ""; + var num_interfaces = 0; + + for (config.drivers) |drv| { + const info = @field(driver_info, drv.name); + num_interfaces += drv.Type.num_interfaces; + ret = ret ++ info.descriptors.serialize(); + } + + break :blk (descriptor.Configuration{ + .total_length = .from(@sizeOf(descriptor.Configuration) + ret.len), + .num_interfaces = num_interfaces, + .configuration_value = 1, + .configuration_s = 0, + .attributes = config.attributes, + .max_current = config.max_current, + }).serialize() ++ ret; + }; + if (enumFromInt(descriptor.Type, setup.value >> 8)) |descriptor_type| { const data: []const u8 = switch (descriptor_type) { .Device => comptime &device_descriptor.serialize(), - .Configuration => comptime &config_descriptor.serialize(), + .Configuration => config_descriptor, .String => if (setup.value & 0xff < string.descriptors.len) string.descriptors[setup.value & 0xff] else @@ -257,43 +322,32 @@ pub fn Controller(comptime config: Config) type { pub fn set_configuration(this: *@This(), device: DeviceInterface, setup: *const types.SetupPacket) void { defer device.control_ack(); - if (this.cfg_num == setup.value) return; - defer this.cfg_num = setup.value; - - if (this.cfg_num > 0) { - this.driver_by_interface = @splat(null); - this.driver_by_endpoint_in = @splat(null); - this.driver_by_endpoint_out = @splat(null); - // TODO: call umount callback if any + if (setup.value == 0) { + this.deinit(); + return; } - if (setup.value == 0) return; + if (setup.value != 1 or this.drivers != null) return; + + this.drivers = undefined; + inline for (config.drivers) |drv| { + const info = @field(driver_info, drv.name); - { // Eventually do this for every driver - const driver = config_descriptor.driver; - comptime var fields = @typeInfo(@TypeOf(driver)).@"struct".fields; + const descriptors = info.descriptors; + const fields = @typeInfo(@TypeOf(descriptors)).@"struct".fields; var assoc_itf_count: u8 = 1; // New class starts optionally from InterfaceAssociation followed by mandatory Interface - if (fields[0].type == descriptor.InterfaceAssociation) { - assoc_itf_count = @field(driver, fields[0].name).interface_count; - fields = fields[1..]; - } + if (fields[0].type == descriptor.InterfaceAssociation) + assoc_itf_count = @field(descriptors, fields[0].name).interface_count; - assert(fields[0].type == descriptor.Interface); - const desc_itf = @field(driver, fields[0].name); - for (0..assoc_itf_count) |itf_offset| { - const itf_num = desc_itf.interface_number + itf_offset; - this.driver_by_interface[itf_num] = &this.driver_data; - } - fields = fields[1..]; + @field(this.drivers.?, drv.name) = .init(device, &descriptors); inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; - const desc_ep = @field(driver, fld.name); + const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .Out) continue; - this.driver_by_endpoint_out[desc_ep.endpoint.num.to_int()] = &this.driver_data; _ = device.endpoint_open( desc_ep.endpoint, desc_ep.attributes.transfer_type, @@ -301,20 +355,19 @@ pub fn Controller(comptime config: Config) type { ) catch unreachable; } - this.driver_data = .init(device, &driver); - inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; - const desc_ep = @field(driver, fld.name); + const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .In) continue; - this.driver_by_endpoint_in[desc_ep.endpoint.num.to_int()] = &this.driver_data; if (device.endpoint_open( desc_ep.endpoint, desc_ep.attributes.transfer_type, desc_ep.max_packet_size.into(), ) catch unreachable) |buf| - this.driver_data.on_tx_ready(device, buf); + this.on_tx_ready(desc_ep.endpoint.num, buf) catch { + std.log.warn("initial buffer unhandled on {any}", .{desc_ep.endpoint.num}); + }; } } } @@ -330,26 +383,43 @@ pub fn Controller(comptime config: Config) type { } } - pub fn interface_setup(this: @This(), setup: *const types.SetupPacket) ?[]const u8 { - if (this.driver_by_interface[setup.index & 0xFF]) |drv| - return drv.interface_setup(setup); - return null; + pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + if (this.drivers) |*drivers| { + inline for (config.drivers) |drv| { + const ptr = &@field(drivers, drv.name); + inline for (@field(driver_info, drv.name).interface_handlers) |handler| { + if (handler.itf == setup.index & 0xFF) + return handler.func(ptr, setup); + } + } + } + return NAK; } - pub fn on_tx_ready(this: *@This(), device: DeviceInterface, ep_num: EpNum, buf: []u8) PacketUnhandled!void { - if (ep_num == .ep0) - return error.UsbPacketUnhandled - else if (this.driver_by_endpoint_in[ep_num.to_int()]) |drv| - // TODO: Route different endpoints to different functions. - return drv.on_tx_ready(device, buf); + pub fn on_tx_ready(this: *@This(), ep_num: EpNum, buf: []u8) PacketUnhandled!void { + if (this.drivers) |*drivers| { + inline for (config.drivers) |drv| { + const ptr = &@field(drivers, drv.name); + inline for (@field(driver_info, drv.name).endpoint_in_handlers) |handler| { + if (handler.ep_num == ep_num) + return handler.func(ptr, buf); + } + } + } + return error.UsbPacketUnhandled; } - pub fn on_data_rx(this: *@This(), device: DeviceInterface, ep_num: EpNum, buf: []const u8) PacketUnhandled!void { - if (ep_num == .ep0) - return error.UsbPacketUnhandled - else if (this.driver_by_endpoint_out[ep_num.to_int()]) |drv| - // TODO: Route different endpoints to different functions. - return drv.on_data_rx(device, buf); + pub fn on_data_rx(this: *@This(), ep_num: EpNum, buf: []const u8) PacketUnhandled!void { + if (this.drivers) |*drivers| { + inline for (config.drivers) |drv| { + const ptr = &@field(drivers, drv.name); + inline for (@field(driver_info, drv.name).endpoint_out_handlers) |handler| { + if (handler.ep_num == ep_num) + return handler.func(ptr, buf); + } + } + } + return error.UsbPacketUnhandled; } }; } diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index dee78e520..4bfc4e7bf 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -61,63 +61,75 @@ pub const CdcClassDriver = struct { rx_buf: ?[]const u8 = null, tx_buf: ?[]u8 = null, - pub fn config_descriptor(first_interface: u8, string_ids: anytype, endpoints: anytype) Descriptor { + pub fn info(first_interface: u8, string_ids: anytype, endpoints: anytype) usb.DriverInfo(@This(), Descriptor) { const endpoint_notifi_size = 8; const endpoint_size = 64; return .{ - .desc1 = .{ - .first_interface = first_interface, - .interface_count = 2, - .function_class = 2, - .function_subclass = 2, - .function_protocol = 0, - .function = 0, + .interface_handlers = &.{ + .{ .itf = first_interface, .func = interface_setup }, + .{ .itf = first_interface + 1, .func = interface_setup }, }, - .desc2 = .{ - .interface_number = first_interface, - .alternate_setting = 0, - .num_endpoints = 1, - .interface_class = 2, - .interface_subclass = 2, - .interface_protocol = 0, - .interface_s = string_ids.name, + .endpoint_in_handlers = &.{ + .{ .ep_num = endpoints.data, .func = on_tx_ready }, }, - .desc3 = .{ .bcd_cdc = .from(0x0120) }, - .desc4 = .{ - .capabilities = 0, - .data_interface = first_interface + 1, + .endpoint_out_handlers = &.{ + .{ .ep_num = endpoints.data, .func = on_data_rx }, }, - .desc5 = .{ .capabilities = 6 }, - .desc6 = .{ - .master_interface = first_interface, - .slave_interface_0 = first_interface + 1, - }, - .ep_notifi = .{ - .endpoint = .in(endpoints.notifi), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, - .max_packet_size = .from(endpoint_notifi_size), - .interval = 16, - }, - .desc8 = .{ - .interface_number = first_interface + 1, - .alternate_setting = 0, - .num_endpoints = 2, - .interface_class = 10, - .interface_subclass = 0, - .interface_protocol = 0, - .interface_s = 0, - }, - .ep_out = .{ - .endpoint = .out(endpoints.data), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, - .max_packet_size = .from(endpoint_size), - .interval = 0, - }, - .ep_in = .{ - .endpoint = .in(endpoints.data), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, - .max_packet_size = .from(endpoint_size), - .interval = 0, + .descriptors = .{ + .desc1 = .{ + .first_interface = first_interface, + .interface_count = 2, + .function_class = 2, + .function_subclass = 2, + .function_protocol = 0, + .function = 0, + }, + .desc2 = .{ + .interface_number = first_interface, + .alternate_setting = 0, + .num_endpoints = 1, + .interface_class = 2, + .interface_subclass = 2, + .interface_protocol = 0, + .interface_s = string_ids.name, + }, + .desc3 = .{ .bcd_cdc = .from(0x0120) }, + .desc4 = .{ + .capabilities = 0, + .data_interface = first_interface + 1, + }, + .desc5 = .{ .capabilities = 6 }, + .desc6 = .{ + .master_interface = first_interface, + .slave_interface_0 = first_interface + 1, + }, + .ep_notifi = .{ + .endpoint = .in(endpoints.notifi), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(endpoint_notifi_size), + .interval = 16, + }, + .desc8 = .{ + .interface_number = first_interface + 1, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 10, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = 0, + }, + .ep_out = .{ + .endpoint = .out(endpoints.data), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, + .ep_in = .{ + .endpoint = .in(endpoints.data), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(endpoint_size), + .interval = 0, + }, }, }; } @@ -136,6 +148,8 @@ pub const CdcClassDriver = struct { }; } + pub fn deinit(_: *@This()) void {} + pub fn available(this: *@This()) usize { return if (this.rx_buf) |rx| rx.len else 0; } @@ -182,9 +196,7 @@ pub const CdcClassDriver = struct { } } - pub fn interface_setup(ptr: *@This(), setup: *const types.SetupPacket) ?[]const u8 { - var this: *@This() = @ptrCast(@alignCast(ptr)); - + pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { return if (enumFromInt( ManagementRequestType, setup.request, @@ -199,13 +211,11 @@ pub const CdcClassDriver = struct { } else |_| usb.ACK; } - pub fn on_tx_ready(ptr: *@This(), _: usb.DeviceInterface, data: []u8) void { - var this: *@This() = @ptrCast(@alignCast(ptr)); + pub fn on_tx_ready(this: *@This(), data: []u8) void { this.tx_buf = data; } - pub fn on_data_rx(ptr: *@This(), _: usb.DeviceInterface, data: []const u8) void { - var this: *@This() = @ptrCast(@alignCast(ptr)); + pub fn on_data_rx(this: *@This(), data: []const u8) void { this.rx_buf = data; } }; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 3d9fc1448..41da0c12f 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -79,6 +79,10 @@ pub const InDescriptor = extern struct { }, }; } + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } }; pub const InOutDescriptor = extern struct { @@ -118,6 +122,10 @@ pub const InOutDescriptor = extern struct { }, }; } + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } }; pub const HidClassDriver = struct { @@ -128,17 +136,24 @@ pub const HidClassDriver = struct { hid_descriptor: []const u8 = &.{}, report_descriptor: []const u8, - pub fn config_descriptor(first_interface: u8, string_ids: anytype, endpoints: anytype) InOutDescriptor { - return .create( - first_interface, - string_ids.name, - 0, - descriptor.hid.report.GenericInOut.len, - endpoints.main, - endpoints.main, - 64, - 0, - ); + pub fn info(first_interface: u8, string_ids: anytype, endpoints: anytype) usb.DriverInfo(@This(), InOutDescriptor) { + return .{ + .interface_handlers = &.{ + .{ .itf = first_interface, .func = interface_setup }, + }, + .endpoint_in_handlers = &.{}, + .endpoint_out_handlers = &.{}, + .descriptors = .create( + first_interface, + string_ids.name, + 0, + descriptor.hid.report.GenericInOut.len, + endpoints.main, + endpoints.main, + 64, + 0, + ), + }; } /// This function is called when the host chooses a configuration that contains this driver. @@ -151,19 +166,19 @@ pub const HidClassDriver = struct { }; } - pub fn interface_setup(ptr: *@This(), setup: *const types.SetupPacket) ?[]const u8 { - const self: *@This() = @ptrCast(@alignCast(ptr)); + pub fn deinit(_: *@This()) void {} + pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { switch (setup.request_type.type) { .Standard => { - const hid_desc_type = enumFromInt(descriptor.hid.SubType, (setup.value >> 8) & 0xff) catch return null; - const request_code = enumFromInt(types.SetupRequest, setup.request) catch return null; + const hid_desc_type = enumFromInt(descriptor.hid.SubType, (setup.value >> 8) & 0xff) catch return usb.NAK; + const request_code = enumFromInt(types.SetupRequest, setup.request) catch return usb.NAK; if (request_code != .GetDescriptor) return usb.ACK; const data = switch (hid_desc_type) { - .Hid => self.hid_descriptor, - .Report => self.report_descriptor, + .Hid => this.hid_descriptor, + .Report => this.report_descriptor, else => return null, }; @@ -172,7 +187,7 @@ pub const HidClassDriver = struct { .Class => return switch (enumFromInt( descriptor.hid.RequestType, setup.request, - ) catch return null) { + ) catch return usb.NAK) { // TODO: The host is attempting to limit bandwidth by requesting that // the device only return report data when its values actually change, // or when the specified duration elapses. In practice, the device can @@ -200,13 +215,13 @@ pub const HidClassDriver = struct { // // https://github.com/ZigEmbeddedGroup/microzig/issues/454 .SetReport => usb.ACK, - else => null, + else => usb.NAK, }, - else => return null, + else => return usb.NAK, } return usb.ACK; } - pub fn on_tx_ready(_: *@This(), _: usb.DeviceInterface, _: []u8) void {} - pub fn on_data_rx(_: *@This(), _: usb.DeviceInterface, _: []const u8) void {} + pub fn on_tx_ready(_: *@This(), _: []u8) void {} + pub fn on_data_rx(_: *@This(), _: []const u8) void {} }; diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 2d58c2f06..405912874 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -32,13 +32,16 @@ const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ .protocol = 1, }, .attributes = .{ .self_powered = true }, - .Driver = microzig.core.usb.hid.HidClassDriver, - .driver_endpoints = &.{ - .{ .name = "main", .value = .ep1 }, - }, - .driver_strings = &.{ - .{ .name = "name", .value = "Board HID" }, - }, + .drivers = &.{.{ + .name = "hid", + .Type = microzig.core.usb.hid.HidClassDriver, + .endpoints = &.{ + .{ .name = "main", .value = .ep1 }, + }, + .strings = &.{ + .{ .name = "name", .value = "Board HID" }, + }, + }}, }) }); var usb: Usb = undefined; @@ -60,10 +63,15 @@ pub fn main() !void { const delay_us = 500_000; while (true) { - usb.controller.driver_data.report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut; // You can now poll for USB events usb.interface().task(); + const hid = if (usb.controller.drivers) |*drivers| + &drivers.hid + else // This means the USB connection has not yet been established + continue; + hid.report_descriptor = µzig.core.usb.descriptor.hid.report.GenericInOut; + const now = time.get_time_since_boot().to_us(); if (now - last_led_toggle > delay_us) { last_led_toggle += delay_us; diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 1330cd8a9..4b78ee4bb 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -27,14 +27,17 @@ const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ .pid = rp2xxx.usb.default.pid, .max_transfer_size = rp2xxx.usb.default.transfer_size, .attributes = .{ .self_powered = true }, - .Driver = microzig.core.usb.cdc.CdcClassDriver, - .driver_endpoints = &.{ - .{ .name = "notifi", .value = .ep1 }, - .{ .name = "data", .value = .ep2 }, - }, - .driver_strings = &.{ - .{ .name = "name", .value = "Board CDC" }, - }, + .drivers = &.{.{ + .name = "serial", + .Type = microzig.core.usb.cdc.CdcClassDriver, + .endpoints = &.{ + .{ .name = "notifi", .value = .ep1 }, + .{ .name = "data", .value = .ep2 }, + }, + .strings = &.{ + .{ .name = "name", .value = "Board CDC" }, + }, + }}, }) }); var usb: Usb = undefined; @@ -59,6 +62,11 @@ pub fn main() !void { // You can now poll for USB events usb.interface().task(); + const usb_serial = if (usb.controller.drivers) |*drivers| + &drivers.serial + else // This means the USB connection has not yet been established + continue; + const now = time.get_time_since_boot().to_us(); if (now - last_led_toggle > delay_us) { last_led_toggle += delay_us; @@ -69,16 +77,16 @@ pub fn main() !void { var tx_buf: [1024]u8 = undefined; const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); - usb.controller.driver_data.writeAll(usb.interface(), text); + usb_serial.writeAll(usb.interface(), text); } // read and print host command if present var rx_buf: [64]u8 = undefined; - const len = usb.controller.driver_data.read(usb.interface(), &rx_buf); + const len = usb_serial.read(usb.interface(), &rx_buf); if (len > 0) { - usb.controller.driver_data.writeAll(usb.interface(), "Your message to me was: '"); - usb.controller.driver_data.writeAll(usb.interface(), rx_buf[0..len]); - usb.controller.driver_data.writeAll(usb.interface(), "'\r\n"); + usb_serial.writeAll(usb.interface(), "Your message to me was: '"); + usb_serial.writeAll(usb.interface(), rx_buf[0..len]); + usb_serial.writeAll(usb.interface(), "'\r\n"); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 2c17731f2..b83061872 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -230,7 +230,7 @@ pub fn Usb(comptime config: Config) type { var this: @This() = .{ .state = .{ .no_buffer = null }, - .controller = .init(), + .controller = .init, }; if (this.interface().endpoint_open(.in(.ep0), .Control, 0) catch unreachable) |tx| @@ -372,9 +372,9 @@ pub fn Usb(comptime config: Config) type { }, } } else if (ep.is_out) - this.controller.on_data_rx(this.interface(), ep.num, buf[0..ep.len()]) + this.controller.on_data_rx(ep.num, buf[0..ep.len()]) else - this.controller.on_tx_ready(this.interface(), ep.num, buf); + this.controller.on_tx_ready(ep.num, buf); result catch { std.log.warn("unhandled usb packet: ep{}{s}", .{ ep.num, if (ep.is_out) "out" else "in" }); @@ -387,7 +387,7 @@ pub fn Usb(comptime config: Config) type { if (ints.BUS_RESET != 0) { this.controller.deinit(); - this.controller = .init(); + this.controller = .init; var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.BUS_RESET = 1; From 06fcadb796c34443b68e3017e1d2ae5322ed929e Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 26 Aug 2025 03:20:01 +0200 Subject: [PATCH 18/33] cleanup --- core/src/core/usb.zig | 84 +++++------- core/src/core/usb/cdc.zig | 63 ++++----- core/src/core/usb/descriptor.zig | 41 ------ core/src/core/usb/descriptor/cdc.zig | 16 --- core/src/core/usb/descriptor/hid.zig | 23 +--- core/src/core/usb/hid.zig | 7 +- core/src/core/usb/types.zig | 16 --- .../rp2xxx/src/rp2040_only/usb_hid.zig | 12 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 33 +++-- port/raspberrypi/rp2xxx/src/hal/usb.zig | 129 +++++++++--------- 10 files changed, 156 insertions(+), 268 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 68da520ae..1a1af1e9f 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -19,18 +19,14 @@ const EpNum = types.Endpoint.Num; pub const DeviceInterface = struct { pub const Vtable = struct { - task: *const fn (ptr: *anyopaque) void, control_transfer: *const fn (ptr: *anyopaque, data: []const u8) void, submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, signal_rx_ready: *const fn (ptr: *anyopaque, ep_out: EpNum, max_len: usize) void, - endpoint_open: *const fn (ptr: *anyopaque, ep: types.Endpoint, transfer_type: types.TransferType, buf_size_hint: usize) anyerror!?[]u8, + endpoint_open: *const fn (ptr: *anyopaque, desc: *const descriptor.Endpoint) ?[]u8, }; ptr: *anyopaque, vtable: *const Vtable, - pub fn task(this: @This()) void { - this.vtable.task(this.ptr); - } pub fn control_transfer(this: @This(), data: []const u8) void { assert(data.len != 0); this.vtable.control_transfer(this.ptr, data); @@ -44,8 +40,8 @@ pub const DeviceInterface = struct { pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); } - pub fn endpoint_open(this: @This(), ep: types.Endpoint, transfer_type: types.TransferType, buf_size_hint: usize) anyerror!?[]u8 { - return this.vtable.endpoint_open(this.ptr, ep, transfer_type, buf_size_hint); + pub fn endpoint_open(this: @This(), desc: *const descriptor.Endpoint) ?[]u8 { + return this.vtable.endpoint_open(this.ptr, desc); } }; /// Manufacturer, product and serial number strings. @@ -82,16 +78,16 @@ pub const Config = struct { bcd_device: u16 = 0x01_00, /// Class, subclass and protocol of device. device_triple: descriptor.Device.DeviceTriple = .unspecified, - /// Manufacturer, product and serial number strings. - strings: Strings, - /// Vendor ID. - vid: u16, - /// Product ID. - pid: u16, - /// Device version number as Binary Coded Decimal. - bcd_usb: u16 = 0x02_00, - /// Maximum endpoint size. - max_transfer_size: comptime_int, + /// Manufacturer, product and serial number strings. Leave at null for device default. + strings: ?Strings = null, + /// Vendor ID. Leave at null for device default. + vid: ?u16 = null, + /// Product ID. Leave at null for device default. + pid: ?u16 = null, + /// Device version number as Binary Coded Decimal. Leave at null for device default. + bcd_usb: ?u16 = null, + /// Maximum endpoint size. Leave at null for device default. + max_transfer_size: ?comptime_int = null, // Eventually the fields below could be in an array to support multiple drivers. drivers: []const Driver, }; @@ -168,9 +164,9 @@ pub fn Controller(comptime config: Config) type { lang.hi, }}; - desc = desc ++ .{descriptor.string(config.strings.manufacturer)}; - desc = desc ++ .{descriptor.string(config.strings.product)}; - desc = desc ++ .{descriptor.string(config.strings.serial)}; + desc = desc ++ .{descriptor.string(config.strings.?.manufacturer)}; + desc = desc ++ .{descriptor.string(config.strings.?.product)}; + desc = desc ++ .{descriptor.string(config.strings.?.serial)}; const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; @@ -219,7 +215,7 @@ pub fn Controller(comptime config: Config) type { const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; - var interface = 0; + var next_interface = 0; for (config.drivers) |drv| { var ep_fields: []const Field = &.{}; @@ -242,7 +238,7 @@ pub fn Controller(comptime config: Config) type { } }); const string_ids = @field(string.ids{}, drv.name); - const info = drv.Type.info(interface, string_ids, ep_struct{}); + const info = drv.Type.info(next_interface, string_ids, ep_struct{}); fields = fields ++ .{Field{ .name = drv.name, .type = @TypeOf(info), @@ -250,7 +246,10 @@ pub fn Controller(comptime config: Config) type { .is_comptime = false, .alignment = @alignOf(@TypeOf(info)), }}; - interface += drv.Type.num_interfaces; + for (@typeInfo(@TypeOf(info.descriptors)).@"struct".fields) |fld| { + if (fld.type == descriptor.Interface) + next_interface += 1; + } } break :blk @Type(.{ .@"struct" = .{ @@ -273,11 +272,11 @@ pub fn Controller(comptime config: Config) type { pub fn get_descriptor(setup: *const types.SetupPacket) ?[]const u8 { const device_descriptor: descriptor.Device = comptime .{ - .bcd_usb = .from(config.bcd_usb), + .bcd_usb = .from(config.bcd_usb.?), .device_triple = config.device_triple, - .max_packet_size0 = config.max_transfer_size, - .vendor = .from(config.vid), - .product = .from(config.pid), + .max_packet_size0 = config.max_transfer_size.?, + .vendor = .from(config.vid.?), + .product = .from(config.pid.?), .bcd_device = .from(config.bcd_device), .manufacturer_s = 1, .product_s = 2, @@ -291,7 +290,10 @@ pub fn Controller(comptime config: Config) type { for (config.drivers) |drv| { const info = @field(driver_info, drv.name); - num_interfaces += drv.Type.num_interfaces; + for (@typeInfo(@TypeOf(info.descriptors)).@"struct".fields) |fld| { + if (fld.type == descriptor.Interface) + num_interfaces += 1; + } ret = ret ++ info.descriptors.serialize(); } @@ -331,40 +333,24 @@ pub fn Controller(comptime config: Config) type { this.drivers = undefined; inline for (config.drivers) |drv| { - const info = @field(driver_info, drv.name); - - const descriptors = info.descriptors; + const descriptors = @field(driver_info, drv.name).descriptors; const fields = @typeInfo(@TypeOf(descriptors)).@"struct".fields; - var assoc_itf_count: u8 = 1; - // New class starts optionally from InterfaceAssociation followed by mandatory Interface - if (fields[0].type == descriptor.InterfaceAssociation) - assoc_itf_count = @field(descriptors, fields[0].name).interface_count; - - @field(this.drivers.?, drv.name) = .init(device, &descriptors); - inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .Out) continue; - - _ = device.endpoint_open( - desc_ep.endpoint, - desc_ep.attributes.transfer_type, - desc_ep.max_packet_size.into(), - ) catch unreachable; + _ = device.endpoint_open(&desc_ep); } + @field(this.drivers.?, drv.name) = .init(device, &descriptors); + inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .In) continue; - if (device.endpoint_open( - desc_ep.endpoint, - desc_ep.attributes.transfer_type, - desc_ep.max_packet_size.into(), - ) catch unreachable) |buf| + if (device.endpoint_open(&desc_ep)) |buf| this.on_tx_ready(desc_ep.endpoint.num, buf) catch { std.log.warn("initial buffer unhandled on {any}", .{desc_ep.endpoint.num}); }; diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 4bfc4e7bf..a21ed148c 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -24,21 +24,17 @@ pub const LineCoding = extern struct { .parity = 0, .data_bits = 8, }; - - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; pub const Descriptor = extern struct { - desc1: descriptor.InterfaceAssociation, - desc2: descriptor.Interface, - desc3: descriptor.cdc.Header, - desc4: descriptor.cdc.CallManagement, - desc5: descriptor.cdc.AbstractControlModel, - desc6: descriptor.cdc.Union, + 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, - desc8: descriptor.Interface, + itf_data: descriptor.Interface, ep_out: descriptor.Endpoint, ep_in: descriptor.Endpoint, @@ -48,9 +44,6 @@ pub const Descriptor = extern struct { }; pub const CdcClassDriver = struct { - pub const num_interfaces = 2; - const max_packet_size = 64; - ep_in_notif: types.Endpoint.Num = .ep0, ep_in: types.Endpoint.Num = .ep0, ep_out: types.Endpoint.Num = .ep0, @@ -76,7 +69,7 @@ pub const CdcClassDriver = struct { .{ .ep_num = endpoints.data, .func = on_data_rx }, }, .descriptors = .{ - .desc1 = .{ + .itf_assoc = .{ .first_interface = first_interface, .interface_count = 2, .function_class = 2, @@ -84,7 +77,7 @@ pub const CdcClassDriver = struct { .function_protocol = 0, .function = 0, }, - .desc2 = .{ + .itf_notifi = .{ .interface_number = first_interface, .alternate_setting = 0, .num_endpoints = 1, @@ -93,13 +86,13 @@ pub const CdcClassDriver = struct { .interface_protocol = 0, .interface_s = string_ids.name, }, - .desc3 = .{ .bcd_cdc = .from(0x0120) }, - .desc4 = .{ + .cdc_header = .{ .bcd_cdc = .from(0x0120) }, + .cdc_call_mgmt = .{ .capabilities = 0, .data_interface = first_interface + 1, }, - .desc5 = .{ .capabilities = 6 }, - .desc6 = .{ + .cdc_acm = .{ .capabilities = 6 }, + .cdc_union = .{ .master_interface = first_interface, .slave_interface_0 = first_interface + 1, }, @@ -109,7 +102,7 @@ pub const CdcClassDriver = struct { .max_packet_size = .from(endpoint_notifi_size), .interval = 16, }, - .desc8 = .{ + .itf_data = .{ .interface_number = first_interface + 1, .alternate_setting = 0, .num_endpoints = 2, @@ -135,8 +128,8 @@ pub const CdcClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn init(controller: usb.DeviceInterface, desc: *const Descriptor) @This() { - controller.signal_rx_ready(desc.ep_out.endpoint.num, std.math.maxInt(usize)); + pub fn init(device: usb.DeviceInterface, desc: *const Descriptor) @This() { + device.signal_rx_ready(desc.ep_out.endpoint.num, std.math.maxInt(usize)); return .{ .line_coding = .init, .awaiting_data = false, @@ -148,13 +141,16 @@ pub const CdcClassDriver = struct { }; } + /// On bus reset, this function is called followed by init(). pub fn deinit(_: *@This()) void {} + /// How many bytes in rx buffer? pub fn available(this: *@This()) usize { return if (this.rx_buf) |rx| rx.len else 0; } - pub fn read(this: *@This(), controller: usb.DeviceInterface, dst: []u8) usize { + /// Read data from rx buffer into dst. + pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { if (this.rx_buf) |rx| { const len = @min(rx.len, dst.len); // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 @@ -162,13 +158,14 @@ pub const CdcClassDriver = struct { if (len < rx.len) this.rx_buf = rx[len..] else { - controller.signal_rx_ready(this.ep_out, std.math.maxInt(usize)); + device.signal_rx_ready(this.ep_out, std.math.maxInt(usize)); this.rx_buf = null; } return len; } else return 0; } + /// Write data from src into tx buffer. pub fn write(this: *@This(), src: []const u8) usize { if (this.tx_buf) |tx| { const len = @min(tx.len, src.len); @@ -179,23 +176,15 @@ pub const CdcClassDriver = struct { } else return 0; } - pub fn flush(this: *@This(), controller: usb.DeviceInterface) void { + /// Submit tx buffer to the device. + pub fn flush(this: *@This(), device: usb.DeviceInterface) void { if (this.tx_buf) |tx| { defer this.tx_buf = null; - controller.submit_tx_buffer(this.ep_in, tx.ptr); - } - } - - pub fn writeAll(this: *@This(), controller: usb.DeviceInterface, data: []const u8) void { - var offset: usize = 0; - while (offset < data.len) { - offset += this.write(data[offset..]); - // TODO: Interrupt-safe. - this.flush(controller); - controller.task(); + device.submit_tx_buffer(this.ep_in, tx.ptr); } } + /// Callback for setup packets. pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { return if (enumFromInt( ManagementRequestType, diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 97ed0cd37..0d45c2952 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -28,27 +28,6 @@ pub const Type = enum(u8) { _, }; -// pub const Any = union(Type) { -// Device: Device, -// Configuration: Configuration, -// String, -// Interface: Interface, -// Endpoint: Endpoint, -// DeviceQualifier: Device.Qualifier, -// InterfaceAssociation: InterfaceAssociation, -// CsDevice, -// CsConfig, -// CsString, -// CsInterface, -// CsEndpoint, - -// pub fn serialize(comptime this: @This()) []const u8 { -// return comptime switch (this) { -// .Interface => |d| d.serialize(), -// }; -// } -// }; - /// 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 { @@ -140,8 +119,6 @@ pub const Device = extern struct { /// Description of a single available device configuration. pub const Configuration = extern struct { - pub const const_descriptor_type: Type = .Configuration; - /// Maximum device current consumption. pub const MaxCurrent = extern struct { multiple_of_2ma: u8, @@ -202,8 +179,6 @@ pub fn string(comptime value: []const u8) []const u8 { /// Describes an endpoint within an interface pub const Endpoint = extern struct { - pub const const_descriptor_type: Type = .Endpoint; - pub const Attributes = packed struct(u8) { pub const Synchronisation = enum(u2) { none = 0, @@ -244,16 +219,10 @@ pub const Endpoint = extern struct { /// Interval for polling interrupt/isochronous endpoints (which we don't /// currently support) in milliseconds. interval: u8, - - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; /// Description of an interface within a configuration. pub const Interface = extern struct { - pub const const_descriptor_type: Type = .Interface; - comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 9); @@ -278,16 +247,10 @@ pub const Interface = extern struct { interface_protocol: u8, /// Index of interface name within string descriptor table. interface_s: u8, - - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; /// USB interface association descriptor (IAD) allows the device to group interfaces that belong to a function. pub const InterfaceAssociation = extern struct { - pub const const_descriptor_type: Type = .InterfaceAssociation; - comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 8); @@ -310,8 +273,4 @@ pub const InterfaceAssociation = extern struct { function_protocol: u8, // Index of the string descriptor describing the associated interfaces. function: u8, - - pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index 64daa5eeb..3a7355bc6 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -23,10 +23,6 @@ pub const Header = extern struct { // USB Class Definitions for Communication Devices Specification release // number in binary-coded decimal. Typically 0x01_10. bcd_cdc: types.U16Le, - - pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; pub const CallManagement = extern struct { @@ -44,10 +40,6 @@ pub const CallManagement = extern struct { capabilities: u8, // Data interface number. data_interface: u8, - - pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; pub const AbstractControlModel = extern struct { @@ -63,10 +55,6 @@ pub const AbstractControlModel = extern struct { descriptor_subtype: SubType = .AbstractControlModel, // Capabilities. Should be 0x02 for use as a serial device. capabilities: u8, - - pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; pub const Union = extern struct { @@ -86,8 +74,4 @@ pub const Union = extern struct { // The interface number of the first slave or associated interface in the // union. slave_interface_0: u8, - - pub fn serialize(this: *const @This()) *const [@sizeOf(@This())]u8 { - return @bitCast(this); - } }; diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index 08908b530..da5e08b0e 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -40,11 +40,14 @@ pub const RequestType = enum(u8) { /// USB HID descriptor pub const Hid = extern struct { - pub const const_descriptor_type = .Hid; + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 9); + } - length: u8 = 9, + length: u8 = @sizeOf(@This()), /// Type of this descriptor - descriptor_type: SubType = const_descriptor_type, + descriptor_type: SubType = .Hid, /// Numeric expression identifying the HID Class Specification release bcd_hid: types.U16Le, /// Numeric expression identifying country code of the localized hardware @@ -55,20 +58,6 @@ pub const Hid = extern struct { report_type: SubType = .Report, /// The total size of the Report descriptor report_length: types.U16Le, - - pub fn serialize(self: *const @This()) [9]u8 { - var out: [9]u8 = undefined; - out[0] = out.len; - out[1] = @intFromEnum(self.descriptor_type); - out[2] = self.bcd_hid.lo; - out[3] = self.bcd_hid.hi; - out[4] = self.country_code; - out[5] = self.num_descriptors; - out[6] = @intFromEnum(self.report_type); - out[7] = self.report_length.lo; - out[8] = self.report_length.hi; - return out; - } }; /// HID interface Subclass (for UsbInterfaceDescriptor) diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 41da0c12f..f865ea6f8 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -129,8 +129,6 @@ pub const InOutDescriptor = extern struct { }; pub const HidClassDriver = struct { - pub const num_interfaces = 1; - ep_in: types.Endpoint.Num = .ep0, ep_out: types.Endpoint.Num = .ep0, hid_descriptor: []const u8 = &.{}, @@ -166,8 +164,10 @@ pub const HidClassDriver = struct { }; } + /// On bus reset, this function is called followed by init(). pub fn deinit(_: *@This()) void {} + /// Callback for setup packets. pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { switch (setup.request_type.type) { .Standard => { @@ -221,7 +221,4 @@ pub const HidClassDriver = struct { } return usb.ACK; } - - pub fn on_tx_ready(_: *@This(), _: []u8) void {} - pub fn on_data_rx(_: *@This(), _: []const u8) void {} }; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 0a2d5af78..09bee8477 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -100,28 +100,12 @@ pub const Endpoint = packed struct(u8) { ep13, ep14, ep15, - - pub fn from_int(int: u4) @This() { - return @enumFromInt(int); - } - - pub fn to_int(this: @This()) u4 { - return @intFromEnum(this); - } }; num: Num, _padding: u3 = 0, dir: Dir, - pub inline fn to_address(this: @This()) u8 { - return @bitCast(this); - } - - pub inline fn from_address(addr: u8) @This() { - return @bitCast(addr & 0x8F); - } - pub inline fn out(num: Num) @This() { return .{ .num = num, .dir = .Out }; } diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index 405912874..247dd945a 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -21,17 +21,13 @@ const pin_config: rp2xxx.pins.GlobalConfiguration = .{ const pins = pin_config.pins(); // This is our device configuration -const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ - .strings = rp2xxx.usb.default.strings, - .vid = rp2xxx.usb.default.vid, - .pid = rp2xxx.usb.default.pid, - .max_transfer_size = rp2xxx.usb.default.transfer_size, +const Usb = rp2xxx.usb.Usb(.{ .controller_config = .{ .device_triple = .{ .class = .Miscellaneous, .subclass = 2, .protocol = 1, }, - .attributes = .{ .self_powered = true }, + .attributes = .{ .self_powered = false }, .drivers = &.{.{ .name = "hid", .Type = microzig.core.usb.hid.HidClassDriver, @@ -42,7 +38,7 @@ const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ .{ .name = "name", .value = "Board HID" }, }, }}, -}) }); +} }); var usb: Usb = undefined; pub fn main() !void { @@ -64,7 +60,7 @@ pub fn main() !void { while (true) { // You can now poll for USB events - usb.interface().task(); + usb.task(); const hid = if (usb.controller.drivers) |*drivers| &drivers.hid diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 4b78ee4bb..7e339a134 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -20,16 +20,14 @@ const pin_config: rp2xxx.pins.GlobalConfiguration = .{ }; const pins = pin_config.pins(); +const CdcDriver = microzig.core.usb.cdc.CdcClassDriver; + // This is our device configuration -const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ - .strings = rp2xxx.usb.default.strings, - .vid = rp2xxx.usb.default.vid, - .pid = rp2xxx.usb.default.pid, - .max_transfer_size = rp2xxx.usb.default.transfer_size, - .attributes = .{ .self_powered = true }, +const Usb = rp2xxx.usb.Usb(.{ .controller_config = .{ + .attributes = .{ .self_powered = false }, .drivers = &.{.{ .name = "serial", - .Type = microzig.core.usb.cdc.CdcClassDriver, + .Type = CdcDriver, .endpoints = &.{ .{ .name = "notifi", .value = .ep1 }, .{ .name = "data", .value = .ep2 }, @@ -38,9 +36,18 @@ const Usb = rp2xxx.usb.Usb(.{ .Controller = microzig.core.usb.Controller(.{ .{ .name = "name", .value = "Board CDC" }, }, }}, -}) }); +} }); var usb: Usb = undefined; +fn write_all(serial: *CdcDriver, data: []const u8) void { + var offset: usize = 0; + while (offset < data.len) { + offset += serial.write(data[offset..]); + serial.flush(usb.interface()); + usb.task(); + } +} + pub fn main() !void { pin_config.apply(); @@ -60,7 +67,7 @@ pub fn main() !void { var i: u32 = 0; while (true) { // You can now poll for USB events - usb.interface().task(); + usb.task(); const usb_serial = if (usb.controller.drivers) |*drivers| &drivers.serial @@ -77,16 +84,16 @@ pub fn main() !void { var tx_buf: [1024]u8 = undefined; const text = try std.fmt.bufPrint(&tx_buf, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); - usb_serial.writeAll(usb.interface(), text); + write_all(usb_serial, text); } // read and print host command if present var rx_buf: [64]u8 = undefined; const len = usb_serial.read(usb.interface(), &rx_buf); if (len > 0) { - usb_serial.writeAll(usb.interface(), "Your message to me was: '"); - usb_serial.writeAll(usb.interface(), rx_buf[0..len]); - usb_serial.writeAll(usb.interface(), "'\r\n"); + write_all(usb_serial, "Your message to me was: '"); + write_all(usb_serial, rx_buf[0..len]); + write_all(usb_serial, "'\r\n"); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index b83061872..c5b8a2234 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -8,19 +8,17 @@ const enumFromInt = std.meta.intToEnum; const microzig = @import("microzig"); const chip = microzig.hal.compatibility.chip; -const peri_dpram = microzig.chip.peripherals.USB_DPRAM; -const peri_usb = microzig.chip.peripherals.USB; +const USB_DPRAM = microzig.chip.peripherals.USB_DPRAM; +const USB = microzig.chip.peripherals.USB; const usb = microzig.core.usb; - -const resets = @import("resets.zig"); +const types = usb.types; pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; pub const Config = struct { synchronization_nops: comptime_int = 3, dpram_allocator: type = DpramAllocatorBump, - Controller: type, - // swap_dpdm: bool = false, + controller_config: usb.Config, }; pub const default = struct { @@ -31,12 +29,13 @@ pub const default = struct { }; pub const vid: u16 = 0x2E8A; pub const pid: u16 = 0x000a; + pub const bcd_usb = 0x02_00; pub const transfer_size = 64; }; const Endpoint = usb.types.Endpoint; -const EpCtrl = @TypeOf(peri_dpram.EP1_IN_CONTROL); -const BufCtrl = @TypeOf(peri_dpram.EP0_IN_BUFFER_CONTROL); +const EpCtrl = @TypeOf(USB_DPRAM.EP1_IN_CONTROL); +const BufCtrl = @TypeOf(USB_DPRAM.EP0_IN_BUFFER_CONTROL); const dpram_size = 4096; pub const DpramBuffer = struct { @@ -46,7 +45,7 @@ pub const DpramBuffer = struct { const Chunk = struct { data: [chunk_len]u8 align(start_align) = undefined }; const memory_raw: *[dpram_size / chunk_len]Chunk = - @alignCast(@volatileCast(@ptrCast(peri_dpram))); + @alignCast(@volatileCast(@ptrCast(USB_DPRAM))); pub const Len = std.meta.Int(.unsigned, std.math.log2_int_ceil(u16, dpram_size)); @@ -103,23 +102,20 @@ pub const DpramAllocatorBump = struct { pub fn Usb(comptime config: Config) type { return struct { pub const interface_vtable: usb.DeviceInterface.Vtable = .{ - .task = &task, .control_transfer = &control_transfer, .signal_rx_ready = &signal_rx_ready, .submit_tx_buffer = &submit_tx_buffer, .endpoint_open = &endpoint_open, }; - pub const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; - pub const bcd_usb = 0x02_00; - pub const id = config.id; + const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; pub const HardwareEndpoint = packed struct(u5) { const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = - @ptrCast(&peri_dpram.EP1_IN_CONTROL); + @ptrCast(&USB_DPRAM.EP1_IN_CONTROL); const buf_ctrl_all: *volatile [2 * (max_endpoints_count)]BufCtrl = - @ptrCast(&peri_dpram.EP0_IN_BUFFER_CONTROL); + @ptrCast(&USB_DPRAM.EP0_IN_BUFFER_CONTROL); is_out: bool, num: Endpoint.Num, @@ -158,6 +154,16 @@ pub fn Usb(comptime config: Config) type { } }; + const Controller = usb.Controller(blk: { + var cfg = config.controller_config; + cfg.strings = cfg.strings orelse default.strings; + cfg.vid = cfg.vid orelse default.vid; + cfg.pid = cfg.pid orelse default.pid; + cfg.bcd_usb = cfg.bcd_usb orelse default.bcd_usb; + cfg.max_transfer_size = cfg.max_transfer_size orelse default.transfer_size; + break :blk cfg; + }); + const State = union(enum) { sending: []const u8, // Slice of data left to be sent. no_buffer: ?u7, // Optionally a new address. @@ -166,7 +172,7 @@ pub fn Usb(comptime config: Config) type { }; state: State, - controller: config.Controller, + controller: Controller, pub fn interface(this: *@This()) usb.DeviceInterface { return .{ @@ -177,12 +183,12 @@ pub fn Usb(comptime config: Config) type { pub fn init() @This() { if (chip == .RP2350) - peri_usb.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); + USB.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); // Clear the control portion of DPRAM. This may not be necessary -- the // datasheet is ambiguous -- but the C examples do it, and so do we. - peri_dpram.SETUP_PACKET_LOW.write_raw(0); - peri_dpram.SETUP_PACKET_HIGH.write_raw(0); + USB_DPRAM.SETUP_PACKET_LOW.write_raw(0); + USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); for (HardwareEndpoint.ep_ctrl_all) |*ep_ctrl| ep_ctrl.write_raw(0); @@ -191,7 +197,7 @@ pub fn Usb(comptime config: Config) type { // Mux the controller to the onboard USB PHY. I was surprised that there are // alternatives to this, but, there are. - peri_usb.USB_MUXING.modify(.{ + USB.USB_MUXING.modify(.{ .TO_PHY = 1, // This bit is also set in the SDK example, without any discussion. It's // undocumented (being named does not count as being documented). @@ -202,24 +208,24 @@ pub fn Usb(comptime config: Config) type { // let us detect being plugged into a host (the Pi Pico, to its credit, // does). For maximum compatibility, we'll set the hardware to always // pretend VBUS has been detected. - peri_usb.USB_PWR.modify(.{ + USB.USB_PWR.modify(.{ .VBUS_DETECT = 1, .VBUS_DETECT_OVERRIDE_EN = 1, }); // Enable controller in device mode. - peri_usb.MAIN_CTRL.modify(.{ + USB.MAIN_CTRL.modify(.{ .CONTROLLER_EN = 1, .HOST_NDEVICE = 0, }); // Request to have an interrupt (which really just means setting a bit in // the `buff_status` register) every time a buffer moves through EP0. - peri_usb.SIE_CTRL.modify(.{ .EP0_INT_1BUF = 1 }); + USB.SIE_CTRL.modify(.{ .EP0_INT_1BUF = 1 }); // Enable interrupts (bits set in the `ints` register) for other conditions // we use: - peri_usb.INTE.modify(.{ + USB.INTE.modify(.{ // A buffer is done .BUFF_STATUS = 1, // The host has reset us @@ -228,20 +234,16 @@ pub fn Usb(comptime config: Config) type { .SETUP_REQ = 1, }); - var this: @This() = .{ - .state = .{ .no_buffer = null }, - .controller = .init, - }; - - if (this.interface().endpoint_open(.in(.ep0), .Control, 0) catch unreachable) |tx| - this.state = .{ .ready = tx }; - - _ = this.interface().endpoint_open(.out(.ep0), .Control, 0) catch unreachable; - // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. - peri_usb.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); - return this; + USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); + + return .{ + .state = .{ + .ready = DpramBuffer.Index.ep0buf0.start()[0..default.transfer_size], + }, + .controller = .init, + }; } fn ep0_send(this: *@This(), tx_buf: []u8, data: []const u8) void { @@ -276,13 +278,13 @@ pub fn Usb(comptime config: Config) type { // Copy the setup packet out of its dedicated buffer at the base of // USB SRAM. The PAC models this buffer as two 32-bit registers. const setup: usb.types.SetupPacket = @bitCast([2]u32{ - peri_dpram.SETUP_PACKET_LOW.raw, - peri_dpram.SETUP_PACKET_HIGH.raw, + USB_DPRAM.SETUP_PACKET_LOW.raw, + USB_DPRAM.SETUP_PACKET_HIGH.raw, }); // Reset PID to 1 for EP0 IN. Every DATA packet we send in response // to an IN on EP0 needs to use PID DATA1. - peri_dpram.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); + USB_DPRAM.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); switch (setup.request_type.recipient) { .Device => blk: { @@ -296,7 +298,7 @@ pub fn Usb(comptime config: Config) type { this.state = .{ .no_buffer = @intCast(setup.value) }; }, .SetConfiguration => this.controller.set_configuration(this.interface(), &setup), - .GetDescriptor => if (config.Controller.get_descriptor(&setup)) |desc| + .GetDescriptor => if (Controller.get_descriptor(&setup)) |desc| this.ep0_send(tx_buf, desc), .SetFeature => if (this.controller.set_feature( @intCast(setup.value >> 8), @@ -311,27 +313,24 @@ pub fn Usb(comptime config: Config) type { } } - // fn ep0_handler(this: *@This(), ep: HardwareEndpoint, buf: []u8) usb.PacketUnhandled!void {} - /// Usb task function meant to be executed in regular intervals after /// initializing the device. - pub fn task(ptr: *anyopaque) void { - const this: *@This() = @alignCast(@ptrCast(ptr)); - const ints = peri_usb.INTS.read(); - const SieStatus = @TypeOf(peri_usb.SIE_STATUS).underlying_type; + pub fn task(this: *@This()) void { + const ints = USB.INTS.read(); + const SieStatus = @TypeOf(USB.SIE_STATUS).underlying_type; switch (this.state) { .ready => |tx_buf| if (ints.SETUP_REQ != 0) { // Clear the status flag (write one to clear) var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.SETUP_REC = 1; - peri_usb.SIE_STATUS.write(sie_status); + USB.SIE_STATUS.write(sie_status); this.process_setup(tx_buf); }, else => {}, } if (ints.BUFF_STATUS != 0) { - const unhandled_initial = peri_usb.BUFF_STATUS.raw; + const unhandled_initial = USB.BUFF_STATUS.raw; var unhandled_pending = unhandled_initial; while (std.math.cast(u5, @ctz(unhandled_pending))) |idx| { @@ -357,7 +356,7 @@ pub fn Usb(comptime config: Config) type { if (ep.is_out) break :blk error.UsbPacketUnhandled; // Finish the delayed SetAddress request, if there is one: if (new_address) |addr| - peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); this.state = .{ .ready = buf }; }, @@ -382,17 +381,19 @@ pub fn Usb(comptime config: Config) type { std.log.warn("{any}", .{buf[0..ep.len()]}); }; } - peri_usb.BUFF_STATUS.write_raw(unhandled_initial); + USB.BUFF_STATUS.write_raw(unhandled_initial); } if (ints.BUS_RESET != 0) { this.controller.deinit(); this.controller = .init; + // TODO: Reset allocator. + var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.BUS_RESET = 1; - peri_usb.SIE_STATUS.write(sie_status); - peri_usb.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); + USB.SIE_STATUS.write(sie_status); + USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = 0 }); } } @@ -441,28 +442,24 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } - pub fn endpoint_open( - _: *anyopaque, - ep: Endpoint, - transfer_type: usb.types.TransferType, - buf_size_hint: usize, - ) error{OutOfBufferMemory}!?[]u8 { - _ = buf_size_hint; + pub fn endpoint_open(ptr: *anyopaque, desc: *const usb.descriptor.Endpoint) ?[]u8 { + const this: *@This() = @alignCast(@ptrCast(ptr)); + _ = this; + + assert(@intFromEnum(desc.endpoint.num) < max_endpoints_count); const ep_hard: HardwareEndpoint = .{ - .num = ep.num, - .is_out = ep.dir == .Out, + .num = desc.endpoint.num, + .is_out = desc.endpoint.dir == .Out, }; - assert(ep.num.to_int() < max_endpoints_count); - - const start = if (ep.num != .ep0) blk: { - const buf = try config.dpram_allocator.alloc(1); + const start = if (desc.endpoint.num != .ep0) blk: { + const buf = config.dpram_allocator.alloc(1) catch unreachable; var ep_ctrl = ep_hard.ep_ctrl().?; var rmw = ep_ctrl.read(); rmw.ENABLE = 1; rmw.INTERRUPT_PER_BUFF = 1; - rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); + rmw.ENDPOINT_TYPE = @enumFromInt(desc.attributes.transfer_type.as_number()); rmw.BUFFER_ADDRESS = buf.to_u16(); ep_ctrl.write(rmw); break :blk buf.start(); From 0331dcd66f02c2618a00685dda5d23fc6c101d7d Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 26 Aug 2025 07:08:31 +0200 Subject: [PATCH 19/33] add documentation and simplify dpram allocation --- core/src/core/usb.zig | 95 +++++------- core/src/core/usb/descriptor.zig | 22 ++- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 1 + port/raspberrypi/rp2xxx/src/hal/usb.zig | 152 +++++++------------- 4 files changed, 111 insertions(+), 159 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 1a1af1e9f..33218b44f 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -19,7 +19,6 @@ const EpNum = types.Endpoint.Num; pub const DeviceInterface = struct { pub const Vtable = struct { - control_transfer: *const fn (ptr: *anyopaque, data: []const u8) void, submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, signal_rx_ready: *const fn (ptr: *anyopaque, ep_out: EpNum, max_len: usize) void, endpoint_open: *const fn (ptr: *anyopaque, desc: *const descriptor.Endpoint) ?[]u8, @@ -27,40 +26,35 @@ pub const DeviceInterface = struct { ptr: *anyopaque, vtable: *const Vtable, - pub fn control_transfer(this: @This(), data: []const u8) void { - assert(data.len != 0); - this.vtable.control_transfer(this.ptr, data); - } - pub fn control_ack(this: @This()) void { - this.vtable.control_transfer(this.ptr, ""); - } + /// Called by drivers when a tx buffer is filled. + /// Submitting an empty buffer signals an ACK. + /// A buffer can only be submitted once. pub fn submit_tx_buffer(this: @This(), ep_in: EpNum, buffer_end: [*]const u8) void { this.vtable.submit_tx_buffer(this.ptr, ep_in, buffer_end); } + /// Called by drivers to report readiness to receive up to `len` bytes. + /// Must be called exactly once before each packet. pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); } + /// Opens an endpoint according to the descriptor. pub fn endpoint_open(this: @This(), desc: *const descriptor.Endpoint) ?[]u8 { return this.vtable.endpoint_open(this.ptr, desc); } }; -/// Manufacturer, product and serial number strings. -pub const Strings = struct { - manufacturer: []const u8, - product: []const u8, - serial: []const u8, -}; pub const Config = struct { + /// Manufacturer, product and serial number strings. + pub const DeviceStrings = struct { + manufacturer: []const u8, + product: []const u8, + serial: []const u8, + }; + pub fn NameValue(T: type) type { return struct { name: [:0]const u8, value: T }; } - /// String descriptor language. - pub const Language = enum(u16) { - English = 0x0409, - }; - pub const Driver = struct { name: [:0]const u8, Type: type, @@ -73,13 +67,13 @@ pub const Config = struct { /// Maximum device current consumption.. max_current: descriptor.Configuration.MaxCurrent = .from_ma(100), /// String descriptor language. - language: Language = .English, + language: descriptor.Language = .English, /// Device version number as Binary Coded Decimal. bcd_device: u16 = 0x01_00, /// Class, subclass and protocol of device. device_triple: descriptor.Device.DeviceTriple = .unspecified, /// Manufacturer, product and serial number strings. Leave at null for device default. - strings: ?Strings = null, + strings: ?DeviceStrings = null, /// Vendor ID. Leave at null for device default. vid: ?u16 = null, /// Product ID. Leave at null for device default. @@ -92,46 +86,29 @@ pub const Config = struct { drivers: []const Driver, }; +/// Information about a usb driver, each is required to return this struct via `.info()`. pub fn DriverInfo(T: type, Descriptors: type) type { return struct { + /// All configuration descriptors concatenated in an extern struct. descriptors: Descriptors, + /// Handlers of setup packets for each used interface. interface_handlers: []const struct { itf: u8, func: fn (*T, *const types.SetupPacket) ?[]const u8 }, + /// Functions called an endpoint is ready to send. endpoint_in_handlers: []const struct { ep_num: EpNum, func: fn (*T, []u8) void }, + /// Functions called when data is received on an endpoint. endpoint_out_handlers: []const struct { ep_num: EpNum, func: fn (*T, []const u8) void }, }; } -/// And endpoint and its corresponding buffer. -pub const EndpointAndBuffer = union(types.Dir) { - Out: struct { ep_num: EpNum, buffer: []const u8 }, - In: struct { ep_num: EpNum, buffer: []u8 }, -}; - pub const PacketUnhandled = error{UsbPacketUnhandled}; pub const ACK = ""; pub const NAK = null; -/// Create a USB device controller. -/// -/// This is an abstract USB device controller implementation that requires -/// the USB device driver to implement a handful of functions to work correctly: -/// -/// * `usb_init_device() ?[]u8` - Initialize the USB device controller (e.g. enable interrupts, etc.). Returns the ep0 tx buffer, if any. -/// * `set_address(addr: u7) void` - Set device address. -/// * `get_events() anytype` - Returns what events need to be handled (rx/tx completed, bus reset etc., specific to device). -/// * `submit_tx_buffer(ep_in: EpNum, buffer_end: [*]const u8) void` - Send the specified buffer to the host. -/// * `signal_rx_ready(ep_out: EpNum, len: usize) void` - Receive n bytes over the specified endpoint. -/// * `bus_reset_clear() void` - Called after handling a bus reset. -/// -/// As well as some declarations: -/// -/// * `max_endpoints_count` - How many endpoints are supported, up to 16. -/// * `max_transfer_size` - How many bytes can be transferred in one packet. -/// * `bcd_usb` - Version of USB specification supported by device. -/// * `default_strings` - Default manufacturer, product and serial (optional). -/// * `default_vid_pid` - Default VID and PID (optional). +/// This is an abstract USB device controller that gets events from +/// a usb device and distributes them to usb drivers, depending on config. pub fn Controller(comptime config: Config) type { return struct { + /// Driver data is stored within the controller. const DriverData = blk: { const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; @@ -154,15 +131,9 @@ pub fn Controller(comptime config: Config) type { drivers: ?DriverData, + /// String descriptors. Each driver can add its own, and its `.init()` gets the list of IDs. const string = blk: { - // String 0 indicates language. First byte is length. - const lang: types.U16Le = .from(@intFromEnum(config.language)); - var desc: []const []const u8 = &.{&.{ - 0x04, - @intFromEnum(descriptor.Type.String), - lang.lo, - lang.hi, - }}; + var desc: []const []const u8 = &.{&config.language.serialize()}; desc = desc ++ .{descriptor.string(config.strings.?.manufacturer)}; desc = desc ++ .{descriptor.string(config.strings.?.product)}; @@ -211,6 +182,7 @@ pub fn Controller(comptime config: Config) type { }; }; + /// Used descriptors and endpoint handlers. const driver_info = blk: { const Field = std.builtin.Type.StructField; var fields: []const Field = &.{}; @@ -262,6 +234,7 @@ pub fn Controller(comptime config: Config) type { pub const init: @This() = .{ .drivers = null }; + /// Deinitializes the drivers if the controller has been configured. pub fn deinit(this: *@This()) void { if (this.drivers) |*drivers| { inline for (config.drivers) |drv| @@ -270,6 +243,7 @@ pub fn Controller(comptime config: Config) type { this.* = .init; } + /// Called whenever a GET_DESCRIPTOR request is received. pub fn get_descriptor(setup: *const types.SetupPacket) ?[]const u8 { const device_descriptor: descriptor.Device = comptime .{ .bcd_usb = .from(config.bcd_usb.?), @@ -322,14 +296,14 @@ pub fn Controller(comptime config: Config) type { } else |_| return null; } - pub fn set_configuration(this: *@This(), device: DeviceInterface, setup: *const types.SetupPacket) void { - defer device.control_ack(); + /// Called whenever a SET_CONFIGURATION request is received. + pub fn set_configuration(this: *@This(), device: DeviceInterface, setup: *const types.SetupPacket) bool { if (setup.value == 0) { this.deinit(); - return; + return true; } - if (setup.value != 1 or this.drivers != null) return; + if (setup.value != 1 or this.drivers != null) return true; this.drivers = undefined; inline for (config.drivers) |drv| { @@ -356,8 +330,10 @@ pub fn Controller(comptime config: Config) type { }; } } + return true; } + /// Called whenever a SET_FEATURE request is received. pub fn set_feature(this: *@This(), feature_selector: u8, index: u16, value: bool) bool { _ = this; _ = index; @@ -369,6 +345,7 @@ pub fn Controller(comptime config: Config) type { } } + /// Called whenever a setup packet is received. pub fn interface_setup(this: *@This(), setup: *const types.SetupPacket) ?[]const u8 { if (this.drivers) |*drivers| { inline for (config.drivers) |drv| { @@ -382,6 +359,7 @@ pub fn Controller(comptime config: Config) type { return NAK; } + /// Called whenever a tx buffer is ready. pub fn on_tx_ready(this: *@This(), ep_num: EpNum, buf: []u8) PacketUnhandled!void { if (this.drivers) |*drivers| { inline for (config.drivers) |drv| { @@ -395,6 +373,7 @@ pub fn Controller(comptime config: Config) type { return error.UsbPacketUnhandled; } + /// Called whenever a packet is received from the host. pub fn on_data_rx(this: *@This(), ep_num: EpNum, buf: []const u8) PacketUnhandled!void { if (this.drivers) |*drivers| { inline for (config.drivers) |drv| { diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 0d45c2952..7a869f02a 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -167,7 +167,7 @@ pub const Configuration = extern struct { /// Maximum device power consumption in units of 2mA. max_current: MaxCurrent, - pub fn serialize(this: @This()) [9]u8 { + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { return @bitCast(this); } }; @@ -177,6 +177,26 @@ pub fn string(comptime value: []const u8) []const u8 { return &[2]u8{ encoded.len + 2, @intFromEnum(Type.String) } ++ encoded; } +/// String descriptor 0. +pub const Language = extern struct { + comptime { + assert(@alignOf(@This()) == 1); + assert(@sizeOf(@This()) == 4); + } + + length: u8 = @sizeOf(@This()), + /// Type of this descriptor, must be `String`. + descriptor_type: Type = .String, + /// See definitions below for possible values. + lang: types.U16Le, + + pub const English: @This() = .{ .lang = .from(0x0409) }; + + pub fn serialize(this: @This()) [@sizeOf(@This())]u8 { + return @bitCast(this); + } +}; + /// Describes an endpoint within an interface pub const Endpoint = extern struct { pub const Attributes = packed struct(u8) { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 7e339a134..47fd53ba1 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -39,6 +39,7 @@ const Usb = rp2xxx.usb.Usb(.{ .controller_config = .{ } }); var usb: Usb = undefined; +// Poll the USB device for events until all data is sent. fn write_all(serial: *CdcDriver, data: []const u8) void { var offset: usize = 0; while (offset < data.len) { diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index c5b8a2234..1df777f9c 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -15,17 +15,11 @@ const types = usb.types; pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; -pub const Config = struct { - synchronization_nops: comptime_int = 3, - dpram_allocator: type = DpramAllocatorBump, - controller_config: usb.Config, -}; - pub const default = struct { - pub const strings: usb.Strings = .{ + pub const strings: usb.Config.DeviceStrings = .{ .manufacturer = "Raspberry Pi", .product = "Pico Test Device", - .serial = "someserial", + .serial = "00000000", }; pub const vid: u16 = 0x2E8A; pub const pid: u16 = 0x000a; @@ -33,76 +27,45 @@ pub const default = struct { pub const transfer_size = 64; }; -const Endpoint = usb.types.Endpoint; +const EpNum = usb.types.Endpoint.Num; const EpCtrl = @TypeOf(USB_DPRAM.EP1_IN_CONTROL); const BufCtrl = @TypeOf(USB_DPRAM.EP0_IN_BUFFER_CONTROL); -const dpram_size = 4096; -pub const DpramBuffer = struct { - const chunk_len = 1 << Index.ignored_lsbs; - const start_align = chunk_len; - - const Chunk = struct { data: [chunk_len]u8 align(start_align) = undefined }; - - const memory_raw: *[dpram_size / chunk_len]Chunk = - @alignCast(@volatileCast(@ptrCast(USB_DPRAM))); - - pub const Len = - std.meta.Int(.unsigned, std.math.log2_int_ceil(u16, dpram_size)); - - pub const Index = enum(Len) { - const ignored_lsbs = 6; - - invalid = 0, - ep0buf0 = (0x100 >> ignored_lsbs), - ep0buf1, - data_start, - _, - - fn from_reg(reg: EpCtrl.underlying_type) @This() { - return @enumFromInt(@shrExact(reg.BUFFER_ADDRESS, ignored_lsbs)); - } - - fn to_u16(this: @This()) u16 { - return @as(u16, @intFromEnum(this)) << ignored_lsbs; - } - - fn start(this: @This()) [*]align(start_align) u8 { - return @ptrCast(&memory_raw[@intFromEnum(this)]); - } - }; -}; +const dpram_buffer_len = 64; +// `@volatileCast` is sound because the USB hardware only modifies the buffers +// after we transfer ownership by accesing a volatile register. +const dpram_buffers: *[4096 / dpram_buffer_len][dpram_buffer_len]u8 = @volatileCast(@ptrCast(USB_DPRAM)); +// First 0x100 bytes are registers +const dpram_ep0buf_idx = 0x100 / dpram_buffer_len; +/// Keeps track of how many buffers have been allocated. pub const DpramAllocatorBump = struct { - // First 0x100 bytes contain control registers and first 2 buffers are for endpoint 0. - var top: DpramBuffer.Index = .data_start; + top: u16, - fn alloc(len: DpramBuffer.Len) error{OutOfBufferMemory}!DpramBuffer.Index { - if (top == .invalid) return error.OutOfBufferMemory; + // First 2 buffers are for endpoint 0. + const init: @This() = .{ .top = dpram_ep0buf_idx + 2 }; - const next, const ovf = @addWithOverflow(len, @intFromEnum(top)); - if (ovf != 0 and next != 0) + /// Allocate a new buffer in dpram, `len` is in units of 64 bytes. + fn alloc(this: *@This(), len: u16) error{OutOfBufferMemory}!u16 { + const next, const ovf = @addWithOverflow(len, this.top); + if (ovf != 0 or next > dpram_buffers.len) return error.OutOfBufferMemory; - const ret: DpramBuffer.Index = @enumFromInt(next); - defer top = ret; - return ret; + defer this.top = next; + return this.top; } }; -// +++++++++++++++++++++++++++++++++++++++++++++++++ -// Code -// +++++++++++++++++++++++++++++++++++++++++++++++++ +pub const Config = struct { + /// How many nops to insert for synchronization with the USB hardware. + synchronization_nops: comptime_int = 3, + /// USB controller configuration. + controller_config: usb.Config, +}; -/// The rp2040 usb device impl -/// -/// We create a concrete implementaion by passing a handful -/// of system specific functions to Usb(). Those functions -/// are used by the abstract USB impl of microzig. pub fn Usb(comptime config: Config) type { return struct { pub const interface_vtable: usb.DeviceInterface.Vtable = .{ - .control_transfer = &control_transfer, .signal_rx_ready = &signal_rx_ready, .submit_tx_buffer = &submit_tx_buffer, .endpoint_open = &endpoint_open, @@ -118,17 +81,17 @@ pub fn Usb(comptime config: Config) type { @ptrCast(&USB_DPRAM.EP0_IN_BUFFER_CONTROL); is_out: bool, - num: Endpoint.Num, + num: EpNum, inline fn to_idx(this: @This()) u5 { return @bitCast(this); } - fn in(num: Endpoint.Num) @This() { + fn in(num: EpNum) @This() { return .{ .num = num, .is_out = false }; } - fn out(num: Endpoint.Num) @This() { + fn out(num: EpNum) @This() { return .{ .num = num, .is_out = true }; } @@ -142,11 +105,11 @@ pub fn Usb(comptime config: Config) type { } fn buffer(this: @This()) []u8 { - const buf: DpramBuffer.Index = if (this.ep_ctrl()) |reg| - .from_reg(reg.read()) + const buf: u16 = if (this.ep_ctrl()) |reg| + @divExact(reg.read().BUFFER_ADDRESS, dpram_buffer_len) else - .ep0buf0; - return buf.start()[0..DpramBuffer.chunk_len]; + dpram_ep0buf_idx; + return &dpram_buffers[buf]; } fn len(this: @This()) u16 { @@ -172,6 +135,7 @@ pub fn Usb(comptime config: Config) type { }; state: State, + dpram_allocator: DpramAllocatorBump, controller: Controller, pub fn interface(this: *@This()) usb.DeviceInterface { @@ -181,6 +145,7 @@ pub fn Usb(comptime config: Config) type { }; } + /// Initialize USB hardware and request enumertation from USB host. pub fn init() @This() { if (chip == .RP2350) USB.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); @@ -239,13 +204,13 @@ pub fn Usb(comptime config: Config) type { USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); return .{ - .state = .{ - .ready = DpramBuffer.Index.ep0buf0.start()[0..default.transfer_size], - }, + .state = .{ .ready = &dpram_buffers[dpram_ep0buf_idx] }, + .dpram_allocator = .init, .controller = .init, }; } + /// Helper function that breaks up long strings. fn ep0_send(this: *@This(), tx_buf: []u8, data: []const u8) void { const len = @min(tx_buf.len, data.len); if (len == 0) @@ -257,23 +222,7 @@ pub fn Usb(comptime config: Config) type { this.interface().submit_tx_buffer(.ep0, tx_buf.ptr + len); } - fn control_transfer(ptr: *anyopaque, data: []const u8) void { - const this: *@This() = @alignCast(@ptrCast(ptr)); - switch (this.state) { - .sending => |residual| { - std.log.err("residual data: {any}", .{residual}); - this.state = .{ .sending = data }; - }, - .no_buffer => |new_address| { - if (new_address) |_| - std.log.err("missed address change!", .{}); - this.state = .{ .sending = data }; - }, - .ready => |tx_buf| this.ep0_send(tx_buf, data), - .waiting_ack => unreachable, - } - } - + /// Called when a setup packet is received. fn process_setup(this: *@This(), tx_buf: []u8) void { // Copy the setup packet out of its dedicated buffer at the base of // USB SRAM. The PAC models this buffer as two 32-bit registers. @@ -297,7 +246,8 @@ pub fn Usb(comptime config: Config) type { this.ep0_send(tx_buf, usb.ACK); this.state = .{ .no_buffer = @intCast(setup.value) }; }, - .SetConfiguration => this.controller.set_configuration(this.interface(), &setup), + .SetConfiguration => if (this.controller.set_configuration(this.interface(), &setup)) + this.ep0_send(tx_buf, usb.ACK), .GetDescriptor => if (Controller.get_descriptor(&setup)) |desc| this.ep0_send(tx_buf, desc), .SetFeature => if (this.controller.set_feature( @@ -387,8 +337,7 @@ pub fn Usb(comptime config: Config) type { if (ints.BUS_RESET != 0) { this.controller.deinit(); this.controller = .init; - - // TODO: Reset allocator. + this.dpram_allocator = .init; var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.BUS_RESET = 1; @@ -397,7 +346,8 @@ pub fn Usb(comptime config: Config) type { } } - pub fn submit_tx_buffer(_: *anyopaque, ep_in: Endpoint.Num, buffer_end: [*]const u8) void { + /// See interface description. + pub fn submit_tx_buffer(_: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void { const ep_hard: HardwareEndpoint = .in(ep_in); const buf = ep_hard.buffer(); @@ -428,7 +378,8 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } - pub fn signal_rx_ready(_: *anyopaque, ep_out: Endpoint.Num, len: usize) void { + /// See interface description. + pub fn signal_rx_ready(_: *anyopaque, ep_out: EpNum, len: usize) void { const ep_hard: HardwareEndpoint = .out(ep_out); // Configure the OUT: @@ -442,9 +393,9 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } + /// See interface description. pub fn endpoint_open(ptr: *anyopaque, desc: *const usb.descriptor.Endpoint) ?[]u8 { const this: *@This() = @alignCast(@ptrCast(ptr)); - _ = this; assert(@intFromEnum(desc.endpoint.num) < max_endpoints_count); @@ -454,18 +405,19 @@ pub fn Usb(comptime config: Config) type { }; const start = if (desc.endpoint.num != .ep0) blk: { - const buf = config.dpram_allocator.alloc(1) catch unreachable; + const buf = this.dpram_allocator.alloc(1) catch + std.debug.panic("USB controller out of memory.", .{}); var ep_ctrl = ep_hard.ep_ctrl().?; var rmw = ep_ctrl.read(); rmw.ENABLE = 1; rmw.INTERRUPT_PER_BUFF = 1; rmw.ENDPOINT_TYPE = @enumFromInt(desc.attributes.transfer_type.as_number()); - rmw.BUFFER_ADDRESS = buf.to_u16(); + rmw.BUFFER_ADDRESS = buf * dpram_buffer_len; ep_ctrl.write(rmw); - break :blk buf.start(); - } else DpramBuffer.Index.ep0buf0.start(); + break :blk buf; + } else dpram_ep0buf_idx; - return start[0..default.transfer_size]; + return &dpram_buffers[start]; } }; } From 34c11e42814128c05627f1711f86276b1d7274f4 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 26 Aug 2025 07:21:02 +0200 Subject: [PATCH 20/33] revert examples build script --- examples/raspberrypi/rp2xxx/build.zig | 111 ++++++++++++-------------- 1 file changed, 50 insertions(+), 61 deletions(-) diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 6a904c4f4..1315569a8 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -16,56 +16,56 @@ pub fn build(b: *std.Build) void { const specific_examples: []const Example = &.{ // RaspberryPi Boards: - // .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, - // .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, - // .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, - // .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, - - // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, - // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, - - // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - - // // WaveShare Boards: - // .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, + .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, + .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, + .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, + + .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, + .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, + + .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + + // WaveShare Boards: + .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, // .{ .target = "board:waveshare/rp2040_eth", .name = "rp2040-eth" }, // .{ .target = "board:waveshare/rp2040_plus_4m", .name = "rp2040-plus-4m" }, // .{ .target = "board:waveshare/rp2040_plus_16m", .name = "rp2040-plus-16m" }, }; const chip_agnostic_examples: []const ChipAgnosticExample = &.{ - // .{ .name = "adc", .file = "src/adc.zig" }, - // .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, - // .{ .name = "pwm", .file = "src/pwm.zig" }, - // .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, - // .{ .name = "uart-log", .file = "src/uart_log.zig" }, - // .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, - // .{ .name = "spi-master", .file = "src/spi_master.zig" }, - // .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, - // .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, - // .{ .name = "squarewave", .file = "src/squarewave.zig" }, - // .{ .name = "ws2812", .file = "src/ws2812.zig" }, - // .{ .name = "blinky", .file = "src/blinky.zig" }, - // .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, - // .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, - // .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, - // .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, - // .{ .name = "interrupts", .file = "src/interrupts.zig" }, - // .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, - // .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, + .{ .name = "adc", .file = "src/adc.zig" }, + .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, + .{ .name = "pwm", .file = "src/pwm.zig" }, + .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, + .{ .name = "uart-log", .file = "src/uart_log.zig" }, + .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, + .{ .name = "spi-master", .file = "src/spi_master.zig" }, + .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, + .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, + .{ .name = "squarewave", .file = "src/squarewave.zig" }, + .{ .name = "ws2812", .file = "src/ws2812.zig" }, + .{ .name = "blinky", .file = "src/blinky.zig" }, + .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, + .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, + .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, + .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, + .{ .name = "interrupts", .file = "src/interrupts.zig" }, + .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, + .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "usb-cdc", .file = "src/usb_cdc.zig" }, - // .{ .name = "dma", .file = "src/dma.zig" }, - // .{ .name = "cyw43", .file = "src/cyw43.zig" }, - // .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, + .{ .name = "dma", .file = "src/dma.zig" }, + .{ .name = "cyw43", .file = "src/cyw43.zig" }, + .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, }; var available_examples = std.ArrayList(Example).init(b.allocator); @@ -92,12 +92,6 @@ pub fn build(b: *std.Build) void { } } - const no_bin = b.option( - bool, - "no-bin", - "skip emitting binaries", - ) orelse false; - for (available_examples.items) |example| { // If we specify example, only select the ones that match if (maybe_example) |selected_example| @@ -116,19 +110,14 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path(example.file), }); - if (no_bin) { - // Skip emitting binaries to get compile errors faster (great with --watch) - b.getInstallStep().dependOn(&firmware.artifact.step); - } else { - // `install_firmware()` is the MicroZig pendant to `Build.installArtifact()` - // and allows installing the firmware as a typical firmware file. - // - // This will also install into `$prefix/firmware` instead of `$prefix/bin`. - mb.install_firmware(firmware, .{}); - - // For debugging, we also always install the firmware as an ELF file - mb.install_firmware(firmware, .{ .format = .elf }); - } + // `install_firmware()` is the MicroZig pendant to `Build.installArtifact()` + // and allows installing the firmware as a typical firmware file. + // + // This will also install into `$prefix/firmware` instead of `$prefix/bin`. + mb.install_firmware(firmware, .{}); + + // For debugging, we also always install the firmware as an ELF file + mb.install_firmware(firmware, .{ .format = .elf }); } } From cd1f282723584d89eca81867576438d5b744d538 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 26 Aug 2025 07:58:14 +0200 Subject: [PATCH 21/33] allow longer evaluation for making string descriptors --- core/src/core/usb/descriptor.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 7a869f02a..9068140e0 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -173,6 +173,7 @@ pub const Configuration = extern struct { }; pub fn string(comptime value: []const u8) []const u8 { + @setEvalBranchQuota(10000); const encoded: []const u8 = @ptrCast(std.unicode.utf8ToUtf16LeStringLiteral(value)); return &[2]u8{ encoded.len + 2, @intFromEnum(Type.String) } ++ encoded; } From 3eae06e3a2088f4338c361a14e62a24a7aab19e4 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 27 Aug 2025 09:36:56 +0200 Subject: [PATCH 22/33] fix typos --- core/src/core/usb/descriptor/cdc.zig | 4 ++-- core/src/core/usb/types.zig | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index 3a7355bc6..0d9f8e5fc 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -31,7 +31,7 @@ pub const CallManagement = extern struct { assert(@sizeOf(@This()) == 5); } - length: u8 = 5, + length: u8 = @sizeOf(@This()), // Type of this descriptor, must be `ClassSpecific`. descriptor_type: Type = .CsInterface, // Subtype of this descriptor, must be `CallManagement`. @@ -63,7 +63,7 @@ pub const Union = extern struct { assert(@sizeOf(@This()) == 5); } - length: u8 = 5, + length: u8 = @sizeOf(@This()), // Type of this descriptor, must be `ClassSpecific`. descriptor_type: Type = .CsInterface, // Subtype of this descriptor, must be `Union`. diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 09bee8477..26c887f39 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -82,7 +82,7 @@ pub const Dir = enum(u1) { }; pub const Endpoint = packed struct(u8) { - // There are up to 15 endpoints for eqch direction. + // There are up to 15 endpoints for each direction. pub const Num = enum(u4) { ep0 = 0, ep1, From 1bb520795129b73c99966fbe1957dce50506a394 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 27 Aug 2025 09:56:46 +0200 Subject: [PATCH 23/33] endpoint_open no longer returns an optional buffer --- core/src/core/usb.zig | 18 +++++++++--------- port/raspberrypi/rp2xxx/src/hal/usb.zig | 16 +++++++++++----- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 33218b44f..2afcc9be6 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -21,7 +21,7 @@ pub const DeviceInterface = struct { pub const Vtable = struct { submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, signal_rx_ready: *const fn (ptr: *anyopaque, ep_out: EpNum, max_len: usize) void, - endpoint_open: *const fn (ptr: *anyopaque, desc: *const descriptor.Endpoint) ?[]u8, + endpoint_open: *const fn (ptr: *anyopaque, desc: *const descriptor.Endpoint) void, }; ptr: *anyopaque, vtable: *const Vtable, @@ -37,8 +37,11 @@ pub const DeviceInterface = struct { pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); } - /// Opens an endpoint according to the descriptor. - pub fn endpoint_open(this: @This(), desc: *const descriptor.Endpoint) ?[]u8 { + /// Opens an endpoint according to the descriptor. Note that if the endpoint + /// direction is IN this may call the controller's `on_tx_ready` function, + /// so driver initialization must be done before this function is called + /// on IN endpoint descriptors. + pub fn endpoint_open(this: @This(), desc: *const descriptor.Endpoint) void { return this.vtable.endpoint_open(this.ptr, desc); } }; @@ -310,11 +313,12 @@ pub fn Controller(comptime config: Config) type { const descriptors = @field(driver_info, drv.name).descriptors; const fields = @typeInfo(@TypeOf(descriptors)).@"struct".fields; + // Driver's init may call `signal_rx_ready()`, so the OUT endpoint is configured first. inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .Out) continue; - _ = device.endpoint_open(&desc_ep); + device.endpoint_open(&desc_ep); } @field(this.drivers.?, drv.name) = .init(device, &descriptors); @@ -323,11 +327,7 @@ pub fn Controller(comptime config: Config) type { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .In) continue; - - if (device.endpoint_open(&desc_ep)) |buf| - this.on_tx_ready(desc_ep.endpoint.num, buf) catch { - std.log.warn("initial buffer unhandled on {any}", .{desc_ep.endpoint.num}); - }; + device.endpoint_open(&desc_ep); } } return true; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 1df777f9c..80f08d294 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -394,7 +394,7 @@ pub fn Usb(comptime config: Config) type { } /// See interface description. - pub fn endpoint_open(ptr: *anyopaque, desc: *const usb.descriptor.Endpoint) ?[]u8 { + pub fn endpoint_open(ptr: *anyopaque, desc: *const usb.descriptor.Endpoint) void { const this: *@This() = @alignCast(@ptrCast(ptr)); assert(@intFromEnum(desc.endpoint.num) < max_endpoints_count); @@ -404,7 +404,7 @@ pub fn Usb(comptime config: Config) type { .is_out = desc.endpoint.dir == .Out, }; - const start = if (desc.endpoint.num != .ep0) blk: { + if (desc.endpoint.num != .ep0) { const buf = this.dpram_allocator.alloc(1) catch std.debug.panic("USB controller out of memory.", .{}); var ep_ctrl = ep_hard.ep_ctrl().?; @@ -414,10 +414,16 @@ pub fn Usb(comptime config: Config) type { rmw.ENDPOINT_TYPE = @enumFromInt(desc.attributes.transfer_type.as_number()); rmw.BUFFER_ADDRESS = buf * dpram_buffer_len; ep_ctrl.write(rmw); - break :blk buf; - } else dpram_ep0buf_idx; - return &dpram_buffers[start]; + if (desc.endpoint.dir == .In) { + // The tx buffer is ready. + this.controller.on_tx_ready(desc.endpoint.num, &dpram_buffers[buf]) catch + std.log.warn( + "USB controller ignored inital tx buffer for ep{}", + .{@intFromEnum(desc.endpoint.num)}, + ); + } + } } }; } From 6d7c78e54159f5a90683caa93f577a8dd678b9fb Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 27 Aug 2025 10:24:13 +0200 Subject: [PATCH 24/33] replace std.mem.copyForwards with @memcpy --- core/src/core/usb/cdc.zig | 6 ++---- port/raspberrypi/rp2xxx/src/hal/usb.zig | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index a21ed148c..14e8baafd 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -153,8 +153,7 @@ pub const CdcClassDriver = struct { pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { if (this.rx_buf) |rx| { const len = @min(rx.len, dst.len); - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, dst, rx[0..len]); + @memcpy(dst[0..len], rx[0..len]); if (len < rx.len) this.rx_buf = rx[len..] else { @@ -169,8 +168,7 @@ pub const CdcClassDriver = struct { pub fn write(this: *@This(), src: []const u8) usize { if (this.tx_buf) |tx| { const len = @min(tx.len, src.len); - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, tx, src[0..len]); + @memcpy(tx[0..len], src[0..len]); this.tx_buf = tx[len..]; return len; } else return 0; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 80f08d294..dbe03f8b0 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -216,7 +216,7 @@ pub fn Usb(comptime config: Config) type { if (len == 0) this.state = .{ .no_buffer = null } else { - std.mem.copyForwards(u8, tx_buf, data[0..len]); + @memcpy(tx_buf[0..len], data[0..len]); this.state = .{ .sending = data[len..] }; } this.interface().submit_tx_buffer(.ep0, tx_buf.ptr + len); From 8a977b428eb2cc8554bbbee29b92281ce10a9022 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 28 Aug 2025 02:31:30 +0200 Subject: [PATCH 25/33] fix a bug around undefined only present in --release=fast --- core/src/core/usb.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 2afcc9be6..e79a403a5 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -308,7 +308,10 @@ pub fn Controller(comptime config: Config) type { if (setup.value != 1 or this.drivers != null) return true; - this.drivers = undefined; + // Driver data undefined, but present (not null). + const drv_udf: DriverData = undefined; + this.drivers = drv_udf; + inline for (config.drivers) |drv| { const descriptors = @field(driver_info, drv.name).descriptors; const fields = @typeInfo(@TypeOf(descriptors)).@"struct".fields; From 4d53dbf7f31de9f293f2564418edb7ff7f28c268 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 28 Aug 2025 02:36:38 +0200 Subject: [PATCH 26/33] change U16Le implementation --- core/src/core/usb.zig | 3 +-- core/src/core/usb/types.zig | 12 +++++------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index e79a403a5..06a1f6970 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -309,8 +309,7 @@ pub fn Controller(comptime config: Config) type { if (setup.value != 1 or this.drivers != null) return true; // Driver data undefined, but present (not null). - const drv_udf: DriverData = undefined; - this.drivers = drv_udf; + this.drivers = @as(DriverData, undefined); inline for (config.drivers) |drv| { const descriptors = @field(driver_info, drv.name).descriptors; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 26c887f39..b5835c41d 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -155,17 +155,15 @@ pub const SetupPacket = extern struct { }; pub const U16Le = extern struct { - lo: u8, - hi: u8, + value: [2]u8, pub fn from(val: u16) @This() { - return .{ - .lo = @truncate(val), - .hi = @intCast(val >> 8), - }; + var this: @This() = undefined; + std.mem.writeInt(u16, &this.value, val, .little); + return this; } pub fn into(this: @This()) u16 { - return (@as(u16, this.hi) << 8) | @as(u16, this.lo); + return std.mem.readInt(U16Le, &this.value, .little); } }; From d6713f8804074e15a033f273418b90c2d5bf5dcd Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 28 Aug 2025 11:44:06 +0200 Subject: [PATCH 27/33] revert back to using copyForwards --- core/src/core/usb/cdc.zig | 4 ++-- core/src/core/usb/hid.zig | 1 - port/raspberrypi/rp2xxx/src/hal/usb.zig | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 14e8baafd..ca1b8214f 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -153,7 +153,7 @@ pub const CdcClassDriver = struct { pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { if (this.rx_buf) |rx| { const len = @min(rx.len, dst.len); - @memcpy(dst[0..len], rx[0..len]); + std.mem.copyForwards(u8, dst[0..len], rx[0..len]); if (len < rx.len) this.rx_buf = rx[len..] else { @@ -168,7 +168,7 @@ pub const CdcClassDriver = struct { pub fn write(this: *@This(), src: []const u8) usize { if (this.tx_buf) |tx| { const len = @min(tx.len, src.len); - @memcpy(tx[0..len], src[0..len]); + std.mem.copyForwards(u8, tx[0..len], src[0..len]); this.tx_buf = tx[len..]; return len; } else return 0; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index f865ea6f8..640888eb3 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -47,7 +47,6 @@ const enumFromInt = std.meta.intToEnum; const usb = @import("../usb.zig"); const descriptor = usb.descriptor; const types = usb.types; -const bos = usb.utils.BosConfig; pub const InDescriptor = extern struct { desc1: descriptor.Interface, diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index dbe03f8b0..efdfc4b81 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -216,7 +216,7 @@ pub fn Usb(comptime config: Config) type { if (len == 0) this.state = .{ .no_buffer = null } else { - @memcpy(tx_buf[0..len], data[0..len]); + std.mem.copyForwards(u8, tx_buf[0..len], data[0..len]); this.state = .{ .sending = data[len..] }; } this.interface().submit_tx_buffer(.ep0, tx_buf.ptr + len); From 437eb6b15c2e4d053a155f935ae5f91d5820e5b5 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Thu, 11 Sep 2025 18:26:41 +0200 Subject: [PATCH 28/33] change endpoint interface --- core/src/core/usb.zig | 28 +-- core/src/core/usb/cdc.zig | 67 +++--- core/src/core/usb/hid.zig | 5 +- examples/raspberrypi/rp2xxx/build.zig | 92 ++++---- .../rp2xxx/src/rp2040_only/usb_hid.zig | 2 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 4 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 213 ++++++++---------- 7 files changed, 194 insertions(+), 217 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 06a1f6970..91c1cc992 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -19,9 +19,8 @@ const EpNum = types.Endpoint.Num; pub const DeviceInterface = struct { pub const Vtable = struct { - submit_tx_buffer: *const fn (ptr: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void, - signal_rx_ready: *const fn (ptr: *anyopaque, ep_out: EpNum, max_len: usize) void, - endpoint_open: *const fn (ptr: *anyopaque, desc: *const descriptor.Endpoint) void, + writev: *const fn (*anyopaque, EpNum, []const []const u8) usize, + listen: *const fn (*anyopaque, EpNum, u16) void, }; ptr: *anyopaque, vtable: *const Vtable, @@ -29,20 +28,13 @@ pub const DeviceInterface = struct { /// Called by drivers when a tx buffer is filled. /// Submitting an empty buffer signals an ACK. /// A buffer can only be submitted once. - pub fn submit_tx_buffer(this: @This(), ep_in: EpNum, buffer_end: [*]const u8) void { - this.vtable.submit_tx_buffer(this.ptr, ep_in, buffer_end); + pub fn writev(this: @This(), ep_in: EpNum, buffers: []const []const u8) usize { + return this.vtable.writev(this.ptr, ep_in, buffers); } /// Called by drivers to report readiness to receive up to `len` bytes. /// Must be called exactly once before each packet. - pub fn signal_rx_ready(this: @This(), ep_out: EpNum, max_len: usize) void { - this.vtable.signal_rx_ready(this.ptr, ep_out, max_len); - } - /// Opens an endpoint according to the descriptor. Note that if the endpoint - /// direction is IN this may call the controller's `on_tx_ready` function, - /// so driver initialization must be done before this function is called - /// on IN endpoint descriptors. - pub fn endpoint_open(this: @This(), desc: *const descriptor.Endpoint) void { - return this.vtable.endpoint_open(this.ptr, desc); + pub fn listen(this: @This(), ep_out: EpNum, len: u16) void { + this.vtable.listen(this.ptr, ep_out, len); } }; @@ -300,7 +292,7 @@ pub fn Controller(comptime config: Config) type { } /// Called whenever a SET_CONFIGURATION request is received. - pub fn set_configuration(this: *@This(), device: DeviceInterface, setup: *const types.SetupPacket) bool { + pub fn set_configuration(this: *@This(), device: anytype, setup: *const types.SetupPacket) bool { if (setup.value == 0) { this.deinit(); return true; @@ -320,16 +312,16 @@ pub fn Controller(comptime config: Config) type { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .Out) continue; - device.endpoint_open(&desc_ep); + device.open(desc_ep.endpoint.num, .Out, desc_ep.attributes.transfer_type); } - @field(this.drivers.?, drv.name) = .init(device, &descriptors); + @field(this.drivers.?, drv.name).init(device.interface(), &descriptors); inline for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .In) continue; - device.endpoint_open(&desc_ep); + device.open(desc_ep.endpoint.num, .In, desc_ep.attributes.transfer_type); } } return true; diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index ca1b8214f..a1898f96c 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -44,15 +44,18 @@ pub const Descriptor = extern struct { }; pub const CdcClassDriver = struct { - ep_in_notif: types.Endpoint.Num = .ep0, - ep_in: types.Endpoint.Num = .ep0, - ep_out: types.Endpoint.Num = .ep0, - awaiting_data: bool = false, + ep_in_notif: types.Endpoint.Num, + ep_in: types.Endpoint.Num, + ep_out: types.Endpoint.Num, + awaiting_data: bool, - line_coding: LineCoding = .init, + line_coding: LineCoding, - rx_buf: ?[]const u8 = null, - tx_buf: ?[]u8 = null, + rx_buf: [64]u8, + rx_seek: u16, + rx_end: u16, + tx_buf: ?[]u8, + tx_end: u16, pub fn info(first_interface: u8, string_ids: anytype, endpoints: anytype) usb.DriverInfo(@This(), Descriptor) { const endpoint_notifi_size = 8; @@ -128,17 +131,20 @@ pub const CdcClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn init(device: usb.DeviceInterface, desc: *const Descriptor) @This() { - device.signal_rx_ready(desc.ep_out.endpoint.num, std.math.maxInt(usize)); - return .{ + pub fn init(this: *@This(), device: usb.DeviceInterface, desc: *const Descriptor) void { + this.* = .{ .line_coding = .init, .awaiting_data = false, - .rx_buf = null, + .rx_buf = @splat(0), + .rx_seek = 0, + .rx_end = 0, .tx_buf = null, + .tx_end = 0, .ep_in_notif = desc.ep_notifi.endpoint.num, .ep_out = desc.ep_out.endpoint.num, .ep_in = desc.ep_in.endpoint.num, }; + device.listen(desc.ep_out.endpoint.num, this.rx_buf.len); } /// On bus reset, this function is called followed by init(). @@ -146,30 +152,26 @@ pub const CdcClassDriver = struct { /// How many bytes in rx buffer? pub fn available(this: *@This()) usize { - return if (this.rx_buf) |rx| rx.len else 0; + return this.rx_end - this.rx_seek; } /// Read data from rx buffer into dst. pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { - if (this.rx_buf) |rx| { - const len = @min(rx.len, dst.len); - std.mem.copyForwards(u8, dst[0..len], rx[0..len]); - if (len < rx.len) - this.rx_buf = rx[len..] - else { - device.signal_rx_ready(this.ep_out, std.math.maxInt(usize)); - this.rx_buf = null; - } - return len; - } else return 0; + const len = @min(dst.len, this.rx_end - this.rx_seek); + @memcpy(dst[0..len], this.rx_buf[this.rx_seek .. this.rx_seek + len]); + this.rx_seek += len; + if (len != 0 and this.rx_seek == this.rx_end) + device.listen(this.ep_out, this.rx_buf.len); + return len; } /// Write data from src into tx buffer. pub fn write(this: *@This(), src: []const u8) usize { if (this.tx_buf) |tx| { - const len = @min(tx.len, src.len); - std.mem.copyForwards(u8, tx[0..len], src[0..len]); - this.tx_buf = tx[len..]; + const avail = tx[this.tx_end..]; + const len = @min(avail.len, src.len); + std.mem.copyForwards(u8, avail[0..len], src[0..len]); + this.tx_end += @intCast(len); return len; } else return 0; } @@ -177,8 +179,10 @@ pub const CdcClassDriver = struct { /// Submit tx buffer to the device. pub fn flush(this: *@This(), device: usb.DeviceInterface) void { if (this.tx_buf) |tx| { - defer this.tx_buf = null; - device.submit_tx_buffer(this.ep_in, tx.ptr); + if (this.tx_end != device.writev(this.ep_in, &.{tx[0..this.tx_end]})) + std.debug.panic("not flushed", .{}); + this.tx_buf = null; + this.tx_end = 0; } } @@ -200,9 +204,14 @@ pub const CdcClassDriver = struct { pub fn on_tx_ready(this: *@This(), data: []u8) void { this.tx_buf = data; + this.tx_end = 0; } pub fn on_data_rx(this: *@This(), data: []const u8) void { - this.rx_buf = data; + if (this.rx_seek != this.rx_end) + std.log.err("RX buffer overwrite", .{}); + this.rx_seek = 0; + this.rx_end = @intCast(data.len); + std.mem.copyForwards(u8, this.rx_buf[0..data.len], data); } }; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 640888eb3..3b756f41f 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -138,7 +138,6 @@ pub const HidClassDriver = struct { .interface_handlers = &.{ .{ .itf = first_interface, .func = interface_setup }, }, - .endpoint_in_handlers = &.{}, .endpoint_out_handlers = &.{}, .descriptors = .create( first_interface, @@ -154,8 +153,8 @@ pub const HidClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn init(_: usb.DeviceInterface, desc: *const InOutDescriptor) @This() { - return .{ + pub fn init(this: *@This(), _: usb.DeviceInterface, desc: *const InOutDescriptor) void { + this.* = .{ .hid_descriptor = std.mem.asBytes(&desc.hid), .report_descriptor = undefined, .ep_in = desc.ep_in.endpoint.num, diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index b222eb7e9..76b54fc7a 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -16,60 +16,60 @@ pub fn build(b: *std.Build) void { const specific_examples: []const Example = &.{ // RaspberryPi Boards: - .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, - .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, - .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, - .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, - .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, - .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_interrupts", .file = "src/interrupts.zig" }, - .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_interrupts", .file = "src/interrupts.zig" }, - - .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, - .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, - - .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - - // WaveShare Boards: - .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, + // .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, + // .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, + // .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, + // .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, + // .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_interrupts", .file = "src/interrupts.zig" }, + // .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_interrupts", .file = "src/interrupts.zig" }, + + // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, + // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, + + // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + + // // WaveShare Boards: + // .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, // .{ .target = "board:waveshare/rp2040_eth", .name = "rp2040-eth" }, // .{ .target = "board:waveshare/rp2040_plus_4m", .name = "rp2040-plus-4m" }, // .{ .target = "board:waveshare/rp2040_plus_16m", .name = "rp2040-plus-16m" }, }; const chip_agnostic_examples: []const ChipAgnosticExample = &.{ - .{ .name = "adc", .file = "src/adc.zig" }, - .{ .name = "i2c-accel", .file = "src/i2c_accel.zig" }, - .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, - .{ .name = "i2c-hall-effect", .file = "src/i2c_hall_effect.zig" }, - .{ .name = "pwm", .file = "src/pwm.zig" }, - .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, - .{ .name = "uart-log", .file = "src/uart_log.zig" }, - .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, - .{ .name = "spi-master", .file = "src/spi_master.zig" }, - .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, - .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, - .{ .name = "squarewave", .file = "src/squarewave.zig" }, - .{ .name = "ws2812", .file = "src/ws2812.zig" }, - .{ .name = "blinky", .file = "src/blinky.zig" }, - .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, - .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, - .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, - .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, - .{ .name = "interrupts", .file = "src/interrupts.zig" }, - .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, - .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, + // .{ .name = "adc", .file = "src/adc.zig" }, + // .{ .name = "i2c-accel", .file = "src/i2c_accel.zig" }, + // .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, + // .{ .name = "i2c-hall-effect", .file = "src/i2c_hall_effect.zig" }, + // .{ .name = "pwm", .file = "src/pwm.zig" }, + // .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, + // .{ .name = "uart-log", .file = "src/uart_log.zig" }, + // .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, + // .{ .name = "spi-master", .file = "src/spi_master.zig" }, + // .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, + // .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, + // .{ .name = "squarewave", .file = "src/squarewave.zig" }, + // .{ .name = "ws2812", .file = "src/ws2812.zig" }, + // .{ .name = "blinky", .file = "src/blinky.zig" }, + // .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, + // .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, + // .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, + // .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, + // .{ .name = "interrupts", .file = "src/interrupts.zig" }, + // .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, + // .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "usb-cdc", .file = "src/usb_cdc.zig" }, - .{ .name = "dma", .file = "src/dma.zig" }, - .{ .name = "cyw43", .file = "src/cyw43.zig" }, - .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, + // .{ .name = "dma", .file = "src/dma.zig" }, + // .{ .name = "cyw43", .file = "src/cyw43.zig" }, + // .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, }; var available_examples: std.array_list.Managed(Example) = .init(b.allocator); diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig index ebaeb305f..964a5b589 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_hid.zig @@ -58,7 +58,7 @@ pub fn main() !void { while (true) { // You can now poll for USB events - usb.task(); + usb.poll(); const hid = if (usb.controller.drivers) |*drivers| &drivers.hid diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 0b44e4105..2b3b8d9c8 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -44,7 +44,7 @@ fn write_all(serial: *CdcDriver, data: []const u8) void { while (offset < data.len) { offset += serial.write(data[offset..]); serial.flush(usb.interface()); - usb.task(); + usb.poll(); } } @@ -66,7 +66,7 @@ pub fn main() !void { var i: u32 = 0; while (true) { // You can now poll for USB events - usb.task(); + usb.poll(); const usb_serial = if (usb.controller.drivers) |*drivers| &drivers.serial diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index efdfc4b81..547adc051 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -34,19 +34,19 @@ const BufCtrl = @TypeOf(USB_DPRAM.EP0_IN_BUFFER_CONTROL); const dpram_buffer_len = 64; // `@volatileCast` is sound because the USB hardware only modifies the buffers // after we transfer ownership by accesing a volatile register. -const dpram_buffers: *[4096 / dpram_buffer_len][dpram_buffer_len]u8 = @volatileCast(@ptrCast(USB_DPRAM)); +const dpram_buffers: *[4096 / dpram_buffer_len][dpram_buffer_len]u8 = @ptrCast(@volatileCast(USB_DPRAM)); // First 0x100 bytes are registers const dpram_ep0buf_idx = 0x100 / dpram_buffer_len; /// Keeps track of how many buffers have been allocated. pub const DpramAllocatorBump = struct { - top: u16, + top: u10, // First 2 buffers are for endpoint 0. const init: @This() = .{ .top = dpram_ep0buf_idx + 2 }; /// Allocate a new buffer in dpram, `len` is in units of 64 bytes. - fn alloc(this: *@This(), len: u16) error{OutOfBufferMemory}!u16 { + fn alloc(this: *@This(), len: u10) error{OutOfBufferMemory}!u10 { const next, const ovf = @addWithOverflow(len, this.top); if (ovf != 0 or next > dpram_buffers.len) return error.OutOfBufferMemory; @@ -63,58 +63,34 @@ pub const Config = struct { controller_config: usb.Config, }; -pub fn Usb(comptime config: Config) type { - return struct { - pub const interface_vtable: usb.DeviceInterface.Vtable = .{ - .signal_rx_ready = &signal_rx_ready, - .submit_tx_buffer = &submit_tx_buffer, - .endpoint_open = &endpoint_open, - }; - - const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; - - pub const HardwareEndpoint = packed struct(u5) { - const ep_ctrl_all: *volatile [2 * (max_endpoints_count - 1)]EpCtrl = - @ptrCast(&USB_DPRAM.EP1_IN_CONTROL); +const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; - const buf_ctrl_all: *volatile [2 * (max_endpoints_count)]BufCtrl = - @ptrCast(&USB_DPRAM.EP0_IN_BUFFER_CONTROL); - - is_out: bool, - num: EpNum, - - inline fn to_idx(this: @This()) u5 { - return @bitCast(this); - } - - fn in(num: EpNum) @This() { - return .{ .num = num, .is_out = false }; - } - - fn out(num: EpNum) @This() { - return .{ .num = num, .is_out = true }; - } - - fn ep_ctrl(this: @This()) ?*volatile EpCtrl { - const i, const ovf = @subWithOverflow(this.to_idx(), 2); - return if (ovf != 0) null else &ep_ctrl_all[i]; - } +fn get_ep_ctrl(ep_num: EpNum, dir: types.Dir) ?*volatile EpCtrl { + if (ep_num == .ep0) return null; + const idx = (@as(usize, @intFromEnum(ep_num)) << 1) | (@as(usize, @intFromEnum(dir)) ^ 1); + const ptr: *volatile [2 * RP2XXX_MAX_ENDPOINTS_COUNT]EpCtrl = @ptrCast(USB_DPRAM); + return &ptr[idx]; +} - fn buf_ctrl(this: @This()) *volatile BufCtrl { - return &buf_ctrl_all[this.to_idx()]; - } +fn get_buf_ctrl(ep_num: EpNum, dir: types.Dir) *volatile BufCtrl { + const idx = (@as(usize, @intFromEnum(ep_num)) << 1) | (@as(usize, @intFromEnum(dir)) ^ 1); + const ptr: *volatile [2 * RP2XXX_MAX_ENDPOINTS_COUNT]BufCtrl = @ptrCast(&USB_DPRAM.EP0_IN_BUFFER_CONTROL); + return &ptr[idx]; +} - fn buffer(this: @This()) []u8 { - const buf: u16 = if (this.ep_ctrl()) |reg| - @divExact(reg.read().BUFFER_ADDRESS, dpram_buffer_len) - else - dpram_ep0buf_idx; - return &dpram_buffers[buf]; - } +fn get_ep_buf(ep_num: EpNum, ep_dir: types.Dir) []u8 { + const idx: usize = if (get_ep_ctrl(ep_num, ep_dir)) |reg| + @shrExact(reg.read().BUFFER_ADDRESS, 6) + else + dpram_ep0buf_idx; + return &dpram_buffers[idx]; +} - fn len(this: @This()) u16 { - return this.buf_ctrl().read().LENGTH_0; - } +pub fn Usb(comptime config: Config) type { + return struct { + pub const interface_vtable: usb.DeviceInterface.Vtable = .{ + .writev = &writev, + .listen = &listen, }; const Controller = usb.Controller(blk: { @@ -152,13 +128,8 @@ pub fn Usb(comptime config: Config) type { // 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. - USB_DPRAM.SETUP_PACKET_LOW.write_raw(0); - USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); - - for (HardwareEndpoint.ep_ctrl_all) |*ep_ctrl| - ep_ctrl.write_raw(0); - for (HardwareEndpoint.buf_ctrl_all) |*buf_ctrl| - buf_ctrl.write_raw(0); + const dpram_data: *volatile [0x100 / @sizeOf(u32)]u32 = @ptrCast(USB_DPRAM); + @memset(dpram_data, 0); // Mux the controller to the onboard USB PHY. I was surprised that there are // alternatives to this, but, there are. @@ -219,7 +190,8 @@ pub fn Usb(comptime config: Config) type { std.mem.copyForwards(u8, tx_buf[0..len], data[0..len]); this.state = .{ .sending = data[len..] }; } - this.interface().submit_tx_buffer(.ep0, tx_buf.ptr + len); + if (len != this.interface().writev(.ep0, &.{tx_buf.ptr[0..len]})) + std.debug.panic("ep0 not flushed", .{}); } /// Called when a setup packet is received. @@ -246,7 +218,7 @@ pub fn Usb(comptime config: Config) type { this.ep0_send(tx_buf, usb.ACK); this.state = .{ .no_buffer = @intCast(setup.value) }; }, - .SetConfiguration => if (this.controller.set_configuration(this.interface(), &setup)) + .SetConfiguration => if (this.controller.set_configuration(this, &setup)) this.ep0_send(tx_buf, usb.ACK), .GetDescriptor => if (Controller.get_descriptor(&setup)) |desc| this.ep0_send(tx_buf, desc), @@ -263,9 +235,9 @@ pub fn Usb(comptime config: Config) type { } } - /// Usb task function meant to be executed in regular intervals after - /// initializing the device. - pub fn task(this: *@This()) void { + /// Polls the device for events. Not thread safe, this must be called + /// from the same thread that interacts with all the drivers. + pub fn poll(this: *@This()) void { const ints = USB.INTS.read(); const SieStatus = @TypeOf(USB.SIE_STATUS).underlying_type; @@ -280,58 +252,72 @@ pub fn Usb(comptime config: Config) type { else => {}, } if (ints.BUFF_STATUS != 0) { - const unhandled_initial = USB.BUFF_STATUS.raw; - var unhandled_pending = unhandled_initial; - - while (std.math.cast(u5, @ctz(unhandled_pending))) |idx| { - unhandled_pending &= unhandled_pending -% 1; // Clear lowest bit. - const ep: HardwareEndpoint = .{ - .num = @enumFromInt(idx >> 1), - .is_out = (idx & 1) == 1, - }; - const buf = ep.buffer(); + const unhandled = USB.BUFF_STATUS.raw; + + for (0..16) |ep_num_usize| { + const shamt: u5 = @intCast(2 * ep_num_usize); + if (unhandled & (@as(u32, 1) << shamt) == 0) continue; - const result = if (ep.num == .ep0) blk: { + const ep_num: EpNum = @enumFromInt(ep_num_usize); + // const buf_ctrl = get_buf_ctrl(ep_num, .In).read(); + const buf = get_ep_buf(ep_num, .In); + + const result = if (ep_num == .ep0) blk: { switch (this.state) { .sending => |data| { - if (ep.is_out) break :blk error.UsbPacketUnhandled; this.ep0_send(buf, data); if (data.len == 0) { - this.interface().signal_rx_ready(.ep0, 0); + this.interface().listen(.ep0, 0); this.state = .waiting_ack; } }, .no_buffer => |new_address| { - if (ep.is_out) break :blk error.UsbPacketUnhandled; // Finish the delayed SetAddress request, if there is one: if (new_address) |addr| USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); this.state = .{ .ready = buf }; }, + .ready => |_| break :blk error.UsbPacketUnhandled, + .waiting_ack => this.state = .{ .ready = buf }, + } + } else this.controller.on_tx_ready(ep_num, buf); + + result catch + std.log.warn("unhandled usb packet: ep{}in", .{ep_num}); + } + + for (0..16) |ep_num_usize| { + const shamt: u5 = @intCast(2 * ep_num_usize); + if (unhandled & (@as(u32, 2) << shamt) == 0) continue; + + const ep_num: EpNum = @enumFromInt(ep_num_usize); + const buf_ctrl = get_buf_ctrl(ep_num, .Out).read(); + const buf = get_ep_buf(ep_num, .Out); + + const result = if (ep_num == .ep0) blk: { + switch (this.state) { + .sending => |_| break :blk error.UsbPacketUnhandled, + .no_buffer => |_| break :blk error.UsbPacketUnhandled, .ready => |_| { - if (ep.is_out) - std.log.err("Got buffer twice!", .{}); + std.log.err("Got buffer twice!", .{}); break :blk error.UsbPacketUnhandled; }, .waiting_ack => { - if (ep.is_out) assert(ep.len() == 0); + assert(buf_ctrl.LENGTH_0 == 0); this.state = .{ .ready = buf }; }, } - } else if (ep.is_out) - this.controller.on_data_rx(ep.num, buf[0..ep.len()]) - else - this.controller.on_tx_ready(ep.num, buf); + } else this.controller.on_data_rx(ep_num, buf[0..buf_ctrl.LENGTH_0]); result catch { - std.log.warn("unhandled usb packet: ep{}{s}", .{ ep.num, if (ep.is_out) "out" else "in" }); - if (ep.is_out) - std.log.warn("{any}", .{buf[0..ep.len()]}); + std.log.warn("unhandled usb packet: ep{}out", .{ep_num}); + std.log.warn("{any}", .{buf[0..get_buf_ctrl(ep_num, .Out).read().LENGTH_0]}); }; } - USB.BUFF_STATUS.write_raw(unhandled_initial); + + USB.BUFF_STATUS.write_raw(unhandled); } if (ints.BUS_RESET != 0) { @@ -347,17 +333,15 @@ pub fn Usb(comptime config: Config) type { } /// See interface description. - pub fn submit_tx_buffer(_: *anyopaque, ep_in: EpNum, buffer_end: [*]const u8) void { - const ep_hard: HardwareEndpoint = .in(ep_in); - const buf = ep_hard.buffer(); + pub fn writev(_: *anyopaque, ep_in: EpNum, buffers: []const []const u8) usize { // It is technically possible to support longer buffers but this demo doesn't bother. - const len = buffer_end - buf.ptr; - if (len > default.transfer_size) - std.log.err("wrong buffer submitted", .{}); + const buf = get_ep_buf(ep_in, .In); + const len = @min(buf.len, buffers[0].len); + // std.mem.copyForwards(u8, buf[0..len], buffers[0][0..len]); // Write the buffer information to the buffer control register - const buf_ctrl = ep_hard.buf_ctrl(); + const buf_ctrl = get_buf_ctrl(ep_in, .In); var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 1; // We have put data in @@ -376,51 +360,44 @@ pub fn Usb(comptime config: Config) type { rmw.AVAILABLE_0 = 1; buf_ctrl.write(rmw); + return len; } /// See interface description. - pub fn signal_rx_ready(_: *anyopaque, ep_out: EpNum, len: usize) void { - const ep_hard: HardwareEndpoint = .out(ep_out); - + pub fn listen(_: *anyopaque, ep_out: EpNum, len: u16) void { // Configure the OUT: - const buf_ctrl = ep_hard.buf_ctrl(); + const buf_ctrl = get_buf_ctrl(ep_out, .Out); var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is empty rmw.AVAILABLE_0 = 1; // And ready to be filled - rmw.LENGTH_0 = @intCast(@min(len, default.transfer_size)); + rmw.LENGTH_0 = @min(len, default.transfer_size); buf_ctrl.write(rmw); } - /// See interface description. - pub fn endpoint_open(ptr: *anyopaque, desc: *const usb.descriptor.Endpoint) void { - const this: *@This() = @alignCast(@ptrCast(ptr)); - - assert(@intFromEnum(desc.endpoint.num) < max_endpoints_count); + pub fn open(this: *@This(), ep_num: EpNum, ep_dir: types.Dir, transfer_type: types.TransferType) void { + assert(@intFromEnum(ep_num) < max_endpoints_count); - const ep_hard: HardwareEndpoint = .{ - .num = desc.endpoint.num, - .is_out = desc.endpoint.dir == .Out, - }; - - if (desc.endpoint.num != .ep0) { - const buf = this.dpram_allocator.alloc(1) catch + if (ep_num != .ep0) { + const buf_idx = this.dpram_allocator.alloc(1) catch std.debug.panic("USB controller out of memory.", .{}); - var ep_ctrl = ep_hard.ep_ctrl().?; + + var ep_ctrl = get_ep_ctrl(ep_num, ep_dir).?; var rmw = ep_ctrl.read(); rmw.ENABLE = 1; rmw.INTERRUPT_PER_BUFF = 1; - rmw.ENDPOINT_TYPE = @enumFromInt(desc.attributes.transfer_type.as_number()); - rmw.BUFFER_ADDRESS = buf * dpram_buffer_len; + rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); + // The datasheet claims bits 0 throigh 5 are ignored, but it is not the case in practice. + rmw.BUFFER_ADDRESS = @shlExact(@as(u16, buf_idx), 6); ep_ctrl.write(rmw); - if (desc.endpoint.dir == .In) { + if (ep_dir == .In) { // The tx buffer is ready. - this.controller.on_tx_ready(desc.endpoint.num, &dpram_buffers[buf]) catch + this.controller.on_tx_ready(ep_num, &dpram_buffers[buf_idx]) catch std.log.warn( "USB controller ignored inital tx buffer for ep{}", - .{@intFromEnum(desc.endpoint.num)}, + .{@intFromEnum(ep_num)}, ); } } From 00a72e074fcbefe9f2bd72a60765a6062004abd4 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 12 Sep 2025 03:04:26 +0200 Subject: [PATCH 29/33] decouple endpoint polling from device polling --- core/src/core/usb.zig | 38 +---------- core/src/core/usb/cdc.zig | 66 ++----------------- core/src/core/usb/hid.zig | 1 - examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 3 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 71 ++++++++++++++------- 5 files changed, 56 insertions(+), 123 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 91c1cc992..9e15df1d9 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -20,7 +20,7 @@ const EpNum = types.Endpoint.Num; pub const DeviceInterface = struct { pub const Vtable = struct { writev: *const fn (*anyopaque, EpNum, []const []const u8) usize, - listen: *const fn (*anyopaque, EpNum, u16) void, + stream: *const fn (*anyopaque, EpNum, *std.Io.Writer, std.Io.Limit) usize, }; ptr: *anyopaque, vtable: *const Vtable, @@ -33,8 +33,8 @@ pub const DeviceInterface = struct { } /// Called by drivers to report readiness to receive up to `len` bytes. /// Must be called exactly once before each packet. - pub fn listen(this: @This(), ep_out: EpNum, len: u16) void { - this.vtable.listen(this.ptr, ep_out, len); + pub fn stream(this: @This(), ep_out: EpNum, w: *std.Io.Writer, limit: std.Io.Limit) usize { + return this.vtable.stream(this.ptr, ep_out, w, limit); } }; @@ -88,10 +88,6 @@ pub fn DriverInfo(T: type, Descriptors: type) type { descriptors: Descriptors, /// Handlers of setup packets for each used interface. interface_handlers: []const struct { itf: u8, func: fn (*T, *const types.SetupPacket) ?[]const u8 }, - /// Functions called an endpoint is ready to send. - endpoint_in_handlers: []const struct { ep_num: EpNum, func: fn (*T, []u8) void }, - /// Functions called when data is received on an endpoint. - endpoint_out_handlers: []const struct { ep_num: EpNum, func: fn (*T, []const u8) void }, }; } @@ -352,33 +348,5 @@ pub fn Controller(comptime config: Config) type { } return NAK; } - - /// Called whenever a tx buffer is ready. - pub fn on_tx_ready(this: *@This(), ep_num: EpNum, buf: []u8) PacketUnhandled!void { - if (this.drivers) |*drivers| { - inline for (config.drivers) |drv| { - const ptr = &@field(drivers, drv.name); - inline for (@field(driver_info, drv.name).endpoint_in_handlers) |handler| { - if (handler.ep_num == ep_num) - return handler.func(ptr, buf); - } - } - } - return error.UsbPacketUnhandled; - } - - /// Called whenever a packet is received from the host. - pub fn on_data_rx(this: *@This(), ep_num: EpNum, buf: []const u8) PacketUnhandled!void { - if (this.drivers) |*drivers| { - inline for (config.drivers) |drv| { - const ptr = &@field(drivers, drv.name); - inline for (@field(driver_info, drv.name).endpoint_out_handlers) |handler| { - if (handler.ep_num == ep_num) - return handler.func(ptr, buf); - } - } - } - return error.UsbPacketUnhandled; - } }; } diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index a1898f96c..49aedd2c9 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -51,12 +51,6 @@ pub const CdcClassDriver = struct { line_coding: LineCoding, - rx_buf: [64]u8, - rx_seek: u16, - rx_end: u16, - tx_buf: ?[]u8, - tx_end: u16, - pub fn info(first_interface: u8, string_ids: anytype, endpoints: anytype) usb.DriverInfo(@This(), Descriptor) { const endpoint_notifi_size = 8; const endpoint_size = 64; @@ -65,12 +59,6 @@ pub const CdcClassDriver = struct { .{ .itf = first_interface, .func = interface_setup }, .{ .itf = first_interface + 1, .func = interface_setup }, }, - .endpoint_in_handlers = &.{ - .{ .ep_num = endpoints.data, .func = on_tx_ready }, - }, - .endpoint_out_handlers = &.{ - .{ .ep_num = endpoints.data, .func = on_data_rx }, - }, .descriptors = .{ .itf_assoc = .{ .first_interface = first_interface, @@ -131,59 +119,28 @@ pub const CdcClassDriver = struct { } /// This function is called when the host chooses a configuration that contains this driver. - pub fn init(this: *@This(), device: usb.DeviceInterface, desc: *const Descriptor) void { + pub fn init(this: *@This(), _: usb.DeviceInterface, desc: *const Descriptor) void { this.* = .{ .line_coding = .init, .awaiting_data = false, - .rx_buf = @splat(0), - .rx_seek = 0, - .rx_end = 0, - .tx_buf = null, - .tx_end = 0, .ep_in_notif = desc.ep_notifi.endpoint.num, .ep_out = desc.ep_out.endpoint.num, .ep_in = desc.ep_in.endpoint.num, }; - device.listen(desc.ep_out.endpoint.num, this.rx_buf.len); } /// On bus reset, this function is called followed by init(). pub fn deinit(_: *@This()) void {} - /// How many bytes in rx buffer? - pub fn available(this: *@This()) usize { - return this.rx_end - this.rx_seek; - } - /// Read data from rx buffer into dst. pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { - const len = @min(dst.len, this.rx_end - this.rx_seek); - @memcpy(dst[0..len], this.rx_buf[this.rx_seek .. this.rx_seek + len]); - this.rx_seek += len; - if (len != 0 and this.rx_seek == this.rx_end) - device.listen(this.ep_out, this.rx_buf.len); - return len; + var w: std.Io.Writer = .fixed(dst); + return device.stream(this.ep_out, &w, .limited(dst.len)); } /// Write data from src into tx buffer. - pub fn write(this: *@This(), src: []const u8) usize { - if (this.tx_buf) |tx| { - const avail = tx[this.tx_end..]; - const len = @min(avail.len, src.len); - std.mem.copyForwards(u8, avail[0..len], src[0..len]); - this.tx_end += @intCast(len); - return len; - } else return 0; - } - - /// Submit tx buffer to the device. - pub fn flush(this: *@This(), device: usb.DeviceInterface) void { - if (this.tx_buf) |tx| { - if (this.tx_end != device.writev(this.ep_in, &.{tx[0..this.tx_end]})) - std.debug.panic("not flushed", .{}); - this.tx_buf = null; - this.tx_end = 0; - } + pub fn write(this: *@This(), src: []const u8, device: usb.DeviceInterface) usize { + return device.writev(this.ep_in, &.{src}); } /// Callback for setup packets. @@ -201,17 +158,4 @@ pub const CdcClassDriver = struct { .SendBreak => usb.ACK, } else |_| usb.ACK; } - - pub fn on_tx_ready(this: *@This(), data: []u8) void { - this.tx_buf = data; - this.tx_end = 0; - } - - pub fn on_data_rx(this: *@This(), data: []const u8) void { - if (this.rx_seek != this.rx_end) - std.log.err("RX buffer overwrite", .{}); - this.rx_seek = 0; - this.rx_end = @intCast(data.len); - std.mem.copyForwards(u8, this.rx_buf[0..data.len], data); - } }; diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index 3b756f41f..dff8a2945 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -138,7 +138,6 @@ pub const HidClassDriver = struct { .interface_handlers = &.{ .{ .itf = first_interface, .func = interface_setup }, }, - .endpoint_out_handlers = &.{}, .descriptors = .create( first_interface, string_ids.name, diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 2b3b8d9c8..871cd5dda 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -42,8 +42,7 @@ var usb: Usb = undefined; fn write_all(serial: *CdcDriver, data: []const u8) void { var offset: usize = 0; while (offset < data.len) { - offset += serial.write(data[offset..]); - serial.flush(usb.interface()); + offset += serial.write(data[offset..], usb.interface()); usb.poll(); } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 547adc051..9d475c804 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -90,7 +90,7 @@ pub fn Usb(comptime config: Config) type { return struct { pub const interface_vtable: usb.DeviceInterface.Vtable = .{ .writev = &writev, - .listen = &listen, + .stream = &stream, }; const Controller = usb.Controller(blk: { @@ -190,8 +190,7 @@ pub fn Usb(comptime config: Config) type { std.mem.copyForwards(u8, tx_buf[0..len], data[0..len]); this.state = .{ .sending = data[len..] }; } - if (len != this.interface().writev(.ep0, &.{tx_buf.ptr[0..len]})) - std.debug.panic("ep0 not flushed", .{}); + submit_buffer_in(.ep0, @intCast(len)); } /// Called when a setup packet is received. @@ -268,7 +267,7 @@ pub fn Usb(comptime config: Config) type { this.ep0_send(buf, data); if (data.len == 0) { - this.interface().listen(.ep0, 0); + listen(.ep0, 0); this.state = .waiting_ack; } }, @@ -282,7 +281,7 @@ pub fn Usb(comptime config: Config) type { .ready => |_| break :blk error.UsbPacketUnhandled, .waiting_ack => this.state = .{ .ready = buf }, } - } else this.controller.on_tx_ready(ep_num, buf); + } else continue; result catch std.log.warn("unhandled usb packet: ep{}in", .{ep_num}); @@ -309,7 +308,7 @@ pub fn Usb(comptime config: Config) type { this.state = .{ .ready = buf }; }, } - } else this.controller.on_data_rx(ep_num, buf[0..buf_ctrl.LENGTH_0]); + } else continue; result catch { std.log.warn("unhandled usb packet: ep{}out", .{ep_num}); @@ -332,14 +331,7 @@ pub fn Usb(comptime config: Config) type { } } - /// See interface description. - pub fn writev(_: *anyopaque, ep_in: EpNum, buffers: []const []const u8) usize { - - // It is technically possible to support longer buffers but this demo doesn't bother. - const buf = get_ep_buf(ep_in, .In); - const len = @min(buf.len, buffers[0].len); - // std.mem.copyForwards(u8, buf[0..len], buffers[0][0..len]); - + fn submit_buffer_in(ep_in: EpNum, len: u16) void { // Write the buffer information to the buffer control register const buf_ctrl = get_buf_ctrl(ep_in, .In); var rmw = buf_ctrl.read(); @@ -360,11 +352,50 @@ pub fn Usb(comptime config: Config) type { rmw.AVAILABLE_0 = 1; buf_ctrl.write(rmw); + } + + /// See interface description. + pub fn writev(_: *anyopaque, ep_in: EpNum, buffers: []const []const u8) usize { + const buf_ctrl = get_buf_ctrl(ep_in, .In); + const rmw = buf_ctrl.read(); + if (rmw.FULL_0 != 0) return 0; + + // It is technically possible to support longer buffers but this demo doesn't bother. + const buf = get_ep_buf(ep_in, .In); + const len = @min(buf.len, buffers[0].len); + std.mem.copyForwards(u8, buf[0..len], buffers[0][0..len]); + + submit_buffer_in(ep_in, @intCast(len)); + + return len; + } + + pub fn stream(_: *anyopaque, ep_out: EpNum, w: *std.Io.Writer, limit: std.Io.Limit) usize { + const dst = limit.slice(w.unusedCapacitySlice()); + if (dst.len == 0) return 0; + + const buf_ctrl = get_buf_ctrl(ep_out, .Out); + var rmw = buf_ctrl.read(); + if (rmw.AVAILABLE_0 == 1) return 0; + + const buffer = get_ep_buf(ep_out, .Out); + const src = buffer[rmw.LENGTH_1..rmw.LENGTH_0]; + + const len = @min(src.len, dst.len); + std.mem.copyForwards(u8, dst[0..len], src[0..len]); + + if (src.len < dst.len) + listen(ep_out, @intCast(dst.len - src.len)) + else { + rmw.FULL_0 = 0; + rmw.LENGTH_1 += @intCast(len); + buf_ctrl.write(rmw); + } return len; } /// See interface description. - pub fn listen(_: *anyopaque, ep_out: EpNum, len: u16) void { + pub fn listen(ep_out: EpNum, len: u16) void { // Configure the OUT: const buf_ctrl = get_buf_ctrl(ep_out, .Out); var rmw = buf_ctrl.read(); @@ -372,6 +403,7 @@ pub fn Usb(comptime config: Config) type { rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is empty rmw.AVAILABLE_0 = 1; // And ready to be filled + rmw.LENGTH_1 = 0; rmw.LENGTH_0 = @min(len, default.transfer_size); buf_ctrl.write(rmw); } @@ -391,15 +423,6 @@ pub fn Usb(comptime config: Config) type { // The datasheet claims bits 0 throigh 5 are ignored, but it is not the case in practice. rmw.BUFFER_ADDRESS = @shlExact(@as(u16, buf_idx), 6); ep_ctrl.write(rmw); - - if (ep_dir == .In) { - // The tx buffer is ready. - this.controller.on_tx_ready(ep_num, &dpram_buffers[buf_idx]) catch - std.log.warn( - "USB controller ignored inital tx buffer for ep{}", - .{@intFromEnum(ep_num)}, - ); - } } } }; From 268864934e62ed586146fb91e7675a7dd16440e6 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 12 Sep 2025 16:01:02 +0200 Subject: [PATCH 30/33] change dpram access --- core/src/core/usb.zig | 4 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 155 +++++++++++++----------- 2 files changed, 83 insertions(+), 76 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 9e15df1d9..1f07e1c7e 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -308,7 +308,7 @@ pub fn Controller(comptime config: Config) type { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .Out) continue; - device.open(desc_ep.endpoint.num, .Out, desc_ep.attributes.transfer_type); + device.open_out(desc_ep.endpoint.num, desc_ep.attributes.transfer_type); } @field(this.drivers.?, drv.name).init(device.interface(), &descriptors); @@ -317,7 +317,7 @@ pub fn Controller(comptime config: Config) type { if (fld.type != descriptor.Endpoint) continue; const desc_ep = @field(descriptors, fld.name); if (desc_ep.endpoint.dir != .In) continue; - device.open(desc_ep.endpoint.num, .In, desc_ep.attributes.transfer_type); + device.open_in(desc_ep.endpoint.num, desc_ep.attributes.transfer_type); } } return true; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 9d475c804..c822ac2f3 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -31,31 +31,6 @@ const EpNum = usb.types.Endpoint.Num; const EpCtrl = @TypeOf(USB_DPRAM.EP1_IN_CONTROL); const BufCtrl = @TypeOf(USB_DPRAM.EP0_IN_BUFFER_CONTROL); -const dpram_buffer_len = 64; -// `@volatileCast` is sound because the USB hardware only modifies the buffers -// after we transfer ownership by accesing a volatile register. -const dpram_buffers: *[4096 / dpram_buffer_len][dpram_buffer_len]u8 = @ptrCast(@volatileCast(USB_DPRAM)); -// First 0x100 bytes are registers -const dpram_ep0buf_idx = 0x100 / dpram_buffer_len; - -/// Keeps track of how many buffers have been allocated. -pub const DpramAllocatorBump = struct { - top: u10, - - // First 2 buffers are for endpoint 0. - const init: @This() = .{ .top = dpram_ep0buf_idx + 2 }; - - /// Allocate a new buffer in dpram, `len` is in units of 64 bytes. - fn alloc(this: *@This(), len: u10) error{OutOfBufferMemory}!u10 { - const next, const ovf = @addWithOverflow(len, this.top); - if (ovf != 0 or next > dpram_buffers.len) - return error.OutOfBufferMemory; - - defer this.top = next; - return this.top; - } -}; - pub const Config = struct { /// How many nops to insert for synchronization with the USB hardware. synchronization_nops: comptime_int = 3, @@ -63,29 +38,74 @@ pub const Config = struct { controller_config: usb.Config, }; -const max_endpoints_count = RP2XXX_MAX_ENDPOINTS_COUNT; +const DualPortRam = extern struct { + const buffer_len_mult = 64; + const total_size = 4096; + + setup: types.SetupPacket, + ep_ctrl_raw: [15][2]EpCtrl, + buf_ctrl_raw: [16][2]BufCtrl, + buffer0: [buffer_len_mult]u8, + buffer1: [buffer_len_mult]u8, + + var alloc_top: u16 = @sizeOf(@This()); + + fn ep_open(this: *@This(), ep_num: EpNum, ep_dir: types.Dir, transfer_type: types.TransferType) u16 { + if (ep_num == .ep0) + std.debug.panic("Endpoint 0 should not be opened.", .{}); + if (transfer_type == .Control) + std.debug.panic("Only endpoint 0 can be a control endpoint.", .{}); + if (transfer_type == .Isochronous) + std.debug.panic("Isochronous endpoints are not implemented", .{}); + + const buf_idx = alloc_top; + alloc_top += buffer_len_mult; + if (alloc_top > total_size) + std.debug.panic("USB controller out of memory.", .{}); + + var reg = this.ep_ctrl(ep_num, ep_dir).?; + var rmw = reg.read(); + rmw.ENABLE = 1; + rmw.INTERRUPT_PER_BUFF = 1; + rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); + // The datasheet claims bits 0 throigh 5 are ignored, but it is not the case in practice. + rmw.BUFFER_ADDRESS = buf_idx; + reg.write(rmw); + return buf_idx; + } -fn get_ep_ctrl(ep_num: EpNum, dir: types.Dir) ?*volatile EpCtrl { - if (ep_num == .ep0) return null; - const idx = (@as(usize, @intFromEnum(ep_num)) << 1) | (@as(usize, @intFromEnum(dir)) ^ 1); - const ptr: *volatile [2 * RP2XXX_MAX_ENDPOINTS_COUNT]EpCtrl = @ptrCast(USB_DPRAM); - return &ptr[idx]; -} + fn ep_ctrl(this: *@This(), num: EpNum, dir: types.Dir) ?*volatile EpCtrl { + if (std.math.sub(u4, @intFromEnum(num), 1)) |idx| + return &this.ep_ctrl_raw[idx][@intFromBool(dir == .Out)] + else |_| + return null; + } -fn get_buf_ctrl(ep_num: EpNum, dir: types.Dir) *volatile BufCtrl { - const idx = (@as(usize, @intFromEnum(ep_num)) << 1) | (@as(usize, @intFromEnum(dir)) ^ 1); - const ptr: *volatile [2 * RP2XXX_MAX_ENDPOINTS_COUNT]BufCtrl = @ptrCast(&USB_DPRAM.EP0_IN_BUFFER_CONTROL); - return &ptr[idx]; -} + fn buf_ctrl(this: *@This(), num: EpNum, dir: types.Dir) *volatile BufCtrl { + return &this.buf_ctrl_raw[@intFromEnum(num)][@intFromBool(dir == .Out)]; + } + + // TODO: Double buffered mode? + fn buffer(this: *@This(), num: EpNum, dir: types.Dir) []u8 { + if (this.ep_ctrl(num, dir)) |ep| { + const addr = ep.read().BUFFER_ADDRESS; + const mem: *[total_size]u8 = @ptrCast(this); + // TODO: Isochronous endpoints can have other sizes. + const size = 64; + return mem[addr .. addr + size]; + } else return &this.buffer0; + } +}; -fn get_ep_buf(ep_num: EpNum, ep_dir: types.Dir) []u8 { - const idx: usize = if (get_ep_ctrl(ep_num, ep_dir)) |reg| - @shrExact(reg.read().BUFFER_ADDRESS, 6) - else - dpram_ep0buf_idx; - return &dpram_buffers[idx]; +comptime { + assert(@offsetOf(DualPortRam, "buf_ctrl_raw") == 0x80); + assert(@offsetOf(DualPortRam, "buffer0") == 0x100); } +// `@volatileCast` is sound because the USB hardware only modifies the buffers +// after we transfer ownership by accesing a volatile register. +const dpram: *DualPortRam = @ptrCast(@volatileCast(USB_DPRAM)); + pub fn Usb(comptime config: Config) type { return struct { pub const interface_vtable: usb.DeviceInterface.Vtable = .{ @@ -111,7 +131,6 @@ pub fn Usb(comptime config: Config) type { }; state: State, - dpram_allocator: DpramAllocatorBump, controller: Controller, pub fn interface(this: *@This()) usb.DeviceInterface { @@ -175,8 +194,7 @@ pub fn Usb(comptime config: Config) type { USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); return .{ - .state = .{ .ready = &dpram_buffers[dpram_ep0buf_idx] }, - .dpram_allocator = .init, + .state = .{ .ready = &dpram.buffer0 }, .controller = .init, }; } @@ -258,8 +276,8 @@ pub fn Usb(comptime config: Config) type { if (unhandled & (@as(u32, 1) << shamt) == 0) continue; const ep_num: EpNum = @enumFromInt(ep_num_usize); - // const buf_ctrl = get_buf_ctrl(ep_num, .In).read(); - const buf = get_ep_buf(ep_num, .In); + // const buf_ctrl = dpram.buf_ctrl(ep_num, .In).read(); + const buf = dpram.buffer(ep_num, .In); const result = if (ep_num == .ep0) blk: { switch (this.state) { @@ -292,8 +310,8 @@ pub fn Usb(comptime config: Config) type { if (unhandled & (@as(u32, 2) << shamt) == 0) continue; const ep_num: EpNum = @enumFromInt(ep_num_usize); - const buf_ctrl = get_buf_ctrl(ep_num, .Out).read(); - const buf = get_ep_buf(ep_num, .Out); + const buf_ctrl = dpram.buf_ctrl(ep_num, .Out).read(); + const buf = dpram.buffer(ep_num, .Out); const result = if (ep_num == .ep0) blk: { switch (this.state) { @@ -312,7 +330,7 @@ pub fn Usb(comptime config: Config) type { result catch { std.log.warn("unhandled usb packet: ep{}out", .{ep_num}); - std.log.warn("{any}", .{buf[0..get_buf_ctrl(ep_num, .Out).read().LENGTH_0]}); + std.log.warn("{any}", .{buf[0..dpram.buf_ctrl(ep_num, .Out).read().LENGTH_0]}); }; } @@ -322,7 +340,6 @@ pub fn Usb(comptime config: Config) type { if (ints.BUS_RESET != 0) { this.controller.deinit(); this.controller = .init; - this.dpram_allocator = .init; var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.BUS_RESET = 1; @@ -333,7 +350,7 @@ pub fn Usb(comptime config: Config) type { fn submit_buffer_in(ep_in: EpNum, len: u16) void { // Write the buffer information to the buffer control register - const buf_ctrl = get_buf_ctrl(ep_in, .In); + const buf_ctrl = dpram.buf_ctrl(ep_in, .In); var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 1; // We have put data in @@ -356,12 +373,12 @@ pub fn Usb(comptime config: Config) type { /// See interface description. pub fn writev(_: *anyopaque, ep_in: EpNum, buffers: []const []const u8) usize { - const buf_ctrl = get_buf_ctrl(ep_in, .In); + const buf_ctrl = dpram.buf_ctrl(ep_in, .In); const rmw = buf_ctrl.read(); if (rmw.FULL_0 != 0) return 0; // It is technically possible to support longer buffers but this demo doesn't bother. - const buf = get_ep_buf(ep_in, .In); + const buf = dpram.buffer(ep_in, .In); const len = @min(buf.len, buffers[0].len); std.mem.copyForwards(u8, buf[0..len], buffers[0][0..len]); @@ -374,11 +391,11 @@ pub fn Usb(comptime config: Config) type { const dst = limit.slice(w.unusedCapacitySlice()); if (dst.len == 0) return 0; - const buf_ctrl = get_buf_ctrl(ep_out, .Out); + const buf_ctrl = dpram.buf_ctrl(ep_out, .Out); var rmw = buf_ctrl.read(); if (rmw.AVAILABLE_0 == 1) return 0; - const buffer = get_ep_buf(ep_out, .Out); + const buffer = dpram.buffer(ep_out, .Out); const src = buffer[rmw.LENGTH_1..rmw.LENGTH_0]; const len = @min(src.len, dst.len); @@ -397,7 +414,7 @@ pub fn Usb(comptime config: Config) type { /// See interface description. pub fn listen(ep_out: EpNum, len: u16) void { // Configure the OUT: - const buf_ctrl = get_buf_ctrl(ep_out, .Out); + const buf_ctrl = dpram.buf_ctrl(ep_out, .Out); var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 @@ -408,22 +425,12 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } - pub fn open(this: *@This(), ep_num: EpNum, ep_dir: types.Dir, transfer_type: types.TransferType) void { - assert(@intFromEnum(ep_num) < max_endpoints_count); - - if (ep_num != .ep0) { - const buf_idx = this.dpram_allocator.alloc(1) catch - std.debug.panic("USB controller out of memory.", .{}); - - var ep_ctrl = get_ep_ctrl(ep_num, ep_dir).?; - var rmw = ep_ctrl.read(); - rmw.ENABLE = 1; - rmw.INTERRUPT_PER_BUFF = 1; - rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); - // The datasheet claims bits 0 throigh 5 are ignored, but it is not the case in practice. - rmw.BUFFER_ADDRESS = @shlExact(@as(u16, buf_idx), 6); - ep_ctrl.write(rmw); - } + pub fn open_in(_: *@This(), ep_num: EpNum, transfer_type: types.TransferType) void { + _ = dpram.ep_open(ep_num, .In, transfer_type); + } + + pub fn open_out(_: *@This(), ep_num: EpNum, transfer_type: types.TransferType) void { + _ = dpram.ep_open(ep_num, .Out, transfer_type); } }; } From 965bb960f7feb2f439f8a05999913bc1a1f22e15 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Fri, 12 Sep 2025 17:37:17 +0200 Subject: [PATCH 31/33] minor cleanup --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 56 ++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index c822ac2f3..d00f82ba9 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -7,13 +7,9 @@ const assert = std.debug.assert; const enumFromInt = std.meta.intToEnum; const microzig = @import("microzig"); -const chip = microzig.hal.compatibility.chip; -const USB_DPRAM = microzig.chip.peripherals.USB_DPRAM; const USB = microzig.chip.peripherals.USB; const usb = microzig.core.usb; -const types = usb.types; - -pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; +const EpNum = usb.types.Endpoint.Num; pub const default = struct { pub const strings: usb.Config.DeviceStrings = .{ @@ -27,10 +23,6 @@ pub const default = struct { pub const transfer_size = 64; }; -const EpNum = usb.types.Endpoint.Num; -const EpCtrl = @TypeOf(USB_DPRAM.EP1_IN_CONTROL); -const BufCtrl = @TypeOf(USB_DPRAM.EP0_IN_BUFFER_CONTROL); - pub const Config = struct { /// How many nops to insert for synchronization with the USB hardware. synchronization_nops: comptime_int = 3, @@ -42,7 +34,11 @@ const DualPortRam = extern struct { const buffer_len_mult = 64; const total_size = 4096; - setup: types.SetupPacket, + const peri = microzig.chip.peripherals.USB_DPRAM; + const EpCtrl = @TypeOf(peri.EP1_IN_CONTROL); + const BufCtrl = @TypeOf(peri.EP0_IN_BUFFER_CONTROL); + + setup: usb.types.SetupPacket, ep_ctrl_raw: [15][2]EpCtrl, buf_ctrl_raw: [16][2]BufCtrl, buffer0: [buffer_len_mult]u8, @@ -50,13 +46,9 @@ const DualPortRam = extern struct { var alloc_top: u16 = @sizeOf(@This()); - fn ep_open(this: *@This(), ep_num: EpNum, ep_dir: types.Dir, transfer_type: types.TransferType) u16 { + fn ep_open(this: *@This(), ep_num: EpNum, ep_dir: usb.types.Dir, transfer_type: usb.types.TransferType) u16 { if (ep_num == .ep0) std.debug.panic("Endpoint 0 should not be opened.", .{}); - if (transfer_type == .Control) - std.debug.panic("Only endpoint 0 can be a control endpoint.", .{}); - if (transfer_type == .Isochronous) - std.debug.panic("Isochronous endpoints are not implemented", .{}); const buf_idx = alloc_top; alloc_top += buffer_len_mult; @@ -67,26 +59,31 @@ const DualPortRam = extern struct { var rmw = reg.read(); rmw.ENABLE = 1; rmw.INTERRUPT_PER_BUFF = 1; - rmw.ENDPOINT_TYPE = @enumFromInt(transfer_type.as_number()); + rmw.ENDPOINT_TYPE = switch (transfer_type) { + .Control => std.debug.panic("Only endpoint 0 can be a control endpoint.", .{}), + .Isochronous => std.debug.panic("Isochronous endpoints are not implemented", .{}), + .Bulk => .bulk, + .Interrupt => .interrupt, + }; // The datasheet claims bits 0 throigh 5 are ignored, but it is not the case in practice. rmw.BUFFER_ADDRESS = buf_idx; reg.write(rmw); return buf_idx; } - fn ep_ctrl(this: *@This(), num: EpNum, dir: types.Dir) ?*volatile EpCtrl { + fn ep_ctrl(this: *@This(), num: EpNum, dir: usb.types.Dir) ?*volatile EpCtrl { if (std.math.sub(u4, @intFromEnum(num), 1)) |idx| return &this.ep_ctrl_raw[idx][@intFromBool(dir == .Out)] else |_| return null; } - fn buf_ctrl(this: *@This(), num: EpNum, dir: types.Dir) *volatile BufCtrl { + fn buf_ctrl(this: *@This(), num: EpNum, dir: usb.types.Dir) *volatile BufCtrl { return &this.buf_ctrl_raw[@intFromEnum(num)][@intFromBool(dir == .Out)]; } // TODO: Double buffered mode? - fn buffer(this: *@This(), num: EpNum, dir: types.Dir) []u8 { + fn buffer(this: *@This(), num: EpNum, dir: usb.types.Dir) []u8 { if (this.ep_ctrl(num, dir)) |ep| { const addr = ep.read().BUFFER_ADDRESS; const mem: *[total_size]u8 = @ptrCast(this); @@ -98,13 +95,17 @@ const DualPortRam = extern struct { }; comptime { + // Sanity check that offsets match the datasheet. + assert(@offsetOf(DualPortRam, "ep_ctrl_raw") == 0x8); assert(@offsetOf(DualPortRam, "buf_ctrl_raw") == 0x80); assert(@offsetOf(DualPortRam, "buffer0") == 0x100); + assert(@offsetOf(DualPortRam, "buffer1") == 0x140); + assert(@sizeOf(DualPortRam) == 0x180); } // `@volatileCast` is sound because the USB hardware only modifies the buffers // after we transfer ownership by accesing a volatile register. -const dpram: *DualPortRam = @ptrCast(@volatileCast(USB_DPRAM)); +const dpram: *DualPortRam = @ptrCast(@volatileCast(DualPortRam.peri)); pub fn Usb(comptime config: Config) type { return struct { @@ -142,12 +143,14 @@ pub fn Usb(comptime config: Config) type { /// Initialize USB hardware and request enumertation from USB host. pub fn init() @This() { + const chip = microzig.hal.compatibility.chip; + if (chip == .RP2350) USB.MAIN_CTRL.modify(.{ .PHY_ISO = 0 }); // Clear the control portion of DPRAM. This may not be necessary -- the // datasheet is ambiguous -- but the C examples do it, and so do we. - const dpram_data: *volatile [0x100 / @sizeOf(u32)]u32 = @ptrCast(USB_DPRAM); + const dpram_data: *volatile [0x100 / @sizeOf(u32)]u32 = @ptrCast(dpram); @memset(dpram_data, 0); // Mux the controller to the onboard USB PHY. I was surprised that there are @@ -215,14 +218,11 @@ pub fn Usb(comptime config: Config) type { fn process_setup(this: *@This(), tx_buf: []u8) void { // Copy the setup packet out of its dedicated buffer at the base of // USB SRAM. The PAC models this buffer as two 32-bit registers. - const setup: usb.types.SetupPacket = @bitCast([2]u32{ - USB_DPRAM.SETUP_PACKET_LOW.raw, - USB_DPRAM.SETUP_PACKET_HIGH.raw, - }); + const setup = dpram.setup; // Reset PID to 1 for EP0 IN. Every DATA packet we send in response // to an IN on EP0 needs to use PID DATA1. - USB_DPRAM.EP0_IN_BUFFER_CONTROL.modify(.{ .PID_0 = 0 }); + dpram.buf_ctrl(.ep0, .In).modify(.{ .PID_0 = 0 }); switch (setup.request_type.recipient) { .Device => blk: { @@ -425,11 +425,11 @@ pub fn Usb(comptime config: Config) type { buf_ctrl.write(rmw); } - pub fn open_in(_: *@This(), ep_num: EpNum, transfer_type: types.TransferType) void { + pub fn open_in(_: *@This(), ep_num: EpNum, transfer_type: usb.types.TransferType) void { _ = dpram.ep_open(ep_num, .In, transfer_type); } - pub fn open_out(_: *@This(), ep_num: EpNum, transfer_type: types.TransferType) void { + pub fn open_out(_: *@This(), ep_num: EpNum, transfer_type: usb.types.TransferType) void { _ = dpram.ep_open(ep_num, .Out, transfer_type); } }; From 2e94a5c90a2800a86d74417c8a6f6598a98599e5 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 13 Sep 2025 05:24:35 +0200 Subject: [PATCH 32/33] change usb device interface... again --- core/src/core/usb.zig | 75 +++++-- core/src/core/usb/cdc.zig | 10 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 261 +++++++++++------------- 3 files changed, 188 insertions(+), 158 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 1f07e1c7e..b88be0f80 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -18,23 +18,72 @@ test "tests" { const EpNum = types.Endpoint.Num; pub const DeviceInterface = struct { - pub const Vtable = struct { - writev: *const fn (*anyopaque, EpNum, []const []const u8) usize, - stream: *const fn (*anyopaque, EpNum, *std.Io.Writer, std.Io.Limit) usize, + pub const Options = union(types.Dir) { + Out: struct { + data: []u8, + listen: ?u16, + }, + In: struct { + data: []const u8, + flush: bool, + }, + + pub fn len(this: @This()) usize { + return switch (this) { + .Out => |x| x.data.len, + .In => |x| x.data.len, + }; + } }; + ptr: *anyopaque, - vtable: *const Vtable, + transfer: *const fn (ptr: *anyopaque, opts: Options, ep_num: EpNum, df: bool) ?u16, + + /// Sends all bytes from `data` to the host in a single packet. Returns true if data was sent. + /// If `data` is empty, an ACK is sent. + /// If the transmit buffer was not empty, the old data is flushed and `false` is returned. + /// Panics if `data` is too long to be sent through this endpoint. + pub fn write_exact(this: @This(), data: []const u8, ep_in: EpNum) bool { + if (this.transfer(this.ptr, .{ .In = .{ + .data = data, + .flush = true, + } }, ep_in, true)) |len| { + assert(len == data.len); + return true; + } else return false; + } + + /// Writes bytes from `data` into the transmit buffer. + /// `data` must contain at least one buffer, which may be empty. + /// If this fills the transmit buffer or `flush` is true, the transmit buffer is submitted. + /// Returns the number of bytes written, or `null` if no space was available. + pub fn write_buffered(this: @This(), data: []const u8, ep_in: EpNum, flush: bool) ?u16 { + return this.transfer(this.ptr, .{ .In = .{ + .data = data, + .flush = flush, + } }, ep_in, false); + } - /// Called by drivers when a tx buffer is filled. - /// Submitting an empty buffer signals an ACK. - /// A buffer can only be submitted once. - pub fn writev(this: @This(), ep_in: EpNum, buffers: []const []const u8) usize { - return this.vtable.writev(this.ptr, ep_in, buffers); + /// Reads a full received packet into `data`, emptying the receive buffer. + /// Returns the number of bytes read, or `null` if no data was available. + /// If `listen` is not null, up to `listen` more bytes are requested from the host. + /// Panics if there was residual data in the receive buffer or the packet does not fit in `data`. + pub fn read_exact(this: @This(), data: []u8, ep_out: EpNum, listen: ?u16) ?u16 { + return this.transfer(this.ptr, .{ .Out = .{ + .data = data, + .listen = listen, + } }, ep_out, true); } - /// Called by drivers to report readiness to receive up to `len` bytes. - /// Must be called exactly once before each packet. - pub fn stream(this: @This(), ep_out: EpNum, w: *std.Io.Writer, limit: std.Io.Limit) usize { - return this.vtable.stream(this.ptr, ep_out, w, limit); + + /// Reads data from the receive buffer into `data`. + /// `data` must contain at least one buffer, which may be empty. + /// Returns the number of bytes read, or `null` if no data was available. + /// If this empties the receive buffer, more data is requested. + pub fn read_buffered(this: @This(), data: []u8, ep_out: EpNum) ?u16 { + return this.transfer(this.ptr, .{ .Out = .{ + .data = data, + .listen = std.math.maxInt(u16), + } }, ep_out, false); } }; diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 49aedd2c9..28935735a 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -134,13 +134,17 @@ pub const CdcClassDriver = struct { /// Read data from rx buffer into dst. pub fn read(this: *@This(), device: usb.DeviceInterface, dst: []u8) usize { - var w: std.Io.Writer = .fixed(dst); - return device.stream(this.ep_out, &w, .limited(dst.len)); + return device.read_buffered(dst[0..1], this.ep_out) orelse 0; } /// Write data from src into tx buffer. pub fn write(this: *@This(), src: []const u8, device: usb.DeviceInterface) usize { - return device.writev(this.ep_in, &.{src}); + return device.write_buffered(src[0..1], this.ep_in, false) orelse 0; + } + + /// Send any buffered data. + pub fn flush(this: *@This(), device: usb.DeviceInterface) void { + _ = device.write_buffered("", this.ep_in, true); } /// Callback for setup packets. diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index d00f82ba9..ce80d0c45 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -47,7 +47,7 @@ const DualPortRam = extern struct { var alloc_top: u16 = @sizeOf(@This()); fn ep_open(this: *@This(), ep_num: EpNum, ep_dir: usb.types.Dir, transfer_type: usb.types.TransferType) u16 { - if (ep_num == .ep0) + var reg = this.ep_ctrl(ep_num, ep_dir) orelse std.debug.panic("Endpoint 0 should not be opened.", .{}); const buf_idx = alloc_top; @@ -55,19 +55,22 @@ const DualPortRam = extern struct { if (alloc_top > total_size) std.debug.panic("USB controller out of memory.", .{}); - var reg = this.ep_ctrl(ep_num, ep_dir).?; - var rmw = reg.read(); - rmw.ENABLE = 1; - rmw.INTERRUPT_PER_BUFF = 1; - rmw.ENDPOINT_TYPE = switch (transfer_type) { - .Control => std.debug.panic("Only endpoint 0 can be a control endpoint.", .{}), - .Isochronous => std.debug.panic("Isochronous endpoints are not implemented", .{}), - .Bulk => .bulk, - .Interrupt => .interrupt, - }; - // The datasheet claims bits 0 throigh 5 are ignored, but it is not the case in practice. - rmw.BUFFER_ADDRESS = buf_idx; - reg.write(rmw); + reg.write(.{ + .ENABLE = 1, + .DOUBLE_BUFFERED = 0, + .INTERRUPT_PER_BUFF = 1, + .INTERRUPT_PER_DOUBLE_BUFF = 1, + .ENDPOINT_TYPE = switch (transfer_type) { + .Control => std.debug.panic("Only endpoint 0 can be a control endpoint.", .{}), + .Isochronous => std.debug.panic("Isochronous endpoints are not implemented", .{}), + .Bulk => .bulk, + .Interrupt => .interrupt, + }, + .INTERRUPT_ON_STALL = 0, + .INTERRUPT_ON_NAK = 0, + .BUFFER_ADDRESS = buf_idx, + }); + return buf_idx; } @@ -109,11 +112,6 @@ const dpram: *DualPortRam = @ptrCast(@volatileCast(DualPortRam.peri)); pub fn Usb(comptime config: Config) type { return struct { - pub const interface_vtable: usb.DeviceInterface.Vtable = .{ - .writev = &writev, - .stream = &stream, - }; - const Controller = usb.Controller(blk: { var cfg = config.controller_config; cfg.strings = cfg.strings orelse default.strings; @@ -127,7 +125,7 @@ pub fn Usb(comptime config: Config) type { const State = union(enum) { sending: []const u8, // Slice of data left to be sent. no_buffer: ?u7, // Optionally a new address. - ready: []u8, // Buffer for next transaction. Always empty if available. + ready, waiting_ack, // Host is expected to send an ACK. }; @@ -137,7 +135,7 @@ pub fn Usb(comptime config: Config) type { pub fn interface(this: *@This()) usb.DeviceInterface { return .{ .ptr = this, - .vtable = &interface_vtable, + .transfer = &transfer, }; } @@ -150,8 +148,13 @@ pub fn Usb(comptime config: Config) type { // 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. - const dpram_data: *volatile [0x100 / @sizeOf(u32)]u32 = @ptrCast(dpram); - @memset(dpram_data, 0); + dpram.* = .{ + .setup = @bitCast(@as(u64, 0)), + .ep_ctrl_raw = @splat(@bitCast(@as(u64, 0))), + .buf_ctrl_raw = @splat(@bitCast(@as(u64, 0))), + .buffer0 = undefined, + .buffer1 = undefined, + }; // Mux the controller to the onboard USB PHY. I was surprised that there are // alternatives to this, but, there are. @@ -197,29 +200,18 @@ pub fn Usb(comptime config: Config) type { USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); return .{ - .state = .{ .ready = &dpram.buffer0 }, + .state = .ready, .controller = .init, }; } - /// Helper function that breaks up long strings. - fn ep0_send(this: *@This(), tx_buf: []u8, data: []const u8) void { - const len = @min(tx_buf.len, data.len); - if (len == 0) - this.state = .{ .no_buffer = null } - else { - std.mem.copyForwards(u8, tx_buf[0..len], data[0..len]); - this.state = .{ .sending = data[len..] }; - } - submit_buffer_in(.ep0, @intCast(len)); - } - /// Called when a setup packet is received. - fn process_setup(this: *@This(), tx_buf: []u8) void { - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers. + fn process_setup(this: *@This()) ?[]const u8 { + // Copy the setup packet out of its dedicated buffer at the base of USB SRAM. const setup = dpram.setup; + std.log.info("setup: {any}", .{setup}); + // Reset PID to 1 for EP0 IN. Every DATA packet we send in response // to an IN on EP0 needs to use PID DATA1. dpram.buf_ctrl(.ep0, .In).modify(.{ .PID_0 = 0 }); @@ -232,24 +224,25 @@ pub fn Usb(comptime config: Config) type { setup.request, ) catch break :blk) { .SetAddress => { - this.ep0_send(tx_buf, usb.ACK); this.state = .{ .no_buffer = @intCast(setup.value) }; + return usb.ACK; }, .SetConfiguration => if (this.controller.set_configuration(this, &setup)) - this.ep0_send(tx_buf, usb.ACK), + return usb.ACK, .GetDescriptor => if (Controller.get_descriptor(&setup)) |desc| - this.ep0_send(tx_buf, desc), + return desc, .SetFeature => if (this.controller.set_feature( @intCast(setup.value >> 8), setup.index, true, - )) this.ep0_send(tx_buf, usb.ACK), + )) return usb.ACK, } }, .Interface => if (this.controller.interface_setup(&setup)) |data| - this.ep0_send(tx_buf, data), + return data, else => {}, } + return usb.NAK; } /// Polls the device for events. Not thread safe, this must be called @@ -259,82 +252,52 @@ pub fn Usb(comptime config: Config) type { const SieStatus = @TypeOf(USB.SIE_STATUS).underlying_type; switch (this.state) { - .ready => |tx_buf| if (ints.SETUP_REQ != 0) { + .ready => |_| if (ints.SETUP_REQ != 0) { // Clear the status flag (write one to clear) var sie_status: SieStatus = @bitCast(@as(u32, 0)); sie_status.SETUP_REC = 1; USB.SIE_STATUS.write(sie_status); - this.process_setup(tx_buf); + if (this.process_setup()) |data| { + std.log.info("setup response: '{}' {}", .{ data.len, dpram.buf_ctrl(.ep0, .In).read().AVAILABLE_0 }); + const len = this.interface().write_buffered(data, .ep0, true).?; + + if (len != 0) { + this.state = .{ .sending = data[len..] }; + } else if (this.state != .no_buffer) { + this.state = .{ .no_buffer = null }; + } + } }, else => {}, } - if (ints.BUFF_STATUS != 0) { - const unhandled = USB.BUFF_STATUS.raw; - - for (0..16) |ep_num_usize| { - const shamt: u5 = @intCast(2 * ep_num_usize); - if (unhandled & (@as(u32, 1) << shamt) == 0) continue; - - const ep_num: EpNum = @enumFromInt(ep_num_usize); - // const buf_ctrl = dpram.buf_ctrl(ep_num, .In).read(); - const buf = dpram.buffer(ep_num, .In); - - const result = if (ep_num == .ep0) blk: { - switch (this.state) { - .sending => |data| { - this.ep0_send(buf, data); - - if (data.len == 0) { - listen(.ep0, 0); - this.state = .waiting_ack; - } - }, - .no_buffer => |new_address| { - // Finish the delayed SetAddress request, if there is one: - if (new_address) |addr| - USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); - - this.state = .{ .ready = buf }; - }, - .ready => |_| break :blk error.UsbPacketUnhandled, - .waiting_ack => this.state = .{ .ready = buf }, - } - } else continue; - - result catch - std.log.warn("unhandled usb packet: ep{}in", .{ep_num}); - } - - for (0..16) |ep_num_usize| { - const shamt: u5 = @intCast(2 * ep_num_usize); - if (unhandled & (@as(u32, 2) << shamt) == 0) continue; - - const ep_num: EpNum = @enumFromInt(ep_num_usize); - const buf_ctrl = dpram.buf_ctrl(ep_num, .Out).read(); - const buf = dpram.buffer(ep_num, .Out); - - const result = if (ep_num == .ep0) blk: { - switch (this.state) { - .sending => |_| break :blk error.UsbPacketUnhandled, - .no_buffer => |_| break :blk error.UsbPacketUnhandled, - .ready => |_| { - std.log.err("Got buffer twice!", .{}); - break :blk error.UsbPacketUnhandled; - }, - .waiting_ack => { - assert(buf_ctrl.LENGTH_0 == 0); - this.state = .{ .ready = buf }; - }, - } - } else continue; - - result catch { - std.log.warn("unhandled usb packet: ep{}out", .{ep_num}); - std.log.warn("{any}", .{buf[0..dpram.buf_ctrl(ep_num, .Out).read().LENGTH_0]}); - }; - } + if (ints.BUFF_STATUS != 0) + USB.BUFF_STATUS.write(USB.BUFF_STATUS.read()); - USB.BUFF_STATUS.write_raw(unhandled); + const buf_ctrl_in = dpram.buf_ctrl(.ep0, .In).read(); + switch (this.state) { + .sending => |data| if (this.interface().write_buffered(data, .ep0, true)) |len| { + if (len != 0) + this.state = .{ .sending = data[len..] } + else { + // TODO: Check return value. + // _ = this.interface().read_exact("", .ep0, 0); + listen(.ep0, 0); + this.state = .waiting_ack; + } + }, + .no_buffer => |new_address| if (buf_ctrl_in.AVAILABLE_0 == 0) { + // Finish the delayed SetAddress request, if there is one: + if (new_address) |addr| + USB.ADDR_ENDP.write(.{ .ENDPOINT = 0, .ADDRESS = addr }); + this.state = .ready; + }, + .ready => {}, + .waiting_ack => if (buf_ctrl_in.AVAILABLE_0 == 0) { + this.state = .ready; + } else if (this.interface().read_exact("", .ep0, null)) |len| { + assert(len == 0); + this.state = .ready; + }, } if (ints.BUS_RESET != 0) { @@ -348,13 +311,13 @@ pub fn Usb(comptime config: Config) type { } } - fn submit_buffer_in(ep_in: EpNum, len: u16) void { + fn submit_buffer_in(ep_in: EpNum, len: u10) void { // Write the buffer information to the buffer control register const buf_ctrl = dpram.buf_ctrl(ep_in, .In); var rmw = buf_ctrl.read(); rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 1; // We have put data in - rmw.LENGTH_0 = @intCast(len); // There are this many bytes + rmw.LENGTH_0 = len; // There are this many bytes // If the CPU is running at a higher clock speed than USB, // the AVAILABLE bit in the buffer control register should be set @@ -372,43 +335,55 @@ pub fn Usb(comptime config: Config) type { } /// See interface description. - pub fn writev(_: *anyopaque, ep_in: EpNum, buffers: []const []const u8) usize { - const buf_ctrl = dpram.buf_ctrl(ep_in, .In); - const rmw = buf_ctrl.read(); - if (rmw.FULL_0 != 0) return 0; + pub fn transfer(ptr: *anyopaque, opts: usb.DeviceInterface.Options, ep_num: EpNum, df: bool) ?u16 { + const this: *@This() = @ptrCast(@alignCast(ptr)); + _ = this; - // It is technically possible to support longer buffers but this demo doesn't bother. - const buf = dpram.buffer(ep_in, .In); - const len = @min(buf.len, buffers[0].len); - std.mem.copyForwards(u8, buf[0..len], buffers[0][0..len]); + const buf_ctrl = dpram.buf_ctrl(ep_num, opts); + var rmw = buf_ctrl.read(); + if (rmw.AVAILABLE_0 == 1) return null; - submit_buffer_in(ep_in, @intCast(len)); + const buffer = dpram.buffer(ep_num, opts); + const avail = switch (opts) { + .Out => buffer[if (rmw.FULL_0 == 1) 0 else rmw.LENGTH_1..rmw.LENGTH_0], + .In => buffer[if (rmw.FULL_0 == 0) 0 else rmw.LENGTH_0..], + }; - return len; - } + if (df and buffer.ptr != avail.ptr) + std.debug.panic("residual {any} data on {any}: {}", .{ opts, ep_num, if (opts == .In) rmw.LENGTH_0 else rmw.LENGTH_1 }); - pub fn stream(_: *anyopaque, ep_out: EpNum, w: *std.Io.Writer, limit: std.Io.Limit) usize { - const dst = limit.slice(w.unusedCapacitySlice()); - if (dst.len == 0) return 0; + const len = @min(avail.len, opts.len()); + rmw.FULL_0 = @intFromEnum(opts); - const buf_ctrl = dpram.buf_ctrl(ep_out, .Out); - var rmw = buf_ctrl.read(); - if (rmw.AVAILABLE_0 == 1) return 0; + switch (opts) { + .Out => |out| { + if (df and len != avail.len) + std.debug.panic("could not read full packet on {any} {} {}", .{ ep_num, len, avail.len }); - const buffer = dpram.buffer(ep_out, .Out); - const src = buffer[rmw.LENGTH_1..rmw.LENGTH_0]; + std.mem.copyForwards(u8, out.data[0..len], avail[0..len]); - const len = @min(src.len, dst.len); - std.mem.copyForwards(u8, dst[0..len], src[0..len]); + if (len == avail.len and out.listen != null) + listen(ep_num, out.listen.?) + else { + rmw.LENGTH_1 = @intCast(avail.ptr - buffer.ptr + len); + buf_ctrl.write(rmw); + } + }, + .In => |in| { + if (df and len != in.data.len) + std.debug.panic("could not send full packet on {any}", .{ep_num}); - if (src.len < dst.len) - listen(ep_out, @intCast(dst.len - src.len)) - else { - rmw.FULL_0 = 0; - rmw.LENGTH_1 += @intCast(len); - buf_ctrl.write(rmw); + std.mem.copyForwards(u8, avail[0..len], in.data[0..len]); + + rmw.LENGTH_0 = @intCast(avail.ptr - buffer.ptr + len); + if (df or len == avail.len or in.flush) + submit_buffer_in(ep_num, rmw.LENGTH_0) + else { + buf_ctrl.write(rmw); + } + }, } - return len; + return @intCast(len); } /// See interface description. @@ -417,6 +392,8 @@ pub fn Usb(comptime config: Config) type { const buf_ctrl = dpram.buf_ctrl(ep_out, .Out); var rmw = buf_ctrl.read(); + if (rmw.AVAILABLE_0 == 1) return; + rmw.PID_0 ^= 1; // Flip DATA0/1 rmw.FULL_0 = 0; // Buffer is empty rmw.AVAILABLE_0 = 1; // And ready to be filled From a8b5eab5a931d6caa96532d99b6a75411b259c39 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 13 Sep 2025 12:43:40 +0200 Subject: [PATCH 33/33] revert build script --- examples/raspberrypi/rp2xxx/build.zig | 92 +++++++++++++-------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/examples/raspberrypi/rp2xxx/build.zig b/examples/raspberrypi/rp2xxx/build.zig index 76b54fc7a..b222eb7e9 100644 --- a/examples/raspberrypi/rp2xxx/build.zig +++ b/examples/raspberrypi/rp2xxx/build.zig @@ -16,60 +16,60 @@ pub fn build(b: *std.Build) void { const specific_examples: []const Example = &.{ // RaspberryPi Boards: - // .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_flash-program", .file = "src/rp2040_only/flash_program.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_flash-id", .file = "src/rp2040_only/flash_id.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_random", .file = "src/rp2040_only/random.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_rtc", .file = "src/rp2040_only/rtc.zig" }, .{ .target = raspberrypi.pico, .name = "pico_usb-hid", .file = "src/rp2040_only/usb_hid.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, - // .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, - // .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, - // .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, - // .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, - // .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_interrupts", .file = "src/interrupts.zig" }, - // .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_interrupts", .file = "src/interrupts.zig" }, - - // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, - // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, - - // .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - // .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, - - // // WaveShare Boards: - // .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_multicore", .file = "src/rp2040_only/blinky_core1.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_hd44780", .file = "src/rp2040_only/hd44780.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" }, + .{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" }, + .{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" }, + .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_blinky", .file = "src/blinky.zig" }, + .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_blinky", .file = "src/blinky.zig" }, + .{ .target = raspberrypi.pico2_arm_flashless, .name = "pico2_arm_flashless_interrupts", .file = "src/interrupts.zig" }, + .{ .target = raspberrypi.pico2_riscv_flashless, .name = "pico2_riscv_flashless_interrupts", .file = "src/interrupts.zig" }, + + .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_random_data", .file = "src/rp2350_only/random_data.zig" }, + .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_random_data", .file = "src/rp2350_only/random_data.zig" }, + + .{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + .{ .target = raspberrypi.pico2_riscv, .name = "pico2_riscv_always_on_timer", .file = "src/rp2350_only/always_on_timer.zig" }, + + // WaveShare Boards: + .{ .target = mb.ports.rp2xxx.boards.waveshare.rp2040_matrix, .name = "rp2040_matrix_tiles", .file = "src/rp2040_only/tiles.zig" }, // .{ .target = "board:waveshare/rp2040_eth", .name = "rp2040-eth" }, // .{ .target = "board:waveshare/rp2040_plus_4m", .name = "rp2040-plus-4m" }, // .{ .target = "board:waveshare/rp2040_plus_16m", .name = "rp2040-plus-16m" }, }; const chip_agnostic_examples: []const ChipAgnosticExample = &.{ - // .{ .name = "adc", .file = "src/adc.zig" }, - // .{ .name = "i2c-accel", .file = "src/i2c_accel.zig" }, - // .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, - // .{ .name = "i2c-hall-effect", .file = "src/i2c_hall_effect.zig" }, - // .{ .name = "pwm", .file = "src/pwm.zig" }, - // .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, - // .{ .name = "uart-log", .file = "src/uart_log.zig" }, - // .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, - // .{ .name = "spi-master", .file = "src/spi_master.zig" }, - // .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, - // .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, - // .{ .name = "squarewave", .file = "src/squarewave.zig" }, - // .{ .name = "ws2812", .file = "src/ws2812.zig" }, - // .{ .name = "blinky", .file = "src/blinky.zig" }, - // .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, - // .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, - // .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, - // .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, - // .{ .name = "interrupts", .file = "src/interrupts.zig" }, - // .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, - // .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, + .{ .name = "adc", .file = "src/adc.zig" }, + .{ .name = "i2c-accel", .file = "src/i2c_accel.zig" }, + .{ .name = "i2c-bus-scan", .file = "src/i2c_bus_scan.zig" }, + .{ .name = "i2c-hall-effect", .file = "src/i2c_hall_effect.zig" }, + .{ .name = "pwm", .file = "src/pwm.zig" }, + .{ .name = "uart-echo", .file = "src/uart_echo.zig" }, + .{ .name = "uart-log", .file = "src/uart_log.zig" }, + .{ .name = "rtt-log", .file = "src/rtt_log.zig", .works_with_riscv = false }, + .{ .name = "spi-master", .file = "src/spi_master.zig" }, + .{ .name = "spi-slave", .file = "src/spi_slave.zig" }, + .{ .name = "spi-loopback-dma", .file = "src/spi_loopback_dma.zig" }, + .{ .name = "squarewave", .file = "src/squarewave.zig" }, + .{ .name = "ws2812", .file = "src/ws2812.zig" }, + .{ .name = "blinky", .file = "src/blinky.zig" }, + .{ .name = "gpio-clock-output", .file = "src/gpio_clock_output.zig" }, + .{ .name = "changing-system-clocks", .file = "src/changing_system_clocks.zig" }, + .{ .name = "custom-clock-config", .file = "src/custom_clock_config.zig" }, + .{ .name = "watchdog-timer", .file = "src/watchdog_timer.zig" }, + .{ .name = "interrupts", .file = "src/interrupts.zig" }, + .{ .name = "stepper_driver", .file = "src/stepper_driver.zig" }, + .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "usb-cdc", .file = "src/usb_cdc.zig" }, - // .{ .name = "dma", .file = "src/dma.zig" }, - // .{ .name = "cyw43", .file = "src/cyw43.zig" }, - // .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, + .{ .name = "dma", .file = "src/dma.zig" }, + .{ .name = "cyw43", .file = "src/cyw43.zig" }, + .{ .name = "mlx90640", .file = "src/mlx90640.zig" }, }; var available_examples: std.array_list.Managed(Example) = .init(b.allocator);