From 1f47131506b98a243ca7cb79cacc46c2fc5adab8 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 00:23:37 +0100 Subject: [PATCH 1/7] add new rp2xxx controller option and assertions --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 38 +++++++++++++++---------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index b8109e0e2..e2de1c373 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -20,8 +20,9 @@ pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; pub const Config = 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, + sync_noops: comptime_int = 3, }; pub const DeviceConfiguration = usb.DeviceConfiguration; @@ -110,10 +111,10 @@ const rp2xxx_endpoints = struct { /// create a concrete one. pub fn Polled( controller_config: usb.Config, - device_config: Config, + config: Config, ) type { comptime { - if (device_config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) + if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); } @@ -125,7 +126,7 @@ pub fn Polled( .endpoint_open = endpoint_open, }; - endpoints: [device_config.max_endpoints_count][2]HardwareEndpoint, + endpoints: [config.max_endpoints_count][2]HardwareEndpoint, data_buffer: []u8, controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, @@ -224,12 +225,12 @@ pub fn Polled( peripherals.USB_DPRAM.SETUP_PACKET_LOW.write_raw(0); peripherals.USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); - for (1..device_config.max_endpoints_count) |i| { + for (1..config.max_endpoints_count) |i| { rp2xxx_endpoints.get_ep_ctrl(@enumFromInt(i), .In).?.write_raw(0); rp2xxx_endpoints.get_ep_ctrl(@enumFromInt(i), .Out).?.write_raw(0); } - for (0..device_config.max_endpoints_count) |i| { + for (0..config.max_endpoints_count) |i| { rp2xxx_endpoints.get_buf_ctrl(@enumFromInt(i), .In).?.write_raw(0); rp2xxx_endpoints.get_buf_ctrl(@enumFromInt(i), .Out).?.write_raw(0); } @@ -283,8 +284,18 @@ pub fn Polled( }; @memset(std.mem.asBytes(&self.endpoints), 0); - endpoint_open(&self.interface, &.{ .endpoint = .in(.ep0), .max_packet_size = .from(64), .attributes = .{ .transfer_type = .Control, .usage = .data }, .interval = 0 }); - endpoint_open(&self.interface, &.{ .endpoint = .out(.ep0), .max_packet_size = .from(64), .attributes = .{ .transfer_type = .Control, .usage = .data }, .interval = 0 }); + endpoint_open(&self.interface, &.{ + .endpoint = .in(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + endpoint_open(&self.interface, &.{ + .endpoint = .out(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. @@ -336,11 +347,7 @@ pub fn Polled( // Nop for some clock cycles // use volatile so the compiler doesn't optimize the nops away - asm volatile ( - \\ nop - \\ nop - \\ nop - ); + asm volatile ("nop\n" ** config.sync_noops); // Set available bit ep.buffer_control.?.modify(.{ @@ -421,12 +428,13 @@ pub fn Polled( fn endpoint_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { const self: *@This() = @fieldParentPtr("interface", itf); - assert(@intFromEnum(desc.endpoint.num) <= device_config.max_endpoints_count); + assert(@intFromEnum(desc.endpoint.num) <= config.max_endpoints_count); const ep = desc.endpoint; const ep_hard = self.hardware_endpoint_get_by_address(ep); ep_hard.ep_addr = ep; + assert(desc.max_packet_size.into() <= 64); ep_hard.max_packet_size = @intCast(desc.max_packet_size.into()); ep_hard.transfer_type = desc.attributes.transfer_type; ep_hard.next_pid_1 = false; From 43095d5c3ebf6997ab2f88641743116e10284118 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 00:30:48 +0100 Subject: [PATCH 2/7] wait for AVAILABLE bit in start_tx --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index e2de1c373..72c5f82db 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -304,8 +304,7 @@ pub fn Polled( return self; } - /// Configures a given endpoint to send data (device-to-host, IN) when the host - /// next asks for it. + /// Configures a given IN endpoint to send data 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 @@ -319,13 +318,12 @@ pub fn Polled( // 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); + assert!(buffer.len <= 64); const ep = self.hardware_endpoint_get_by_address(.in(ep_num)); - // wait for controller to give processor ownership of the buffer before writing it. - // while (ep.buffer_control.?.read().AVAILABLE_0 == 1) {} + // Wait for controller to give processor ownership of the buffer before writing it. + // This is technically not neccessary, but the usb cdc driver is bugged. + while (ep.buffer_control.?.read().AVAILABLE_0 == 1) {} // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 std.mem.copyForwards(u8, ep.data_buffer[0..buffer.len], buffer); From 41bf5fda93ae7ebb03e7ed5938edd168575f8fe3 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 00:41:18 +0100 Subject: [PATCH 3/7] fix #452 --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 28 +++++++++++++++++-------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 72c5f82db..3b5454517 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -54,7 +54,7 @@ const HardwareEndpoint = struct { max_packet_size: u11, buffer_control: ?*BufferControlMmio, endpoint_control: ?*EndpointControlMimo, - data_buffer: []u8, + data_buffer: []align(4) u8, }; const rp2xxx_buffers = struct { @@ -69,9 +69,9 @@ const rp2xxx_buffers = struct { const USB_DATA_BUFFER = USB_DPRAM_DATA_BUFFER_BASE + (2 * CTRL_EP_BUFFER_SIZE); const USB_DATA_BUFFER_SIZE = 3840 - (2 * CTRL_EP_BUFFER_SIZE); - const ep0_buffer0: *[CTRL_EP_BUFFER_SIZE]u8 = @as(*[CTRL_EP_BUFFER_SIZE]u8, @ptrFromInt(USB_EP0_BUFFER0)); - const ep0_buffer1: *[CTRL_EP_BUFFER_SIZE]u8 = @as(*[CTRL_EP_BUFFER_SIZE]u8, @ptrFromInt(USB_EP0_BUFFER1)); - const data_buffer: *[USB_DATA_BUFFER_SIZE]u8 = @as(*[USB_DATA_BUFFER_SIZE]u8, @ptrFromInt(USB_DATA_BUFFER)); + const ep0_buffer0: *align(64) [CTRL_EP_BUFFER_SIZE]u8 = @ptrFromInt(USB_EP0_BUFFER0); + const ep0_buffer1: *align(64) [CTRL_EP_BUFFER_SIZE]u8 = @ptrFromInt(USB_EP0_BUFFER1); + const data_buffer: *align(64) [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]); @@ -127,7 +127,7 @@ pub fn Polled( }; endpoints: [config.max_endpoints_count][2]HardwareEndpoint, - data_buffer: []u8, + data_buffer: []align(4) u8, controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, @@ -318,15 +318,25 @@ pub fn Polled( // It is technically possible to support longer buffers but this demo // doesn't bother. - assert!(buffer.len <= 64); + assert(buffer.len <= 64); const ep = self.hardware_endpoint_get_by_address(.in(ep_num)); // Wait for controller to give processor ownership of the buffer before writing it. // This is technically not neccessary, but the usb cdc driver is bugged. while (ep.buffer_control.?.read().AVAILABLE_0 == 1) {} - // TODO: please fixme: https://github.com/ZigEmbeddedGroup/microzig/issues/452 - std.mem.copyForwards(u8, ep.data_buffer[0..buffer.len], buffer); + const len = buffer.len; + switch (chip) { + .RP2040 => @memcpy(ep.data_buffer[0..len], buffer[0..len]), + .RP2350 => { + const dst: [*]align(4) u32 = @ptrCast(ep.data_buffer.ptr); + const src: [*]align(1) const u32 = @ptrCast(buffer.ptr); + for (0..len / 4) |i| + dst[i] = src[i]; + for (0..len % 4) |i| + ep.data_buffer[len - i - 1] = buffer[len - i - 1]; + }, + } // Configure the IN: const np: u1 = if (ep.next_pid_1) 1 else 0; @@ -461,7 +471,7 @@ pub fn Polled( std.debug.assert(self.data_buffer.len >= size); ep.data_buffer = self.data_buffer[0..size]; - self.data_buffer = self.data_buffer[size..]; + self.data_buffer = @alignCast(self.data_buffer[size..]); } fn endpoint_enable(ep: *HardwareEndpoint) void { From a27e6d2ab1fbea8bbf0e05697be6b7542bdd62f7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 01:03:10 +0100 Subject: [PATCH 4/7] usb tx/rx cleanup --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 72 ++++++++++++------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 3b5454517..2d04a653b 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -338,29 +338,24 @@ pub fn Polled( }, } - // Configure the IN: - const np: u1 = if (ep.next_pid_1) 1 else 0; - - // The AVAILABLE bit in the buffer control register should be set - // separately to the rest of the data in the buffer control register, - // so that the rest of the data in the buffer control register is - // accurate when the AVAILABLE bit is set. + var bufctrl = ep.buffer_control.?.read(); // Write the buffer information to the buffer control register - ep.buffer_control.?.modify(.{ - .PID_0 = np, // DATA0/1, depending - .FULL_0 = 1, // We have put data in - .LENGTH_0 = @as(u10, @intCast(buffer.len)), // There are this many bytes - }); - - // Nop for some clock cycles - // use volatile so the compiler doesn't optimize the nops away - asm volatile ("nop\n" ** config.sync_noops); - + bufctrl.PID_0 = if (ep.next_pid_1) 1 else 0; // flip DATA0/1 + bufctrl.FULL_0 = 1; // We have put data in + bufctrl.LENGTH_0 = @intCast(len); // There are this many bytes + + if (config.sync_noops != 0) { + ep.buffer_control.?.write(bufctrl); + // 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. + asm volatile ("nop\n" ** config.sync_noops); + } // Set available bit - ep.buffer_control.?.modify(.{ - .AVAILABLE_0 = 1, // The data is for the computer to use now - }); + bufctrl.AVAILABLE_0 = 1; + ep.buffer_control.?.write(bufctrl); ep.next_pid_1 = !ep.next_pid_1; } @@ -368,28 +363,33 @@ pub fn Polled( fn start_rx(itf: *usb.DeviceInterface, ep_num: EpNum, len: usize) void { const self: *@This() = @fieldParentPtr("interface", itf); - // 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); + // It is technically possible to support longer buffers but this demo doesn't bother. + assert(len <= 64); const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); - if (ep.awaiting_rx) - return; + // This function should only be called when the buffer is known to be available, + // but the current driver implementations do not conform to that. + if (ep.awaiting_rx) return; - // Check which DATA0/1 PID this endpoint is expecting next. - const np: u1 = if (ep.next_pid_1) 1 else 0; // Configure the OUT: - ep.buffer_control.?.modify(.{ - .PID_0 = np, // DATA0/1 depending - .FULL_0 = 0, // Buffer is NOT full, we want the computer to fill it - .AVAILABLE_0 = 1, // It is, however, available to be filled - .LENGTH_0 = @as(u10, @intCast(len)), // Up tho this many bytes - }); + var bufctrl = ep.buffer_control.?.read(); + bufctrl.PID_0 = if (ep.next_pid_1) 1 else 0; // Flip DATA0/1 + bufctrl.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it + bufctrl.LENGTH_0 = @intCast(len); // Up tho this many bytes + + if (config.sync_noops != 0) { + ep.buffer_control.?.write(bufctrl); + // 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. + asm volatile ("nop\n" ** config.sync_noops); + } + // Set available bit + bufctrl.AVAILABLE_0 = 1; + ep.buffer_control.?.write(bufctrl); - // Flip the DATA0/1 PID for the next receive ep.next_pid_1 = !ep.next_pid_1; ep.awaiting_rx = true; } From 38f57b16a92dae288c676326e947154df6ef8abf Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 01:17:18 +0100 Subject: [PATCH 5/7] remove pointless declarations and endpoint data --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 68 ++++++++----------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 2d04a653b..c0416d88d 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -10,11 +10,9 @@ const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; const chip = microzig.hal.compatibility.chip; -pub const usb = microzig.core.usb; -pub const types = usb.types; -pub const hid = usb.hid; -pub const cdc = usb.cdc; -const EpNum = usb.types.Endpoint.Num; +const usb = microzig.core.usb; +const types = usb.types; +const EpNum = types.Endpoint.Num; pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; @@ -25,36 +23,18 @@ pub const Config = struct { sync_noops: comptime_int = 3, }; -pub const DeviceConfiguration = usb.DeviceConfiguration; -pub const DeviceDescriptor = usb.DeviceDescriptor; -pub const DescType = usb.descriptor.Type; -pub const InterfaceDescriptor = usb.types.InterfaceDescriptor; -pub const ConfigurationDescriptor = usb.types.ConfigurationDescriptor; -pub const EndpointDescriptor = usb.types.EndpointDescriptor; -pub const EndpointConfiguration = usb.EndpointConfiguration; -pub const Dir = usb.types.Dir; -pub const TransferType = usb.types.TransferType; -pub const Endpoint = usb.types.Endpoint; - -pub const utf8ToUtf16Le = usb.utf8ToUtf16Le; - const BufferControlMmio = microzig.mmio.Mmio(@TypeOf(microzig.chip.peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL).underlying_type); const EndpointControlMimo = microzig.mmio.Mmio(@TypeOf(peripherals.USB_DPRAM.EP1_IN_CONTROL).underlying_type); const EndpointType = microzig.chip.types.peripherals.USB_DPRAM.EndpointType; const HardwareEndpoint = struct { - configured: bool, - ep_addr: types.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: []align(4) u8, + data_buffer: []align(64) u8, }; const rp2xxx_buffers = struct { @@ -127,7 +107,7 @@ pub fn Polled( }; endpoints: [config.max_endpoints_count][2]HardwareEndpoint, - data_buffer: []align(4) u8, + data_buffer: []align(64) u8, controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, @@ -142,7 +122,7 @@ pub fn Polled( // to an IN on EP0 needs to use PID DATA1, and this line will ensure // that. var ep = self.hardware_endpoint_get_by_address(.in(.ep0)); - ep.next_pid_1 = true; + ep.buffer_control.?.modify(.{ .PID_0 = 0 }); const setup = get_setup_packet(); self.controller.on_setup_req(&self.interface, &setup); @@ -341,7 +321,7 @@ pub fn Polled( var bufctrl = ep.buffer_control.?.read(); // Write the buffer information to the buffer control register - bufctrl.PID_0 = if (ep.next_pid_1) 1 else 0; // flip DATA0/1 + bufctrl.PID_0 ^= 1; // flip DATA0/1 bufctrl.FULL_0 = 1; // We have put data in bufctrl.LENGTH_0 = @intCast(len); // There are this many bytes @@ -356,8 +336,6 @@ pub fn Polled( // Set available bit bufctrl.AVAILABLE_0 = 1; ep.buffer_control.?.write(bufctrl); - - ep.next_pid_1 = !ep.next_pid_1; } fn start_rx(itf: *usb.DeviceInterface, ep_num: EpNum, len: usize) void { @@ -374,7 +352,7 @@ pub fn Polled( // Configure the OUT: var bufctrl = ep.buffer_control.?.read(); - bufctrl.PID_0 = if (ep.next_pid_1) 1 else 0; // Flip DATA0/1 + bufctrl.PID_0 ^= 1; // Flip DATA0/1 bufctrl.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it bufctrl.LENGTH_0 = @intCast(len); // Up tho this many bytes @@ -390,7 +368,6 @@ pub fn Polled( bufctrl.AVAILABLE_0 = 1; ep.buffer_control.?.write(bufctrl); - ep.next_pid_1 = !ep.next_pid_1; ep.awaiting_rx = true; } @@ -441,30 +418,34 @@ pub fn Polled( const ep = desc.endpoint; const ep_hard = self.hardware_endpoint_get_by_address(ep); - ep_hard.ep_addr = ep; assert(desc.max_packet_size.into() <= 64); ep_hard.max_packet_size = @intCast(desc.max_packet_size.into()); ep_hard.transfer_type = desc.attributes.transfer_type; - ep_hard.next_pid_1 = false; ep_hard.awaiting_rx = false; ep_hard.buffer_control = rp2xxx_endpoints.get_buf_ctrl(ep.num, ep.dir); - ep_hard.endpoint_control = rp2xxx_endpoints.get_ep_ctrl(ep.num, ep.dir); + + ep_hard.buffer_control.?.modify(.{ .PID_0 = 1 }); if (ep.num == .ep0) { // ep0 has fixed data buffer ep_hard.data_buffer = rp2xxx_buffers.ep0_buffer0; } else { - self.endpoint_alloc(ep_hard) catch {}; - endpoint_enable(ep_hard); + self.endpoint_alloc(ep_hard, desc) catch {}; + rp2xxx_endpoints.get_ep_ctrl(ep.num, ep.dir).?.modify(.{ + .ENABLE = 1, + .INTERRUPT_PER_BUFF = 1, + .ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(@intFromEnum(desc.attributes.transfer_type))), + .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data_buffer), + }); } } - fn endpoint_alloc(self: *@This(), ep: *HardwareEndpoint) !void { + fn endpoint_alloc(self: *@This(), ep: *HardwareEndpoint, desc: *const usb.descriptor.Endpoint) !void { // round up size to multiple of 64 - var size = try std.math.divCeil(u11, ep.max_packet_size, 64) * 64; + var size = try std.math.divCeil(u16, desc.max_packet_size.into(), 64) * 64; // double buffered Bulk endpoint - if (ep.transfer_type == .Bulk) { + if (desc.attributes.transfer_type == .Bulk) { size *= 2; } @@ -473,14 +454,5 @@ pub fn Polled( ep.data_buffer = self.data_buffer[0..size]; self.data_buffer = @alignCast(self.data_buffer[size..]); } - - fn endpoint_enable(ep: *HardwareEndpoint) void { - ep.endpoint_control.?.modify(.{ - .ENABLE = 1, - .INTERRUPT_PER_BUFF = 1, - .ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(@intFromEnum(ep.transfer_type))), - .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep.data_buffer), - }); - } }; } From a024b29d71546060b1ffb378668ce7b2e4e0c93c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 02:10:14 +0100 Subject: [PATCH 6/7] redo endpoints --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 103 ++++++++++-------------- 1 file changed, 44 insertions(+), 59 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index c0416d88d..d338093c2 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -23,17 +23,8 @@ pub const Config = struct { sync_noops: comptime_int = 3, }; -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 { - transfer_type: types.TransferType, +const HardwareEndpointData = struct { awaiting_rx: bool, - - max_packet_size: u11, - buffer_control: ?*BufferControlMmio, - endpoint_control: ?*EndpointControlMimo, data_buffer: []align(64) u8, }; @@ -60,28 +51,26 @@ const rp2xxx_buffers = struct { } }; -const rp2xxx_endpoints = struct { - const USB_DPRAM_BASE = 0x50100000; - const USB_DPRAM_BUFFERS_BASE = USB_DPRAM_BASE + 0x100; - const USB_DPRAM_BUFFERS_CTRL_BASE = USB_DPRAM_BASE + 0x80; - const USB_DPRAM_ENDPOINTS_CTRL_BASE = USB_DPRAM_BASE + 0x8; - - pub fn get_ep_ctrl(ep_num: EpNum, ep_dir: types.Dir) ?*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[@intFromEnum(ep_num) - 1][dir_index]); +fn PerEndpoint(T: type) type { + return extern struct { + in: T, + out: T, + + fn get(self: *volatile @This(), dir: types.Dir) *volatile T { + return switch (dir) { + .In => &self.in, + .Out => &self.out, + }; } - } + }; +} - pub fn get_buf_ctrl(ep_num: EpNum, ep_dir: types.Dir) ?*BufferControlMmio { - const dir_index: u8 = if (ep_dir == .In) 0 else 1; - const buf_ctrl_base = @as([*][2]u32, @ptrFromInt(USB_DPRAM_BUFFERS_CTRL_BASE)); - return @ptrCast(&buf_ctrl_base[@intFromEnum(ep_num)][dir_index]); - } -}; +// It would be nice to instead generate those arrays automatically with a regz patch. +const BufferControlMmio = microzig.mmio.Mmio(@TypeOf(peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL).underlying_type); +const buffer_control: *volatile [16]PerEndpoint(BufferControlMmio) = @ptrCast(&peripherals.USB_DPRAM.EP0_IN_BUFFER_CONTROL); + +const EndpointControlMimo = microzig.mmio.Mmio(@TypeOf(peripherals.USB_DPRAM.EP1_IN_CONTROL).underlying_type); +const endpoint_control: *volatile [15]PerEndpoint(EndpointControlMimo) = @ptrCast(&peripherals.USB_DPRAM.EP1_IN_CONTROL); // +++++++++++++++++++++++++++++++++++++++++++++++++ // Code @@ -106,7 +95,7 @@ pub fn Polled( .endpoint_open = endpoint_open, }; - endpoints: [config.max_endpoints_count][2]HardwareEndpoint, + endpoints: [config.max_endpoints_count][2]HardwareEndpointData, data_buffer: []align(64) u8, controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, @@ -121,8 +110,7 @@ pub fn Polled( // 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. - var ep = self.hardware_endpoint_get_by_address(.in(.ep0)); - ep.buffer_control.?.modify(.{ .PID_0 = 0 }); + buffer_control[0].in.modify(.{ .PID_0 = 0 }); const setup = get_setup_packet(); self.controller.on_setup_req(&self.interface, &setup); @@ -172,7 +160,7 @@ pub fn Polled( // Get the actual length of the data, which may be less // than the buffer size. - const len = ep_hard.buffer_control.?.read().LENGTH_0; + const len = buffer_control[@intFromEnum(ep.num)].get(ep.dir).read().LENGTH_0; self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); @@ -206,13 +194,13 @@ pub fn Polled( peripherals.USB_DPRAM.SETUP_PACKET_HIGH.write_raw(0); for (1..config.max_endpoints_count) |i| { - rp2xxx_endpoints.get_ep_ctrl(@enumFromInt(i), .In).?.write_raw(0); - rp2xxx_endpoints.get_ep_ctrl(@enumFromInt(i), .Out).?.write_raw(0); + endpoint_control[i - 1].in.write_raw(0); + endpoint_control[i - 1].out.write_raw(0); } for (0..config.max_endpoints_count) |i| { - rp2xxx_endpoints.get_buf_ctrl(@enumFromInt(i), .In).?.write_raw(0); - rp2xxx_endpoints.get_buf_ctrl(@enumFromInt(i), .Out).?.write_raw(0); + buffer_control[i].in.write_raw(0); + buffer_control[i].out.write_raw(0); } // Mux the controller to the onboard USB PHY. I was surprised that there are @@ -300,10 +288,11 @@ pub fn Polled( // doesn't bother. assert(buffer.len <= 64); + const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].in; const ep = self.hardware_endpoint_get_by_address(.in(ep_num)); // Wait for controller to give processor ownership of the buffer before writing it. // This is technically not neccessary, but the usb cdc driver is bugged. - while (ep.buffer_control.?.read().AVAILABLE_0 == 1) {} + while (bufctrl_ptr.read().AVAILABLE_0 == 1) {} const len = buffer.len; switch (chip) { @@ -318,7 +307,7 @@ pub fn Polled( }, } - var bufctrl = ep.buffer_control.?.read(); + var bufctrl = bufctrl_ptr.read(); // Write the buffer information to the buffer control register bufctrl.PID_0 ^= 1; // flip DATA0/1 @@ -326,7 +315,7 @@ pub fn Polled( bufctrl.LENGTH_0 = @intCast(len); // There are this many bytes if (config.sync_noops != 0) { - ep.buffer_control.?.write(bufctrl); + bufctrl_ptr.write(bufctrl); // 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 @@ -335,7 +324,7 @@ pub fn Polled( } // Set available bit bufctrl.AVAILABLE_0 = 1; - ep.buffer_control.?.write(bufctrl); + bufctrl_ptr.write(bufctrl); } fn start_rx(itf: *usb.DeviceInterface, ep_num: EpNum, len: usize) void { @@ -344,6 +333,7 @@ pub fn Polled( // It is technically possible to support longer buffers but this demo doesn't bother. assert(len <= 64); + const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].out; const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); // This function should only be called when the buffer is known to be available, @@ -351,13 +341,13 @@ pub fn Polled( if (ep.awaiting_rx) return; // Configure the OUT: - var bufctrl = ep.buffer_control.?.read(); + var bufctrl = bufctrl_ptr.read(); bufctrl.PID_0 ^= 1; // Flip DATA0/1 bufctrl.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it bufctrl.LENGTH_0 = @intCast(len); // Up tho this many bytes if (config.sync_noops != 0) { - ep.buffer_control.?.write(bufctrl); + bufctrl_ptr.write(bufctrl); // 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 @@ -366,7 +356,7 @@ pub fn Polled( } // Set available bit bufctrl.AVAILABLE_0 = 1; - ep.buffer_control.?.write(bufctrl); + bufctrl_ptr.write(bufctrl); ep.awaiting_rx = true; } @@ -406,7 +396,7 @@ pub fn Polled( peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = addr }); } - fn hardware_endpoint_get_by_address(self: *@This(), ep: types.Endpoint) *HardwareEndpoint { + fn hardware_endpoint_get_by_address(self: *@This(), ep: types.Endpoint) *HardwareEndpointData { return &self.endpoints[@intFromEnum(ep.num)][@intFromEnum(ep.dir)]; } @@ -419,40 +409,35 @@ pub fn Polled( const ep_hard = self.hardware_endpoint_get_by_address(ep); assert(desc.max_packet_size.into() <= 64); - ep_hard.max_packet_size = @intCast(desc.max_packet_size.into()); - ep_hard.transfer_type = desc.attributes.transfer_type; ep_hard.awaiting_rx = false; - ep_hard.buffer_control = rp2xxx_endpoints.get_buf_ctrl(ep.num, ep.dir); - - ep_hard.buffer_control.?.modify(.{ .PID_0 = 1 }); + buffer_control[@intFromEnum(ep.num)].get(ep.dir).modify(.{ .PID_0 = 1 }); if (ep.num == .ep0) { // ep0 has fixed data buffer ep_hard.data_buffer = rp2xxx_buffers.ep0_buffer0; } else { - self.endpoint_alloc(ep_hard, desc) catch {}; - rp2xxx_endpoints.get_ep_ctrl(ep.num, ep.dir).?.modify(.{ + ep_hard.data_buffer = self.endpoint_alloc(desc) catch unreachable; + endpoint_control[@intFromEnum(ep.num) - 1].get(ep.dir).write(.{ .ENABLE = 1, .INTERRUPT_PER_BUFF = 1, - .ENDPOINT_TYPE = @as(EndpointType, @enumFromInt(@intFromEnum(desc.attributes.transfer_type))), + .ENDPOINT_TYPE = @enumFromInt(@intFromEnum(desc.attributes.transfer_type)), .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data_buffer), }); } } - fn endpoint_alloc(self: *@This(), ep: *HardwareEndpoint, desc: *const usb.descriptor.Endpoint) !void { + fn endpoint_alloc(self: *@This(), desc: *const usb.descriptor.Endpoint) ![]align(64) u8 { // round up size to multiple of 64 var size = try std.math.divCeil(u16, desc.max_packet_size.into(), 64) * 64; // double buffered Bulk endpoint - if (desc.attributes.transfer_type == .Bulk) { + if (desc.attributes.transfer_type == .Bulk) size *= 2; - } std.debug.assert(self.data_buffer.len >= size); - ep.data_buffer = self.data_buffer[0..size]; - self.data_buffer = @alignCast(self.data_buffer[size..]); + defer self.data_buffer = @alignCast(self.data_buffer[size..]); + return self.data_buffer[0..size]; } }; } From 70f9b9af3fea86e7f72ea6a38f2a6ebb98daeed5 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 02:28:30 +0100 Subject: [PATCH 7/7] simpler get_setup_packet() --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 38 +++++++++++-------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index d338093c2..20bcc5136 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -9,10 +9,7 @@ const assert = std.debug.assert; const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; const chip = microzig.hal.compatibility.chip; - const usb = microzig.core.usb; -const types = usb.types; -const EpNum = types.Endpoint.Num; pub const RP2XXX_MAX_ENDPOINTS_COUNT = 16; @@ -56,7 +53,7 @@ fn PerEndpoint(T: type) type { in: T, out: T, - fn get(self: *volatile @This(), dir: types.Dir) *volatile T { + fn get(self: *volatile @This(), dir: usb.types.Dir) *volatile T { return switch (dir) { .In => &self.in, .Out => &self.out, @@ -134,9 +131,9 @@ pub fn Polled( const epnum = @as(u4, @intCast(lowbit_index >> 1)); // Of the pair, the IN endpoint comes first, followed by OUT, so // we can get the direction by: - const dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out; + const dir: usb.types.Dir = if (lowbit_index & 1 == 0) .In else .Out; - const ep: types.Endpoint = .{ .num = @enumFromInt(epnum), .dir = dir }; + const ep: usb.types.Endpoint = .{ .num = @enumFromInt(epnum), .dir = dir }; // Process the buffer-done event. // Process the buffer-done event. @@ -279,7 +276,7 @@ pub fn Polled( /// packet to be sent. fn start_tx( itf: *usb.DeviceInterface, - ep_num: EpNum, + ep_num: usb.types.Endpoint.Num, buffer: []const u8, ) void { const self: *@This() = @fieldParentPtr("interface", itf); @@ -327,7 +324,11 @@ pub fn Polled( bufctrl_ptr.write(bufctrl); } - fn start_rx(itf: *usb.DeviceInterface, ep_num: EpNum, len: usize) void { + fn start_rx( + itf: *usb.DeviceInterface, + ep_num: usb.types.Endpoint.Num, + len: usize, + ) void { const self: *@This() = @fieldParentPtr("interface", itf); // It is technically possible to support longer buffers but this demo doesn't bother. @@ -366,7 +367,7 @@ pub fn Polled( /// 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. + /// setup request flag is set. fn get_setup_packet() usb.types.SetupPacket { // Clear the status flag (write-one-to-clear) peripherals.USB.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); @@ -375,18 +376,11 @@ pub fn Polled( // control endpoint. Which it should be. We don't have any other // Control endpoints. - // Copy the setup packet out of its dedicated buffer at the base of - // USB SRAM. The PAC models this buffer as two 32-bit registers, - // which is, like, not _wrong_ but slightly awkward since it means - // we can't just treat it as bytes. Instead, copy it out to a byte - // array. - var setup_packet: [8]u8 = @splat(0); - const spl: u32 = peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw; - const sph: u32 = peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw; - @memcpy(setup_packet[0..4], std.mem.asBytes(&spl)); - @memcpy(setup_packet[4..8], std.mem.asBytes(&sph)); - // Reinterpret as setup packet - return std.mem.bytesToValue(usb.types.SetupPacket, &setup_packet); + // The PAC models this buffer as two 32-bit registers. + return @bitCast([2]u32{ + peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw, + peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw, + }); } fn set_address(itf: *usb.DeviceInterface, addr: u7) void { @@ -396,7 +390,7 @@ pub fn Polled( peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = addr }); } - fn hardware_endpoint_get_by_address(self: *@This(), ep: types.Endpoint) *HardwareEndpointData { + fn hardware_endpoint_get_by_address(self: *@This(), ep: usb.types.Endpoint) *HardwareEndpointData { return &self.endpoints[@intFromEnum(ep.num)][@intFromEnum(ep.dir)]; }