From d2725893fdf697b65ffdf29d810d82524df32441 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 14:40:46 +0100 Subject: [PATCH 01/80] neccessary -> necessary and fix linter bot issue --- examples/raspberrypi/rp2xxx/src/custom_clock_config.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/clocks/common.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig b/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig index 7fe3c7968..950df80c3 100644 --- a/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig +++ b/examples/raspberrypi/rp2xxx/src/custom_clock_config.zig @@ -73,7 +73,7 @@ const system_clock_cfg: GlobalConfig = val: { .integer_divisor = 1, }, - // Change peri to also run off XOSC, not neccessarily reccomended, but interesting! + // Change peri to also run off XOSC, not necessarily reccomended, but interesting! .peri = .{ .input = .{ .source = .src_xosc, diff --git a/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig b/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig index 903e9c359..00b6e3219 100644 --- a/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig +++ b/port/raspberrypi/rp2xxx/src/hal/clocks/common.zig @@ -7,7 +7,7 @@ const CLOCKS = peripherals.CLOCKS; const XOSC = peripherals.XOSC; /// The current HAL requires XOSC configuration with the RP2xxx chip, although this isn't -/// strictly neccessary as the system could be driven from an external clock. +/// strictly necessary as the system could be driven from an external clock. /// TODO: Find a way to allow this to be "null" as it's not explicitly required as long /// as a user isn't using XOSC functionality in their clock setup. pub const xosc_freq = microzig.board.xosc_freq; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 20bcc5136..5b533f7fb 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -149,9 +149,9 @@ pub fn Polled( // 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; + // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS + // So we wait for it just to be sure. + while (buffer_control[epnum].get(ep.dir).read().AVAILABLE_0 != 0) {} // Cool. Checks out. @@ -288,7 +288,7 @@ pub fn Polled( 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. + // This is technically not necessary, but the usb cdc driver is bugged. while (bufctrl_ptr.read().AVAILABLE_0 == 1) {} const len = buffer.len; From 3e3b6de712113f757a7c95d40105c1c27062c58c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 15:47:44 +0100 Subject: [PATCH 02/80] scan unhandled buffer linearly --- core/src/core/usb/drivers/cdc.zig | 6 ++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 68 ++++++++++++------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 30b80ff7b..72cc3e53e 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -118,8 +118,8 @@ pub fn CdcClassDriver(options: Options) type { ep_out: types.Endpoint.Num, line_coding: LineCoding align(4), - rx: FIFO = .empty, - tx: FIFO = .empty, + rx: FIFO, + tx: FIFO, epin_buf: [options.max_packet_size]u8 = undefined, @@ -177,6 +177,8 @@ pub fn CdcClassDriver(options: Options) type { .parity = 0, .data_bits = 8, }, + .rx = .empty, + .tx = .empty, }; } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 5b533f7fb..bb4eb280c 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -113,36 +113,22 @@ pub fn Polled( self.controller.on_setup_req(&self.interface, &setup); } + var buff_status: u32 = 0; // Events on one or more buffers? (In practice, always one.) if (ints.BUFF_STATUS != 0) { const bufbits_init = peripherals.USB.BUFF_STATUS.raw; - var bufbits = bufbits_init; - - while (true) { - // Who's still outstanding? Find their bit index by counting how - // many LSBs are zero. - const lowbit_index = std.math.cast(u5, @ctz(bufbits)) orelse break; - // Remove their bit from our set. - bufbits ^= @as(u32, @intCast(1)) << lowbit_index; - - // Here we exploit knowledge of the ordering of buffer control - // registers in the peripheral. Each endpoint has a pair of - // registers, so we can determine the endpoint number by: - const epnum = @as(u4, @intCast(lowbit_index >> 1)); - // Of the pair, the IN endpoint comes first, followed by OUT, so - // we can get the direction by: - const dir: usb.types.Dir = if (lowbit_index & 1 == 0) .In else .Out; - - const ep: usb.types.Endpoint = .{ .num = @enumFromInt(epnum), .dir = dir }; - // Process the buffer-done event. - - // Process the buffer-done event. - // - // Scan the device table to figure out which endpoint struct - // corresponds to this address. We could use a smarter - // method here, but in practice, the number of endpoints is - // small so a linear scan doesn't kill us. + buff_status |= bufbits_init; + peripherals.USB.BUFF_STATUS.write_raw(bufbits_init); + } + // Here we exploit knowledge of the ordering of buffer control + // registers in the peripheral. Each endpoint has a pair of + // registers, IN being first + for (0..16) |ep_num| { + const shift: u5 = @intCast(2 * ep_num); + + if (buff_status & (@as(u32, 1) << shift) != 0) { + const ep: usb.types.Endpoint = .in(@enumFromInt(ep_num)); const ep_hard = self.hardware_endpoint_get_by_address(ep); // We should only get here if we've been notified that @@ -151,22 +137,36 @@ pub fn Polled( // // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS // So we wait for it just to be sure. - while (buffer_control[epnum].get(ep.dir).read().AVAILABLE_0 != 0) {} + while (buffer_control[ep_num].in.read().AVAILABLE_0 != 0) {} - // Cool. Checks out. + // Get the actual length of the data, which may be less + // than the buffer size. + const len = buffer_control[@intFromEnum(ep.num)].in.read().LENGTH_0; + + self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); + } + + if (buff_status & (@as(u32, 2) << shift) != 0) { + const ep: usb.types.Endpoint = .out(@enumFromInt(ep_num)); + const ep_hard = self.hardware_endpoint_get_by_address(ep); + + // We should only get here if we've been notified that + // the buffer is ours again. This is indicated by the hw + // _clearing_ the AVAILABLE bit. + // + // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS + // So we wait for it just to be sure. + while (buffer_control[ep_num].out.read().AVAILABLE_0 != 0) {} // Get the actual length of the data, which may be less // than the buffer size. - const len = buffer_control[@intFromEnum(ep.num)].get(ep.dir).read().LENGTH_0; + const len = buffer_control[@intFromEnum(ep.num)].out.read().LENGTH_0; self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); - if (ep.dir == .Out) - ep_hard.awaiting_rx = false; + ep_hard.awaiting_rx = false; } - - peripherals.USB.BUFF_STATUS.write_raw(bufbits_init); - } // <-- END of buf status handling + } // Has the host signaled a bus reset? if (ints.BUS_RESET != 0) { From b587b46e861feb688e2299cd62a53c6a11be38eb Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 17:42:23 +0100 Subject: [PATCH 03/80] redo endpoint handling --- core/src/core/usb.zig | 105 ++++++++++++++---------- core/src/core/usb/drivers/cdc.zig | 31 ++++--- core/src/core/usb/drivers/hid.zig | 7 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 42 +++------- 4 files changed, 100 insertions(+), 85 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index d5e04641b..b038fe619 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const log = std.log.scoped(.usb); pub const descriptor = @import("usb/descriptor.zig"); @@ -66,6 +67,11 @@ pub const Config = struct { configurations: []const Configuration, }; +const Handler = struct { + driver: []const u8, + function: []const u8, +}; + /// USB device controller /// /// This code handles usb enumeration and configuration and routes packets to drivers. @@ -147,6 +153,31 @@ pub fn DeviceController(config: Config) type { } }){}; }; + const handlers = blk: { + var ret: struct { In: [16]Handler, Out: [16]Handler } = .{ + .In = @splat(.{ .driver = "", .function = "" }), + .Out = @splat(.{ .driver = "", .function = "" }), + }; + for (driver_fields) |fld_drv| { + const cfg = @field(config_descriptor, fld_drv.name); + const fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; + for (fields) |fld| { + if (fld.type != descriptor.Endpoint) continue; + const desc: descriptor.Endpoint = @field(cfg, fld.name); + const handler = &@field(ret, @tagName(desc.endpoint.dir))[@intFromEnum(desc.endpoint.num)]; + // assert(handler.driver.len == 0 and handler.function.len == 0); + handler.* = .{ + .driver = fld_drv.name, + .function = switch (desc.endpoint.dir) { + .In => "on_rx", + .Out => "on_tx_ready", + }, + }; + } + } + break :blk ret; + }; + /// When the host sets the device address, the acknowledgement /// step must use the _old_ address. new_address: ?u8, @@ -213,61 +244,49 @@ pub fn DeviceController(config: Config) type { } /// Called by the device implementation when a packet has been sent or received. - pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, ep: types.Endpoint, buffer: []u8) void { + pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint, buffer: []u8) void { if (config.debug) log.info("buff status", .{}); - if (config.debug) log.info(" data: {any}", .{buffer}); - // Perform any required action on the data. For OUT, the `data` - // will be whatever was sent by the host. For IN, it's a copy of - // whatever we sent. - switch (ep.num) { - .ep0 => if (ep.dir == .In) { - if (config.debug) log.info(" EP0_IN_ADDR", .{}); - - // We use this opportunity to finish the delayed - // SetAddress request, if there is one: - if (self.new_address) |addr| { - // Change our address: - device_itf.set_address(@intCast(addr)); - self.new_address = null; - } + const handler = comptime @field(handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; - if (buffer.len > 0 and self.tx_slice.len > 0) { - self.tx_slice = self.tx_slice[buffer.len..]; + if (comptime ep == types.Endpoint.in(.ep0)) { + if (config.debug) log.info(" EP0_IN_ADDR", .{}); - const next_data_chunk = self.tx_slice[0..@min(64, self.tx_slice.len)]; - if (next_data_chunk.len > 0) { - device_itf.start_tx(.ep0, next_data_chunk); - } else { - device_itf.start_rx(.ep0, 0); + // We use this opportunity to finish the delayed + // SetAddress request, if there is one: + if (self.new_address) |addr| { + // Change our address: + device_itf.set_address(@intCast(addr)); + self.new_address = null; + } - if (self.driver_last) |drv| - self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); - } + if (buffer.len > 0 and self.tx_slice.len > 0) { + self.tx_slice = self.tx_slice[buffer.len..]; + + const next_data_chunk = self.tx_slice[0..@min(64, self.tx_slice.len)]; + if (next_data_chunk.len > 0) { + device_itf.start_tx(.ep0, next_data_chunk); } else { - // Otherwise, we've just finished sending - // something to the host. We expect an ensuing - // status phase where the host sends us (via EP0 - // OUT) a zero-byte DATA packet, so, set that - // up: device_itf.start_rx(.ep0, 0); if (self.driver_last) |drv| self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); } - }, - inline else => |ep_num| inline for (driver_fields) |fld_drv| { - const cfg = @field(config_descriptor, fld_drv.name); - const fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; - inline for (fields) |fld| { - const desc = @field(cfg, fld.name); - if (comptime fld.type == descriptor.Endpoint and desc.endpoint.num == ep_num) { - if (ep.dir == desc.endpoint.dir) - @field(self.driver_data.?, fld_drv.name).transfer(ep, buffer); - } - } - }, + } else { + // Otherwise, we've just finished sending + // something to the host. We expect an ensuing + // status phase where the host sends us (via EP0 + // OUT) a zero-byte DATA packet, so, set that + // up: + device_itf.start_rx(.ep0, 0); + + if (self.driver_last) |drv| + self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); + } + } else if (comptime handler.driver.len != 0) { + const drv = &@field(self.driver_data.?, handler.driver); + @field(@FieldType(config0.Drivers, handler.driver), handler.function)(drv, ep.num, buffer); } } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 72cc3e53e..7fd9362b8 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -121,6 +121,8 @@ pub fn CdcClassDriver(options: Options) type { rx: FIFO, tx: FIFO, + last_len: u11, + epin_buf: [options.max_packet_size]u8 = undefined, pub fn available(self: *@This()) usize { @@ -155,6 +157,7 @@ pub fn CdcClassDriver(options: Options) type { } const len = self.tx.read(&self.epin_buf); self.device.start_tx(self.ep_in, self.epin_buf[0..len]); + self.last_len = @intCast(len); return len; } @@ -179,6 +182,7 @@ pub fn CdcClassDriver(options: Options) type { }, .rx = .empty, .tx = .empty, + .last_len = 0, }; } @@ -200,19 +204,22 @@ pub fn CdcClassDriver(options: Options) type { return usb.nak; } - pub fn transfer(self: *@This(), ep: types.Endpoint, data: []u8) void { - if (ep == types.Endpoint.out(self.ep_out)) { - self.rx.write(data) catch {}; - self.prep_out_transaction(); - } + pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num, data: []u8) void { + if (ep_num != self.ep_out) return; + + self.rx.write(data) catch {}; + self.prep_out_transaction(); + } + + pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num, _: []u8) void { + if (ep_num != self.ep_in) return; - if (ep == types.Endpoint.in(self.ep_in)) { - if (self.write_flush() == 0) { - // If there is no data left, a empty packet should be sent if - // data len is multiple of EP Packet size and not zero - if (self.tx.get_readable_len() == 0 and data.len > 0 and data.len == options.max_packet_size) { - self.device.start_tx(self.ep_in, &.{}); - } + if (self.write_flush() == 0) { + // If there is no data left, a empty packet should be sent if + // data len is multiple of EP Packet size and not zero + if (self.tx.get_readable_len() == 0 and self.last_len == options.max_packet_size) { + self.device.start_tx(self.ep_in, usb.ack); + self.last_len = 0; } } } diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index a12f4f64e..c525c1b3c 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -123,10 +123,15 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return usb.nak; } - pub fn transfer(self: *@This(), ep: types.Endpoint, data: []u8) void { + pub fn on_tx_ready(self: *@This(), ep: types.Endpoint.Num, data: []u8) void { _ = self; _ = ep; _ = data; } + + pub fn on_rx(self: *@This(), ep: types.Endpoint.Num, _: []u8) void { + _ = self; + _ = ep; + } }; } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index bb4eb280c..cbebe1bd2 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -99,7 +99,6 @@ pub fn Polled( pub fn poll(self: *@This()) void { // Check which interrupt flags are set. - const ints = peripherals.USB.INTS.read(); // Setup request received? @@ -121,33 +120,17 @@ pub fn Polled( peripherals.USB.BUFF_STATUS.write_raw(bufbits_init); } - // Here we exploit knowledge of the ordering of buffer control - // registers in the peripheral. Each endpoint has a pair of - // registers, IN being first - for (0..16) |ep_num| { - const shift: u5 = @intCast(2 * ep_num); - + inline for (0..2 * config.max_endpoints_count) |shift| { if (buff_status & (@as(u32, 1) << shift) != 0) { - const ep: usb.types.Endpoint = .in(@enumFromInt(ep_num)); - const ep_hard = self.hardware_endpoint_get_by_address(ep); - - // We should only get here if we've been notified that - // the buffer is ours again. This is indicated by the hw - // _clearing_ the AVAILABLE bit. - // - // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS - // So we wait for it just to be sure. - while (buffer_control[ep_num].in.read().AVAILABLE_0 != 0) {} - - // Get the actual length of the data, which may be less - // than the buffer size. - const len = buffer_control[@intFromEnum(ep.num)].in.read().LENGTH_0; - - self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); - } + // Here we exploit knowledge of the ordering of buffer control + // registers in the peripheral. Each endpoint has a pair of + // registers, IN being first + const ep_num = shift / 2; + const ep: usb.types.Endpoint = comptime .{ + .num = @enumFromInt(ep_num), + .dir = if (shift & 1 == 0) .In else .Out, + }; - if (buff_status & (@as(u32, 2) << shift) != 0) { - const ep: usb.types.Endpoint = .out(@enumFromInt(ep_num)); const ep_hard = self.hardware_endpoint_get_by_address(ep); // We should only get here if we've been notified that @@ -156,15 +139,16 @@ pub fn Polled( // // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS // So we wait for it just to be sure. - while (buffer_control[ep_num].out.read().AVAILABLE_0 != 0) {} + while (buffer_control[ep_num].get(ep.dir).read().AVAILABLE_0 != 0) {} // Get the actual length of the data, which may be less // than the buffer size. - const len = buffer_control[@intFromEnum(ep.num)].out.read().LENGTH_0; + const len = buffer_control[ep_num].get(ep.dir).read().LENGTH_0; self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); - ep_hard.awaiting_rx = false; + if (ep.dir == .Out) + ep_hard.awaiting_rx = false; } } From a6537115a5d818d2146a261420417c700955bd74 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 18:33:05 +0100 Subject: [PATCH 04/80] separate getting usb rx data and requesting more --- core/src/core/usb.zig | 20 ++++++++---- core/src/core/usb/drivers/cdc.zig | 12 ++++--- core/src/core/usb/drivers/hid.zig | 5 ++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 43 +++++++++++++++++-------- 4 files changed, 53 insertions(+), 27 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index b038fe619..b69b98d12 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -17,7 +17,8 @@ pub const nak: ?[]const u8 = null; pub const DeviceInterface = struct { pub const VTable = struct { start_tx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, buffer: []const u8) void, - start_rx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: usize) void, + ep_readv: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) usize, + ep_listen: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: usize) void, endpoint_open: *const fn (self: *DeviceInterface, desc: *const descriptor.Endpoint) void, set_address: *const fn (self: *DeviceInterface, addr: u7) void, }; @@ -31,10 +32,15 @@ pub const DeviceInterface = struct { return self.vtable.start_tx(self, ep_num, buffer); } - /// Called by drivers to report readiness to receive up to `len` bytes. + /// Called by drivers to retrieve a received packet. /// Must be called exactly once before each packet. - pub fn start_rx(self: *@This(), ep_num: types.Endpoint.Num, len: usize) void { - return self.vtable.start_rx(self, ep_num, len); + /// Buffers in `data` must collectively be long enough to fit the whole packet. + pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) usize { + return self.vtable.ep_readv(self, ep_num, data); + } + + pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: usize) void { + return self.vtable.ep_listen(self, ep_num, len); } /// Opens an endpoint according to the descriptor. Note that if the endpoint @@ -268,7 +274,7 @@ pub fn DeviceController(config: Config) type { if (next_data_chunk.len > 0) { device_itf.start_tx(.ep0, next_data_chunk); } else { - device_itf.start_rx(.ep0, 0); + device_itf.ep_listen(.ep0, 0); if (self.driver_last) |drv| self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); @@ -279,14 +285,14 @@ pub fn DeviceController(config: Config) type { // status phase where the host sends us (via EP0 // OUT) a zero-byte DATA packet, so, set that // up: - device_itf.start_rx(.ep0, 0); + device_itf.ep_listen(.ep0, 0); if (self.driver_last) |drv| self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); } } else if (comptime handler.driver.len != 0) { const drv = &@field(self.driver_data.?, handler.driver); - @field(@FieldType(config0.Drivers, handler.driver), handler.function)(drv, ep.num, buffer); + @field(@FieldType(config0.Drivers, handler.driver), handler.function)(drv, ep.num); } } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 7fd9362b8..d9e1bf736 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -122,6 +122,7 @@ pub fn CdcClassDriver(options: Options) type { tx: FIFO, last_len: u11, + rx_ready: bool, epin_buf: [options.max_packet_size]u8 = undefined, @@ -164,7 +165,7 @@ pub fn CdcClassDriver(options: Options) type { fn prep_out_transaction(self: *@This()) void { if (self.rx.get_writable_len() >= options.max_packet_size) { // Let endpoint know that we are ready for next packet - self.device.start_rx(self.ep_out, options.max_packet_size); + self.device.ep_listen(self.ep_out, options.max_packet_size); } } @@ -183,6 +184,7 @@ pub fn CdcClassDriver(options: Options) type { .rx = .empty, .tx = .empty, .last_len = 0, + .rx_ready = true, }; } @@ -204,14 +206,16 @@ pub fn CdcClassDriver(options: Options) type { return usb.nak; } - pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num, data: []u8) void { + pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { if (ep_num != self.ep_out) return; - self.rx.write(data) catch {}; + var buf: [options.max_packet_size]u8 = undefined; + const len = self.device.ep_readv(ep_num, &.{&buf}); + self.rx.write(buf[0..len]) catch {}; self.prep_out_transaction(); } - pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num, _: []u8) void { + pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { if (ep_num != self.ep_in) return; if (self.write_flush() == 0) { diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index c525c1b3c..e8896db6f 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -123,13 +123,12 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return usb.nak; } - pub fn on_tx_ready(self: *@This(), ep: types.Endpoint.Num, data: []u8) void { + pub fn on_tx_ready(self: *@This(), ep: types.Endpoint.Num) void { _ = self; _ = ep; - _ = data; } - pub fn on_rx(self: *@This(), ep: types.Endpoint.Num, _: []u8) void { + pub fn on_rx(self: *@This(), ep: types.Endpoint.Num) void { _ = self; _ = ep; } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index cbebe1bd2..f5d317f9f 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -87,7 +87,8 @@ pub fn Polled( return struct { const vtable: usb.DeviceInterface.VTable = .{ .start_tx = start_tx, - .start_rx = start_rx, + .ep_readv = ep_readv, + .ep_listen = ep_listen, .set_address = set_address, .endpoint_open = endpoint_open, }; @@ -156,18 +157,15 @@ pub fn Polled( if (ints.BUS_RESET != 0) { // 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 }); + set_address(&self.interface, 0); self.controller.on_bus_reset(); } } pub fn init() @This() { - if (chip == .RP2350) { - peripherals.USB.MAIN_CTRL.modify(.{ - .PHY_ISO = 0, - }); - } + if (chip == .RP2350) + peripherals.USB.MAIN_CTRL.write(.{ .PHY_ISO = 0 }); // Clear the control portion of DPRAM. This may not be necessary -- the // datasheet is ambiguous -- but the C examples do it, and so do we. @@ -308,7 +306,29 @@ pub fn Polled( bufctrl_ptr.write(bufctrl); } - fn start_rx( + fn ep_readv( + itf: *usb.DeviceInterface, + ep_num: usb.types.Endpoint.Num, + data: []const []u8, + ) usize { + const self: *@This() = @fieldParentPtr("interface", itf); + assert(data.len > 0); + + const bufctrl = &buffer_control[@intFromEnum(ep_num)].out.read(); + const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); + var hw_buf: []align(1) u8 = ep.data_buffer[0..bufctrl.LENGTH_0]; + for (data) |dst| { + const len = @min(dst.len, hw_buf.len); + // make sure reads from device memory of size 1 + for (dst[0..len], hw_buf[0..len]) |*d, *s| + @atomicStore(u8, d, @atomicLoad(u8, s, .unordered), .unordered); + hw_buf = hw_buf[len..]; + if (hw_buf.len == 0) return hw_buf.ptr - ep.data_buffer.ptr; + } + unreachable; + } + + fn ep_listen( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usize, @@ -367,11 +387,8 @@ pub fn Polled( }); } - fn set_address(itf: *usb.DeviceInterface, addr: u7) void { - const self: *@This() = @fieldParentPtr("interface", itf); - _ = self; - - peripherals.USB.ADDR_ENDP.modify(.{ .ADDRESS = addr }); + fn set_address(_: *usb.DeviceInterface, addr: u7) void { + peripherals.USB.ADDR_ENDP.write(.{ .ADDRESS = addr }); } fn hardware_endpoint_get_by_address(self: *@This(), ep: usb.types.Endpoint) *HardwareEndpointData { From 9504866c28eca36ed3657dfb14f3e07634cbae6c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 19:12:10 +0100 Subject: [PATCH 05/80] make CdcClassDriver rx interrupt-safe --- core/src/core/usb.zig | 8 ++-- core/src/core/usb/drivers/cdc.zig | 52 ++++++++++++++----------- port/raspberrypi/rp2xxx/src/hal/usb.zig | 7 ++-- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index b69b98d12..371ad54af 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -17,8 +17,8 @@ pub const nak: ?[]const u8 = null; pub const DeviceInterface = struct { pub const VTable = struct { start_tx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, buffer: []const u8) void, - ep_readv: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) usize, - ep_listen: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: usize) void, + ep_readv: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) u11, + ep_listen: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: u11) void, endpoint_open: *const fn (self: *DeviceInterface, desc: *const descriptor.Endpoint) void, set_address: *const fn (self: *DeviceInterface, addr: u7) void, }; @@ -35,11 +35,11 @@ pub const DeviceInterface = struct { /// Called by drivers to retrieve a received packet. /// Must be called exactly once before each packet. /// Buffers in `data` must collectively be long enough to fit the whole packet. - pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) usize { + pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) u11 { return self.vtable.ep_readv(self, ep_num, data); } - pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: usize) void { + pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: u11) void { return self.vtable.ep_listen(self, ep_num, len); } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index d9e1bf736..f6d2c8e19 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -115,14 +115,18 @@ pub fn CdcClassDriver(options: Options) type { device: *usb.DeviceInterface, ep_notif: types.Endpoint.Num, ep_in: types.Endpoint.Num, - ep_out: types.Endpoint.Num, line_coding: LineCoding align(4), - rx: FIFO, + /// OUT endpoint on which there is data ready to be read, + /// or .ep0 when no data is available. + ep_out: types.Endpoint.Num, + rx_data: [options.max_packet_size]u8, + rx_seek: u11, + rx_end: u11, + tx: FIFO, last_len: u11, - rx_ready: bool, epin_buf: [options.max_packet_size]u8 = undefined, @@ -131,9 +135,21 @@ pub fn CdcClassDriver(options: Options) type { } pub fn read(self: *@This(), dst: []u8) usize { - const read_count = self.rx.read(dst); - self.prep_out_transaction(); - return read_count; + const len = @min(dst.len, self.rx_end - self.rx_seek); + @memcpy(dst[0..len], self.rx_data[self.rx_seek..][0..len]); + self.rx_seek += len; + + if (self.rx_seek != self.rx_end) return len; + + // request more data + const ep_out = @atomicLoad(types.Endpoint.Num, &self.ep_out, .acquire); + if (ep_out != .ep0) { + self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); + self.rx_seek = 0; + self.device.ep_listen(ep_out, options.max_packet_size); + } + + return len; } pub fn write(self: *@This(), data: []const u8) []const u8 { @@ -162,29 +178,26 @@ pub fn CdcClassDriver(options: Options) type { return len; } - fn prep_out_transaction(self: *@This()) void { - if (self.rx.get_writable_len() >= options.max_packet_size) { - // Let endpoint know that we are ready for next packet - self.device.ep_listen(self.ep_out, options.max_packet_size); - } - } - pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { + defer device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); return .{ .device = device, .ep_notif = desc.ep_notifi.endpoint.num, .ep_in = desc.ep_in.endpoint.num, - .ep_out = desc.ep_out.endpoint.num, .line_coding = .{ .bit_rate = 115200, .stop_bits = 0, .parity = 0, .data_bits = 8, }, - .rx = .empty, + + .rx_data = undefined, + .rx_seek = 0, + .rx_end = 0, + .ep_out = .ep0, + .tx = .empty, .last_len = 0, - .rx_ready = true, }; } @@ -207,12 +220,7 @@ pub fn CdcClassDriver(options: Options) type { } pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { - if (ep_num != self.ep_out) return; - - var buf: [options.max_packet_size]u8 = undefined; - const len = self.device.ep_readv(ep_num, &.{&buf}); - self.rx.write(buf[0..len]) catch {}; - self.prep_out_transaction(); + @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .release); } pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index f5d317f9f..66bc4f8d0 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -310,7 +310,7 @@ pub fn Polled( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, data: []const []u8, - ) usize { + ) u11 { const self: *@This() = @fieldParentPtr("interface", itf); assert(data.len > 0); @@ -323,7 +323,8 @@ pub fn Polled( for (dst[0..len], hw_buf[0..len]) |*d, *s| @atomicStore(u8, d, @atomicLoad(u8, s, .unordered), .unordered); hw_buf = hw_buf[len..]; - if (hw_buf.len == 0) return hw_buf.ptr - ep.data_buffer.ptr; + if (hw_buf.len == 0) + return @intCast(hw_buf.ptr - ep.data_buffer.ptr); } unreachable; } @@ -331,7 +332,7 @@ pub fn Polled( fn ep_listen( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, - len: usize, + len: u11, ) void { const self: *@This() = @fieldParentPtr("interface", itf); From 52ea727646337f489c5ce5da5c9d3a06945b6c6c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 19:35:46 +0100 Subject: [PATCH 06/80] add usb packet length type --- core/src/core/usb.zig | 12 ++++++------ core/src/core/usb/drivers/cdc.zig | 10 +++++----- core/src/core/usb/types.zig | 3 +++ port/raspberrypi/rp2xxx/src/hal/usb.zig | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 371ad54af..b4df180f4 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -17,8 +17,8 @@ pub const nak: ?[]const u8 = null; pub const DeviceInterface = struct { pub const VTable = struct { start_tx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, buffer: []const u8) void, - ep_readv: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) u11, - ep_listen: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: u11) void, + ep_readv: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len, + ep_listen: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void, endpoint_open: *const fn (self: *DeviceInterface, desc: *const descriptor.Endpoint) void, set_address: *const fn (self: *DeviceInterface, addr: u7) void, }; @@ -35,11 +35,11 @@ pub const DeviceInterface = struct { /// Called by drivers to retrieve a received packet. /// Must be called exactly once before each packet. /// Buffers in `data` must collectively be long enough to fit the whole packet. - pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) u11 { + pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) types.Len { return self.vtable.ep_readv(self, ep_num, data); } - pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: u11) void { + pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: types.Len) void { return self.vtable.ep_listen(self, ep_num, len); } @@ -175,8 +175,8 @@ pub fn DeviceController(config: Config) type { handler.* = .{ .driver = fld_drv.name, .function = switch (desc.endpoint.dir) { - .In => "on_rx", - .Out => "on_tx_ready", + .In => "on_tx_ready", + .Out => "on_rx", }, }; } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index f6d2c8e19..6acfd9786 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -121,12 +121,12 @@ pub fn CdcClassDriver(options: Options) type { /// or .ep0 when no data is available. ep_out: types.Endpoint.Num, rx_data: [options.max_packet_size]u8, - rx_seek: u11, - rx_end: u11, + rx_seek: types.Len, + rx_end: types.Len, tx: FIFO, - last_len: u11, + last_len: types.Len, epin_buf: [options.max_packet_size]u8 = undefined, @@ -219,11 +219,11 @@ pub fn CdcClassDriver(options: Options) type { return usb.nak; } - pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { + pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .release); } - pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { + pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { if (ep_num != self.ep_in) return; if (self.write_flush() == 0) { diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index d0f010825..342e9c044 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -142,6 +142,9 @@ pub const SetupPacket = extern struct { length: u16, }; +/// Represents packet length. +pub const Len = u11; + /// u16 value, little endian regardless of native endianness. pub const U16Le = extern struct { value: [2]u8, diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 66bc4f8d0..063c9d8f2 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -310,7 +310,7 @@ pub fn Polled( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, data: []const []u8, - ) u11 { + ) usb.types.Len { const self: *@This() = @fieldParentPtr("interface", itf); assert(data.len > 0); @@ -332,7 +332,7 @@ pub fn Polled( fn ep_listen( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, - len: u11, + len: usb.types.Len, ) void { const self: *@This() = @fieldParentPtr("interface", itf); From b65598916456c620f44d5b36ba287d4361e89fc6 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 20:34:28 +0100 Subject: [PATCH 07/80] make CdcClass driver tx interrupt-safe --- core/src/core/usb/drivers/cdc.zig | 90 ++++++++++----------- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 8 +- 2 files changed, 46 insertions(+), 52 deletions(-) diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 6acfd9786..386ebb265 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -3,8 +3,6 @@ const usb = @import("../../usb.zig"); const descriptor = usb.descriptor; const types = usb.types; -const utilities = @import("../../../utilities.zig"); - pub const ManagementRequestType = enum(u8) { SetLineCoding = 0x20, GetLineCoding = 0x21, @@ -31,8 +29,6 @@ pub const Options = struct { }; pub fn CdcClassDriver(options: Options) type { - const FIFO = utilities.CircularBuffer(u8, options.max_packet_size); - return struct { pub const Descriptor = extern struct { itf_assoc: descriptor.InterfaceAssociation, @@ -114,7 +110,6 @@ pub fn CdcClassDriver(options: Options) type { device: *usb.DeviceInterface, ep_notif: types.Endpoint.Num, - ep_in: types.Endpoint.Num, line_coding: LineCoding align(4), /// OUT endpoint on which there is data ready to be read, @@ -124,14 +119,15 @@ pub fn CdcClassDriver(options: Options) type { rx_seek: types.Len, rx_end: types.Len, - tx: FIFO, - - last_len: types.Len, - - epin_buf: [options.max_packet_size]u8 = undefined, + /// IN endpoint where data can be sent, + /// or .ep0 when data is being sent. + ep_in: types.Endpoint.Num, + ep_in_original: types.Endpoint.Num, + tx_data: [options.max_packet_size]u8, + tx_end: types.Len, - pub fn available(self: *@This()) usize { - return self.rx.get_readable_len(); + pub fn available(self: *@This()) types.Len { + return self.rx_end - self.rx_seek; } pub fn read(self: *@This(), dst: []u8) usize { @@ -139,51 +135,54 @@ pub fn CdcClassDriver(options: Options) type { @memcpy(dst[0..len], self.rx_data[self.rx_seek..][0..len]); self.rx_seek += len; - if (self.rx_seek != self.rx_end) return len; + if (self.available() > 0) return len; // request more data - const ep_out = @atomicLoad(types.Endpoint.Num, &self.ep_out, .acquire); + const ep_out = @atomicLoad(types.Endpoint.Num, &self.ep_out, .seq_cst); if (ep_out != .ep0) { self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); self.rx_seek = 0; + @atomicStore(types.Endpoint.Num, &self.ep_out, .ep0, .seq_cst); self.device.ep_listen(ep_out, options.max_packet_size); } return len; } - pub fn write(self: *@This(), data: []const u8) []const u8 { - const write_count = @min(self.tx.get_writable_len(), data.len); + pub fn write(self: *@This(), data: []const u8) usize { + const len = @min(self.tx_data.len - self.tx_end, data.len); - if (write_count > 0) { - self.tx.write_assume_capacity(data[0..write_count]); - } else { - return data[0..]; - } + if (len == 0) return 0; - if (self.tx.get_writable_len() == 0) { - _ = self.write_flush(); - } + @memcpy(self.tx_data[self.tx_end..][0..len], data[0..len]); + self.tx_end += @intCast(len); - return data[write_count..]; - } + if (self.tx_end == self.tx_data.len) + _ = self.flush(); - pub fn write_flush(self: *@This()) usize { - if (self.tx.get_readable_len() == 0) { - return 0; - } - const len = self.tx.read(&self.epin_buf); - self.device.start_tx(self.ep_in, self.epin_buf[0..len]); - self.last_len = @intCast(len); return len; } + pub fn flush(self: *@This()) bool { + if (self.tx_end == 0) + return true; + + const ep_in = @atomicLoad(types.Endpoint.Num, &self.ep_in, .seq_cst); + if (ep_in == .ep0) + return false; + + @atomicStore(types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); + + self.device.start_tx(ep_in, self.tx_data[0..self.tx_end]); + self.tx_end = 0; + return true; + } + pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { defer device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); return .{ .device = device, .ep_notif = desc.ep_notifi.endpoint.num, - .ep_in = desc.ep_in.endpoint.num, .line_coding = .{ .bit_rate = 115200, .stop_bits = 0, @@ -191,13 +190,15 @@ pub fn CdcClassDriver(options: Options) type { .data_bits = 8, }, + .ep_out = .ep0, .rx_data = undefined, .rx_seek = 0, .rx_end = 0, - .ep_out = .ep0, - .tx = .empty, - .last_len = 0, + .ep_in = desc.ep_in.endpoint.num, + .ep_in_original = desc.ep_in.endpoint.num, + .tx_data = undefined, + .tx_end = 0, }; } @@ -220,20 +221,13 @@ pub fn CdcClassDriver(options: Options) type { } pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { - @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .release); + @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .seq_cst); } pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { - if (ep_num != self.ep_in) return; - - if (self.write_flush() == 0) { - // If there is no data left, a empty packet should be sent if - // data len is multiple of EP Packet size and not zero - if (self.tx.get_readable_len() == 0 and self.last_len == options.max_packet_size) { - self.device.start_tx(self.ep_in, usb.ack); - self.last_len = 0; - } - } + if (ep_num != self.ep_in_original) return; + + @atomicStore(types.Endpoint.Num, &self.ep_in, ep_num, .seq_cst); } }; } diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 2d39844db..ec28753ae 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -88,7 +88,7 @@ pub fn main() !void { old = new; led.toggle(); i += 1; - std.log.info("cdc test: {}\r\n", .{i}); + std.log.info("cdc test: {}", .{i}); usb_cdc_write(&drivers.serial, "This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); } @@ -111,12 +111,12 @@ pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype var write_buff = text; while (write_buff.len > 0) { - write_buff = serial.write(write_buff); + write_buff = write_buff[serial.write(write_buff)..]; usb_dev.poll(); } // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them - _ = serial.write_flush(); - usb_dev.poll(); + while (!serial.flush()) + usb_dev.poll(); } var usb_rx_buff: [1024]u8 = undefined; From 503c998b0ac7107f4c4d11754f6885f3ae6261c2 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 21:59:36 +0100 Subject: [PATCH 08/80] allow partial usb tx and require calling ep_writev and ep_listen only once per packet --- core/src/core/usb.zig | 64 ++++++++++++------------- core/src/core/usb/drivers/cdc.zig | 3 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 53 +++++++------------- 3 files changed, 49 insertions(+), 71 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index b4df180f4..8e91fc904 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -16,20 +16,23 @@ pub const nak: ?[]const u8 = null; /// Any device implementation used with DeviceController must implement those functions pub const DeviceInterface = struct { pub const VTable = struct { - start_tx: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, buffer: []const u8) void, - ep_readv: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len, - ep_listen: *const fn (self: *DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void, - endpoint_open: *const fn (self: *DeviceInterface, desc: *const descriptor.Endpoint) void, - set_address: *const fn (self: *DeviceInterface, addr: u7) void, + ep_writev: *const fn (*DeviceInterface, types.Endpoint.Num, []const []const u8) types.Len, + ep_readv: *const fn (*DeviceInterface, types.Endpoint.Num, []const []u8) types.Len, + ep_listen: *const fn (*DeviceInterface, types.Endpoint.Num, types.Len) void, + endpoint_open: *const fn (*DeviceInterface, *const descriptor.Endpoint) void, + set_address: *const fn (*DeviceInterface, u7) void, }; vtable: *const VTable, /// Called by drivers to send a packet. /// Submitting an empty slice signals an ACK. - /// If you intend to send ACK, please use the constant `usb.ack`. - pub fn start_tx(self: *@This(), ep_num: types.Endpoint.Num, buffer: []const u8) void { - return self.vtable.start_tx(self, ep_num, buffer); + pub fn ep_writev(self: *@This(), ep_num: types.Endpoint.Num, data: []const []const u8) types.Len { + return self.vtable.ep_writev(self, ep_num, data); + } + + pub fn ep_ack(self: *@This(), ep_num: types.Endpoint.Num) void { + assert(0 == self.ep_writev(ep_num, &.{ack})); } /// Called by drivers to retrieve a received packet. @@ -190,7 +193,7 @@ pub fn DeviceController(config: Config) type { /// 0 - no config set cfg_num: u16, /// Ep0 data waiting to be sent - tx_slice: []const u8, + tx_slice: ?[]const u8, /// Last setup packet request setup_packet: types.SetupPacket, /// Class driver associated with last setup request if any @@ -202,7 +205,7 @@ pub fn DeviceController(config: Config) type { pub const init: @This() = .{ .new_address = null, .cfg_num = 0, - .tx_slice = "", + .tx_slice = null, .setup_packet = undefined, .driver_last = null, .driver_data = null, @@ -267,30 +270,22 @@ pub fn DeviceController(config: Config) type { self.new_address = null; } - if (buffer.len > 0 and self.tx_slice.len > 0) { - self.tx_slice = self.tx_slice[buffer.len..]; - - const next_data_chunk = self.tx_slice[0..@min(64, self.tx_slice.len)]; - if (next_data_chunk.len > 0) { - device_itf.start_tx(.ep0, next_data_chunk); + if (self.tx_slice) |slice| { + if (slice.len > 0) { + const len = device_itf.ep_writev(.ep0, &.{slice}); + self.tx_slice = slice[len..]; } else { - device_itf.ep_listen(.ep0, 0); + // device_itf.ep_listen(.ep0, 0); + self.tx_slice = null; if (self.driver_last) |drv| self.driver_class_control(device_itf, drv, .Ack, &self.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: - device_itf.ep_listen(.ep0, 0); - - if (self.driver_last) |drv| - self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); } - } else if (comptime handler.driver.len != 0) { + } else if (comptime ep == types.Endpoint.out(.ep0)) { + log.info("ep0_out {}", .{buffer.len}); + } + if (comptime handler.driver.len != 0) { const drv = &@field(self.driver_data.?, handler.driver); @field(@FieldType(config0.Drivers, handler.driver), handler.function)(drv, ep.num); } @@ -308,9 +303,10 @@ pub fn DeviceController(config: Config) type { /// Command response utility function that can split long data in multiple packets fn send_cmd_response(self: *@This(), device_itf: *DeviceInterface, data: []const u8, expected_max_length: u16) void { - self.tx_slice = data[0..@min(data.len, expected_max_length)]; - const len = @min(config.device_descriptor.max_packet_size0, self.tx_slice.len); - device_itf.start_tx(.ep0, data[0..len]); + const limited = data[0..@min(data.len, expected_max_length)]; + const len = device_itf.ep_writev(.ep0, &.{limited}); + assert(len <= config.device_descriptor.max_packet_size0); + self.tx_slice = limited[len..]; } fn driver_class_control(self: *@This(), device_itf: *DeviceInterface, driver: DriverEnum, stage: types.ControlStage, setup: *const types.SetupPacket) void { @@ -333,7 +329,7 @@ pub fn DeviceController(config: Config) type { switch (std.meta.intToEnum(types.SetupRequest, setup.request) catch return) { .SetAddress => { self.new_address = @as(u8, @intCast(setup.value & 0xff)); - device_itf.start_tx(.ep0, ack); + device_itf.ep_ack(.ep0); if (config.debug) log.info(" SetAddress: {}", .{self.new_address.?}); }, .SetConfiguration => { @@ -348,7 +344,7 @@ pub fn DeviceController(config: Config) type { // TODO: call umount callback if any } } - device_itf.start_tx(.ep0, ack); + device_itf.ep_ack(.ep0); }, .GetDescriptor => { const descriptor_type = std.meta.intToEnum(descriptor.Type, setup.value >> 8) catch null; @@ -359,7 +355,7 @@ pub fn DeviceController(config: Config) type { .SetFeature => { if (std.meta.intToEnum(types.FeatureSelector, setup.value >> 8)) |feat| { switch (feat) { - .DeviceRemoteWakeup, .EndpointHalt => device_itf.start_tx(.ep0, ack), + .DeviceRemoteWakeup, .EndpointHalt => device_itf.ep_ack(.ep0), // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 .TestMode => {}, } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 386ebb265..b1b20b8b4 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -1,5 +1,6 @@ const std = @import("std"); const usb = @import("../../usb.zig"); +const assert = std.debug.assert; const descriptor = usb.descriptor; const types = usb.types; @@ -173,7 +174,7 @@ pub fn CdcClassDriver(options: Options) type { @atomicStore(types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); - self.device.start_tx(ep_in, self.tx_data[0..self.tx_end]); + assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); self.tx_end = 0; return true; } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 063c9d8f2..2ef57bdc5 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -21,7 +21,6 @@ pub const Config = struct { }; const HardwareEndpointData = struct { - awaiting_rx: bool, data_buffer: []align(64) u8, }; @@ -86,7 +85,7 @@ pub fn Polled( return struct { const vtable: usb.DeviceInterface.VTable = .{ - .start_tx = start_tx, + .ep_writev = ep_writev, .ep_readv = ep_readv, .ep_listen = ep_listen, .set_address = set_address, @@ -147,9 +146,6 @@ pub fn Polled( const len = buffer_control[ep_num].get(ep.dir).read().LENGTH_0; self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); - - if (ep.dir == .Out) - ep_hard.awaiting_rx = false; } } @@ -248,6 +244,8 @@ pub fn Polled( // where the host will notice our presence. peripherals.USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); + self.interface.ep_listen(.ep0, 0); + return self; } @@ -256,38 +254,31 @@ pub fn Polled( /// The contents of `buffer` will be _copied_ into USB SRAM, so you can /// reuse `buffer` immediately after this returns. No need to wait for the /// packet to be sent. - fn start_tx( + fn ep_writev( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, - buffer: []const u8, - ) void { + data: []const []const u8, + ) usb.types.Len { const self: *@This() = @fieldParentPtr("interface", itf); - // It is technically possible to support longer buffers but this demo - // 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 necessary, but the usb cdc driver is bugged. - while (bufctrl_ptr.read().AVAILABLE_0 == 1) {} - const len = buffer.len; + const len = @min(data[0].len, ep.data_buffer.len); switch (chip) { - .RP2040 => @memcpy(ep.data_buffer[0..len], buffer[0..len]), + .RP2040 => @memcpy(ep.data_buffer[0..len], data[0][0..len]), .RP2350 => { const dst: [*]align(4) u32 = @ptrCast(ep.data_buffer.ptr); - const src: [*]align(1) const u32 = @ptrCast(buffer.ptr); + const src: [*]align(1) const u32 = @ptrCast(data[0].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]; + ep.data_buffer[len - i - 1] = data[0][len - i - 1]; }, } var bufctrl = bufctrl_ptr.read(); - + assert(bufctrl.AVAILABLE_0 == 0); // Write the buffer information to the buffer control register bufctrl.PID_0 ^= 1; // flip DATA0/1 bufctrl.FULL_0 = 1; // We have put data in @@ -304,6 +295,8 @@ pub fn Polled( // Set available bit bufctrl.AVAILABLE_0 = 1; bufctrl_ptr.write(bufctrl); + + return @intCast(len); } fn ep_readv( @@ -330,27 +323,18 @@ pub fn Polled( } fn ep_listen( - itf: *usb.DeviceInterface, + _: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usb.types.Len, ) void { - const self: *@This() = @fieldParentPtr("interface", itf); - - // 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, - // but the current driver implementations do not conform to that. - if (ep.awaiting_rx) return; - - // Configure the OUT: var bufctrl = bufctrl_ptr.read(); + assert(bufctrl.AVAILABLE_0 == 0); + // Configure the OUT: bufctrl.PID_0 ^= 1; // Flip DATA0/1 bufctrl.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it - bufctrl.LENGTH_0 = @intCast(len); // Up tho this many bytes + bufctrl.LENGTH_0 = @intCast(@min(len, 64)); // Up tho this many bytes if (config.sync_noops != 0) { bufctrl_ptr.write(bufctrl); @@ -363,8 +347,6 @@ pub fn Polled( // Set available bit bufctrl.AVAILABLE_0 = 1; bufctrl_ptr.write(bufctrl); - - ep.awaiting_rx = true; } /// Returns a received USB setup packet @@ -405,7 +387,6 @@ pub fn Polled( const ep_hard = self.hardware_endpoint_get_by_address(ep); assert(desc.max_packet_size.into() <= 64); - ep_hard.awaiting_rx = false; buffer_control[@intFromEnum(ep.num)].get(ep.dir).modify(.{ .PID_0 = 1 }); From 93ce5961bf01503a490810ff3d7cadc64e8b4a59 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 23:35:35 +0100 Subject: [PATCH 09/80] documentation and configurable handler names --- core/src/core/usb.zig | 27 ++++++++++------ core/src/core/usb/drivers/cdc.zig | 41 ++++++++++++++++--------- core/src/core/usb/drivers/hid.zig | 7 ++++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 1 + 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 8e91fc904..1127450bb 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -26,22 +26,23 @@ pub const DeviceInterface = struct { vtable: *const VTable, /// Called by drivers to send a packet. - /// Submitting an empty slice signals an ACK. pub fn ep_writev(self: *@This(), ep_num: types.Endpoint.Num, data: []const []const u8) types.Len { return self.vtable.ep_writev(self, ep_num, data); } + /// Send ack on given IN endpoint. pub fn ep_ack(self: *@This(), ep_num: types.Endpoint.Num) void { assert(0 == self.ep_writev(ep_num, &.{ack})); } /// Called by drivers to retrieve a received packet. - /// Must be called exactly once before each packet. + /// Must be called exactly once for each packet. /// Buffers in `data` must collectively be long enough to fit the whole packet. pub fn ep_readv(self: *@This(), ep_num: types.Endpoint.Num, data: []const []u8) types.Len { return self.vtable.ep_readv(self, ep_num, data); } + /// Called by drivers to report readiness to receive up to `len` bytes. pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: types.Len) void { return self.vtable.ep_listen(self, ep_num, len); } @@ -162,7 +163,7 @@ pub fn DeviceController(config: Config) type { } }){}; }; - const handlers = blk: { + const endpoint_handlers = blk: { var ret: struct { In: [16]Handler, Out: [16]Handler } = .{ .In = @splat(.{ .driver = "", .function = "" }), .Out = @splat(.{ .driver = "", .function = "" }), @@ -173,14 +174,17 @@ pub fn DeviceController(config: Config) type { for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc: descriptor.Endpoint = @field(cfg, fld.name); - const handler = &@field(ret, @tagName(desc.endpoint.dir))[@intFromEnum(desc.endpoint.num)]; - // assert(handler.driver.len == 0 and handler.function.len == 0); + const ep_num = @intFromEnum(desc.endpoint.num); + const handler = &@field(ret, @tagName(desc.endpoint.dir))[ep_num]; + const function = @field(fld_drv.type.handlers, fld.name); + if (handler.driver.len != 0 or handler.function.len != 0) + @compileError(std.fmt.comptimePrint( + "ep{} {t}: multiple handlers: {s}.{s} and {s}.{s}", + .{ ep_num, desc.endpoint.dir, handler.driver, handler.function, fld_drv.name, function }, + )); handler.* = .{ .driver = fld_drv.name, - .function = switch (desc.endpoint.dir) { - .In => "on_tx_ready", - .Out => "on_rx", - }, + .function = function, }; } } @@ -257,7 +261,7 @@ pub fn DeviceController(config: Config) type { if (config.debug) log.info("buff status", .{}); if (config.debug) log.info(" data: {any}", .{buffer}); - const handler = comptime @field(handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; + const handler = comptime @field(endpoint_handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; if (comptime ep == types.Endpoint.in(.ep0)) { if (config.debug) log.info(" EP0_IN_ADDR", .{}); @@ -275,6 +279,9 @@ pub fn DeviceController(config: Config) type { const len = device_itf.ep_writev(.ep0, &.{slice}); self.tx_slice = slice[len..]; } else { + // Otherwise, we've just finished sending tx_slice. + // We expect an ensuing status phase where the host + // sends us a zero-byte DATA packet via EP0 OUT. // device_itf.ep_listen(.ep0, 0); self.tx_slice = null; diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index b1b20b8b4..003e92a8c 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -3,6 +3,7 @@ const usb = @import("../../usb.zig"); const assert = std.debug.assert; const descriptor = usb.descriptor; const types = usb.types; +const EpNum = types.Endpoint.Num; pub const ManagementRequestType = enum(u8) { SetLineCoding = 0x20, @@ -109,21 +110,27 @@ pub fn CdcClassDriver(options: Options) type { } }; + pub const handlers = .{ + .ep_notifi = "on_notifi_ready", + .ep_out = "on_rx", + .ep_in = "on_tx_ready", + }; + device: *usb.DeviceInterface, - ep_notif: types.Endpoint.Num, + ep_notifi: EpNum, line_coding: LineCoding align(4), /// OUT endpoint on which there is data ready to be read, /// or .ep0 when no data is available. - ep_out: types.Endpoint.Num, + ep_out: EpNum, rx_data: [options.max_packet_size]u8, rx_seek: types.Len, rx_end: types.Len, /// IN endpoint where data can be sent, /// or .ep0 when data is being sent. - ep_in: types.Endpoint.Num, - ep_in_original: types.Endpoint.Num, + ep_in: EpNum, + ep_in_original: EpNum, tx_data: [options.max_packet_size]u8, tx_end: types.Len, @@ -139,11 +146,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.available() > 0) return len; // request more data - const ep_out = @atomicLoad(types.Endpoint.Num, &self.ep_out, .seq_cst); + const ep_out = @atomicLoad(EpNum, &self.ep_out, .seq_cst); if (ep_out != .ep0) { self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); self.rx_seek = 0; - @atomicStore(types.Endpoint.Num, &self.ep_out, .ep0, .seq_cst); + @atomicStore(EpNum, &self.ep_out, .ep0, .seq_cst); self.device.ep_listen(ep_out, options.max_packet_size); } @@ -164,15 +171,16 @@ pub fn CdcClassDriver(options: Options) type { return len; } + /// Returns true if flush operation succeded. pub fn flush(self: *@This()) bool { if (self.tx_end == 0) return true; - const ep_in = @atomicLoad(types.Endpoint.Num, &self.ep_in, .seq_cst); + const ep_in = @atomicLoad(EpNum, &self.ep_in, .seq_cst); if (ep_in == .ep0) return false; - @atomicStore(types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); + @atomicStore(EpNum, &self.ep_in, .ep0, .seq_cst); assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); self.tx_end = 0; @@ -183,7 +191,7 @@ pub fn CdcClassDriver(options: Options) type { defer device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); return .{ .device = device, - .ep_notif = desc.ep_notifi.endpoint.num, + .ep_notifi = desc.ep_notifi.endpoint.num, .line_coding = .{ .bit_rate = 115200, .stop_bits = 0, @@ -221,14 +229,19 @@ pub fn CdcClassDriver(options: Options) type { return usb.nak; } - pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { - @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .seq_cst); + pub fn on_rx(self: *@This(), ep_num: EpNum) void { + assert(EpNum.ep0 == @atomicLoad(EpNum, &self.ep_out, .seq_cst)); + @atomicStore(EpNum, &self.ep_out, ep_num, .seq_cst); } - pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { - if (ep_num != self.ep_in_original) return; + pub fn on_tx_ready(self: *@This(), ep_num: EpNum) void { + assert(EpNum.ep0 == @atomicLoad(EpNum, &self.ep_in, .seq_cst)); + @atomicStore(EpNum, &self.ep_in, ep_num, .seq_cst); + } - @atomicStore(types.Endpoint.Num, &self.ep_in, ep_num, .seq_cst); + pub fn on_notifi_ready(self: *@This(), ep_num: EpNum) void { + assert(EpNum.ep0 == @atomicLoad(EpNum, &self.ep_notifi, .seq_cst)); + @atomicStore(EpNum, &self.ep_notifi, ep_num, .seq_cst); } }; } diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index e8896db6f..8df44a778 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -35,7 +35,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }, .hid = hid_descriptor, .ep_out = .{ - .endpoint = .in(@enumFromInt(first_endpoint_out)), + .endpoint = .out(@enumFromInt(first_endpoint_out)), .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, .max_packet_size = .from(options.max_packet_size), .interval = options.endpoint_interval, @@ -57,6 +57,11 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { .report_length = .from(@sizeOf(@TypeOf(report_descriptor))), }; + pub const handlers = .{ + .ep_out = "on_rx", + .ep_in = "on_tx_ready", + }; + device: *usb.DeviceInterface, ep_in: types.Endpoint.Num, ep_out: types.Endpoint.Num, diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 2ef57bdc5..9cbfd181f 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -244,6 +244,7 @@ pub fn Polled( // where the host will notice our presence. peripherals.USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); + // Listen for ACKs self.interface.ep_listen(.ep0, 0); return self; From f21007636d0ca6b90c6e8f268d7ff94537959a5a Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 10 Jan 2026 23:54:51 +0100 Subject: [PATCH 10/80] first draft of an example driver that explains the comptime driver interface --- core/src/core/usb.zig | 1 + core/src/core/usb/drivers/example.zig | 96 +++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 core/src/core/usb/drivers/example.zig diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 1127450bb..75cfe31ea 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -5,6 +5,7 @@ const log = std.log.scoped(.usb); pub const descriptor = @import("usb/descriptor.zig"); pub const drivers = struct { pub const cdc = @import("usb/drivers/cdc.zig"); + pub const example = @import("usb/drivers/example.zig"); pub const hid = @import("usb/drivers/hid.zig"); }; pub const types = @import("usb/types.zig"); diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig new file mode 100644 index 000000000..b074a6b6d --- /dev/null +++ b/core/src/core/usb/drivers/example.zig @@ -0,0 +1,96 @@ +const std = @import("std"); +const usb = @import("../../usb.zig"); +const descriptor = usb.descriptor; + +pub const ExampleDriver = struct { + /// The descriptors need to have the same memory layout as the sent data. + pub const Descriptor = extern struct { + example_interface: descriptor.Interface, + ep_in1: descriptor.Endpoint, + ep_in2: descriptor.Endpoint, + ep_out: descriptor.Endpoint, + + /// This function is used during descriptor creation. If multiple instances + /// of a driver are used, a descriptor will be created for each. + pub fn create( + first_interface: u8, + first_string: u8, + first_endpoint_in: u4, + first_endpoint_out: u4, + ) @This() { + return .{ + .example_interface = .{ + .interface_number = first_interface + 1, + .alternate_setting = 0, + .num_endpoints = 2, + .interface_class = 10, + .interface_subclass = 0, + .interface_protocol = 0, + .interface_s = first_string, + }, + .ep_in1 = .{ + .endpoint = .in(@enumFromInt(first_endpoint_in)), + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(64), + .interval = 16, + }, + .ep_in2 = .{ + .endpoint = .in(@enumFromInt(first_endpoint_in + 1)), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(64), + .interval = 0, + }, + .ep_out = .{ + .endpoint = .out(@enumFromInt(first_endpoint_out)), + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(64), + .interval = 0, + }, + }; + } + }; + + /// This is a mapping from endpoint descriptor field names + /// to handler function names. + pub const handlers = .{ + .ep_in1 = "handler1", + .ep_in2 = "handler2", + .ep_out = "handler3", + }; + + /// This function is called when the host chooses a configuration + /// that contains this driver. + pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { + defer device.ep_listen(desc.ep_out.endpoint.num, 64); + return .{}; + } + + /// Used for configuration through endpoint 0. + /// Data returned by this function is sent on endpoint 0. + pub fn class_control(self: *@This(), stage: usb.types.ControlStage, setup: *const usb.types.SetupPacket) ?[]const u8 { + _ = self; + _ = setup; + if (stage == .Setup) + return usb.ack + else + return usb.nak; + } + + /// Each endpoint (as defined in the descriptor) has its own handler. + /// Endpoint number is passed as an argument so that it does not need + /// to be stored in the driver. + pub fn handler1(self: *@This(), ep_num: usb.types.Endpoint.Num) void { + _ = self; + _ = ep_num; + } + + pub fn handler2(self: *@This(), ep_num: usb.types.Endpoint.Num) void { + _ = self; + _ = ep_num; + } + + pub fn handler3(self: *@This(), ep_num: usb.types.Endpoint.Num) void { + _ = self; + _ = ep_num; + } +}; From 942aa42d0eb7e8a6e9013cb5b3474a69b371383c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 11 Jan 2026 00:26:52 +0100 Subject: [PATCH 11/80] more error checking around interfaces and string descriptors --- core/src/core/usb.zig | 49 +++++++++++++++------------ core/src/core/usb/drivers/example.zig | 6 ++-- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 75cfe31ea..7d759a929 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -139,6 +139,12 @@ pub fn DeviceController(config: Config) type { } } + if (num_strings != config.string_descriptors.len) + @compileError(std.fmt.comptimePrint( + "expected {} string descriptros, got {}", + .{ num_strings, config.string_descriptors.len }, + )); + const desc_cfg: descriptor.Configuration = .{ .total_length = .from(size), .num_interfaces = num_interfaces, @@ -164,14 +170,28 @@ pub fn DeviceController(config: Config) type { } }){}; }; - const endpoint_handlers = blk: { - var ret: struct { In: [16]Handler, Out: [16]Handler } = .{ + const handlers = blk: { + var ret: struct { In: [16]Handler, Out: [16]Handler, itf: []const DriverEnum } = .{ .In = @splat(.{ .driver = "", .function = "" }), .Out = @splat(.{ .driver = "", .function = "" }), + .itf = &.{}, }; + var itf_handlers = ret.itf; for (driver_fields) |fld_drv| { const cfg = @field(config_descriptor, fld_drv.name); const fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; + + const itf0 = @field(cfg, fields[0].name); + const itf_start, const itf_count = if (fields[0].type == descriptor.InterfaceAssociation) + .{ itf0.first_interface, itf0.interface_count } + else + .{ itf0.interface_number, 1 }; + + if (itf_start != itf_handlers.len) + @compileError("interface numbering mismatch"); + + itf_handlers = itf_handlers ++ &[1]DriverEnum{@field(DriverEnum, fld_drv.name)} ** itf_count; + for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc: descriptor.Endpoint = @field(cfg, fld.name); @@ -189,6 +209,7 @@ pub fn DeviceController(config: Config) type { }; } } + ret.itf = itf_handlers; break :blk ret; }; @@ -232,24 +253,10 @@ pub fn DeviceController(config: Config) type { switch (setup.request_type.recipient) { .Device => try self.process_setup_request(device_itf, setup), .Interface => switch (@as(u8, @truncate(setup.index))) { - inline else => |itf_num| inline for (driver_fields) |fld_drv| { - const cfg = @field(config_descriptor, fld_drv.name); - comptime var fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; - - const itf_count = if (fields[0].type != descriptor.InterfaceAssociation) - 1 - else blk: { - defer fields = fields[1..]; - break :blk @field(cfg, fields[0].name).interface_count; - }; - - const itf_start = @field(cfg, fields[0].name).interface_number; - - if (comptime itf_num >= itf_start and itf_num < itf_start + itf_count) { - const drv = @field(DriverEnum, fld_drv.name); - self.driver_last = drv; - self.driver_class_control(device_itf, drv, .Setup, setup); - } + inline else => |itf_num| if (itf_num < handlers.itf.len) { + const drv = handlers.itf[itf_num]; + self.driver_last = drv; + self.driver_class_control(device_itf, drv, .Setup, setup); }, }, .Endpoint => {}, @@ -262,7 +269,7 @@ pub fn DeviceController(config: Config) type { if (config.debug) log.info("buff status", .{}); if (config.debug) log.info(" data: {any}", .{buffer}); - const handler = comptime @field(endpoint_handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; + const handler = comptime @field(handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; if (comptime ep == types.Endpoint.in(.ep0)) { if (config.debug) log.info(" EP0_IN_ADDR", .{}); diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index b074a6b6d..722c2d889 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -20,10 +20,10 @@ pub const ExampleDriver = struct { ) @This() { return .{ .example_interface = .{ - .interface_number = first_interface + 1, + .interface_number = first_interface, .alternate_setting = 0, - .num_endpoints = 2, - .interface_class = 10, + .num_endpoints = 3, + .interface_class = 0, .interface_subclass = 0, .interface_protocol = 0, .interface_s = first_string, From 340d9c3ab9b712d49956027e70e3d04afd60715b Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 11 Jan 2026 00:38:35 +0100 Subject: [PATCH 12/80] endpoint_open -> ep_open for consistency --- core/src/core/usb.zig | 8 ++++---- port/raspberrypi/rp2xxx/src/hal/usb.zig | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 7d759a929..00a4a39ea 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -20,7 +20,7 @@ pub const DeviceInterface = struct { ep_writev: *const fn (*DeviceInterface, types.Endpoint.Num, []const []const u8) types.Len, ep_readv: *const fn (*DeviceInterface, types.Endpoint.Num, []const []u8) types.Len, ep_listen: *const fn (*DeviceInterface, types.Endpoint.Num, types.Len) void, - endpoint_open: *const fn (*DeviceInterface, *const descriptor.Endpoint) void, + ep_open: *const fn (*DeviceInterface, *const descriptor.Endpoint) void, set_address: *const fn (*DeviceInterface, u7) void, }; @@ -52,8 +52,8 @@ pub const DeviceInterface = struct { /// direction is IN this may call the controller's `on_buffer` function, /// so driver initialization must be done before this function is called /// on IN endpoint descriptors. - pub fn endpoint_open(self: *@This(), desc: *const descriptor.Endpoint) void { - return self.vtable.endpoint_open(self, desc); + pub fn ep_open(self: *@This(), desc: *const descriptor.Endpoint) void { + return self.vtable.ep_open(self, desc); } /// Immediately sets the device address. @@ -434,7 +434,7 @@ pub fn DeviceController(config: Config) type { inline for (@typeInfo(@TypeOf(cfg)).@"struct".fields) |fld| { if (comptime fld.type == descriptor.Endpoint) - device_itf.endpoint_open(&@field(cfg, fld.name)); + device_itf.ep_open(&@field(cfg, fld.name)); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 9cbfd181f..145ae1b01 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -88,8 +88,8 @@ pub fn Polled( .ep_writev = ep_writev, .ep_readv = ep_readv, .ep_listen = ep_listen, + .ep_open = ep_open, .set_address = set_address, - .endpoint_open = endpoint_open, }; endpoints: [config.max_endpoints_count][2]HardwareEndpointData, @@ -227,13 +227,13 @@ pub fn Polled( }; @memset(std.mem.asBytes(&self.endpoints), 0); - endpoint_open(&self.interface, &.{ + ep_open(&self.interface, &.{ .endpoint = .in(.ep0), .max_packet_size = .from(64), .attributes = .{ .transfer_type = .Control, .usage = .data }, .interval = 0, }); - endpoint_open(&self.interface, &.{ + ep_open(&self.interface, &.{ .endpoint = .out(.ep0), .max_packet_size = .from(64), .attributes = .{ .transfer_type = .Control, .usage = .data }, @@ -379,7 +379,7 @@ pub fn Polled( return &self.endpoints[@intFromEnum(ep.num)][@intFromEnum(ep.dir)]; } - fn endpoint_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { + fn ep_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { const self: *@This() = @fieldParentPtr("interface", itf); assert(@intFromEnum(desc.endpoint.num) <= config.max_endpoints_count); From 9eabcabd34c1b30611a46de235a27684994bc78c Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 11 Jan 2026 00:46:43 +0100 Subject: [PATCH 13/80] style fixes --- core/src/core/usb.zig | 11 +++++----- core/src/core/usb/drivers/cdc.zig | 35 +++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 00a4a39ea..442868e63 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -44,6 +44,7 @@ pub const DeviceInterface = struct { } /// Called by drivers to report readiness to receive up to `len` bytes. + /// After being called it may not be called again until a packet is received. pub fn ep_listen(self: *@This(), ep_num: types.Endpoint.Num, len: types.Len) void { return self.vtable.ep_listen(self, ep_num, len); } @@ -78,11 +79,6 @@ pub const Config = struct { configurations: []const Configuration, }; -const Handler = struct { - driver: []const u8, - function: []const u8, -}; - /// USB device controller /// /// This code handles usb enumeration and configuration and routes packets to drivers. @@ -171,6 +167,11 @@ pub fn DeviceController(config: Config) type { }; const handlers = blk: { + const Handler = struct { + driver: []const u8, + function: []const u8, + }; + var ret: struct { In: [16]Handler, Out: [16]Handler, itf: []const DriverEnum } = .{ .In = @splat(.{ .driver = "", .function = "" }), .Out = @splat(.{ .driver = "", .function = "" }), diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 003e92a8c..a5999478a 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -3,7 +3,6 @@ const usb = @import("../../usb.zig"); const assert = std.debug.assert; const descriptor = usb.descriptor; const types = usb.types; -const EpNum = types.Endpoint.Num; pub const ManagementRequestType = enum(u8) { SetLineCoding = 0x20, @@ -117,20 +116,20 @@ pub fn CdcClassDriver(options: Options) type { }; device: *usb.DeviceInterface, - ep_notifi: EpNum, + ep_notifi: types.Endpoint.Num, line_coding: LineCoding align(4), /// OUT endpoint on which there is data ready to be read, /// or .ep0 when no data is available. - ep_out: EpNum, + ep_out: types.Endpoint.Num, rx_data: [options.max_packet_size]u8, rx_seek: types.Len, rx_end: types.Len, /// IN endpoint where data can be sent, /// or .ep0 when data is being sent. - ep_in: EpNum, - ep_in_original: EpNum, + ep_in: types.Endpoint.Num, + ep_in_original: types.Endpoint.Num, tx_data: [options.max_packet_size]u8, tx_end: types.Len, @@ -146,11 +145,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.available() > 0) return len; // request more data - const ep_out = @atomicLoad(EpNum, &self.ep_out, .seq_cst); + const ep_out = @atomicLoad(types.Endpoint.Num, &self.ep_out, .seq_cst); if (ep_out != .ep0) { self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); self.rx_seek = 0; - @atomicStore(EpNum, &self.ep_out, .ep0, .seq_cst); + @atomicStore(types.Endpoint.Num, &self.ep_out, .ep0, .seq_cst); self.device.ep_listen(ep_out, options.max_packet_size); } @@ -176,11 +175,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.tx_end == 0) return true; - const ep_in = @atomicLoad(EpNum, &self.ep_in, .seq_cst); + const ep_in = @atomicLoad(types.Endpoint.Num, &self.ep_in, .seq_cst); if (ep_in == .ep0) return false; - @atomicStore(EpNum, &self.ep_in, .ep0, .seq_cst); + @atomicStore(types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); self.tx_end = 0; @@ -229,19 +228,19 @@ pub fn CdcClassDriver(options: Options) type { return usb.nak; } - pub fn on_rx(self: *@This(), ep_num: EpNum) void { - assert(EpNum.ep0 == @atomicLoad(EpNum, &self.ep_out, .seq_cst)); - @atomicStore(EpNum, &self.ep_out, ep_num, .seq_cst); + pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { + assert(.ep0 == @atomicLoad(types.Endpoint.Num, &self.ep_out, .seq_cst)); + @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .seq_cst); } - pub fn on_tx_ready(self: *@This(), ep_num: EpNum) void { - assert(EpNum.ep0 == @atomicLoad(EpNum, &self.ep_in, .seq_cst)); - @atomicStore(EpNum, &self.ep_in, ep_num, .seq_cst); + pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { + assert(.ep0 == @atomicLoad(types.Endpoint.Num, &self.ep_in, .seq_cst)); + @atomicStore(types.Endpoint.Num, &self.ep_in, ep_num, .seq_cst); } - pub fn on_notifi_ready(self: *@This(), ep_num: EpNum) void { - assert(EpNum.ep0 == @atomicLoad(EpNum, &self.ep_notifi, .seq_cst)); - @atomicStore(EpNum, &self.ep_notifi, ep_num, .seq_cst); + pub fn on_notifi_ready(self: *@This(), ep_num: types.Endpoint.Num) void { + assert(.ep0 == @atomicLoad(types.Endpoint.Num, &self.ep_notifi, .seq_cst)); + @atomicStore(types.Endpoint.Num, &self.ep_notifi, ep_num, .seq_cst); } }; } From 870f0d60e6a8e5fa626fbe8d53ec28b93bdaa881 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 11 Jan 2026 01:08:14 +0100 Subject: [PATCH 14/80] appease linter bot --- core/src/core/usb.zig | 11 +++++------ core/src/core/usb/descriptor.zig | 16 ++++++++-------- core/src/core/usb/descriptor/cdc.zig | 2 +- core/src/core/usb/descriptor/hid.zig | 4 ++-- core/src/core/usb/drivers/cdc.zig | 4 +--- core/src/core/usb/drivers/hid.zig | 14 +++++++------- core/src/core/usb/types.zig | 2 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 8 ++++---- 8 files changed, 29 insertions(+), 32 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 442868e63..5f9db5d4e 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -351,14 +351,13 @@ pub fn DeviceController(config: Config) type { .SetConfiguration => { if (config.debug) log.info(" SetConfiguration", .{}); if (self.cfg_num != setup.value) { + // if (self.cfg_num > 0) + // deinitialize drivers + self.cfg_num = setup.value; - if (self.cfg_num > 0) { + if (self.cfg_num > 0) try self.process_set_config(device_itf, self.cfg_num - 1); - // TODO: call mount callback if any - } else { - // TODO: call umount callback if any - } } device_itf.ep_ack(.ep0); }, @@ -427,7 +426,7 @@ pub fn DeviceController(config: Config) type { } fn process_set_config(self: *@This(), device_itf: *DeviceInterface, _: u16) !void { - // TODO: we support just one config for now so ignore config index + // We support just one config for now so ignore config index self.driver_data = @as(config0.Drivers, undefined); inline for (driver_fields) |fld_drv| { const cfg = @field(config_descriptor, fld_drv.name); diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 63777e064..2e22d83e2 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -57,7 +57,7 @@ pub const Device = extern struct { /// Type of this descriptor, must be `DeviceQualifier`. descriptor_type: Type = .DeviceQualifier, /// Specification version as Binary Coded Decimal - bcd_usb: types.U16Le, + bcd_usb: types.U16_Le, /// Class, subclass and protocol of device. device_triple: DeviceTriple, /// Maximum unit of data this device can move. @@ -77,17 +77,17 @@ pub const Device = extern struct { /// Type of this descriptor, must be `Device`. descriptor_type: Type = .Device, /// Specification version as Binary Coded Decimal - bcd_usb: types.U16Le, + bcd_usb: types.U16_Le, /// Class, subclass and protocol of device. device_triple: DeviceTriple, /// Maximum length of data this device can move. max_packet_size0: u8, /// ID of product vendor. - vendor: types.U16Le, + vendor: types.U16_Le, /// ID of product. - product: types.U16Le, + product: types.U16_Le, /// Device version number as Binary Coded Decimal. - bcd_device: types.U16Le, + bcd_device: types.U16_Le, /// Index of manufacturer name in string descriptor table. manufacturer_s: u8, /// Index of product name in string descriptor table. @@ -144,7 +144,7 @@ pub const Configuration = extern struct { /// Total length of all descriptors in this configuration, concatenated. /// This will include this descriptor, plus at least one interface /// descriptor, plus each interface descriptor's endpoint descriptors. - total_length: types.U16Le, + total_length: types.U16_Le, /// Number of interface descriptors in this configuration. num_interfaces: u8, /// Number to use when requesting this configuration via a @@ -171,7 +171,7 @@ pub const String = struct { const ret: *const extern struct { length: u8 = @sizeOf(@This()), descriptor_type: Type = .String, - lang: types.U16Le, + lang: types.U16_Le, } = comptime &.{ .lang = .from(@intFromEnum(lang)) }; return .{ .data = @ptrCast(ret) }; } @@ -221,7 +221,7 @@ pub const Endpoint = extern struct { /// control the transfer type using the values from `TransferType`. attributes: Attributes, /// Maximum packet size this endpoint can accept/produce. - max_packet_size: types.U16Le, + max_packet_size: types.U16_Le, /// Interval for polling interrupt/isochronous endpoints (which we don't /// currently support) in milliseconds. interval: u8, diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index 0d9f8e5fc..bf6478f59 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -22,7 +22,7 @@ pub const Header = extern struct { descriptor_subtype: SubType = .Header, // USB Class Definitions for Communication Devices Specification release // number in binary-coded decimal. Typically 0x01_10. - bcd_cdc: types.U16Le, + bcd_cdc: types.U16_Le, }; pub const CallManagement = extern struct { diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index c3ce76afb..a77224545 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -70,7 +70,7 @@ pub const Hid = extern struct { /// Type of this descriptor descriptor_type: descriptor.Type = .CsDevice, /// Numeric expression identifying the HID Class Specification release - bcd_hid: types.U16Le, + bcd_hid: types.U16_Le, /// Numeric expression identifying country code of the localized hardware country_code: CountryCode, /// Numeric expression specifying the number of class descriptors @@ -78,7 +78,7 @@ pub const Hid = extern struct { /// Type of HID class report report_type: Type = .Report, /// The total size of the Report descriptor - report_length: types.U16Le, + report_length: types.U16_Le, }; // +++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index a5999478a..79d5ba32c 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -129,7 +129,6 @@ pub fn CdcClassDriver(options: Options) type { /// IN endpoint where data can be sent, /// or .ep0 when data is being sent. ep_in: types.Endpoint.Num, - ep_in_original: types.Endpoint.Num, tx_data: [options.max_packet_size]u8, tx_end: types.Len, @@ -204,7 +203,6 @@ pub fn CdcClassDriver(options: Options) type { .rx_end = 0, .ep_in = desc.ep_in.endpoint.num, - .ep_in_original = desc.ep_in.endpoint.num, .tx_data = undefined, .tx_end = 0, }; @@ -213,7 +211,7 @@ pub fn CdcClassDriver(options: Options) type { pub fn class_control(self: *@This(), stage: types.ControlStage, setup: *const types.SetupPacket) ?[]const u8 { if (std.meta.intToEnum(ManagementRequestType, setup.request)) |request| { if (stage == .Setup) switch (request) { - .SetLineCoding => return usb.ack, // HACK, we should handle data phase somehow to read sent line_coding + .SetLineCoding => return usb.ack, // we should handle data phase somehow to read sent line_coding .GetLineCoding => return std.mem.asBytes(&self.line_coding), .SetControlLineState => { // const DTR_BIT = 1; diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 8df44a778..f4a055f10 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -90,18 +90,19 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { const hid_request_type = std.meta.intToEnum(descriptor.hid.RequestType, setup.request) catch return usb.nak; switch (hid_request_type) { .SetIdle => { - // TODO: The host is attempting to limit bandwidth by requesting that + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 + // The host is attempting to limit bandwidth by requesting that // the device only return report data when its values actually change, // or when the specified duration elapses. In practice, the device can // still send reports as often as it wants, but for completeness this // should be implemented eventually. // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 return usb.ack; }, .SetProtocol => { - // TODO: The device should switch the format of its reports from the - // boot keyboard/mouse protocol to the format described in its report descriptor, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 + // The device should switch the format of its reports from the boot + // keyboard/mouse protocol to the format described in its report descriptor, // or vice versa. // // For now, this request is ACKed without doing anything; in practice, @@ -109,15 +110,14 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { // Unless the report format matches the boot protocol exactly (see ReportDescriptorKeyboard), // our device might not work in a limited BIOS environment. // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 return usb.ack; }, .SetReport => { - // TODO: This request sends a feature or output report to the device, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 + // This request sends a feature or output report to the device, // e.g. turning on the caps lock LED. This must be handled in an // application-specific way, so notify the application code of the event. // - // https://github.com/ZigEmbeddedGroup/microzig/issues/454 return usb.ack; }, else => {}, diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 342e9c044..9a87c98ae 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -146,7 +146,7 @@ pub const SetupPacket = extern struct { pub const Len = u11; /// u16 value, little endian regardless of native endianness. -pub const U16Le = extern struct { +pub const U16_Le = extern struct { value: [2]u8, pub fn from(val: u16) @This() { diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index ec28753ae..e1063c41b 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -11,7 +11,7 @@ const led = gpio.num(25); const uart = rp2xxx.uart.instance.num(0); const uart_tx_pin = gpio.num(0); -const UsbSerial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); +const USB_Serial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); var usb_dev: rp2xxx.usb.Polled( usb.Config{ @@ -43,7 +43,7 @@ var usb_dev: rp2xxx.usb.Polled( .configuration_s = 0, .attributes = .{ .self_powered = true }, .max_current_ma = 100, - .Drivers = struct { serial: UsbSerial }, + .Drivers = struct { serial: USB_Serial }, }}, }, .{}, @@ -106,7 +106,7 @@ var usb_tx_buff: [1024]u8 = undefined; // Transfer data to host // NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled -pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype) void { +pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytype) void { const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; var write_buff = text; @@ -124,7 +124,7 @@ var usb_rx_buff: [1024]u8 = undefined; // Receive data from host // NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation pub fn usb_cdc_read( - serial: *UsbSerial, + serial: *USB_Serial, ) []const u8 { var total_read: usize = 0; var read_buff: []u8 = usb_rx_buff[0..]; From 2a79c7888ebd81c80545437648d8819db8d0fa83 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 11 Jan 2026 17:08:34 +0100 Subject: [PATCH 15/80] add u32 little endian wrapper --- core/src/core/usb/types.zig | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 9a87c98ae..63c9e423d 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -147,15 +147,26 @@ pub const Len = u11; /// u16 value, little endian regardless of native endianness. pub const U16_Le = extern struct { - value: [2]u8, + value_le: u16 align(1), pub fn from(val: u16) @This() { - var self: @This() = undefined; - std.mem.writeInt(u16, &self.value, val, .little); - return self; + return .{ .value_le = std.mem.nativeToLittle(u16, val) }; } pub fn into(self: @This()) u16 { - return std.mem.readInt(u16, &self.value, .little); + return std.mem.littleToNative(u16, self.value_le); + } +}; + +/// u32 value, little endian regardless of native endianness. +pub const U32_Le = extern struct { + value_le: u32 align(1), + + pub fn from(val: u32) @This() { + return .{ .value_le = std.mem.nativeToLittle(u32, val) }; + } + + pub fn into(self: @This()) u32 { + return std.mem.littleToNative(u32, self.value_le); } }; From f553d34238a8f2fe942dd7530a26147ed722a4cd Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 11 Jan 2026 17:22:46 +0100 Subject: [PATCH 16/80] respect endianness in setup packet and shortcuts for bulk and interrupt endpoints --- core/src/core/usb.zig | 27 ++++++++++++++------------- core/src/core/usb/descriptor.zig | 3 +++ core/src/core/usb/drivers/cdc.zig | 4 ++-- core/src/core/usb/drivers/example.zig | 6 +++--- core/src/core/usb/drivers/hid.zig | 6 +++--- core/src/core/usb/types.zig | 6 +++--- 6 files changed, 28 insertions(+), 24 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 5f9db5d4e..d43dea4d4 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -253,7 +253,7 @@ pub fn DeviceController(config: Config) type { switch (setup.request_type.recipient) { .Device => try self.process_setup_request(device_itf, setup), - .Interface => switch (@as(u8, @truncate(setup.index))) { + .Interface => switch (@as(u8, @truncate(setup.index.into()))) { inline else => |itf_num| if (itf_num < handlers.itf.len) { const drv = handlers.itf[itf_num]; self.driver_last = drv; @@ -330,7 +330,7 @@ pub fn DeviceController(config: Config) type { inline else => |d| { const drv = &@field(self.driver_data.?, @tagName(d)); if (drv.class_control(stage, setup)) |response| - self.send_cmd_response(device_itf, response, setup.length); + self.send_cmd_response(device_itf, response, setup.length.into()); }, }; } @@ -344,31 +344,32 @@ pub fn DeviceController(config: Config) type { .Standard => { switch (std.meta.intToEnum(types.SetupRequest, setup.request) catch return) { .SetAddress => { - self.new_address = @as(u8, @intCast(setup.value & 0xff)); + self.new_address = @truncate(setup.value.into()); device_itf.ep_ack(.ep0); if (config.debug) log.info(" SetAddress: {}", .{self.new_address.?}); }, .SetConfiguration => { if (config.debug) log.info(" SetConfiguration", .{}); - if (self.cfg_num != setup.value) { + const new_cfg = setup.value.into(); + if (self.cfg_num != new_cfg) { // if (self.cfg_num > 0) // deinitialize drivers - self.cfg_num = setup.value; + self.cfg_num = new_cfg; - if (self.cfg_num > 0) + if (new_cfg > 0) try self.process_set_config(device_itf, self.cfg_num - 1); } device_itf.ep_ack(.ep0); }, .GetDescriptor => { - const descriptor_type = std.meta.intToEnum(descriptor.Type, setup.value >> 8) catch null; + const descriptor_type = std.meta.intToEnum(descriptor.Type, setup.value.into() >> 8) catch null; if (descriptor_type) |dt| { try self.process_get_descriptor(device_itf, setup, dt); } }, .SetFeature => { - if (std.meta.intToEnum(types.FeatureSelector, setup.value >> 8)) |feat| { + if (std.meta.intToEnum(types.FeatureSelector, setup.value.into() >> 8)) |feat| { switch (feat) { .DeviceRemoteWakeup, .EndpointHalt => device_itf.ep_ack(.ep0), // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 @@ -387,25 +388,25 @@ pub fn DeviceController(config: Config) type { .Device => { if (config.debug) log.info(" Device", .{}); - self.send_cmd_response(device_itf, @ptrCast(&config.device_descriptor), setup.length); + self.send_cmd_response(device_itf, @ptrCast(&config.device_descriptor), setup.length.into()); }, .Configuration => { if (config.debug) log.info(" Config", .{}); - self.send_cmd_response(device_itf, @ptrCast(&config_descriptor), setup.length); + self.send_cmd_response(device_itf, @ptrCast(&config_descriptor), setup.length.into()); }, .String => { if (config.debug) log.info(" String", .{}); // String descriptor index is in bottom 8 bits of // `value`. - const i: usize = @intCast(setup.value & 0xff); + const i: u8 = @truncate(setup.value.into()); if (i >= config.string_descriptors.len) log.warn("host requested invalid string descriptor {}", .{i}) else self.send_cmd_response( device_itf, config.string_descriptors[i].data, - setup.length, + setup.length.into(), ); }, .Interface => { @@ -419,7 +420,7 @@ pub fn DeviceController(config: Config) type { // We will just copy parts of the DeviceDescriptor because // the DeviceQualifierDescriptor can be seen as a subset. const qualifier = comptime &config.device_descriptor.qualifier(); - self.send_cmd_response(device_itf, @ptrCast(qualifier), setup.length); + self.send_cmd_response(device_itf, @ptrCast(qualifier), setup.length.into()); }, else => {}, } diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 2e22d83e2..c565602cb 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -204,6 +204,9 @@ pub const Endpoint = extern struct { synchronisation: Synchronisation = .none, usage: Usage, reserved: u2 = 0, + + pub const bulk: @This() = .{ .transfer_type = .Bulk, .usage = .data }; + pub const interrupt: @This() = .{ .transfer_type = .Interrupt, .usage = .data }; }; comptime { diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 79d5ba32c..e87f3c1c5 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -95,13 +95,13 @@ pub fn CdcClassDriver(options: Options) type { }, .ep_out = .{ .endpoint = .out(@enumFromInt(first_endpoint_out)), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .attributes = .bulk, .max_packet_size = .from(options.max_packet_size), .interval = 0, }, .ep_in = .{ .endpoint = .in(@enumFromInt(first_endpoint_in + 1)), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .attributes = .bulk, .max_packet_size = .from(options.max_packet_size), .interval = 0, }, diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index 722c2d889..a00fc7dcb 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -30,19 +30,19 @@ pub const ExampleDriver = struct { }, .ep_in1 = .{ .endpoint = .in(@enumFromInt(first_endpoint_in)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .attributes = .interrupt, .max_packet_size = .from(64), .interval = 16, }, .ep_in2 = .{ .endpoint = .in(@enumFromInt(first_endpoint_in + 1)), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .attributes = .bulk, .max_packet_size = .from(64), .interval = 0, }, .ep_out = .{ .endpoint = .out(@enumFromInt(first_endpoint_out)), - .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .attributes = .bulk, .max_packet_size = .from(64), .interval = 0, }, diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index f4a055f10..93e061842 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -36,13 +36,13 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { .hid = hid_descriptor, .ep_out = .{ .endpoint = .out(@enumFromInt(first_endpoint_out)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .attributes = .interrupt, .max_packet_size = .from(options.max_packet_size), .interval = options.endpoint_interval, }, .ep_in = .{ .endpoint = .in(@enumFromInt(first_endpoint_in)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .attributes = .interrupt, .max_packet_size = .from(options.max_packet_size), .interval = options.endpoint_interval, }, @@ -78,7 +78,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { _ = self; if (stage == .Setup) switch (setup.request_type.type) { .Standard => { - const hid_desc_type = std.meta.intToEnum(descriptor.hid.Hid.Type, (setup.value >> 8) & 0xff) catch return usb.nak; + const hid_desc_type = std.meta.intToEnum(descriptor.hid.Hid.Type, setup.value.into() >> 8) catch return usb.nak; const request_code = std.meta.intToEnum(types.SetupRequest, setup.request) catch return usb.nak; if (request_code == .GetDescriptor and hid_desc_type == .Hid) diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 63c9e423d..54db44e0e 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -133,13 +133,13 @@ pub const SetupPacket = extern struct { /// conflict. request: u8, /// A simple argument of up to 16 bits, specific to the request. - value: u16, + value: U16_Le, /// Not used in the requests we support. - index: u16, + index: U16_Le, /// If data will be transferred after this request (in the direction given /// by `request_type`), this gives the number of bytes (OUT) or maximum /// number of bytes (IN). - length: u16, + length: U16_Le, }; /// Represents packet length. From 27edab3cbe6060684077b4e5248e852857041559 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 11 Jan 2026 12:04:35 -0500 Subject: [PATCH 17/80] start at ch32v usb --- port/wch/ch32v/src/hals/usb.zig | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 port/wch/ch32v/src/hals/usb.zig diff --git a/port/wch/ch32v/src/hals/usb.zig b/port/wch/ch32v/src/hals/usb.zig new file mode 100644 index 000000000..81503c046 --- /dev/null +++ b/port/wch/ch32v/src/hals/usb.zig @@ -0,0 +1,153 @@ +//! USB device implementation +//! +//! + +const std = @import("std"); +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 PHY = enum { + device, + host_device, +}; + +pub const HardwareConfig = struct { + max_endpoints_count: comptime_int = 3, // datasheet text says 16 but only has 8 registers? + max_interfaces_count: comptime_int = 2, // not sure on this + num_can_active: u2 = 0, // 0, 1 or 2, changes amount of shared SRAM + phy: PHY = .device, + buffer_size: u32 = 64, +}; + +const BufferDescription = packed struct { + const Count = packed struct { + val: u10, + res: u6, + res2: u16, + }; + address: u32, + count: Count, +}; + +const SHARED_SRAM_SIZE: comptime_int = 512; // bytes +const SHARED_SRAM_ADDR: comptime_int = 0x40006000; +pub fn Polled( + controller_config: usb.Config, + config: HardwareConfig, +) type { + const buffer_desc_size = 2 * config.max_endpoints_count * @sizeOf(BufferDescription); + // seems like we must have 2 buffers per endpoint + // they're either double buffered or tx/rx? + const buffer_size = 2 * config.max_endpoints_count * config.buffer_size; + const used_sram = buffer_desc_size + buffer_size; + comptime { + // Do hardware config validity checks here + const can_sram_size: u32 = 128 * @as(u32, @intCast(config.num_can_active)); + const available_sram = SHARED_SRAM_SIZE - can_sram_size; + + if (used_sram > available_sram) { + @compileLog("USB Buffer configuration overflows available sram"); + } + } + _ = controller_config; + // _ = config; + const USB = peripherals.USB; + const EXT = peripherals.EXTEND; + + // always putting the buffer descriptor table at the start of shared sram + const buffer_descriptor_table: *align(2) [2 * config.max_endpoints_count]BufferDescription = @ptrFromInt(SHARED_SRAM_ADDR); + const ep_buffers: *align(2) [2 * config.max_endpoints_count][config.buffer_size]u8 = @ptrFromInt(SHARED_SRAM_ADDR + buffer_desc_size); + + return struct { + const vtable: usb.DeviceInterface.VTable = .{ + .ep_writev = start_tx, + .ep_readv = start_rx, + .set_address = set_address, + .ep_open = ep_open, + }; + + pub fn poll() void { + // do the recurring work here + } + pub fn init() @This() { + // setup hardware here + // first check for 48MHz clock then enable the USB Clock in RCC_CFGR0 + const RCC = peripherals.RCC; + // RCC.CFGR0 // USBPRE + RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); + RCC.APB1PCENR.modify(.{ .USBDEN = 1 }); + USB.CNTR.modify(.{ .FRES = 1 }); // reset the peripheral if it's not already + + init_shared_sram(); + // endpoint config + + //packet buffer description table + + USB.DADDR.modify(.{ .ADD = 0, .EF = 1 }); + EXT.EXTEND_CTR.modify(.{ .USBDPU = 1 }); // enable pull up + USB.CNTR.modify(.{ .FRES = 0 }); // bring device out of reset + USB.ISTR.raw = 0; // reset all interrupt bits + // enable interrupts here if needed + USB.CNTR.modify(.{ .ESOFM = 0, .SOFM = 0, .RESETM = 0, .SUSPM = 0, .WKUPM = 0, .ERRM = 0, .PMAOVRM = 0, .CTRM = 0 }); + + return .{}; + } + + fn init_shared_sram() void { + // put table at start of sram + USB.BTABLE.modify(.{ .BTABLE = 0 }); + for (buffer_descriptor_table, ep_buffers) |*desc, *buf| { + desc.*.address = @intFromPtr(buf); + desc.*.count.val = 64; // is EP0 64 bytes? + } + } + + pub fn start_tx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, buffer: []const u8) void { + _ = self; + _ = ep_num; + _ = buffer; + } + pub fn start_rx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usize) void { + _ = self; + _ = ep_num; + _ = len; + } + pub fn ep_open(self: *@This(), desc: *const usb.descriptor.Endpoint) void { + _ = self; + // how to tell if IN or OUT endpoint? + + const epr = get_epr_reg(desc.endpoint.num); + epr.modify(.{ + .EP_TYPE = switch (desc.attributes.transfer_type) { + .Bulk => 0b00, + .Control => 0b01, + .Isochronous => 0b10, + .Interrupt => 0b11, + }, + .EP_KIND = 0, // no double buffering for now + + }); + } + pub fn set_address(self: *usb.DeviceInterface, addr: u7) void { + _ = self; + _ = addr; + } + fn get_epr_reg(ep_num: usb.types.Endpoint.Num) @TypeOf(USB.EPR0) { + return switch (ep_num) { + .ep0 => USB.EPR0, + .ep1 => USB.EPR1, + .ep2 => USB.EPR2, + .ep3 => USB.EPR3, + .ep4 => USB.EPR4, + .ep5 => USB.EPR5, + .ep6 => USB.EPR6, + .ep7 => USB.EPR7, + else => @compileError("Unsupported endpoint number"), + }; + } + }; +} From 2ed6b4dbf4a52ef3320e7edc0932801b9b8384b3 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 15:30:41 +0100 Subject: [PATCH 18/80] cleanum usb examples --- core/src/core/usb/drivers/cdc.zig | 2 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 21 +++++++++------------ examples/raspberrypi/rp2xxx/src/usb_hid.zig | 6 ++---- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index e87f3c1c5..4a9f22a03 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -80,7 +80,7 @@ pub fn CdcClassDriver(options: Options) type { }, .ep_notifi = .{ .endpoint = .in(@enumFromInt(first_endpoint_in)), - .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .attributes = .interrupt, .max_packet_size = .from(endpoint_notifi_size), .interval = 16, }, diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index e1063c41b..0cf159620 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -41,8 +41,8 @@ var usb_dev: rp2xxx.usb.Polled( .configurations = &.{.{ .num = 1, .configuration_s = 0, - .attributes = .{ .self_powered = true }, - .max_current_ma = 100, + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, .Drivers = struct { serial: USB_Serial }, }}, }, @@ -107,11 +107,10 @@ var usb_tx_buff: [1024]u8 = undefined; // Transfer data to host // NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytype) void { - const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; + var tx = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; - var write_buff = text; - while (write_buff.len > 0) { - write_buff = write_buff[serial.write(write_buff)..]; + while (tx.len > 0) { + tx = tx[serial.write(tx)..]; usb_dev.poll(); } // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them @@ -126,15 +125,13 @@ var usb_rx_buff: [1024]u8 = undefined; pub fn usb_cdc_read( serial: *USB_Serial, ) []const u8 { - var total_read: usize = 0; - var read_buff: []u8 = usb_rx_buff[0..]; + var rx_len: usize = 0; while (true) { - const len = serial.read(read_buff); - read_buff = read_buff[len..]; - total_read += len; + const len = serial.read(usb_rx_buff[rx_len..]); + rx_len += len; if (len == 0) break; } - return usb_rx_buff[0..total_read]; + return usb_rx_buff[0..rx_len]; } diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 2e66911fa..f3f7b888f 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -16,8 +16,6 @@ const HidDriver = usb.drivers.hid.HidClassDriver( usb.descriptor.hid.ReportDescriptorKeyboard, ); -const usb_config_descriptor = microzig.core.usb.descriptor.Configuration.create(); - var usb_dev: rp2xxx.usb.Polled( usb.Config{ .device_descriptor = .{ @@ -46,8 +44,8 @@ var usb_dev: rp2xxx.usb.Polled( .configurations = &.{.{ .num = 1, .configuration_s = 0, - .attributes = .{ .self_powered = true }, - .max_current_ma = 100, + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, .Drivers = struct { hid: HidDriver }, }}, }, From cd4e348342816629f8619d611ab6896b776c25c1 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 15:44:24 +0100 Subject: [PATCH 19/80] change the example usb driver to a simple echo --- core/src/core/usb.zig | 2 +- core/src/core/usb/drivers/example.zig | 85 +++++++++++++++------------ 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index d43dea4d4..2e9aad3c6 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -5,7 +5,7 @@ const log = std.log.scoped(.usb); pub const descriptor = @import("usb/descriptor.zig"); pub const drivers = struct { pub const cdc = @import("usb/drivers/cdc.zig"); - pub const example = @import("usb/drivers/example.zig"); + pub const echo = @import("usb/drivers/example.zig"); pub const hid = @import("usb/drivers/hid.zig"); }; pub const types = @import("usb/types.zig"); diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index a00fc7dcb..c5cab6b15 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -1,14 +1,14 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const descriptor = usb.descriptor; +const log = std.log.scoped(.usb_echo); -pub const ExampleDriver = struct { +/// This is an example driver that sends any data received on ep2 to ep1. +pub const EchoExampleDriver = struct { /// The descriptors need to have the same memory layout as the sent data. pub const Descriptor = extern struct { - example_interface: descriptor.Interface, - ep_in1: descriptor.Endpoint, - ep_in2: descriptor.Endpoint, - ep_out: descriptor.Endpoint, + example_interface: usb.descriptor.Interface, + ep_in: usb.descriptor.Endpoint, + ep_out: usb.descriptor.Endpoint, /// This function is used during descriptor creation. If multiple instances /// of a driver are used, a descriptor will be created for each. @@ -22,26 +22,20 @@ pub const ExampleDriver = struct { .example_interface = .{ .interface_number = first_interface, .alternate_setting = 0, - .num_endpoints = 3, - .interface_class = 0, - .interface_subclass = 0, - .interface_protocol = 0, + .num_endpoints = 2, + .interface_class = 0xFF, + .interface_subclass = 0xFF, + .interface_protocol = 0xFF, .interface_s = first_string, }, - .ep_in1 = .{ + .ep_in = .{ .endpoint = .in(@enumFromInt(first_endpoint_in)), - .attributes = .interrupt, - .max_packet_size = .from(64), - .interval = 16, - }, - .ep_in2 = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in + 1)), .attributes = .bulk, .max_packet_size = .from(64), .interval = 0, }, .ep_out = .{ - .endpoint = .out(@enumFromInt(first_endpoint_out)), + .endpoint = .out(@enumFromInt(first_endpoint_out + 1)), .attributes = .bulk, .max_packet_size = .from(64), .interval = 0, @@ -50,19 +44,27 @@ pub const ExampleDriver = struct { } }; - /// This is a mapping from endpoint descriptor field names - /// to handler function names. + /// This is a mapping from endpoint descriptor field names to handler + /// function names. Counterintuitively, usb devices send data on 'in' + /// endpoints and receive on 'out' endpoints. pub const handlers = .{ - .ep_in1 = "handler1", - .ep_in2 = "handler2", - .ep_out = "handler3", + .ep_in = "on_tx_ready", + .ep_out = "on_rx", }; + device: *usb.DeviceInterface, + ep_tx: usb.types.Endpoint.Num, + /// This function is called when the host chooses a configuration /// that contains this driver. pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - defer device.ep_listen(desc.ep_out.endpoint.num, 64); - return .{}; + const self: @This() = .{ + .device = device, + .ep_tx = desc.ep_in.endpoint.num, + }; + // Listen for first packet + device.ep_listen(desc.ep_out.endpoint.num, 64); + return self; } /// Used for configuration through endpoint 0. @@ -79,18 +81,29 @@ pub const ExampleDriver = struct { /// Each endpoint (as defined in the descriptor) has its own handler. /// Endpoint number is passed as an argument so that it does not need /// to be stored in the driver. - pub fn handler1(self: *@This(), ep_num: usb.types.Endpoint.Num) void { - _ = self; - _ = ep_num; + pub fn on_tx_ready(self: *@This(), ep_tx: usb.types.Endpoint.Num) void { + log.info("tx ready", .{}); + // Mark transmission as available + @atomicStore(usb.types.Endpoint.Num, &self.ep_tx, ep_tx, .seq_cst); } - pub fn handler2(self: *@This(), ep_num: usb.types.Endpoint.Num) void { - _ = self; - _ = ep_num; - } - - pub fn handler3(self: *@This(), ep_num: usb.types.Endpoint.Num) void { - _ = self; - _ = ep_num; + pub fn on_rx(self: *@This(), ep_rx: usb.types.Endpoint.Num) void { + var buf: [64]u8 = undefined; + // Read incoming packet into a local buffer + const len_rx = self.device.ep_readv(ep_rx, &.{&buf}); + log.info("Received: {s}", .{buf[0..len_rx]}); + // Check if we can transmit + const ep_tx = @atomicLoad(usb.types.Endpoint.Num, &self.ep_tx, .seq_cst); + if (ep_tx != .ep0) { + // Mark transmission as not available + @atomicStore(usb.types.Endpoint.Num, &self.ep_tx, .ep0, .seq_cst); + // Send received packet + log.info("Sending {} bytes", .{len_rx}); + const len_tx = self.device.ep_writev(ep_tx, &.{buf[0..len_rx]}); + if (len_tx != len_rx) + log.err("Only sent {} bytes", .{len_tx}); + } + // Listen for next packet + self.device.ep_listen(ep_rx, 64); } }; From 0959d374c706fdb23ef1e7d1b9ba459610671fe2 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 18:10:31 +0100 Subject: [PATCH 20/80] assign endpoints and interfaces (more) automatically --- core/src/core/usb.zig | 70 ++++++++++++++++++--------- core/src/core/usb/drivers/cdc.zig | 27 +++++------ core/src/core/usb/drivers/example.zig | 22 ++++----- core/src/core/usb/drivers/hid.zig | 10 ++-- 4 files changed, 75 insertions(+), 54 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 2e9aad3c6..a4cba0842 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -13,6 +13,34 @@ pub const types = @import("usb/types.zig"); pub const ack: []const u8 = ""; pub const nak: ?[]const u8 = null; +pub const DescriptorAllocator = struct { + next_ep_num: [2]u8, + next_itf_num: u8, + unique_endpoints: bool, + + pub fn init(unique_endpoints: bool) @This() { + return .{ + .next_ep_num = @splat(1), + .next_itf_num = 0, + .unique_endpoints = unique_endpoints, + }; + } + + pub fn next_ep(self: *@This(), dir: types.Dir) types.Endpoint { + const idx: u1 = @intFromEnum(dir); + const ret = self.next_ep_num[idx]; + self.next_ep_num[idx] += 1; + if (self.unique_endpoints) + self.next_ep_num[idx ^ 1] += 1; + return .{ .dir = dir, .num = @enumFromInt(ret) }; + } + + pub fn next_itf(self: *@This()) u8 { + defer self.next_itf_num += 1; + return self.next_itf_num; + } +}; + /// USB Device interface /// Any device implementation used with DeviceController must implement those functions pub const DeviceInterface = struct { @@ -77,6 +105,10 @@ pub const Config = struct { debug: bool = false, /// Currently only a single configuration is supported. configurations: []const Configuration, + /// Only use either IN or OUT on each endpoint. Useful for debugging. + /// Realistically, it should only be turned off if you are exhausting + /// the 15 endpoint limit. + unique_endpoints: bool = true, }; /// USB device controller @@ -90,39 +122,25 @@ pub fn DeviceController(config: Config) type { const driver_fields = @typeInfo(config0.Drivers).@"struct".fields; const DriverEnum = std.meta.FieldEnum(config0.Drivers); const config_descriptor = blk: { - var num_interfaces = 0; - var num_strings = 4; - var num_ep_in = 1; - var num_ep_out = 1; + var alloc: DescriptorAllocator = .init(config.unique_endpoints); + var next_string = 4; var size = @sizeOf(descriptor.Configuration); var fields: [driver_fields.len + 1]std.builtin.Type.StructField = undefined; for (driver_fields, 1..) |drv, i| { - const payload = drv.type.Descriptor.create(num_interfaces, num_strings, num_ep_in, num_ep_out); + const payload = drv.type.Descriptor.create(&alloc, next_string); const Payload = @TypeOf(payload); size += @sizeOf(Payload); - fields[i] = .{ - .name = drv.name, - .type = Payload, - .default_value_ptr = &payload, - .is_comptime = false, - .alignment = 1, - }; - for (@typeInfo(Payload).@"struct".fields) |fld| { const desc = @field(payload, fld.name); switch (fld.type) { descriptor.Interface => { - num_interfaces += 1; if (desc.interface_s != 0) - num_strings += 1; - }, - descriptor.Endpoint => switch (desc.endpoint.dir) { - .In => num_ep_in += 1, - .Out => num_ep_out += 1, + next_string += 1; }, + descriptor.Endpoint, descriptor.InterfaceAssociation, descriptor.cdc.Header, descriptor.cdc.CallManagement, @@ -133,17 +151,25 @@ pub fn DeviceController(config: Config) type { else => @compileLog(fld), } } + + fields[i] = .{ + .name = drv.name, + .type = Payload, + .default_value_ptr = &payload, + .is_comptime = false, + .alignment = 1, + }; } - if (num_strings != config.string_descriptors.len) + if (next_string != config.string_descriptors.len) @compileError(std.fmt.comptimePrint( "expected {} string descriptros, got {}", - .{ num_strings, config.string_descriptors.len }, + .{ next_string, config.string_descriptors.len }, )); const desc_cfg: descriptor.Configuration = .{ .total_length = .from(size), - .num_interfaces = num_interfaces, + .num_interfaces = alloc.next_itf_num, .configuration_value = config0.num, .configuration_s = config0.configuration_s, .attributes = config0.attributes, diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 4a9f22a03..1bebb2f87 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -44,15 +44,14 @@ pub fn CdcClassDriver(options: Options) type { ep_in: descriptor.Endpoint, pub fn create( - first_interface: u8, + alloc: *usb.DescriptorAllocator, first_string: u8, - first_endpoint_in: u4, - first_endpoint_out: u4, ) @This() { - const endpoint_notifi_size = 8; + const itf_notifi = alloc.next_itf(); + const itf_data = alloc.next_itf(); return .{ .itf_assoc = .{ - .first_interface = first_interface, + .first_interface = itf_notifi, .interface_count = 2, .function_class = 2, .function_subclass = 2, @@ -60,7 +59,7 @@ pub fn CdcClassDriver(options: Options) type { .function = 0, }, .itf_notifi = .{ - .interface_number = first_interface, + .interface_number = itf_notifi, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, @@ -71,21 +70,21 @@ pub fn CdcClassDriver(options: Options) type { .cdc_header = .{ .bcd_cdc = .from(0x0120) }, .cdc_call_mgmt = .{ .capabilities = 0, - .data_interface = first_interface + 1, + .data_interface = itf_data, }, .cdc_acm = .{ .capabilities = 6 }, .cdc_union = .{ - .master_interface = first_interface, - .slave_interface_0 = first_interface + 1, + .master_interface = itf_notifi, + .slave_interface_0 = itf_data, }, .ep_notifi = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in)), + .endpoint = alloc.next_ep(.In), .attributes = .interrupt, - .max_packet_size = .from(endpoint_notifi_size), + .max_packet_size = .from(8), .interval = 16, }, .itf_data = .{ - .interface_number = first_interface + 1, + .interface_number = itf_data, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, @@ -94,13 +93,13 @@ pub fn CdcClassDriver(options: Options) type { .interface_s = 0, }, .ep_out = .{ - .endpoint = .out(@enumFromInt(first_endpoint_out)), + .endpoint = alloc.next_ep(.Out), .attributes = .bulk, .max_packet_size = .from(options.max_packet_size), .interval = 0, }, .ep_in = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in + 1)), + .endpoint = alloc.next_ep(.In), .attributes = .bulk, .max_packet_size = .from(options.max_packet_size), .interval = 0, diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index c5cab6b15..b065eecc8 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -12,15 +12,15 @@ pub const EchoExampleDriver = struct { /// This function is used during descriptor creation. If multiple instances /// of a driver are used, a descriptor will be created for each. + /// Endpoint numbers are allocated automatically, this function should + /// use placeholder .ep0 values on all endpoints. pub fn create( - first_interface: u8, + alloc: *usb.DescriptorAllocator, first_string: u8, - first_endpoint_in: u4, - first_endpoint_out: u4, ) @This() { return .{ .example_interface = .{ - .interface_number = first_interface, + .interface_number = alloc.next_itf(), .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xFF, @@ -28,14 +28,14 @@ pub const EchoExampleDriver = struct { .interface_protocol = 0xFF, .interface_s = first_string, }, - .ep_in = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in)), + .ep_out = .{ + .endpoint = alloc.next_ep(.Out), .attributes = .bulk, .max_packet_size = .from(64), .interval = 0, }, - .ep_out = .{ - .endpoint = .out(@enumFromInt(first_endpoint_out + 1)), + .ep_in = .{ + .endpoint = alloc.next_ep(.In), .attributes = .bulk, .max_packet_size = .from(64), .interval = 0, @@ -58,13 +58,11 @@ pub const EchoExampleDriver = struct { /// This function is called when the host chooses a configuration /// that contains this driver. pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - const self: @This() = .{ + defer device.ep_listen(desc.ep_out.endpoint.num, 64); + return .{ .device = device, .ep_tx = desc.ep_in.endpoint.num, }; - // Listen for first packet - device.ep_listen(desc.ep_out.endpoint.num, 64); - return self; } /// Used for configuration through endpoint 0. diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 93e061842..3921462d4 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -18,14 +18,12 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { ep_in: descriptor.Endpoint, pub fn create( - first_interface: u8, + alloc: *usb.DescriptorAllocator, first_string: u8, - first_endpoint_in: u4, - first_endpoint_out: u4, ) @This() { return .{ .interface = .{ - .interface_number = first_interface, + .interface_number = alloc.next_itf(), .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, @@ -35,13 +33,13 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }, .hid = hid_descriptor, .ep_out = .{ - .endpoint = .out(@enumFromInt(first_endpoint_out)), + .endpoint = alloc.next_ep(.Out), .attributes = .interrupt, .max_packet_size = .from(options.max_packet_size), .interval = options.endpoint_interval, }, .ep_in = .{ - .endpoint = .in(@enumFromInt(first_endpoint_in)), + .endpoint = alloc.next_ep(.In), .attributes = .interrupt, .max_packet_size = .from(options.max_packet_size), .interval = options.endpoint_interval, From 36f02ea822c413288928a6aed4049806c813b71b Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 19:39:18 +0100 Subject: [PATCH 21/80] better handling of device class, subclass and protocol --- core/src/core/usb/descriptor.zig | 28 +-- core/src/core/usb/drivers/cdc.zig | 8 +- core/src/core/usb/drivers/example.zig | 4 +- core/src/core/usb/drivers/hid.zig | 8 +- core/src/core/usb/types.zig | 224 +++++++++++++++++--- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 6 +- examples/raspberrypi/rp2xxx/src/usb_hid.zig | 6 +- 7 files changed, 210 insertions(+), 74 deletions(-) diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index c565602cb..f7371ba99 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -29,22 +29,6 @@ pub const Type = enum(u8) { /// Describes a device. This is the most broad description in USB and is /// typically the first thing the host asks for. pub const Device = extern struct { - /// Class, subclass and protocol of device. - pub const DeviceTriple = extern struct { - /// Class of device, giving a broad functional area. - class: types.ClassCode, - /// Subclass of device, refining the class. - subclass: u8, - /// Protocol within the subclass. - protocol: u8, - - pub const unspecified: @This() = .{ - .class = .Unspecified, - .subclass = 0, - .protocol = 0, - }; - }; - /// USB Device Qualifier Descriptor /// This descriptor is a subset of the DeviceDescriptor pub const Qualifier = extern struct { @@ -59,7 +43,7 @@ pub const Device = extern struct { /// Specification version as Binary Coded Decimal bcd_usb: types.U16_Le, /// Class, subclass and protocol of device. - device_triple: DeviceTriple, + device_triple: types.ClassSubclassProtocol, /// Maximum unit of data this device can move. max_packet_size0: u8, /// Number of configurations supported by this device. @@ -79,7 +63,7 @@ pub const Device = extern struct { /// Specification version as Binary Coded Decimal bcd_usb: types.U16_Le, /// Class, subclass and protocol of device. - device_triple: DeviceTriple, + device_triple: types.ClassSubclassProtocol, /// Maximum length of data this device can move. max_packet_size0: u8, /// ID of product vendor. @@ -248,12 +232,8 @@ pub const Interface = extern struct { alternate_setting: u8, /// Number of endpoint descriptors in this interface. num_endpoints: u8, - /// Interface class code, distinguishing the type of interface. - interface_class: u8, - /// Interface subclass code, refining the class of interface. - interface_subclass: u8, - /// Protocol within the interface class/subclass. - interface_protocol: u8, + /// Interface class, subclass and protocol. + interface_triple: types.ClassSubclassProtocol, /// Index of interface name within string descriptor table. interface_s: u8, }; diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 1bebb2f87..d8e3dddca 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -62,9 +62,7 @@ pub fn CdcClassDriver(options: Options) type { .interface_number = itf_notifi, .alternate_setting = 0, .num_endpoints = 1, - .interface_class = 2, - .interface_subclass = 2, - .interface_protocol = 0, + .interface_triple = .from(.Cdc, .Abstract, .NoneRequired), .interface_s = first_string, }, .cdc_header = .{ .bcd_cdc = .from(0x0120) }, @@ -87,9 +85,7 @@ pub fn CdcClassDriver(options: Options) type { .interface_number = itf_data, .alternate_setting = 0, .num_endpoints = 2, - .interface_class = 10, - .interface_subclass = 0, - .interface_protocol = 0, + .interface_triple = .from(.CdcData, .Unused, .NoneRequired), .interface_s = 0, }, .ep_out = .{ diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index b065eecc8..0b067f5f1 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -23,9 +23,7 @@ pub const EchoExampleDriver = struct { .interface_number = alloc.next_itf(), .alternate_setting = 0, .num_endpoints = 2, - .interface_class = 0xFF, - .interface_subclass = 0xFF, - .interface_protocol = 0xFF, + .interface_triple = .vendor_specific, .interface_s = first_string, }, .ep_out = .{ diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 3921462d4..5f9f8dc2b 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -26,9 +26,11 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { .interface_number = alloc.next_itf(), .alternate_setting = 0, .num_endpoints = 2, - .interface_class = 3, - .interface_subclass = @intFromBool(options.boot_protocol), - .interface_protocol = @intFromBool(options.boot_protocol), + .interface_triple = .from( + .Hid, + if (options.boot_protocol) .Boot else .Unspecified, + if (options.boot_protocol) .Boot else .None, + ), .interface_s = first_string, }, .hid = hid_descriptor, diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 54db44e0e..c5e018f07 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -1,34 +1,202 @@ const std = @import("std"); const assert = std.debug.assert; -/// Class of device, giving a broad functional area. -pub const ClassCode = enum(u8) { - Unspecified = 0x00, - Audio = 0x01, - Cdc = 0x02, - Hid = 0x03, - Physical = 0x05, - Image = 0x06, - Printer = 0x07, - MassStorage = 0x08, - Hub = 0x09, - CdcData = 0x0A, - SmartCard = 0x0B, - ContentSecurity = 0x0D, - Video = 0x0E, - PersonalHealthcare = 0x0F, - AudioVideoDevice = 0x10, - BillboardDevice = 0x11, - USBTypeCBridge = 0x12, - USBBulkDisplayProtocol = 0x13, - MCTPoverUSBProtocolEndpoint = 0x14, - I3C = 0x3C, - DiagnosticDevice = 0xDC, - WirelessController = 0xE0, - Miscellaneous = 0xEF, - ApplicationSpecific = 0xFE, - VendorSpecific = 0xFF, - _, +pub const ClassSubclassProtocol = extern struct { + /// Class of device, giving a broad functional area. + pub const ClassCode = enum(u8) { + Unspecified = 0x00, + Audio = 0x01, + Cdc = 0x02, + Hid = 0x03, + Physical = 0x05, + Image = 0x06, + Printer = 0x07, + MassStorage = 0x08, + Hub = 0x09, + CdcData = 0x0A, + SmartCard = 0x0B, + ContentSecurity = 0x0D, + Video = 0x0E, + PersonalHealthcare = 0x0F, + AudioVideoDevice = 0x10, + BillboardDevice = 0x11, + USBTypeCBridge = 0x12, + USBBulkDisplayProtocol = 0x13, + MCTPoverUSBProtocolEndpoint = 0x14, + I3C = 0x3C, + DiagnosticDevice = 0xDC, + WirelessController = 0xE0, + Miscellaneous = 0xEF, + ApplicationSpecific = 0xFE, + VendorSpecific = 0xFF, + _, + + pub fn Subclass(self: @This()) type { + const name = "Subclass" ++ @tagName(self); + return if (@hasDecl(ClassSubclassProtocol, name)) + @field(ClassSubclassProtocol, name) + else + ClassSubclassProtocol.SubclassDefault; + } + + pub fn Protocol(self: @This()) type { + const name = "Protocol" ++ @tagName(self); + return if (@hasDecl(ClassSubclassProtocol, name)) + @field(ClassSubclassProtocol, name) + else + ClassSubclassProtocol.ProtocolDefault; + } + }; + + pub const SubclassDefault = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + _, + }; + + pub const ProtocolDefault = enum(u8) { + NoneRequired = 0x00, + VendorSpecific = 0xFF, + _, + }; + + pub const SubclassCdc = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + /// Direct Line Control Model + DirectLine = 0x01, + /// Abstract Control Model + Abstract = 0x02, + /// Telephone Control Model + Telephone = 0x03, + /// Multi-Channel Control Model + MultChannel = 0x04, + /// CAPI Control Model + CAPI = 0x05, + /// Ethernet Networking Control Model + Ethernet = 0x06, + /// ATM Networking Control Model + ATM_Networking = 0x07, + /// Wireless Handset Control Model + WirelessHeadest = 0x08, + /// Device Management + DeviceManagement = 0x09, + /// Mobile Direct Line Model + MobileDirect = 0x0A, + /// OBEX + OBEX = 0x0B, + /// Ethernet Emulation Model + EthernetEmulation = 0x0C, + /// Network Control Model + Network = 0x0D, + _, + }; + + pub const ProtocolCdc = enum(u8) { + /// USB specification No class specific protocol required + NoneRequired = 0x00, + /// ITU-T V.250 AT Commands: V.250 etc + AT_ITU_T_V_250 = 0x01, + /// PCCA-101 AT Commands defined by PCCA-101 + AT_PCCA_101 = 0x02, + /// PCCA-101 AT Commands defined by PCCA-101 & Annex O + AT_PCCA_101_O = 0x03, + /// GSM 7.07 AT Commands defined by GSM 07.07 + AT_GSM_7_07 = 0x04, + /// 3GPP 27.07 AT Commands defined by 3GPP 27.007 + AT_3GPP_27_07 = 0x05, + /// C-S0017-0 AT Commands defined by TIA for CDMA + AT_C_S0017_0 = 0x06, + /// USB EEM Ethernet Emulation module + USB_EEM = 0x07, + /// External Protocol: Commands defined by Command Set Functional Descriptor + External = 0xFE, + /// USB Specification Vendor-specific + VendorSpecific = 0xFF, + _, + }; + + pub const SubclassCdcData = enum(u8) { + Unused = 0, + VendorSpecific = 0xFF, + _, + }; + + pub const ProtocolCdcData = enum(u8) { + NoneRequired = 0, + VendorSpecific = 0xFF, + /// Network Transfer Block + NetworkTransferBlock = 0x01, + /// Physical interface protocol for ISDN BRI + ISDN_BRI = 0x30, + /// HDLC + HDLC = 0x31, + /// Transparent + Transparent = 0x32, + /// Management protocol for Q.921 data link protocol + Management_Q_921 = 0x50, + /// Data link protocol for Q.931 + DataLink_Q_931 = 0x51, + /// TEI-multiplexor for Q.921 data link protocol + TEI_Multiplexor_Q_921 = 0x52, + /// Data compression procedures + DataCompressionProcedures = 0x90, + /// Euro-ISDN protocol control + Euro_ISDN = 0x91, + /// V.24 rate adaptation to ISDN + RateAdaptation_V_24 = 0x92, + /// CAPI Commands + CAPI = 0x93, + /// Host based driver. Note: This protocol code should only be used + /// in messages between host and device to identify the host driver + /// portion of a protocol stack. + HostBasedDriver = 0xFD, + /// CDC Specification The protocol(s) are described using a Protocol + /// Unit Functional Descriptors on Communications Class Interface + SpecifiedIn_PUF_Descriptor = 0xFE, + _, + }; + + pub const SubclassHid = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + /// + Boot = 0x01, + _, + }; + + pub const ProtocolHid = enum(u8) { + NoneRequired = 0x00, + VendorSpecific = 0xFF, + /// + Boot = 0x01, + _, + }; + + /// Class code, distinguishing the type of interface. + class: ClassCode, + /// Interface subclass code, refining the class of interface. + subclass: u8, + /// Protocol within the interface class/subclass. + protocol: u8, + + pub const unspecified: @This() = + .from(.Unspecified, .Unspecified, .NoneRequired); + + pub const vendor_specific: @This() = + .from(.VendorSpecific, .VendorSpecific, .VendorSpecific); + + pub fn from( + comptime class: ClassCode, + subclass: class.Subclass(), + protocol: class.Protocol(), + ) @This() { + return .{ + .class = class, + .subclass = @intFromEnum(subclass), + .protocol = @intFromEnum(protocol), + }; + } }; /// Types of transfer that can be indicated by the `attributes` field on `EndpointDescriptor`. diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 0cf159620..6905367fe 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -17,11 +17,7 @@ var usb_dev: rp2xxx.usb.Polled( usb.Config{ .device_descriptor = .{ .bcd_usb = .from(0x0200), - .device_triple = .{ - .class = .Miscellaneous, - .subclass = 2, - .protocol = 1, - }, + .device_triple = .from(.Miscellaneous, @enumFromInt(2), @enumFromInt(1)), .max_packet_size0 = 64, .vendor = .from(0x2E8A), .product = .from(0x000A), diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index f3f7b888f..cded00432 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -20,11 +20,7 @@ var usb_dev: rp2xxx.usb.Polled( usb.Config{ .device_descriptor = .{ .bcd_usb = .from(0x0200), - .device_triple = .{ - .class = .Unspecified, - .subclass = 0, - .protocol = 0, - }, + .device_triple = .unspecified, .max_packet_size0 = 64, .vendor = .from(0x2E8A), .product = .from(0x000A), From b7bed055f72ace83f62e3f58e8935e628bb1c187 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 20:22:19 +0100 Subject: [PATCH 22/80] separate usb device and controller --- core/src/core/usb.zig | 11 ++++ examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 69 ++++++++++----------- examples/raspberrypi/rp2xxx/src/usb_hid.zig | 65 ++++++++++--------- port/raspberrypi/rp2xxx/src/hal/usb.zig | 17 +++-- 4 files changed, 84 insertions(+), 78 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index a4cba0842..a496daa64 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -111,6 +111,17 @@ pub const Config = struct { unique_endpoints: bool = true, }; +pub fn validate_controller(T: type) void { + comptime { + const info = @typeInfo(T); + if (info != .pointer or info.pointer.is_const or info.pointer.size != .one) + @compileError("Expected a mutable pointer to the usb controller, got: " ++ @typeName(T)); + const Controller = info.pointer.child; + _ = Controller; + // More checks + } +} + /// USB device controller /// /// This code handles usb enumeration and configuration and routes packets to drivers. diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 6905367fe..211d4728a 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -13,37 +13,36 @@ const uart_tx_pin = gpio.num(0); const USB_Serial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); -var usb_dev: rp2xxx.usb.Polled( - usb.Config{ - .device_descriptor = .{ - .bcd_usb = .from(0x0200), - .device_triple = .from(.Miscellaneous, @enumFromInt(2), @enumFromInt(1)), - .max_packet_size0 = 64, - .vendor = .from(0x2E8A), - .product = .from(0x000A), - .bcd_device = .from(0x0100), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .string_descriptors = &.{ - .from_lang(.English), - .from_str("Raspberry Pi"), - .from_str("Pico Test Device"), - .from_str("someserial"), - .from_str("Board CDC"), - }, - .configurations = &.{.{ - .num = 1, - .configuration_s = 0, - .attributes = .{ .self_powered = false }, - .max_current_ma = 50, - .Drivers = struct { serial: USB_Serial }, - }}, +var usb_device: rp2xxx.usb.Polled(.{}) = undefined; + +var usb_controller: usb.DeviceController(.{ + .device_descriptor = .{ + .bcd_usb = .from(0x0200), + .device_triple = .from(.Miscellaneous, @enumFromInt(2), @enumFromInt(1)), + .max_packet_size0 = 64, + .vendor = .from(0x2E8A), + .product = .from(0x000A), + .bcd_device = .from(0x0100), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, }, - .{}, -) = undefined; + .string_descriptors = &.{ + .from_lang(.English), + .from_str("Raspberry Pi"), + .from_str("Pico Test Device"), + .from_str("someserial"), + .from_str("Board CDC"), + }, + .configurations = &.{.{ + .num = 1, + .configuration_s = 0, + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, + .Drivers = struct { serial: USB_Serial }, + }}, +}) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -68,7 +67,7 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev = .init(); + usb_device = .init(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -76,9 +75,9 @@ pub fn main() !void { var i: u32 = 0; while (true) { // You can now poll for USB events - usb_dev.poll(); + usb_device.poll(&usb_controller); - if (usb_dev.controller.drivers()) |drivers| { + if (usb_controller.drivers()) |drivers| { new = time.get_time_since_boot().to_us(); if (new - old > 500000) { old = new; @@ -107,11 +106,11 @@ pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytyp while (tx.len > 0) { tx = tx[serial.write(tx)..]; - usb_dev.poll(); + usb_device.poll(&usb_controller); } // Short messages are not sent right away; instead, they accumulate in a buffer, so we have to force a flush to send them while (!serial.flush()) - usb_dev.poll(); + usb_device.poll(&usb_controller); } var usb_rx_buff: [1024]u8 = undefined; diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index cded00432..8a9b4ba43 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -16,37 +16,36 @@ const HidDriver = usb.drivers.hid.HidClassDriver( usb.descriptor.hid.ReportDescriptorKeyboard, ); -var usb_dev: rp2xxx.usb.Polled( - usb.Config{ - .device_descriptor = .{ - .bcd_usb = .from(0x0200), - .device_triple = .unspecified, - .max_packet_size0 = 64, - .vendor = .from(0x2E8A), - .product = .from(0x000A), - .bcd_device = .from(0x0100), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .string_descriptors = &.{ - .from_lang(.English), - .from_str("Raspberry Pi"), - .from_str("Pico Test Device"), - .from_str("someserial"), - .from_str("Boot Keyboard"), - }, - .configurations = &.{.{ - .num = 1, - .configuration_s = 0, - .attributes = .{ .self_powered = false }, - .max_current_ma = 50, - .Drivers = struct { hid: HidDriver }, - }}, +var usb_device: rp2xxx.usb.Polled(.{}) = undefined; + +var usb_controller: usb.DeviceController(.{ + .device_descriptor = .{ + .bcd_usb = .from(0x0200), + .device_triple = .unspecified, + .max_packet_size0 = 64, + .vendor = .from(0x2E8A), + .product = .from(0x000A), + .bcd_device = .from(0x0100), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }, + .string_descriptors = &.{ + .from_lang(.English), + .from_str("Raspberry Pi"), + .from_str("Pico Test Device"), + .from_str("someserial"), + .from_str("Boot Keyboard"), }, - .{}, -) = undefined; + .configurations = &.{.{ + .num = 1, + .configuration_s = 0, + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, + .Drivers = struct { hid: HidDriver }, + }}, +}) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { std.log.err("panic: {s}", .{message}); @@ -71,15 +70,15 @@ pub fn main() !void { led.put(1); // Then initialize the USB device using the configuration defined above - usb_dev = .init(); + usb_device = .init(); var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; while (true) { // You can now poll for USB events - usb_dev.poll(); + usb_device.poll(&usb_controller); - if (usb_dev.controller.drivers()) |drivers| { + if (usb_controller.drivers()) |drivers| { _ = drivers; // TODO new = time.get_time_since_boot().to_us(); diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 145ae1b01..1e668c3a7 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -74,10 +74,7 @@ const endpoint_control: *volatile [15]PerEndpoint(EndpointControlMimo) = @ptrCas /// A set of functions required by the abstract USB impl to /// create a concrete one. -pub fn Polled( - controller_config: usb.Config, - config: Config, -) type { +pub fn Polled(config: Config) type { comptime { if (config.max_endpoints_count > RP2XXX_MAX_ENDPOINTS_COUNT) @compileError("RP2XXX USB endpoints number can't be grater than RP2XXX_MAX_ENDPOINTS_COUNT"); @@ -94,10 +91,11 @@ pub fn Polled( endpoints: [config.max_endpoints_count][2]HardwareEndpointData, data_buffer: []align(64) u8, - controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, - pub fn poll(self: *@This()) void { + pub fn poll(self: *@This(), controller: anytype) void { + comptime usb.validate_controller(@TypeOf(controller)); + // Check which interrupt flags are set. const ints = peripherals.USB.INTS.read(); @@ -109,7 +107,7 @@ pub fn Polled( buffer_control[0].in.modify(.{ .PID_0 = 0 }); const setup = get_setup_packet(); - self.controller.on_setup_req(&self.interface, &setup); + controller.on_setup_req(&self.interface, &setup); } var buff_status: u32 = 0; @@ -145,7 +143,7 @@ pub fn Polled( // than the buffer size. const len = buffer_control[ep_num].get(ep.dir).read().LENGTH_0; - self.controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); + controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); } } @@ -155,7 +153,7 @@ pub fn Polled( peripherals.USB.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); set_address(&self.interface, 0); - self.controller.on_bus_reset(); + controller.on_bus_reset(); } } @@ -223,7 +221,6 @@ pub fn Polled( .endpoints = undefined, .data_buffer = rp2xxx_buffers.data_buffer, .interface = .{ .vtable = &vtable }, - .controller = .init, }; @memset(std.mem.asBytes(&self.endpoints), 0); From 93cbc874e4d3009e02406183d072bea2dd58abf7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 20:51:12 +0100 Subject: [PATCH 23/80] more convenient descriptor creation --- core/src/core/usb.zig | 7 ++++- core/src/core/usb/descriptor.zig | 30 ++++++++++++++++++--- core/src/core/usb/drivers/cdc.zig | 22 +++------------ core/src/core/usb/drivers/example.zig | 15 +++-------- core/src/core/usb/drivers/hid.zig | 18 +++---------- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 1 + examples/raspberrypi/rp2xxx/src/usb_hid.zig | 3 ++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 24 +++++++---------- 8 files changed, 56 insertions(+), 64 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index a496daa64..db9692a7f 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -109,6 +109,8 @@ pub const Config = struct { /// Realistically, it should only be turned off if you are exhausting /// the 15 endpoint limit. unique_endpoints: bool = true, + /// Device specific, either 8, 16, 32 or 64. + max_supported_packet_size: types.Len, }; pub fn validate_controller(T: type) void { @@ -133,6 +135,9 @@ pub fn DeviceController(config: Config) type { const driver_fields = @typeInfo(config0.Drivers).@"struct".fields; const DriverEnum = std.meta.FieldEnum(config0.Drivers); const config_descriptor = blk: { + const max_psize = config.max_supported_packet_size; + assert(max_psize == 8 or max_psize == 16 or max_psize == 32 or max_psize == 64); + var alloc: DescriptorAllocator = .init(config.unique_endpoints); var next_string = 4; @@ -140,7 +145,7 @@ pub fn DeviceController(config: Config) type { var fields: [driver_fields.len + 1]std.builtin.Type.StructField = undefined; for (driver_fields, 1..) |drv, i| { - const payload = drv.type.Descriptor.create(&alloc, next_string); + const payload = drv.type.Descriptor.create(&alloc, next_string, max_psize); const Payload = @TypeOf(payload); size += @sizeOf(Payload); diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index f7371ba99..b46be3d9c 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -188,9 +188,6 @@ pub const Endpoint = extern struct { synchronisation: Synchronisation = .none, usage: Usage, reserved: u2 = 0, - - pub const bulk: @This() = .{ .transfer_type = .Bulk, .usage = .data }; - pub const interrupt: @This() = .{ .transfer_type = .Interrupt, .usage = .data }; }; comptime { @@ -212,6 +209,33 @@ pub const Endpoint = extern struct { /// Interval for polling interrupt/isochronous endpoints (which we don't /// currently support) in milliseconds. interval: u8, + + pub fn control(ep: types.Endpoint, max_packet_size: types.Len) @This() { + return .{ + .endpoint = ep, + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .max_packet_size = .from(max_packet_size), + .interval = 0, // Unused for bulk endpoints + }; + } + + pub fn bulk(ep: types.Endpoint, max_packet_size: types.Len) @This() { + return .{ + .endpoint = ep, + .attributes = .{ .transfer_type = .Bulk, .usage = .data }, + .max_packet_size = .from(max_packet_size), + .interval = 0, // Unused for bulk endpoints + }; + } + + pub fn interrupt(ep: types.Endpoint, max_packet_size: types.Len, poll_interval: u8) @This() { + return .{ + .endpoint = ep, + .attributes = .{ .transfer_type = .Interrupt, .usage = .data }, + .max_packet_size = .from(max_packet_size), + .interval = poll_interval, + }; + } }; /// Description of an interface within a configuration. diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index d8e3dddca..0a1878986 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -46,6 +46,7 @@ pub fn CdcClassDriver(options: Options) type { pub fn create( alloc: *usb.DescriptorAllocator, first_string: u8, + max_supported_packet_size: usb.types.Len, ) @This() { const itf_notifi = alloc.next_itf(); const itf_data = alloc.next_itf(); @@ -75,12 +76,7 @@ pub fn CdcClassDriver(options: Options) type { .master_interface = itf_notifi, .slave_interface_0 = itf_data, }, - .ep_notifi = .{ - .endpoint = alloc.next_ep(.In), - .attributes = .interrupt, - .max_packet_size = .from(8), - .interval = 16, - }, + .ep_notifi = .interrupt(alloc.next_ep(.In), 8, 16), .itf_data = .{ .interface_number = itf_data, .alternate_setting = 0, @@ -88,18 +84,8 @@ pub fn CdcClassDriver(options: Options) type { .interface_triple = .from(.CdcData, .Unused, .NoneRequired), .interface_s = 0, }, - .ep_out = .{ - .endpoint = alloc.next_ep(.Out), - .attributes = .bulk, - .max_packet_size = .from(options.max_packet_size), - .interval = 0, - }, - .ep_in = .{ - .endpoint = alloc.next_ep(.In), - .attributes = .bulk, - .max_packet_size = .from(options.max_packet_size), - .interval = 0, - }, + .ep_out = .bulk(alloc.next_ep(.Out), max_supported_packet_size), + .ep_in = .bulk(alloc.next_ep(.In), max_supported_packet_size), }; } }; diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index 0b067f5f1..88eb4b2ff 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -17,6 +17,7 @@ pub const EchoExampleDriver = struct { pub fn create( alloc: *usb.DescriptorAllocator, first_string: u8, + max_supported_packet_size: usb.types.Len, ) @This() { return .{ .example_interface = .{ @@ -26,18 +27,8 @@ pub const EchoExampleDriver = struct { .interface_triple = .vendor_specific, .interface_s = first_string, }, - .ep_out = .{ - .endpoint = alloc.next_ep(.Out), - .attributes = .bulk, - .max_packet_size = .from(64), - .interval = 0, - }, - .ep_in = .{ - .endpoint = alloc.next_ep(.In), - .attributes = .bulk, - .max_packet_size = .from(64), - .interval = 0, - }, + .ep_out = .bulk(alloc.next_ep(.Out), max_supported_packet_size), + .ep_in = .bulk(alloc.next_ep(.In), max_supported_packet_size), }; } }; diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 5f9f8dc2b..a9ba15780 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -4,9 +4,8 @@ const descriptor = usb.descriptor; const types = usb.types; pub const Options = struct { - max_packet_size: u16, boot_protocol: bool, - endpoint_interval: u8, + poll_interval: u8, }; pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { @@ -20,6 +19,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { pub fn create( alloc: *usb.DescriptorAllocator, first_string: u8, + max_supported_packet_size: usb.types.Len, ) @This() { return .{ .interface = .{ @@ -34,18 +34,8 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { .interface_s = first_string, }, .hid = hid_descriptor, - .ep_out = .{ - .endpoint = alloc.next_ep(.Out), - .attributes = .interrupt, - .max_packet_size = .from(options.max_packet_size), - .interval = options.endpoint_interval, - }, - .ep_in = .{ - .endpoint = alloc.next_ep(.In), - .attributes = .interrupt, - .max_packet_size = .from(options.max_packet_size), - .interval = options.endpoint_interval, - }, + .ep_out = .interrupt(alloc.next_ep(.Out), max_supported_packet_size, options.poll_interval), + .ep_in = .interrupt(alloc.next_ep(.In), max_supported_packet_size, options.poll_interval), }; } }; diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 211d4728a..c127c44c7 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -42,6 +42,7 @@ var usb_controller: usb.DeviceController(.{ .max_current_ma = 50, .Drivers = struct { serial: USB_Serial }, }}, + .max_supported_packet_size = @TypeOf(usb_device).max_supported_packet_size, }) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 8a9b4ba43..29d127e91 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -12,7 +12,7 @@ const uart = rp2xxx.uart.instance.num(0); const uart_tx_pin = gpio.num(0); const HidDriver = usb.drivers.hid.HidClassDriver( - .{ .max_packet_size = 64, .boot_protocol = true, .endpoint_interval = 0 }, + .{ .boot_protocol = true, .poll_interval = 0 }, usb.descriptor.hid.ReportDescriptorKeyboard, ); @@ -45,6 +45,7 @@ var usb_controller: usb.DeviceController(.{ .max_current_ma = 50, .Drivers = struct { hid: HidDriver }, }}, + .max_supported_packet_size = @TypeOf(usb_device).max_supported_packet_size, }) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 1e668c3a7..e2bbabafa 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -81,6 +81,8 @@ pub fn Polled(config: Config) type { } return struct { + pub const max_supported_packet_size = 64; + const vtable: usb.DeviceInterface.VTable = .{ .ep_writev = ep_writev, .ep_readv = ep_readv, @@ -224,18 +226,8 @@ pub fn Polled(config: Config) type { }; @memset(std.mem.asBytes(&self.endpoints), 0); - ep_open(&self.interface, &.{ - .endpoint = .in(.ep0), - .max_packet_size = .from(64), - .attributes = .{ .transfer_type = .Control, .usage = .data }, - .interval = 0, - }); - ep_open(&self.interface, &.{ - .endpoint = .out(.ep0), - .max_packet_size = .from(64), - .attributes = .{ .transfer_type = .Control, .usage = .data }, - .interval = 0, - }); + ep_open(&self.interface, &.control(.in(.ep0), max_supported_packet_size)); + ep_open(&self.interface, &.control(.out(.ep0), max_supported_packet_size)); // Present full-speed device by enabling pullup on DP. This is the point // where the host will notice our presence. @@ -321,18 +313,20 @@ pub fn Polled(config: Config) type { } fn ep_listen( - _: *usb.DeviceInterface, + itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usb.types.Len, ) void { + const self: *@This() = @fieldParentPtr("interface", itf); const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].out; var bufctrl = bufctrl_ptr.read(); + const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); assert(bufctrl.AVAILABLE_0 == 0); // Configure the OUT: bufctrl.PID_0 ^= 1; // Flip DATA0/1 bufctrl.FULL_0 = 0; // Buffer is NOT full, we want the computer to fill it - bufctrl.LENGTH_0 = @intCast(@min(len, 64)); // Up tho this many bytes + bufctrl.LENGTH_0 = @intCast(@min(len, ep.data_buffer.len)); // Up tho this many bytes if (config.sync_noops != 0) { bufctrl_ptr.write(bufctrl); @@ -384,7 +378,7 @@ pub fn Polled(config: Config) type { const ep = desc.endpoint; const ep_hard = self.hardware_endpoint_get_by_address(ep); - assert(desc.max_packet_size.into() <= 64); + assert(desc.max_packet_size.into() <= max_supported_packet_size); buffer_control[@intFromEnum(ep.num)].get(ep.dir).modify(.{ .PID_0 = 1 }); From 55921cc00c41b2e4c0574be19abde28859a9ca45 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 21:32:17 +0100 Subject: [PATCH 24/80] handle bus resets correctly --- core/src/core/usb.zig | 59 ++++++++++++++++--------- core/src/core/usb/drivers/cdc.zig | 6 +-- core/src/core/usb/drivers/example.zig | 11 +++-- core/src/core/usb/drivers/hid.zig | 4 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 7 ++- 5 files changed, 55 insertions(+), 32 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index db9692a7f..b7a2a5e93 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -294,7 +294,7 @@ pub fn DeviceController(config: Config) type { self.driver_last = null; switch (setup.request_type.recipient) { - .Device => try self.process_setup_request(device_itf, setup), + .Device => self.process_setup_request(device_itf, setup), .Interface => switch (@as(u8, @truncate(setup.index.into()))) { inline else => |itf_num| if (itf_num < handlers.itf.len) { const drv = handlers.itf[itf_num]; @@ -350,9 +350,11 @@ pub fn DeviceController(config: Config) type { } /// Called by the device implementation on bus reset. - pub fn on_bus_reset(self: *@This()) void { + pub fn on_bus_reset(self: *@This(), device_itf: *DeviceInterface) void { if (config.debug) log.info("bus reset", .{}); + self.process_set_config(device_itf, 0); + // Reset our state. self.* = .init; } @@ -377,7 +379,7 @@ pub fn DeviceController(config: Config) type { }; } - fn process_setup_request(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) !void { + fn process_setup_request(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) void { switch (setup.request_type.type) { .Class => { //const itfIndex = setup.index & 0x00ff; @@ -386,28 +388,19 @@ pub fn DeviceController(config: Config) type { .Standard => { switch (std.meta.intToEnum(types.SetupRequest, setup.request) catch return) { .SetAddress => { + if (config.debug) log.info(" SetAddress: {}", .{self.new_address.?}); self.new_address = @truncate(setup.value.into()); device_itf.ep_ack(.ep0); - if (config.debug) log.info(" SetAddress: {}", .{self.new_address.?}); }, .SetConfiguration => { if (config.debug) log.info(" SetConfiguration", .{}); - const new_cfg = setup.value.into(); - if (self.cfg_num != new_cfg) { - // if (self.cfg_num > 0) - // deinitialize drivers - - self.cfg_num = new_cfg; - - if (new_cfg > 0) - try self.process_set_config(device_itf, self.cfg_num - 1); - } + self.process_set_config(device_itf, setup.value.into()); device_itf.ep_ack(.ep0); }, .GetDescriptor => { const descriptor_type = std.meta.intToEnum(descriptor.Type, setup.value.into() >> 8) catch null; if (descriptor_type) |dt| { - try self.process_get_descriptor(device_itf, setup, dt); + self.process_get_descriptor(device_itf, setup, dt); } }, .SetFeature => { @@ -425,7 +418,7 @@ pub fn DeviceController(config: Config) type { } } - fn process_get_descriptor(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket, descriptor_type: descriptor.Type) !void { + fn process_get_descriptor(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket, descriptor_type: descriptor.Type) void { switch (descriptor_type) { .Device => { if (config.debug) log.info(" Device", .{}); @@ -468,16 +461,40 @@ pub fn DeviceController(config: Config) type { } } - fn process_set_config(self: *@This(), device_itf: *DeviceInterface, _: u16) !void { + fn process_set_config(self: *@This(), device_itf: *DeviceInterface, cfg_num: u16) void { + if (cfg_num == self.cfg_num) return; + + if (self.driver_data) |data_old| { + _ = data_old; + // deinitialize drivers + } + + self.cfg_num = cfg_num; + if (cfg_num == 0) { + self.driver_data = null; + return; + } + // We support just one config for now so ignore config index self.driver_data = @as(config0.Drivers, undefined); inline for (driver_fields) |fld_drv| { const cfg = @field(config_descriptor, fld_drv.name); - @field(self.driver_data.?, fld_drv.name) = .init(&cfg, device_itf); + const desc_fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; + + // Open OUT endpoint first so that the driver can call ep_listen in init + inline for (desc_fields) |fld| { + const desc = &@field(cfg, fld.name); + if (comptime fld.type == descriptor.Endpoint and desc.endpoint.dir == .Out) + device_itf.ep_open(desc); + } + + @field(self.driver_data.?, fld_drv.name).init(&cfg, device_itf); - inline for (@typeInfo(@TypeOf(cfg)).@"struct".fields) |fld| { - if (comptime fld.type == descriptor.Endpoint) - device_itf.ep_open(&@field(cfg, fld.name)); + // Open IN endpoint last so that callbacks can happen + inline for (desc_fields) |fld| { + const desc = &@field(cfg, fld.name); + if (comptime fld.type == descriptor.Endpoint and desc.endpoint.dir == .In) + device_itf.ep_open(desc); } } } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 0a1878986..918a12fff 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -166,9 +166,8 @@ pub fn CdcClassDriver(options: Options) type { return true; } - pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - defer device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); - return .{ + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + self.* = .{ .device = device, .ep_notifi = desc.ep_notifi.endpoint.num, .line_coding = .{ @@ -187,6 +186,7 @@ pub fn CdcClassDriver(options: Options) type { .tx_data = undefined, .tx_end = 0, }; + device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); } pub fn class_control(self: *@This(), stage: types.ControlStage, setup: *const types.SetupPacket) ?[]const u8 { diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index 88eb4b2ff..fc92190bc 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -45,13 +45,16 @@ pub const EchoExampleDriver = struct { ep_tx: usb.types.Endpoint.Num, /// This function is called when the host chooses a configuration - /// that contains this driver. - pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - defer device.ep_listen(desc.ep_out.endpoint.num, 64); - return .{ + /// that contains this driver. `self` points to undefined memory. + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + self.* = .{ .device = device, .ep_tx = desc.ep_in.endpoint.num, }; + device.ep_listen( + desc.ep_out.endpoint.num, + @intCast(desc.ep_out.max_packet_size.into()), + ); } /// Used for configuration through endpoint 0. diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index a9ba15780..b092060b1 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -56,8 +56,8 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { ep_in: types.Endpoint.Num, ep_out: types.Endpoint.Num, - pub fn init(desc: *const Descriptor, device: *usb.DeviceInterface) @This() { - return .{ + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + self.* = .{ .device = device, .ep_in = desc.ep_in.endpoint.num, .ep_out = desc.ep_out.endpoint.num, diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index e2bbabafa..4b37b8017 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -151,11 +151,14 @@ pub fn Polled(config: Config) type { // Has the host signaled a bus reset? if (ints.BUS_RESET != 0) { + // Abort all endpoints + peripherals.USB.EP_ABORT.raw = 0xFFFFFFFF; // Acknowledge by writing the write-one-to-clear status bit. peripherals.USB.SIE_STATUS.modify(.{ .BUS_RESET = 1 }); set_address(&self.interface, 0); - - controller.on_bus_reset(); + controller.on_bus_reset(&self.interface); + while (peripherals.USB.EP_ABORT_DONE.raw != 0xFFFFFFFF) {} + peripherals.USB.EP_ABORT.raw = 0; } } From f373714ae1b81fe475101d3e0b055cabcf296786 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 17 Jan 2026 00:40:10 -0500 Subject: [PATCH 25/80] fix startup --- port/wch/ch32v/src/cpus/main.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/port/wch/ch32v/src/cpus/main.zig b/port/wch/ch32v/src/cpus/main.zig index 593ac98d2..876335a33 100644 --- a/port/wch/ch32v/src/cpus/main.zig +++ b/port/wch/ch32v/src/cpus/main.zig @@ -314,8 +314,13 @@ pub const startup_logic = struct { cpu_impl.system_init(microzig.chip); } - export fn _reset_vector() linksection("microzig_flash_start") callconv(.naked) void { - asm volatile ("j _start"); + comptime { + asm ( + \\.section microzig_flash_start, "ax" + \\.global _reset_vector + \\_reset_vector: + \\j _start + ); } }; From bbe968ff7a646c78749273ab834f489b968195c0 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Tue, 13 Jan 2026 22:00:13 +0100 Subject: [PATCH 26/80] more examples cleanup --- core/src/core/usb/drivers/example.zig | 2 +- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 34 ++++++++++--------- examples/raspberrypi/rp2xxx/src/usb_hid.zig | 36 +++++++++++---------- port/raspberrypi/rp2xxx/src/hal/usb.zig | 1 + 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index fc92190bc..b40f8051f 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -7,8 +7,8 @@ pub const EchoExampleDriver = struct { /// The descriptors need to have the same memory layout as the sent data. pub const Descriptor = extern struct { example_interface: usb.descriptor.Interface, - ep_in: usb.descriptor.Endpoint, ep_out: usb.descriptor.Endpoint, + ep_in: usb.descriptor.Endpoint, /// This function is used during descriptor creation. If multiple instances /// of a driver are used, a descriptor will be created for each. diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index c127c44c7..03257af82 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -4,22 +4,17 @@ const microzig = @import("microzig"); const rp2xxx = microzig.hal; const time = rp2xxx.time; const gpio = rp2xxx.gpio; - const usb = microzig.core.usb; +const USB_Device = rp2xxx.usb.Polled(.{}); +const USB_Serial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = USB_Device.max_supported_packet_size }); -const led = gpio.num(25); -const uart = rp2xxx.uart.instance.num(0); -const uart_tx_pin = gpio.num(0); - -const USB_Serial = usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); - -var usb_device: rp2xxx.usb.Polled(.{}) = undefined; +var usb_device: USB_Device = undefined; var usb_controller: usb.DeviceController(.{ .device_descriptor = .{ - .bcd_usb = .from(0x0200), + .bcd_usb = USB_Device.max_supported_bcd_usb, .device_triple = .from(.Miscellaneous, @enumFromInt(2), @enumFromInt(1)), - .max_packet_size0 = 64, + .max_packet_size0 = USB_Device.max_supported_packet_size, .vendor = .from(0x2E8A), .product = .from(0x000A), .bcd_device = .from(0x0100), @@ -42,7 +37,7 @@ var usb_controller: usb.DeviceController(.{ .max_current_ma = 50, .Drivers = struct { serial: USB_Serial }, }}, - .max_supported_packet_size = @TypeOf(usb_device).max_supported_packet_size, + .max_supported_packet_size = USB_Device.max_supported_packet_size, }) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { @@ -56,16 +51,23 @@ pub const microzig_options = microzig.Options{ .logFn = rp2xxx.uart.log, }; +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO0 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; + +const pins = pin_config.pins(); + pub fn main() !void { - uart_tx_pin.set_function(.uart); + pin_config.apply(); + + const uart = rp2xxx.uart.instance.num(0); uart.apply(.{ .clock_config = rp2xxx.clock_config, }); rp2xxx.uart.init_logger(uart); - led.set_function(.sio); - led.set_direction(.out); - led.put(1); + pins.led.put(1); // Then initialize the USB device using the configuration defined above usb_device = .init(); @@ -82,7 +84,7 @@ pub fn main() !void { new = time.get_time_since_boot().to_us(); if (new - old > 500000) { old = new; - led.toggle(); + pins.led.toggle(); i += 1; std.log.info("cdc test: {}", .{i}); diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 29d127e91..13dc99021 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -4,25 +4,20 @@ const microzig = @import("microzig"); const rp2xxx = microzig.hal; const time = rp2xxx.time; const gpio = rp2xxx.gpio; - const usb = microzig.core.usb; - -const led = gpio.num(25); -const uart = rp2xxx.uart.instance.num(0); -const uart_tx_pin = gpio.num(0); - -const HidDriver = usb.drivers.hid.HidClassDriver( +const USB_Device = rp2xxx.usb.Polled(.{}); +const HID_Driver = usb.drivers.hid.HidClassDriver( .{ .boot_protocol = true, .poll_interval = 0 }, usb.descriptor.hid.ReportDescriptorKeyboard, ); -var usb_device: rp2xxx.usb.Polled(.{}) = undefined; +var usb_device: USB_Device = undefined; var usb_controller: usb.DeviceController(.{ .device_descriptor = .{ - .bcd_usb = .from(0x0200), + .bcd_usb = USB_Device.max_supported_bcd_usb, .device_triple = .unspecified, - .max_packet_size0 = 64, + .max_packet_size0 = USB_Device.max_supported_packet_size, .vendor = .from(0x2E8A), .product = .from(0x000A), .bcd_device = .from(0x0100), @@ -43,9 +38,9 @@ var usb_controller: usb.DeviceController(.{ .configuration_s = 0, .attributes = .{ .self_powered = false }, .max_current_ma = 50, - .Drivers = struct { hid: HidDriver }, + .Drivers = struct { hid: HID_Driver }, }}, - .max_supported_packet_size = @TypeOf(usb_device).max_supported_packet_size, + .max_supported_packet_size = USB_Device.max_supported_packet_size, }) = .init; pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { @@ -59,16 +54,23 @@ pub const microzig_options = microzig.Options{ .logFn = rp2xxx.uart.log, }; +const pin_config: rp2xxx.pins.GlobalConfiguration = .{ + .GPIO0 = .{ .function = .UART0_TX }, + .GPIO25 = .{ .name = "led", .direction = .out }, +}; + +const pins = pin_config.pins(); + pub fn main() !void { - uart_tx_pin.set_function(.uart); + pin_config.apply(); + + const uart = rp2xxx.uart.instance.num(0); uart.apply(.{ .clock_config = rp2xxx.clock_config, }); rp2xxx.uart.init_logger(uart); - led.set_function(.sio); - led.set_direction(.out); - led.put(1); + pins.led.put(1); // Then initialize the USB device using the configuration defined above usb_device = .init(); @@ -85,7 +87,7 @@ pub fn main() !void { new = time.get_time_since_boot().to_us(); if (new - old > 500000) { old = new; - led.toggle(); + pins.led.toggle(); } } } diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 4b37b8017..86f37712f 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -82,6 +82,7 @@ pub fn Polled(config: Config) type { return struct { pub const max_supported_packet_size = 64; + pub const max_supported_bcd_usb: usb.types.U16_Le = .from(0x0210); const vtable: usb.DeviceInterface.VTable = .{ .ep_writev = ep_writev, From baee9804c8acd791d9cc319aed80f3ede679ca7f Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 13:19:17 +0100 Subject: [PATCH 27/80] add BOS descriptor --- core/src/core/usb/descriptor.zig | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index b46be3d9c..8baada416 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -17,7 +17,8 @@ pub const Type = enum(u8) { Interface = 0x04, Endpoint = 0x05, DeviceQualifier = 0x06, - InterfaceAssociation = 0x0b, + InterfaceAssociation = 0x0B, + BOS = 0x0F, CsDevice = 0x21, CsConfig = 0x22, CsString = 0x23, @@ -287,3 +288,20 @@ pub const InterfaceAssociation = extern struct { // Index of the string descriptor describing the associated interfaces. function: u8, }; + +pub const BOS = struct { + pub const Object = union(enum) {}; + + data: []const u8, + + pub fn from(objects: []const Object) @This() { + const data: []const u8 = ""; + const header: []const u8 = @ptrCast(&extern struct { + length: u8 = @sizeOf(@This()), + descriptor_type: Type = .BOS, + total_length: types.U16_Le = .from(@sizeOf(@This()) + data.len), + num_descriptors: u8 = @intCast(objects.len), + }{}); + return .{ .data = header ++ data }; + } +}; From ae7d20316bce87840a7b794fff0a25b6539ac876 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 13:42:14 +0100 Subject: [PATCH 28/80] simplify setup packet handling --- core/src/core/usb.zig | 212 +++++++++++--------------- core/src/core/usb/drivers/cdc.zig | 33 ++-- core/src/core/usb/drivers/example.zig | 9 +- core/src/core/usb/drivers/hid.zig | 6 +- core/src/core/usb/types.zig | 10 +- 5 files changed, 110 insertions(+), 160 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index b7a2a5e93..6cc234a29 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -102,7 +102,6 @@ pub const Config = struct { device_descriptor: descriptor.Device, string_descriptors: []const descriptor.String, - debug: bool = false, /// Currently only a single configuration is supported. configurations: []const Configuration, /// Only use either IN or OUT on each endpoint. Useful for debugging. @@ -256,27 +255,22 @@ pub fn DeviceController(config: Config) type { break :blk ret; }; - /// When the host sets the device address, the acknowledgement - /// step must use the _old_ address. - new_address: ?u8, + /// If not zero, change the device address at the next opportunity. + /// Necessary because when the host sets the device address, + /// the acknowledgement step must use the _old_ address. + new_address: u8, /// 0 - no config set cfg_num: u16, /// Ep0 data waiting to be sent tx_slice: ?[]const u8, - /// Last setup packet request - setup_packet: types.SetupPacket, - /// Class driver associated with last setup request if any - driver_last: ?DriverEnum, /// Driver state driver_data: ?config0.Drivers, /// Initial values pub const init: @This() = .{ - .new_address = null, + .new_address = 0, .cfg_num = 0, .tx_slice = null, - .setup_packet = undefined, - .driver_last = null, .driver_data = null, }; @@ -288,41 +282,37 @@ pub fn DeviceController(config: Config) type { /// Called by the device implementation when a setup request has been received. pub fn on_setup_req(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) void { - if (config.debug) log.info("setup req", .{}); - - self.setup_packet = setup.*; - self.driver_last = null; - - switch (setup.request_type.recipient) { - .Device => self.process_setup_request(device_itf, setup), - .Interface => switch (@as(u8, @truncate(setup.index.into()))) { - inline else => |itf_num| if (itf_num < handlers.itf.len) { - const drv = handlers.itf[itf_num]; - self.driver_last = drv; - self.driver_class_control(device_itf, drv, .Setup, setup); - }, - }, - .Endpoint => {}, - else => {}, + log.debug("on_setup_req", .{}); + + const ret = switch (setup.request_type.recipient) { + .Device => self.process_device_setup(device_itf, setup), + .Interface => self.process_interface_setup(setup), + else => nak, + }; + if (ret) |data| { + if (data.len == 0) + device_itf.ep_ack(.ep0) + else { + const limited = data[0..@min(data.len, setup.length.into())]; + const len = device_itf.ep_writev(.ep0, &.{limited}); + assert(len <= config.device_descriptor.max_packet_size0); + self.tx_slice = limited[len..]; + } } } /// Called by the device implementation when a packet has been sent or received. - pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint, buffer: []u8) void { - if (config.debug) log.info("buff status", .{}); - if (config.debug) log.info(" data: {any}", .{buffer}); + pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint, _: []u8) void { + log.debug("on_buffer {t} {t}", .{ ep.num, ep.dir }); const handler = comptime @field(handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; if (comptime ep == types.Endpoint.in(.ep0)) { - if (config.debug) log.info(" EP0_IN_ADDR", .{}); - // We use this opportunity to finish the delayed // SetAddress request, if there is one: - if (self.new_address) |addr| { - // Change our address: - device_itf.set_address(@intCast(addr)); - self.new_address = null; + if (self.new_address != 0) { + device_itf.set_address(@intCast(self.new_address)); + self.new_address = 0; } if (self.tx_slice) |slice| { @@ -336,13 +326,14 @@ pub fn DeviceController(config: Config) type { // device_itf.ep_listen(.ep0, 0); self.tx_slice = null; - if (self.driver_last) |drv| - self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); + // None of the drivers so far are using the ACK phase + // if (self.driver_last) |drv| + // self.driver_class_control(device_itf, drv, .Ack, &self.setup_packet); } } - } else if (comptime ep == types.Endpoint.out(.ep0)) { - log.info("ep0_out {}", .{buffer.len}); - } + } else if (comptime ep == types.Endpoint.out(.ep0)) + log.warn("Unhandled packet on ep0 Out", .{}); + if (comptime handler.driver.len != 0) { const drv = &@field(self.driver_data.?, handler.driver); @field(@FieldType(config0.Drivers, handler.driver), handler.function)(drv, ep.num); @@ -351,7 +342,7 @@ pub fn DeviceController(config: Config) type { /// Called by the device implementation on bus reset. pub fn on_bus_reset(self: *@This(), device_itf: *DeviceInterface) void { - if (config.debug) log.info("bus reset", .{}); + log.debug("on_bus_reset", .{}); self.process_set_config(device_itf, 0); @@ -361,104 +352,71 @@ pub fn DeviceController(config: Config) type { // Utility functions - /// Command response utility function that can split long data in multiple packets - fn send_cmd_response(self: *@This(), device_itf: *DeviceInterface, data: []const u8, expected_max_length: u16) void { - const limited = data[0..@min(data.len, expected_max_length)]; - const len = device_itf.ep_writev(.ep0, &.{limited}); - assert(len <= config.device_descriptor.max_packet_size0); - self.tx_slice = limited[len..]; - } - - fn driver_class_control(self: *@This(), device_itf: *DeviceInterface, driver: DriverEnum, stage: types.ControlStage, setup: *const types.SetupPacket) void { - return switch (driver) { - inline else => |d| { - const drv = &@field(self.driver_data.?, @tagName(d)); - if (drv.class_control(stage, setup)) |response| - self.send_cmd_response(device_itf, response, setup.length.into()); - }, - }; - } - - fn process_setup_request(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) void { + fn process_device_setup(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket) ?[]const u8 { switch (setup.request_type.type) { - .Class => { - //const itfIndex = setup.index & 0x00ff; - log.info("Device.Class", .{}); - }, .Standard => { - switch (std.meta.intToEnum(types.SetupRequest, setup.request) catch return) { - .SetAddress => { - if (config.debug) log.info(" SetAddress: {}", .{self.new_address.?}); - self.new_address = @truncate(setup.value.into()); - device_itf.ep_ack(.ep0); - }, - .SetConfiguration => { - if (config.debug) log.info(" SetConfiguration", .{}); - self.process_set_config(device_itf, setup.value.into()); - device_itf.ep_ack(.ep0); - }, - .GetDescriptor => { - const descriptor_type = std.meta.intToEnum(descriptor.Type, setup.value.into() >> 8) catch null; - if (descriptor_type) |dt| { - self.process_get_descriptor(device_itf, setup, dt); + const request: types.SetupRequest = @enumFromInt(setup.request); + log.debug("Device setup: {t}", .{request}); + switch (request) { + .SetAddress => self.new_address = @truncate(setup.value.into()), + .SetConfiguration => self.process_set_config(device_itf, setup.value.into()), + .GetDescriptor => return get_descriptor(setup.value.into()), + .SetFeature => { + const feature: types.FeatureSelector = @enumFromInt(setup.value.into() >> 8); + switch (feature) { + .DeviceRemoteWakeup, .EndpointHalt => {}, + // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 + .TestMode => return nak, + else => return nak, } }, - .SetFeature => { - if (std.meta.intToEnum(types.FeatureSelector, setup.value.into() >> 8)) |feat| { - switch (feat) { - .DeviceRemoteWakeup, .EndpointHalt => device_itf.ep_ack(.ep0), - // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/453 - .TestMode => {}, - } - } else |_| {} + _ => { + log.warn("Unsupported standard request: {}", .{setup.request}); + return nak; }, } + return ack; + }, + else => |t| { + log.warn("Unhandled device setup request: {t}", .{t}); + return nak; }, - else => {}, } } - fn process_get_descriptor(self: *@This(), device_itf: *DeviceInterface, setup: *const types.SetupPacket, descriptor_type: descriptor.Type) void { - switch (descriptor_type) { - .Device => { - if (config.debug) log.info(" Device", .{}); - - self.send_cmd_response(device_itf, @ptrCast(&config.device_descriptor), setup.length.into()); + fn process_interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + const itf_num: u8 = @truncate(setup.index.into()); + switch (itf_num) { + inline else => |itf| if (comptime itf < handlers.itf.len) { + const drv = handlers.itf[itf]; + return @field(self.driver_data.?, @tagName(drv)).interface_setup(setup); + } else { + log.warn("Interface index ({}) out of range ({})", .{ itf_num, handlers.itf.len }); + return nak; }, - .Configuration => { - if (config.debug) log.info(" Config", .{}); + } + } - self.send_cmd_response(device_itf, @ptrCast(&config_descriptor), setup.length.into()); - }, - .String => { - if (config.debug) log.info(" String", .{}); - // String descriptor index is in bottom 8 bits of - // `value`. - const i: u8 = @truncate(setup.value.into()); - if (i >= config.string_descriptors.len) - log.warn("host requested invalid string descriptor {}", .{i}) - else - self.send_cmd_response( - device_itf, - config.string_descriptors[i].data, - setup.length.into(), - ); - }, - .Interface => { - if (config.debug) log.info(" Interface", .{}); - }, - .Endpoint => { - if (config.debug) log.info(" Endpoint", .{}); + fn get_descriptor(value: u16) ?[]const u8 { + const asBytes = std.mem.asBytes; + const desc_type: descriptor.Type = @enumFromInt(value >> 8); + const desc_idx: u8 = @truncate(value); + log.debug("Request for {t} descriptor {}", .{ desc_type, desc_idx }); + return switch (desc_type) { + .Device => asBytes(&config.device_descriptor), + .DeviceQualifier => asBytes(comptime &config.device_descriptor.qualifier()), + .Configuration => asBytes(&config_descriptor), + .String => if (desc_idx < config.string_descriptors.len) + config.string_descriptors[desc_idx].data + else { + log.warn( + "Descriptor index ({}) out of range ({})", + .{ desc_idx, config.string_descriptors.len }, + ); + return nak; }, - .DeviceQualifier => { - if (config.debug) log.info(" DeviceQualifier", .{}); - // We will just copy parts of the DeviceDescriptor because - // the DeviceQualifierDescriptor can be seen as a subset. - const qualifier = comptime &config.device_descriptor.qualifier(); - self.send_cmd_response(device_itf, @ptrCast(qualifier), setup.length.into()); - }, - else => {}, - } + else => nak, + }; } fn process_set_config(self: *@This(), device_itf: *DeviceInterface, cfg_num: u16) void { diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 918a12fff..3ff9446e4 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -9,6 +9,7 @@ pub const ManagementRequestType = enum(u8) { GetLineCoding = 0x21, SetControlLineState = 0x22, SendBreak = 0x23, + _, }; pub const LineCoding = extern struct { @@ -189,22 +190,22 @@ pub fn CdcClassDriver(options: Options) type { device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); } - pub fn class_control(self: *@This(), stage: types.ControlStage, setup: *const types.SetupPacket) ?[]const u8 { - if (std.meta.intToEnum(ManagementRequestType, setup.request)) |request| { - if (stage == .Setup) switch (request) { - .SetLineCoding => return usb.ack, // we should handle data phase somehow to read sent line_coding - .GetLineCoding => return std.mem.asBytes(&self.line_coding), - .SetControlLineState => { - // const DTR_BIT = 1; - // self.is_ready = (setup.value & DTR_BIT) != 0; - // self.line_state = @intCast(setup.value & 0xFF); - return usb.ack; - }, - .SendBreak => return usb.ack, - }; - } else |_| {} - - return usb.nak; + pub fn interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + const mgmt_request: ManagementRequestType = @enumFromInt(setup.request); + std.log.debug("cdc setup: {t}", .{mgmt_request}); + + return switch (mgmt_request) { + .SetLineCoding => usb.ack, // we should handle data phase somehow to read sent line_coding + .GetLineCoding => std.mem.asBytes(&self.line_coding), + .SetControlLineState => blk: { + // const DTR_BIT = 1; + // self.is_ready = (setup.value & DTR_BIT) != 0; + // self.line_state = @intCast(setup.value & 0xFF); + break :blk usb.ack; + }, + .SendBreak => usb.ack, + else => usb.nak, + }; } pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index b40f8051f..156f3ba41 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -57,15 +57,12 @@ pub const EchoExampleDriver = struct { ); } - /// Used for configuration through endpoint 0. + /// Used for interface configuration through endpoint 0. /// Data returned by this function is sent on endpoint 0. - pub fn class_control(self: *@This(), stage: usb.types.ControlStage, setup: *const usb.types.SetupPacket) ?[]const u8 { + pub fn interface_setup(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { _ = self; _ = setup; - if (stage == .Setup) - return usb.ack - else - return usb.nak; + return usb.ack; } /// Each endpoint (as defined in the descriptor) has its own handler. diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index b092060b1..669cde2d1 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -64,9 +64,9 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }; } - pub fn class_control(self: *@This(), stage: types.ControlStage, setup: *const types.SetupPacket) ?[]const u8 { + pub fn interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { _ = self; - if (stage == .Setup) switch (setup.request_type.type) { + switch (setup.request_type.type) { .Standard => { const hid_desc_type = std.meta.intToEnum(descriptor.hid.Hid.Type, setup.value.into() >> 8) catch return usb.nak; const request_code = std.meta.intToEnum(types.SetupRequest, setup.request) catch return usb.nak; @@ -114,7 +114,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { } }, else => {}, - }; + } return usb.nak; } diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index c5e018f07..8cfa83390 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -207,19 +207,13 @@ pub const TransferType = enum(u2) { Interrupt = 3, }; -pub const ControlStage = enum { - Idle, - Setup, - Data, - Ack, -}; - /// The types of USB SETUP requests that we understand. pub const SetupRequest = enum(u8) { SetFeature = 0x03, SetAddress = 0x05, GetDescriptor = 0x06, SetConfiguration = 0x09, + _, }; pub const FeatureSelector = enum(u8) { @@ -227,6 +221,7 @@ pub const FeatureSelector = enum(u8) { DeviceRemoteWakeup = 0x01, TestMode = 0x02, // The remaining features only apply to OTG devices. + _, }; /// USB deals in two different transfer directions, called OUT (host-to-device) @@ -302,7 +297,6 @@ pub const SetupPacket = extern struct { request: u8, /// A simple argument of up to 16 bits, specific to the request. value: U16_Le, - /// Not used in the requests we support. index: U16_Le, /// If data will be transferred after this request (in the direction given /// by `request_type`), this gives the number of bytes (OUT) or maximum From a6e8c89deae736e65de3331d13f482e4705f22dd Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 14:08:43 +0100 Subject: [PATCH 29/80] add usb device logging --- core/src/core/usb.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 89 +++++++++++++++---------- 2 files changed, 55 insertions(+), 36 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 6cc234a29..cd6f56c02 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -302,7 +302,7 @@ pub fn DeviceController(config: Config) type { } /// Called by the device implementation when a packet has been sent or received. - pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint, _: []u8) void { + pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint) void { log.debug("on_buffer {t} {t}", .{ ep.num, ep.dir }); const handler = comptime @field(handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 86f37712f..9a75f2984 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -5,6 +5,7 @@ const std = @import("std"); const assert = std.debug.assert; +const log = std.log.scoped(.usb_dev); const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; @@ -18,6 +19,7 @@ pub const Config = struct { max_endpoints_count: comptime_int = RP2XXX_MAX_ENDPOINTS_COUNT, max_interfaces_count: comptime_int = 16, sync_noops: comptime_int = 3, + log_level: std.log.Level = .warn, }; const HardwareEndpointData = struct { @@ -82,7 +84,7 @@ pub fn Polled(config: Config) type { return struct { pub const max_supported_packet_size = 64; - pub const max_supported_bcd_usb: usb.types.U16_Le = .from(0x0210); + pub const max_supported_bcd_usb: usb.types.U16_Le = .from(0x0110); const vtable: usb.DeviceInterface.VTable = .{ .ep_writev = ep_writev, @@ -110,48 +112,47 @@ pub fn Polled(config: Config) type { buffer_control[0].in.modify(.{ .PID_0 = 0 }); const setup = get_setup_packet(); + if (config.log_level == .debug) + log.debug("setup {any}", .{setup}); controller.on_setup_req(&self.interface, &setup); } - var buff_status: u32 = 0; // Events on one or more buffers? (In practice, always one.) if (ints.BUFF_STATUS != 0) { - const bufbits_init = peripherals.USB.BUFF_STATUS.raw; - buff_status |= bufbits_init; - peripherals.USB.BUFF_STATUS.write_raw(bufbits_init); - } - - inline for (0..2 * config.max_endpoints_count) |shift| { - if (buff_status & (@as(u32, 1) << shift) != 0) { - // Here we exploit knowledge of the ordering of buffer control - // registers in the peripheral. Each endpoint has a pair of - // registers, IN being first - const ep_num = shift / 2; - const ep: usb.types.Endpoint = comptime .{ - .num = @enumFromInt(ep_num), - .dir = if (shift & 1 == 0) .In else .Out, - }; - - const ep_hard = self.hardware_endpoint_get_by_address(ep); - - // We should only get here if we've been notified that - // the buffer is ours again. This is indicated by the hw - // _clearing_ the AVAILABLE bit. - // - // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS - // So we wait for it just to be sure. - while (buffer_control[ep_num].get(ep.dir).read().AVAILABLE_0 != 0) {} - - // Get the actual length of the data, which may be less - // than the buffer size. - const len = buffer_control[ep_num].get(ep.dir).read().LENGTH_0; - - controller.on_buffer(&self.interface, ep, ep_hard.data_buffer[0..len]); + const buff_status = peripherals.USB.BUFF_STATUS.raw; + + inline for (0..2 * config.max_endpoints_count) |shift| { + if (buff_status & (@as(u32, 1) << shift) != 0) { + // Here we exploit knowledge of the ordering of buffer control + // registers in the peripheral. Each endpoint has a pair of + // registers, IN being first + const ep_num = shift / 2; + const ep: usb.types.Endpoint = comptime .{ + .num = @enumFromInt(ep_num), + .dir = if (shift % 2 == 0) .In else .Out, + }; + + // We should only get here if we've been notified that + // the buffer is ours again. This is indicated by the hw + // _clearing_ the AVAILABLE bit. + // + // It seems the hardware sets the AVAILABLE bit _after_ setting BUFF_STATUS + // So we wait for it just to be sure. + while (buffer_control[ep_num].get(ep.dir).read().AVAILABLE_0 != 0) {} + + if (config.log_level == .debug) + log.debug("buffer ep{} {t}", .{ ep_num, ep.dir }); + controller.on_buffer(&self.interface, ep); + } } + peripherals.USB.BUFF_STATUS.raw = buff_status; } // Has the host signaled a bus reset? if (ints.BUS_RESET != 0) { + if (config.log_level == .debug or config.log_level == .info) + log.debug("bus reset", .{}); + // Abort all endpoints peripherals.USB.EP_ABORT.raw = 0xFFFFFFFF; // Acknowledge by writing the write-one-to-clear status bit. @@ -253,6 +254,9 @@ pub fn Polled(config: Config) type { ep_num: usb.types.Endpoint.Num, data: []const []const u8, ) usb.types.Len { + if (config.log_level == .debug) + log.debug("writev {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); + const self: *@This() = @fieldParentPtr("interface", itf); const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].in; @@ -298,6 +302,9 @@ pub fn Polled(config: Config) type { ep_num: usb.types.Endpoint.Num, data: []const []u8, ) usb.types.Len { + if (config.log_level == .debug) + log.debug("readv {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); + const self: *@This() = @fieldParentPtr("interface", itf); assert(data.len > 0); @@ -321,6 +328,9 @@ pub fn Polled(config: Config) type { ep_num: usb.types.Endpoint.Num, len: usb.types.Len, ) void { + if (config.log_level == .debug) + log.debug("listen {t} {}", .{ ep_num, len }); + const self: *@This() = @fieldParentPtr("interface", itf); const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].out; @@ -367,6 +377,9 @@ pub fn Polled(config: Config) type { } fn set_address(_: *usb.DeviceInterface, addr: u7) void { + if (config.log_level == .debug) + log.debug("set addr {}", .{addr}); + peripherals.USB.ADDR_ENDP.write(.{ .ADDRESS = addr }); } @@ -375,11 +388,17 @@ pub fn Polled(config: Config) type { } fn ep_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { + const ep = desc.endpoint; + const attr = desc.attributes; + if (config.log_level == .debug) log.debug( + "ep open {t} {t} {{ type: {t}, sync: {t}, usage: {t}, size: {} }}", + .{ ep.num, ep.dir, attr.transfer_type, attr.synchronisation, attr.usage, desc.max_packet_size.into() }, + ); + const self: *@This() = @fieldParentPtr("interface", itf); assert(@intFromEnum(desc.endpoint.num) <= config.max_endpoints_count); - const ep = desc.endpoint; const ep_hard = self.hardware_endpoint_get_by_address(ep); assert(desc.max_packet_size.into() <= max_supported_packet_size); @@ -394,7 +413,7 @@ pub fn Polled(config: Config) type { endpoint_control[@intFromEnum(ep.num) - 1].get(ep.dir).write(.{ .ENABLE = 1, .INTERRUPT_PER_BUFF = 1, - .ENDPOINT_TYPE = @enumFromInt(@intFromEnum(desc.attributes.transfer_type)), + .ENDPOINT_TYPE = @enumFromInt(@intFromEnum(attr.transfer_type)), .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data_buffer), }); } From 66f01165b391585f9b77f8d75fa551f2590e1123 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 14:26:06 +0100 Subject: [PATCH 30/80] better use scoped logging --- core/src/core/usb.zig | 2 +- core/src/core/usb/drivers/cdc.zig | 67 ++++++++++----------- core/src/core/usb/drivers/hid.zig | 30 ++++----- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 5 ++ examples/raspberrypi/rp2xxx/src/usb_hid.zig | 5 ++ port/raspberrypi/rp2xxx/src/hal/usb.zig | 25 +++----- 6 files changed, 68 insertions(+), 66 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index cd6f56c02..5b13350f5 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -1,6 +1,6 @@ const std = @import("std"); const assert = std.debug.assert; -const log = std.log.scoped(.usb); +const log = std.log.scoped(.usb_ctrl); pub const descriptor = @import("usb/descriptor.zig"); pub const drivers = struct { diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 3ff9446e4..fcad72f28 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -1,8 +1,7 @@ const std = @import("std"); const usb = @import("../../usb.zig"); const assert = std.debug.assert; -const descriptor = usb.descriptor; -const types = usb.types; +const log = std.log.scoped(.usb_cdc); pub const ManagementRequestType = enum(u8) { SetLineCoding = 0x20, @@ -33,16 +32,16 @@ pub const Options = struct { pub fn CdcClassDriver(options: Options) type { return struct { pub const Descriptor = extern struct { - itf_assoc: descriptor.InterfaceAssociation, - itf_notifi: descriptor.Interface, - cdc_header: descriptor.cdc.Header, - cdc_call_mgmt: descriptor.cdc.CallManagement, - cdc_acm: descriptor.cdc.AbstractControlModel, - cdc_union: descriptor.cdc.Union, - ep_notifi: descriptor.Endpoint, - itf_data: descriptor.Interface, - ep_out: descriptor.Endpoint, - ep_in: descriptor.Endpoint, + itf_assoc: usb.descriptor.InterfaceAssociation, + itf_notifi: usb.descriptor.Interface, + cdc_header: usb.descriptor.cdc.Header, + cdc_call_mgmt: usb.descriptor.cdc.CallManagement, + cdc_acm: usb.descriptor.cdc.AbstractControlModel, + cdc_union: usb.descriptor.cdc.Union, + ep_notifi: usb.descriptor.Endpoint, + itf_data: usb.descriptor.Interface, + ep_out: usb.descriptor.Endpoint, + ep_in: usb.descriptor.Endpoint, pub fn create( alloc: *usb.DescriptorAllocator, @@ -98,23 +97,23 @@ pub fn CdcClassDriver(options: Options) type { }; device: *usb.DeviceInterface, - ep_notifi: types.Endpoint.Num, + ep_notifi: usb.types.Endpoint.Num, line_coding: LineCoding align(4), /// OUT endpoint on which there is data ready to be read, /// or .ep0 when no data is available. - ep_out: types.Endpoint.Num, + ep_out: usb.types.Endpoint.Num, rx_data: [options.max_packet_size]u8, - rx_seek: types.Len, - rx_end: types.Len, + rx_seek: usb.types.Len, + rx_end: usb.types.Len, /// IN endpoint where data can be sent, /// or .ep0 when data is being sent. - ep_in: types.Endpoint.Num, + ep_in: usb.types.Endpoint.Num, tx_data: [options.max_packet_size]u8, - tx_end: types.Len, + tx_end: usb.types.Len, - pub fn available(self: *@This()) types.Len { + pub fn available(self: *@This()) usb.types.Len { return self.rx_end - self.rx_seek; } @@ -126,11 +125,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.available() > 0) return len; // request more data - const ep_out = @atomicLoad(types.Endpoint.Num, &self.ep_out, .seq_cst); + const ep_out = @atomicLoad(usb.types.Endpoint.Num, &self.ep_out, .seq_cst); if (ep_out != .ep0) { self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); self.rx_seek = 0; - @atomicStore(types.Endpoint.Num, &self.ep_out, .ep0, .seq_cst); + @atomicStore(usb.types.Endpoint.Num, &self.ep_out, .ep0, .seq_cst); self.device.ep_listen(ep_out, options.max_packet_size); } @@ -156,11 +155,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.tx_end == 0) return true; - const ep_in = @atomicLoad(types.Endpoint.Num, &self.ep_in, .seq_cst); + const ep_in = @atomicLoad(usb.types.Endpoint.Num, &self.ep_in, .seq_cst); if (ep_in == .ep0) return false; - @atomicStore(types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); + @atomicStore(usb.types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); self.tx_end = 0; @@ -190,9 +189,9 @@ pub fn CdcClassDriver(options: Options) type { device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); } - pub fn interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + pub fn interface_setup(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { const mgmt_request: ManagementRequestType = @enumFromInt(setup.request); - std.log.debug("cdc setup: {t}", .{mgmt_request}); + log.debug("cdc setup: {t}", .{mgmt_request}); return switch (mgmt_request) { .SetLineCoding => usb.ack, // we should handle data phase somehow to read sent line_coding @@ -208,19 +207,19 @@ pub fn CdcClassDriver(options: Options) type { }; } - pub fn on_rx(self: *@This(), ep_num: types.Endpoint.Num) void { - assert(.ep0 == @atomicLoad(types.Endpoint.Num, &self.ep_out, .seq_cst)); - @atomicStore(types.Endpoint.Num, &self.ep_out, ep_num, .seq_cst); + pub fn on_rx(self: *@This(), ep_num: usb.types.Endpoint.Num) void { + assert(.ep0 == @atomicLoad(usb.types.Endpoint.Num, &self.ep_out, .seq_cst)); + @atomicStore(usb.types.Endpoint.Num, &self.ep_out, ep_num, .seq_cst); } - pub fn on_tx_ready(self: *@This(), ep_num: types.Endpoint.Num) void { - assert(.ep0 == @atomicLoad(types.Endpoint.Num, &self.ep_in, .seq_cst)); - @atomicStore(types.Endpoint.Num, &self.ep_in, ep_num, .seq_cst); + pub fn on_tx_ready(self: *@This(), ep_num: usb.types.Endpoint.Num) void { + assert(.ep0 == @atomicLoad(usb.types.Endpoint.Num, &self.ep_in, .seq_cst)); + @atomicStore(usb.types.Endpoint.Num, &self.ep_in, ep_num, .seq_cst); } - pub fn on_notifi_ready(self: *@This(), ep_num: types.Endpoint.Num) void { - assert(.ep0 == @atomicLoad(types.Endpoint.Num, &self.ep_notifi, .seq_cst)); - @atomicStore(types.Endpoint.Num, &self.ep_notifi, ep_num, .seq_cst); + pub fn on_notifi_ready(self: *@This(), ep_num: usb.types.Endpoint.Num) void { + assert(.ep0 == @atomicLoad(usb.types.Endpoint.Num, &self.ep_notifi, .seq_cst)); + @atomicStore(usb.types.Endpoint.Num, &self.ep_notifi, ep_num, .seq_cst); } }; } diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 669cde2d1..1052e11af 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -1,7 +1,7 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const descriptor = usb.descriptor; -const types = usb.types; +const assert = std.debug.assert; +const log = std.log.scoped(.usb_hid); pub const Options = struct { boot_protocol: bool, @@ -11,10 +11,10 @@ pub const Options = struct { pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return struct { pub const Descriptor = extern struct { - interface: descriptor.Interface, - hid: descriptor.hid.Hid, - ep_out: descriptor.Endpoint, - ep_in: descriptor.Endpoint, + interface: usb.descriptor.Interface, + hid: usb.descriptor.hid.Hid, + ep_out: usb.descriptor.Endpoint, + ep_in: usb.descriptor.Endpoint, pub fn create( alloc: *usb.DescriptorAllocator, @@ -40,7 +40,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { } }; - const hid_descriptor: descriptor.hid.Hid = .{ + const hid_descriptor: usb.descriptor.hid.Hid = .{ .bcd_hid = .from(0x0111), .country_code = .NotSupported, .num_descriptors = 1, @@ -53,8 +53,8 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }; device: *usb.DeviceInterface, - ep_in: types.Endpoint.Num, - ep_out: types.Endpoint.Num, + ep_in: usb.types.Endpoint.Num, + ep_out: usb.types.Endpoint.Num, pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { self.* = .{ @@ -64,12 +64,12 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }; } - pub fn interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + pub fn interface_setup(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { _ = self; switch (setup.request_type.type) { .Standard => { - const hid_desc_type = std.meta.intToEnum(descriptor.hid.Hid.Type, setup.value.into() >> 8) catch return usb.nak; - const request_code = std.meta.intToEnum(types.SetupRequest, setup.request) catch return usb.nak; + const hid_desc_type = std.meta.intToEnum(usb.descriptor.hid.Hid.Type, setup.value.into() >> 8) catch return usb.nak; + const request_code = std.meta.intToEnum(usb.types.SetupRequest, setup.request) catch return usb.nak; if (request_code == .GetDescriptor and hid_desc_type == .Hid) return @as([]const u8, @ptrCast(&hid_descriptor)) @@ -77,7 +77,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return @as([]const u8, @ptrCast(&report_descriptor)); }, .Class => { - const hid_request_type = std.meta.intToEnum(descriptor.hid.RequestType, setup.request) catch return usb.nak; + const hid_request_type = std.meta.intToEnum(usb.descriptor.hid.RequestType, setup.request) catch return usb.nak; switch (hid_request_type) { .SetIdle => { // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 @@ -118,12 +118,12 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return usb.nak; } - pub fn on_tx_ready(self: *@This(), ep: types.Endpoint.Num) void { + pub fn on_tx_ready(self: *@This(), ep: usb.types.Endpoint.Num) void { _ = self; _ = ep; } - pub fn on_rx(self: *@This(), ep: types.Endpoint.Num) void { + pub fn on_rx(self: *@This(), ep: usb.types.Endpoint.Num) void { _ = self; _ = ep; } diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 03257af82..30949bc32 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -48,6 +48,11 @@ pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noretu pub const microzig_options = microzig.Options{ .log_level = .debug, + .log_scope_levels = &.{ + .{ .scope = .usb_dev, .level = .warn }, + .{ .scope = .usb_ctrl, .level = .warn }, + .{ .scope = .usb_cdc, .level = .warn }, + }, .logFn = rp2xxx.uart.log, }; diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 13dc99021..2105a9b34 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -51,6 +51,11 @@ pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noretu pub const microzig_options = microzig.Options{ .log_level = .debug, + .log_scope_levels = &.{ + .{ .scope = .usb_dev, .level = .warn }, + .{ .scope = .usb_ctrl, .level = .warn }, + .{ .scope = .usb_hid, .level = .warn }, + }, .logFn = rp2xxx.uart.log, }; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 9a75f2984..4033f7484 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -19,7 +19,6 @@ pub const Config = struct { max_endpoints_count: comptime_int = RP2XXX_MAX_ENDPOINTS_COUNT, max_interfaces_count: comptime_int = 16, sync_noops: comptime_int = 3, - log_level: std.log.Level = .warn, }; const HardwareEndpointData = struct { @@ -112,8 +111,8 @@ pub fn Polled(config: Config) type { buffer_control[0].in.modify(.{ .PID_0 = 0 }); const setup = get_setup_packet(); - if (config.log_level == .debug) - log.debug("setup {any}", .{setup}); + + log.debug("setup {any}", .{setup}); controller.on_setup_req(&self.interface, &setup); } @@ -140,8 +139,7 @@ pub fn Polled(config: Config) type { // So we wait for it just to be sure. while (buffer_control[ep_num].get(ep.dir).read().AVAILABLE_0 != 0) {} - if (config.log_level == .debug) - log.debug("buffer ep{} {t}", .{ ep_num, ep.dir }); + log.debug("buffer ep{} {t}", .{ ep_num, ep.dir }); controller.on_buffer(&self.interface, ep); } } @@ -150,8 +148,7 @@ pub fn Polled(config: Config) type { // Has the host signaled a bus reset? if (ints.BUS_RESET != 0) { - if (config.log_level == .debug or config.log_level == .info) - log.debug("bus reset", .{}); + log.info("bus reset", .{}); // Abort all endpoints peripherals.USB.EP_ABORT.raw = 0xFFFFFFFF; @@ -254,8 +251,7 @@ pub fn Polled(config: Config) type { ep_num: usb.types.Endpoint.Num, data: []const []const u8, ) usb.types.Len { - if (config.log_level == .debug) - log.debug("writev {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); + log.debug("writev {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); const self: *@This() = @fieldParentPtr("interface", itf); @@ -302,8 +298,7 @@ pub fn Polled(config: Config) type { ep_num: usb.types.Endpoint.Num, data: []const []u8, ) usb.types.Len { - if (config.log_level == .debug) - log.debug("readv {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); + log.debug("readv {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); const self: *@This() = @fieldParentPtr("interface", itf); assert(data.len > 0); @@ -328,8 +323,7 @@ pub fn Polled(config: Config) type { ep_num: usb.types.Endpoint.Num, len: usb.types.Len, ) void { - if (config.log_level == .debug) - log.debug("listen {t} {}", .{ ep_num, len }); + log.debug("listen {t} {}", .{ ep_num, len }); const self: *@This() = @fieldParentPtr("interface", itf); const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].out; @@ -377,8 +371,7 @@ pub fn Polled(config: Config) type { } fn set_address(_: *usb.DeviceInterface, addr: u7) void { - if (config.log_level == .debug) - log.debug("set addr {}", .{addr}); + log.debug("set addr {}", .{addr}); peripherals.USB.ADDR_ENDP.write(.{ .ADDRESS = addr }); } @@ -390,7 +383,7 @@ pub fn Polled(config: Config) type { fn ep_open(itf: *usb.DeviceInterface, desc: *const usb.descriptor.Endpoint) void { const ep = desc.endpoint; const attr = desc.attributes; - if (config.log_level == .debug) log.debug( + log.debug( "ep open {t} {t} {{ type: {t}, sync: {t}, usage: {t}, size: {} }}", .{ ep.num, ep.dir, attr.transfer_type, attr.synchronisation, attr.usage, desc.max_packet_size.into() }, ); From f07fe5c941bbe2e87f9e94e98786e21293294611 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 14:37:28 +0100 Subject: [PATCH 31/80] reorganize ClassSubclassProtocol --- core/src/core/usb/types.zig | 334 ++++++++++++++++++++++-------------- 1 file changed, 209 insertions(+), 125 deletions(-) diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 8cfa83390..e592e0ed0 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -32,145 +32,229 @@ pub const ClassSubclassProtocol = extern struct { _, pub fn Subclass(self: @This()) type { - const name = "Subclass" ++ @tagName(self); - return if (@hasDecl(ClassSubclassProtocol, name)) - @field(ClassSubclassProtocol, name) - else - ClassSubclassProtocol.SubclassDefault; + return @field(ClassSubclassProtocol.Subclass, @tagName(self)); } pub fn Protocol(self: @This()) type { - const name = "Protocol" ++ @tagName(self); - return if (@hasDecl(ClassSubclassProtocol, name)) - @field(ClassSubclassProtocol, name) - else - ClassSubclassProtocol.ProtocolDefault; + return @field(ClassSubclassProtocol.Protocol, @tagName(self)); } }; - pub const SubclassDefault = enum(u8) { - Unspecified = 0x00, - VendorSpecific = 0xFF, - _, - }; + pub const Subclass = struct { + pub const Default = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + _, + }; - pub const ProtocolDefault = enum(u8) { - NoneRequired = 0x00, - VendorSpecific = 0xFF, - _, - }; + pub const Unspecified = Default; + pub const Audio = Default; + + pub const Cdc = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + /// Direct Line Control Model + DirectLine = 0x01, + /// Abstract Control Model + Abstract = 0x02, + /// Telephone Control Model + Telephone = 0x03, + /// Multi-Channel Control Model + MultChannel = 0x04, + /// CAPI Control Model + CAPI = 0x05, + /// Ethernet Networking Control Model + Ethernet = 0x06, + /// ATM Networking Control Model + ATM_Networking = 0x07, + /// Wireless Handset Control Model + WirelessHeadest = 0x08, + /// Device Management + DeviceManagement = 0x09, + /// Mobile Direct Line Model + MobileDirect = 0x0A, + /// OBEX + OBEX = 0x0B, + /// Ethernet Emulation Model + EthernetEmulation = 0x0C, + /// Network Control Model + Network = 0x0D, + _, + }; - pub const SubclassCdc = enum(u8) { - Unspecified = 0x00, - VendorSpecific = 0xFF, - /// Direct Line Control Model - DirectLine = 0x01, - /// Abstract Control Model - Abstract = 0x02, - /// Telephone Control Model - Telephone = 0x03, - /// Multi-Channel Control Model - MultChannel = 0x04, - /// CAPI Control Model - CAPI = 0x05, - /// Ethernet Networking Control Model - Ethernet = 0x06, - /// ATM Networking Control Model - ATM_Networking = 0x07, - /// Wireless Handset Control Model - WirelessHeadest = 0x08, - /// Device Management - DeviceManagement = 0x09, - /// Mobile Direct Line Model - MobileDirect = 0x0A, - /// OBEX - OBEX = 0x0B, - /// Ethernet Emulation Model - EthernetEmulation = 0x0C, - /// Network Control Model - Network = 0x0D, - _, - }; + pub const Hid = enum(u8) { + Unspecified = 0x00, + VendorSpecific = 0xFF, + /// + Boot = 0x01, + _, + }; - pub const ProtocolCdc = enum(u8) { - /// USB specification No class specific protocol required - NoneRequired = 0x00, - /// ITU-T V.250 AT Commands: V.250 etc - AT_ITU_T_V_250 = 0x01, - /// PCCA-101 AT Commands defined by PCCA-101 - AT_PCCA_101 = 0x02, - /// PCCA-101 AT Commands defined by PCCA-101 & Annex O - AT_PCCA_101_O = 0x03, - /// GSM 7.07 AT Commands defined by GSM 07.07 - AT_GSM_7_07 = 0x04, - /// 3GPP 27.07 AT Commands defined by 3GPP 27.007 - AT_3GPP_27_07 = 0x05, - /// C-S0017-0 AT Commands defined by TIA for CDMA - AT_C_S0017_0 = 0x06, - /// USB EEM Ethernet Emulation module - USB_EEM = 0x07, - /// External Protocol: Commands defined by Command Set Functional Descriptor - External = 0xFE, - /// USB Specification Vendor-specific - VendorSpecific = 0xFF, - _, - }; + pub const Physical = Default; + pub const Image = Default; + pub const Printer = Default; + + pub const MassStorage = enum(u8) { + /// SCSI command set not reported. De facto use. + Unspecified = 0x00, + /// Allocated by USB-IF for RBC. RBC is defined outside of USB. + RBC = 0x01, + /// Allocated by USB-IF for MMC-5 (ATAPI). MMC-5 is defined outside of USB. + MMC_5 = 0x02, + /// Obsolete. Was QIC-157 + QIC_157 = 0x03, + /// Specifies how to interface Floppy Disk Drives to USB + UFI = 0x04, + /// Obsolete. Was SFF-8070i + SFF_8070i = 0x05, + /// SCSI transparent command set. Allocated by USB-IF for SCSI. SCSI standards are defined outside of USB. + SCSI = 0x06, + /// LSDFS. specifies how host has to negotiate access before trying SCSI + LSD_FS = 0x07, + /// Allocated by USB-IF for IEEE 1667. IEEE 1667 is defined outside of USB. + IEEE_1667 = 0x08, + /// Specific to device vendor. De facto use + VendorSpecific = 0xFF, + _, + }; - pub const SubclassCdcData = enum(u8) { - Unused = 0, - VendorSpecific = 0xFF, - _, - }; + pub const Hub = Default; - pub const ProtocolCdcData = enum(u8) { - NoneRequired = 0, - VendorSpecific = 0xFF, - /// Network Transfer Block - NetworkTransferBlock = 0x01, - /// Physical interface protocol for ISDN BRI - ISDN_BRI = 0x30, - /// HDLC - HDLC = 0x31, - /// Transparent - Transparent = 0x32, - /// Management protocol for Q.921 data link protocol - Management_Q_921 = 0x50, - /// Data link protocol for Q.931 - DataLink_Q_931 = 0x51, - /// TEI-multiplexor for Q.921 data link protocol - TEI_Multiplexor_Q_921 = 0x52, - /// Data compression procedures - DataCompressionProcedures = 0x90, - /// Euro-ISDN protocol control - Euro_ISDN = 0x91, - /// V.24 rate adaptation to ISDN - RateAdaptation_V_24 = 0x92, - /// CAPI Commands - CAPI = 0x93, - /// Host based driver. Note: This protocol code should only be used - /// in messages between host and device to identify the host driver - /// portion of a protocol stack. - HostBasedDriver = 0xFD, - /// CDC Specification The protocol(s) are described using a Protocol - /// Unit Functional Descriptors on Communications Class Interface - SpecifiedIn_PUF_Descriptor = 0xFE, - _, - }; + pub const CdcData = enum(u8) { + Unused = 0, + VendorSpecific = 0xFF, + _, + }; - pub const SubclassHid = enum(u8) { - Unspecified = 0x00, - VendorSpecific = 0xFF, - /// - Boot = 0x01, - _, + pub const SmartCard = Default; + pub const ContentSecurity = Default; + pub const Video = Default; + pub const PersonalHealthcare = Default; + pub const AudioVideoDevice = Default; + pub const BillboardDevice = Default; + pub const USBTypeCBridge = Default; + pub const USBBulkDisplayProtocol = Default; + pub const MCTPoverUSBProtocolEndpoint = Default; + pub const I3C = Default; + pub const DiagnosticDevice = Default; + pub const WirelessController = Default; + pub const Miscellaneous = Default; + pub const ApplicationSpecific = Default; + pub const VendorSpecific = Default; }; - pub const ProtocolHid = enum(u8) { - NoneRequired = 0x00, - VendorSpecific = 0xFF, - /// - Boot = 0x01, - _, + pub const Protocol = struct { + pub const Default = enum(u8) { + NoneRequired = 0x00, + VendorSpecific = 0xFF, + _, + }; + + pub const Unspecified = Default; + pub const Audio = Default; + + pub const Cdc = enum(u8) { + /// USB specification No class specific protocol required + NoneRequired = 0x00, + /// ITU-T V.250 AT Commands: V.250 etc + AT_ITU_T_V_250 = 0x01, + /// PCCA-101 AT Commands defined by PCCA-101 + AT_PCCA_101 = 0x02, + /// PCCA-101 AT Commands defined by PCCA-101 & Annex O + AT_PCCA_101_O = 0x03, + /// GSM 7.07 AT Commands defined by GSM 07.07 + AT_GSM_7_07 = 0x04, + /// 3GPP 27.07 AT Commands defined by 3GPP 27.007 + AT_3GPP_27_07 = 0x05, + /// C-S0017-0 AT Commands defined by TIA for CDMA + AT_C_S0017_0 = 0x06, + /// USB EEM Ethernet Emulation module + USB_EEM = 0x07, + /// External Protocol: Commands defined by Command Set Functional Descriptor + External = 0xFE, + /// USB Specification Vendor-specific + VendorSpecific = 0xFF, + _, + }; + + pub const Hid = enum(u8) { + NoneRequired = 0x00, + VendorSpecific = 0xFF, + /// + Boot = 0x01, + _, + }; + + pub const Physical = Default; + pub const Image = Default; + pub const Printer = Default; + + pub const MassStorage = enum(u8) { + /// USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport with command completion interrupt + CBI_with_interrupt = 0x00, + /// USB Mass Storage Class Control/Bulk/Interrupt (CBI) Transport with no command completion interrupt + CBI_no_interrupt = 0x01, + /// USB Mass Storage Class Bulk-Only (BBB) Transport + BulkOnly = 0x50, + /// Allocated by USB-IF for UAS. UAS is defined outside of USB. + UAS = 0x62, + /// Specific to device vendor De facto use + VendorSpecific = 0xFF, + _, + }; + + pub const Hub = Default; + + pub const CdcData = enum(u8) { + NoneRequired = 0, + VendorSpecific = 0xFF, + /// Network Transfer Block + NetworkTransferBlock = 0x01, + /// Physical interface protocol for ISDN BRI + ISDN_BRI = 0x30, + /// HDLC + HDLC = 0x31, + /// Transparent + Transparent = 0x32, + /// Management protocol for Q.921 data link protocol + Management_Q_921 = 0x50, + /// Data link protocol for Q.931 + DataLink_Q_931 = 0x51, + /// TEI-multiplexor for Q.921 data link protocol + TEI_Multiplexor_Q_921 = 0x52, + /// Data compression procedures + DataCompressionProcedures = 0x90, + /// Euro-ISDN protocol control + Euro_ISDN = 0x91, + /// V.24 rate adaptation to ISDN + RateAdaptation_V_24 = 0x92, + /// CAPI Commands + CAPI = 0x93, + /// Host based driver. Note: This protocol code should only be used + /// in messages between host and device to identify the host driver + /// portion of a protocol stack. + HostBasedDriver = 0xFD, + /// CDC Specification The protocol(s) are described using a Protocol + /// Unit Functional Descriptors on Communications Class Interface + SpecifiedIn_PUF_Descriptor = 0xFE, + _, + }; + + pub const SmartCard = Default; + pub const ContentSecurity = Default; + pub const Video = Default; + pub const PersonalHealthcare = Default; + pub const AudioVideoDevice = Default; + pub const BillboardDevice = Default; + pub const USBTypeCBridge = Default; + pub const USBBulkDisplayProtocol = Default; + pub const MCTPoverUSBProtocolEndpoint = Default; + pub const I3C = Default; + pub const DiagnosticDevice = Default; + pub const WirelessController = Default; + pub const Miscellaneous = Default; + pub const ApplicationSpecific = Default; + pub const VendorSpecific = Default; }; /// Class code, distinguishing the type of interface. From c40f9075be01abc1ce760cc90f8f5dc5fdf66428 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 14:42:59 +0100 Subject: [PATCH 32/80] adhere more to style guidelines --- core/src/core/usb.zig | 2 +- core/src/core/usb/descriptor/hid.zig | 4 ++-- core/src/core/usb/drivers/cdc.zig | 4 ++-- core/src/core/usb/drivers/hid.zig | 10 ++++---- core/src/core/usb/types.zig | 36 ++++++++++++++-------------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 5b13350f5..74efc30ae 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -161,7 +161,7 @@ pub fn DeviceController(config: Config) type { descriptor.cdc.CallManagement, descriptor.cdc.AbstractControlModel, descriptor.cdc.Union, - descriptor.hid.Hid, + descriptor.hid.HID, => {}, else => @compileLog(fld), } diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index a77224545..cb5995e1f 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -14,7 +14,7 @@ pub const RequestType = enum(u8) { }; /// USB HID descriptor -pub const Hid = extern struct { +pub const HID = extern struct { /// HID country codes pub const CountryCode = enum(u8) { NotSupported = 0, @@ -56,7 +56,7 @@ pub const Hid = extern struct { }; pub const Type = enum(u8) { - Hid = 0x21, + HID = 0x21, Report = 0x22, Physical = 0x23, }; diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index fcad72f28..934419782 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -63,7 +63,7 @@ pub fn CdcClassDriver(options: Options) type { .interface_number = itf_notifi, .alternate_setting = 0, .num_endpoints = 1, - .interface_triple = .from(.Cdc, .Abstract, .NoneRequired), + .interface_triple = .from(.CDC, .Abstract, .NoneRequired), .interface_s = first_string, }, .cdc_header = .{ .bcd_cdc = .from(0x0120) }, @@ -81,7 +81,7 @@ pub fn CdcClassDriver(options: Options) type { .interface_number = itf_data, .alternate_setting = 0, .num_endpoints = 2, - .interface_triple = .from(.CdcData, .Unused, .NoneRequired), + .interface_triple = .from(.CDC_Data, .Unused, .NoneRequired), .interface_s = 0, }, .ep_out = .bulk(alloc.next_ep(.Out), max_supported_packet_size), diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 1052e11af..63529c42d 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -12,7 +12,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return struct { pub const Descriptor = extern struct { interface: usb.descriptor.Interface, - hid: usb.descriptor.hid.Hid, + hid: usb.descriptor.hid.HID, ep_out: usb.descriptor.Endpoint, ep_in: usb.descriptor.Endpoint, @@ -27,7 +27,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { .alternate_setting = 0, .num_endpoints = 2, .interface_triple = .from( - .Hid, + .HID, if (options.boot_protocol) .Boot else .Unspecified, if (options.boot_protocol) .Boot else .None, ), @@ -40,7 +40,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { } }; - const hid_descriptor: usb.descriptor.hid.Hid = .{ + const hid_descriptor: usb.descriptor.hid.HID = .{ .bcd_hid = .from(0x0111), .country_code = .NotSupported, .num_descriptors = 1, @@ -68,10 +68,10 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { _ = self; switch (setup.request_type.type) { .Standard => { - const hid_desc_type = std.meta.intToEnum(usb.descriptor.hid.Hid.Type, setup.value.into() >> 8) catch return usb.nak; + const hid_desc_type = std.meta.intToEnum(usb.descriptor.hid.HID.Type, setup.value.into() >> 8) catch return usb.nak; const request_code = std.meta.intToEnum(usb.types.SetupRequest, setup.request) catch return usb.nak; - if (request_code == .GetDescriptor and hid_desc_type == .Hid) + if (request_code == .GetDescriptor and hid_desc_type == .HID) return @as([]const u8, @ptrCast(&hid_descriptor)) else if (request_code == .GetDescriptor and hid_desc_type == .Report) return @as([]const u8, @ptrCast(&report_descriptor)); diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index e592e0ed0..60862742a 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -6,23 +6,23 @@ pub const ClassSubclassProtocol = extern struct { pub const ClassCode = enum(u8) { Unspecified = 0x00, Audio = 0x01, - Cdc = 0x02, - Hid = 0x03, + CDC = 0x02, + HID = 0x03, Physical = 0x05, Image = 0x06, Printer = 0x07, MassStorage = 0x08, Hub = 0x09, - CdcData = 0x0A, + CDC_Data = 0x0A, SmartCard = 0x0B, ContentSecurity = 0x0D, Video = 0x0E, PersonalHealthcare = 0x0F, AudioVideoDevice = 0x10, BillboardDevice = 0x11, - USBTypeCBridge = 0x12, - USBBulkDisplayProtocol = 0x13, - MCTPoverUSBProtocolEndpoint = 0x14, + TypeCBridge = 0x12, + BulkDisplayProtocol = 0x13, + MCTP_over_USB_ProtocolEndpoint = 0x14, I3C = 0x3C, DiagnosticDevice = 0xDC, WirelessController = 0xE0, @@ -50,7 +50,7 @@ pub const ClassSubclassProtocol = extern struct { pub const Unspecified = Default; pub const Audio = Default; - pub const Cdc = enum(u8) { + pub const CDC = enum(u8) { Unspecified = 0x00, VendorSpecific = 0xFF, /// Direct Line Control Model @@ -82,7 +82,7 @@ pub const ClassSubclassProtocol = extern struct { _, }; - pub const Hid = enum(u8) { + pub const HID = enum(u8) { Unspecified = 0x00, VendorSpecific = 0xFF, /// @@ -120,7 +120,7 @@ pub const ClassSubclassProtocol = extern struct { pub const Hub = Default; - pub const CdcData = enum(u8) { + pub const CDC_Data = enum(u8) { Unused = 0, VendorSpecific = 0xFF, _, @@ -132,9 +132,9 @@ pub const ClassSubclassProtocol = extern struct { pub const PersonalHealthcare = Default; pub const AudioVideoDevice = Default; pub const BillboardDevice = Default; - pub const USBTypeCBridge = Default; - pub const USBBulkDisplayProtocol = Default; - pub const MCTPoverUSBProtocolEndpoint = Default; + pub const TypeCBridge = Default; + pub const BulkDisplayProtocol = Default; + pub const MCTP_over_USB_ProtocolEndpoint = Default; pub const I3C = Default; pub const DiagnosticDevice = Default; pub const WirelessController = Default; @@ -153,7 +153,7 @@ pub const ClassSubclassProtocol = extern struct { pub const Unspecified = Default; pub const Audio = Default; - pub const Cdc = enum(u8) { + pub const CDC = enum(u8) { /// USB specification No class specific protocol required NoneRequired = 0x00, /// ITU-T V.250 AT Commands: V.250 etc @@ -177,7 +177,7 @@ pub const ClassSubclassProtocol = extern struct { _, }; - pub const Hid = enum(u8) { + pub const HID = enum(u8) { NoneRequired = 0x00, VendorSpecific = 0xFF, /// @@ -205,7 +205,7 @@ pub const ClassSubclassProtocol = extern struct { pub const Hub = Default; - pub const CdcData = enum(u8) { + pub const CDC_Data = enum(u8) { NoneRequired = 0, VendorSpecific = 0xFF, /// Network Transfer Block @@ -246,9 +246,9 @@ pub const ClassSubclassProtocol = extern struct { pub const PersonalHealthcare = Default; pub const AudioVideoDevice = Default; pub const BillboardDevice = Default; - pub const USBTypeCBridge = Default; - pub const USBBulkDisplayProtocol = Default; - pub const MCTPoverUSBProtocolEndpoint = Default; + pub const TypeCBridge = Default; + pub const BulkDisplayProtocol = Default; + pub const MCTP_over_USB_ProtocolEndpoint = Default; pub const I3C = Default; pub const DiagnosticDevice = Default; pub const WirelessController = Default; From b9f61f5c6eedd2bac547767ff2fe74e7e19c2c9b Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sat, 17 Jan 2026 14:45:05 +0100 Subject: [PATCH 33/80] ...again --- core/src/core/usb/types.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 60862742a..7a53ede4a 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -20,9 +20,9 @@ pub const ClassSubclassProtocol = extern struct { PersonalHealthcare = 0x0F, AudioVideoDevice = 0x10, BillboardDevice = 0x11, - TypeCBridge = 0x12, + Type_C_Bridge = 0x12, BulkDisplayProtocol = 0x13, - MCTP_over_USB_ProtocolEndpoint = 0x14, + MCTP_Over_USB_ProtocolEndpoint = 0x14, I3C = 0x3C, DiagnosticDevice = 0xDC, WirelessController = 0xE0, @@ -132,9 +132,9 @@ pub const ClassSubclassProtocol = extern struct { pub const PersonalHealthcare = Default; pub const AudioVideoDevice = Default; pub const BillboardDevice = Default; - pub const TypeCBridge = Default; + pub const Type_C_Bridge = Default; pub const BulkDisplayProtocol = Default; - pub const MCTP_over_USB_ProtocolEndpoint = Default; + pub const MCTP_Over_USB_ProtocolEndpoint = Default; pub const I3C = Default; pub const DiagnosticDevice = Default; pub const WirelessController = Default; @@ -246,9 +246,9 @@ pub const ClassSubclassProtocol = extern struct { pub const PersonalHealthcare = Default; pub const AudioVideoDevice = Default; pub const BillboardDevice = Default; - pub const TypeCBridge = Default; + pub const Type_C_Bridge = Default; pub const BulkDisplayProtocol = Default; - pub const MCTP_over_USB_ProtocolEndpoint = Default; + pub const MCTP_Over_USB_ProtocolEndpoint = Default; pub const I3C = Default; pub const DiagnosticDevice = Default; pub const WirelessController = Default; From 8dd3a7bf4c7b1814455fcde4d8653c0ce04432a3 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 17 Jan 2026 17:51:46 -0500 Subject: [PATCH 34/80] add USBHD/USBHS device backend implementation --- examples/wch/ch32v/src/usb_cdc.zig | 59 +++ port/wch/ch32v/src/hals/usbhs.zig | 665 +++++++++++++++++++++++++++++ 2 files changed, 724 insertions(+) create mode 100644 examples/wch/ch32v/src/usb_cdc.zig create mode 100644 port/wch/ch32v/src/hals/usbhs.zig diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig new file mode 100644 index 000000000..922a20cdc --- /dev/null +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -0,0 +1,59 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const time = hal.time; +const gpio = hal.gpio; + +const RCC = microzig.chip.peripherals.RCC; +const AFIO = microzig.chip.peripherals.AFIO; + +const usart = hal.usart.instance.USART1; +const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 + +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = hal.usart.log, +}; + +pub fn main() !void { + // Board brings up clocks and time + microzig.board.init(); + microzig.hal.init(); + + // Enable peripheral clocks for USART2 and GPIOA + RCC.APB2PCENR.modify(.{ + .IOPAEN = 1, // Enable GPIOA clock + .AFIOEN = 1, // Enable AFIO clock + .USART1EN = 1, // Enable USART1 clock + }); + RCC.APB1PCENR.modify(.{ + .USART2EN = 1, // Enable USART2 clock + }); + + // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) + AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); + + // Configure PA2 as alternate function push-pull for USART2 TX + usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); + + // Initialize USART2 at 115200 baud + usart.apply(.{ .baud_rate = 115200 }); + + hal.usart.init_logger(usart); + std.log.info("UART logging initialized.", .{}); + + std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); + microzig.cpu.interrupt.enable_interrupts(); + std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); + + var last = time.get_time_since_boot(); + var i: u32 = 0; + while (true) : (i += 1) { + const now = time.get_time_since_boot(); + if (now.diff(last).to_us() > 1000000) { + std.log.info("what {}", .{i}); + last = now; + } + hal.run_usb_demo(); + } +} diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig new file mode 100644 index 000000000..84acf5209 --- /dev/null +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -0,0 +1,665 @@ +//! WCH USBHD/USBHS device backend (polled) +//! +//! Key semantics (per DeviceController in usb.zig): +//! - Must call controller.on_buffer() on IN completion (especially EP0 IN). +//! - Must call controller.on_buffer() on OUT receipt; driver will call ep_readv() exactly once. +//! - EP0 OUT must always accept status-stage OUT ZLP (controller does not always re-arm EP0 OUT). +//! +//! Ownership safety: +//! - OUT buffer is only read after UIF_TRANSFER OUT event; endpoint set to NAK immediately after. +//! - IN buffer is only written when not tx_busy; endpoint set to NAK immediately after IN complete. + +const std = @import("std"); +const assert = std.debug.assert; + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const usb = microzig.core.usb; +const types = usb.types; +const descriptor = usb.descriptor; + +pub const USBHD_MAX_ENDPOINTS_COUNT = 16; + +var pool: [2048]u8 = undefined; + +pub const Config = struct { + max_endpoints_count: comptime_int = USBHD_MAX_ENDPOINTS_COUNT, + + /// Some chips have USBHD regs but no HS PHY. Force FS in that case. + has_hs_phy: bool = true, + prefer_high_speed: bool = true, + + /// Static buffer pool in SRAM, bump-allocated. + buffer_bytes: comptime_int = 2048, + + /// Enable SIE "int busy" behavior: pauses during UIF_TRANSFER so polling won't lose INT_ST context. + int_busy: bool = true, + + /// Future seam only; not implemented in this initial version. + use_interrupts: bool = false, +}; + +const Regs = @TypeOf(peripherals.USBHS); +fn regs() Regs { + return peripherals.USBHS; +} + +const EpState = struct { + buf: []align(4) u8 = &[_]u8{}, + // OUT: + rx_armed: bool = false, + rx_limit: u16 = 0, + rx_last_len: u16 = 0, // valid until ep_readv() consumes it + // IN: + tx_busy: bool = false, + tx_last_len: u16 = 0, +}; + +fn PerEndpointArray(comptime N: comptime_int) type { + return [N][2]EpState; // [ep][dir] +} + +fn epn(ep: types.Endpoint.Num) u4 { + return @as(u4, @intCast(@intFromEnum(ep))); +} +fn dir_index(d: types.Dir) u1 { + return @as(u1, @intCast(@intFromEnum(d))); +} + +fn speed_type(comptime cfg: Config) u2 { + if (!cfg.has_hs_phy) return 0; // FS + if (!cfg.prefer_high_speed) return 0; // FS + return 1; // HS (per USBHS.zig field comment) +} + +// --- USBHD token encodings --- +const TOKEN_OUT: u2 = 0; +const TOKEN_SOF: u2 = 1; +const TOKEN_IN: u2 = 2; +const TOKEN_SETUP: u2 = 3; + +// --- INT_FG raw bits (match USBHS.zig packed order) --- +const UIF_BUS_RST: u8 = 1 << 0; +const UIF_TRANSFER: u8 = 1 << 1; +const UIF_SUSPEND: u8 = 1 << 2; +const UIF_HST_SOF: u8 = 1 << 3; +const UIF_FIFO_OV: u8 = 1 << 4; +const UIF_SETUP_ACT: u8 = 1 << 5; +const UIF_ISO_ACT: u8 = 1 << 6; + +// --- endpoint response encodings (WCH style) --- +const RES_ACK: u2 = 0; +const RES_NAK: u2 = 2; +const RES_STALL: u2 = 3; + +const TOG_DATA0: u2 = 0; +const TOG_DATA1: u2 = 1; + +// We use offset-based access for per-EP regs to keep helpers compact. +const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); +const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); +const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); + +fn baseAddr() usize { + return @intFromPtr(peripherals.USBHS); +} +fn mmio_u32(off: usize) *volatile RegU32 { + return @ptrFromInt(baseAddr() + off); +} +fn mmio_u16(off: usize) *volatile RegU16 { + return @ptrFromInt(baseAddr() + off); +} +fn mmio_u8(off: usize) *volatile RegU8 { + return @ptrFromInt(baseAddr() + off); +} + +// RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) +// EP0 has its own dedicated DMA reg. +// URGENT TODO: FIX EP0 DMA handling! +fn ep0_dma() *volatile RegU32 { + return mmio_u32(0x1C); +} +fn uep_rx_dma(ep: u4) *volatile RegU32 { + return mmio_u32(0x20 + (@as(usize, ep - 1) * 4)); +} +// TX DMA: 0x5C + (ep-1)*4 (EP1..EP15) +fn uep_tx_dma(ep: u4) *volatile RegU32 { + return mmio_u32(0x5C + (@as(usize, ep - 1) * 4)); +} + +// MAX_LEN: EP0..EP15 at 0x98 + ep*4 +fn uep_max_len(ep: u4) *volatile RegU16 { + return mmio_u16(0x98 + (@as(usize, ep) * 4)); +} +// T_LEN: EP0..EP15 at 0xD8 + ep*4 +fn uep_t_len(ep: u4) *volatile RegU16 { + return mmio_u16(0xD8 + (@as(usize, ep) * 4)); +} +// TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 +fn uep_tx_ctrl(ep: u4) *volatile RegU8 { + return mmio_u8(0xDA + (@as(usize, ep) * 4)); +} +fn uep_rx_ctrl(ep: u4) *volatile RegU8 { + return mmio_u8(0xDB + (@as(usize, ep) * 4)); +} + +fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { + // [1:0]=RES, [4:3]=TOG, [5]=AUTO + var v: u8 = 0; + v |= @as(u8, res); + v |= @as(u8, tog) << 3; + if (auto) v |= 1 << 5; + uep_tx_ctrl(ep).write_raw(v); +} +fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { + var v: u8 = 0; + v |= @as(u8, res); + v |= @as(u8, tog) << 3; + if (auto) v |= 1 << 5; + uep_rx_ctrl(ep).write_raw(v); +} + +/// Polled USBHD device backend for microzig core USB controller. +pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { + comptime { + if (cfg.max_endpoints_count > USBHD_MAX_ENDPOINTS_COUNT) + @compileError("USBHD max_endpoints_count cannot exceed 16"); + if (cfg.buffer_bytes < 64) + @compileError("USBHD buffer_bytes must be at least 64"); + } + + return struct { + const Self = @This(); + + const vtable: usb.DeviceInterface.VTable = .{ + .ep_writev = ep_writev, + .ep_readv = ep_readv, + .ep_listen = ep_listen, + .ep_open = ep_open, + .set_address = set_address, + }; + + endpoints: PerEndpointArray(cfg.max_endpoints_count), + buffer_pool: []align(4) u8, + + controller: usb.DeviceController(controller_config), + interface: usb.DeviceInterface, + + pub fn init() Self { + var self: Self = .{ + .endpoints = undefined, + .buffer_pool = undefined, + .controller = .init, + .interface = .{ .vtable = &vtable }, + }; + @memset(std.mem.asBytes(&self.endpoints), 0); + + self.buffer_pool = @alignCast(pool[0..]); + + usbhd_hw_init(); + + // EP0 is required; open OUT then IN (or vice versa), we will share buffer. + self.interface.ep_open(&.{ + .endpoint = .out(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + self.interface.ep_open(&.{ + .endpoint = .in(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + + // EP0 OUT should always be able to accept status-stage OUT ZLP. + // So keep EP0 RX in ACK state permanently. + self.arm_ep0_out_always(); + + // Connect pull-up to signal device ready + regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); + return self; + } + + // TODO: replace with fixedbuffer allocator + fn endpoint_alloc(self: *Self, size: usize) []align(4) u8 { + assert(self.buffer_pool.len >= size); + const out = self.buffer_pool[0..size]; + self.buffer_pool = @alignCast(self.buffer_pool[size..]); + return out; + } + + fn st(self: *Self, ep_num: types.Endpoint.Num, dir: types.Dir) *EpState { + return &self.endpoints[@intFromEnum(ep_num)][@intFromEnum(dir)]; + } + + fn arm_ep0_out_always(self: *Self) void { + _ = self; + // EP0 OUT is always ACK with auto-toggle. + set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + // EP0 IN remains NAK until data is queued. + set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + } + + fn on_bus_reset_local(self: *Self) void { + // Clear state + inline for (0..cfg.max_endpoints_count) |i| { + self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; + self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; + self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; + self.endpoints[i][@intFromEnum(types.Dir.In)].tx_last_len = 0; + } + + // Default: NAK all non-EP0 endpoints. + for (1..cfg.max_endpoints_count) |i| { + const e: u4 = @as(u4, @intCast(i)); + set_rx_ctrl(e, RES_NAK, TOG_DATA0, true); + set_tx_ctrl(e, RES_NAK, TOG_DATA0, true); + } + + // EP0 special. + self.arm_ep0_out_always(); + } + + fn read_setup_from_ep0(self: *Self) types.SetupPacket { + const ep0 = self.st(.ep0, .Out); + assert(ep0.buf.len >= 8); + const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); + return @bitCast(words_ptr.*); + } + + // ---- comptime dispatch helpers (required by DeviceController API) ---- + + fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, buf: []u8) void { + // controller.on_buffer requires comptime ep parameter + switch (dir) { + .In => switch (ep) { + inline 0...15 => |i| { + const num: types.Endpoint.Num = @enumFromInt(i); + self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }, buf); + }, + }, + .Out => switch (ep) { + inline 0...15 => |i| { + const num: types.Endpoint.Num = @enumFromInt(i); + self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }, buf); + }, + }, + } + } + + // ---- Poll loop ------------------------------------------------------- + + pub fn poll(self: *Self) void { + // std.log.debug("USBHS: poll start", .{}); + while (true) { + const fg: u8 = regs().USB_INT_FG.raw; + if (fg == 0) break; + + std.log.debug("USBHS: INT_FG={x}", .{fg}); + + if ((fg & UIF_BUS_RST) != 0) { + std.log.info("USB: bus reset", .{}); + // clear + regs().USB_INT_FG.write_raw(UIF_BUS_RST); + + // address back to 0 + set_address(&self.interface, 0); + + self.on_bus_reset_local(); + self.controller.on_bus_reset(); + continue; + } + + if ((fg & UIF_SETUP_ACT) != 0) { + std.log.info("USB: SETUP received", .{}); + // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. + regs().USB_INT_FG.write_raw(UIF_SETUP_ACT); + if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.write_raw(UIF_TRANSFER); + + const setup = self.read_setup_from_ep0(); + + // After SETUP, EP0 IN data stage starts with DATA1. + set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + + self.controller.on_setup_req(&self.interface, &setup); + continue; + } + + if ((fg & UIF_TRANSFER) != 0) { + std.log.info("USB: TRANSFER event", .{}); + const stv = regs().USB_INT_ST.read(); + const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); + const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + + // clear transfer + regs().USB_INT_FG.write_raw(UIF_TRANSFER); + + self.handle_transfer(ep, token); + continue; + } + + // Clear anything else we don't handle explicitly + regs().USB_INT_FG.write_raw(fg); + } + } + + fn handle_transfer(self: *Self, ep: u4, token: u2) void { + if (ep >= cfg.max_endpoints_count) return; + + switch (token) { + TOKEN_OUT => self.handle_out(ep), + TOKEN_IN => self.handle_in(ep), + TOKEN_SETUP => { + // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. + if (ep == 0) { + const setup = self.read_setup_from_ep0(); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + self.controller.on_setup_req(&self.interface, &setup); + } + }, + TOKEN_SOF => {}, + } + } + + fn handle_out(self: *Self, ep: u4) void { + const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; + std.log.info("USB: EP{} OUT received {} bytes", .{ ep, len }); + // EP0 OUT is always armed; accept status ZLP. + if (ep == 0) { + asm volatile ("" ::: .{ .memory = true }); + const st_out = self.st(.ep0, .Out); + st_out.rx_last_len = len; + // stay ACK + self.call_on_buffer(.Out, 0, st_out.buf[0..@min(@as(usize, len), st_out.buf.len)]); + return; + } + + const num: types.Endpoint.Num = @enumFromInt(ep); + const st_out = self.st(num, .Out); + + // Only read if previously armed (ep_listen) + if (!st_out.rx_armed) { + set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + return; + } + + asm volatile ("" ::: .{ .memory = true }); + // Disarm immediately (NAK) to regain ownership. + st_out.rx_armed = false; + set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + + const n = @min(@as(usize, len), st_out.buf.len); + st_out.rx_last_len = @as(u16, @intCast(n)); + + self.call_on_buffer(.Out, ep, st_out.buf[0..n]); + } + + fn handle_in(self: *Self, ep: u4) void { + const num: types.Endpoint.Num = @enumFromInt(ep); + const st_in = self.st(num, .In); + + if (!st_in.tx_busy) { + set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + return; + } + + asm volatile ("" ::: .{ .memory = true }); + + // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. + const sent_len = st_in.tx_last_len; + st_in.tx_busy = false; + st_in.tx_last_len = 0; + + set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + + // Notify controller/drivers of IN completion. + self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); + + // EP0 OUT must remain ACK + if (ep == 0) self.arm_ep0_out_always(); + } + + // ---- VTable functions ------------------------------------------------ + + fn set_address(_: *usb.DeviceInterface, addr: u7) void { + std.log.info("USB: set_address {}", .{addr}); + regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); + } + + fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { + std.log.info("USB: ep_open called", .{}); + const self: *Self = @fieldParentPtr("interface", itf); + const desc = desc_ptr.*; + + const e = desc.endpoint; + const ep_i: u4 = epn(e.num); + assert(ep_i < cfg.max_endpoints_count); + + const mps: u16 = desc.max_packet_size.into(); + assert(mps > 0 and mps <= 2047); + + // EP0 shares a single DMA buffer for both directions. + if (e.num == .ep0) { + const out_st = self.st(.ep0, .Out); + const in_st = self.st(.ep0, .In); + if (ep0_dma().raw == 0) { + std.log.warn("USBHS: EP0 DMA not initialized yet!", .{}); + } + if (out_st.buf.len == 0 and in_st.buf.len == 0) { + // this should probably be fixed at 64 bytes for EP0? + const buf = self.endpoint_alloc(64); + out_st.buf = buf; + in_st.buf = buf; + + ep0_dma().write_raw(@as(u32, @intCast(@intFromPtr(buf.ptr)))); + uep_max_len(0).write_raw(@intCast(64)); + } else { + // Ensure both directions point at the same backing buffer. + if (out_st.buf.len == 0) out_st.buf = in_st.buf; + if (in_st.buf.len == 0) in_st.buf = out_st.buf; + } + } else { + // Non-EP0: separate DMA buffers per direction + const st_ep = self.st(e.num, e.dir); + + if (st_ep.buf.len == 0) { + st_ep.buf = self.endpoint_alloc(@as(usize, mps)); + } + + const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); + + if (e.dir == .Out) { + uep_rx_dma(ep_i).write_raw(ptr_val); + uep_max_len(ep_i).write_raw(mps); + set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + } else { + uep_tx_dma(ep_i).write_raw(ptr_val); + set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + } + } + + // Enable endpoint direction in UEP_CONFIG bitmaps. + var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; + if (e.num != .ep0) { + // if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + ep_i)); + if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); + } + regs().UEP_CONFIG__UHOST_CTRL.write_raw(cfg_raw); + + // Endpoint type ISO marking (optional, only if you use ISO). + if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { + var type_raw: u32 = regs().UEP_TYPE.raw; + // if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + ep_i)); + if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); + regs().UEP_TYPE.write_raw(type_raw); + } + + // EP0 OUT always ACK + if (e.num == .ep0 and e.dir == .Out) { + self.arm_ep0_out_always(); + } + + // IMPORTANT per usb.zig comment: + // For IN endpoints, ep_open may call controller.on_buffer so drivers can prime initial data. + // Must be comptime-dispatched. + if (e.dir == .In and e.num != .ep0) { + // Empty slice -> “buffer is available / endpoint opened” + switch (e.num) { + inline else => |num_ct| { + const st_in = self.st(num_ct, .In); + self.controller.on_buffer(&self.interface, .{ .num = num_ct, .dir = .In }, st_in.buf[0..0]); + }, + } + } + } + + fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { + const self: *Self = @fieldParentPtr("interface", itf); + + // EP0 OUT is always armed; ignore listen semantics here. + if (ep_num == .ep0) { + const st0 = self.st(.ep0, .Out); + st0.rx_limit = @as(u16, @intCast(len)); + set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + return; + } + + const ep_i: u4 = epn(ep_num); + assert(ep_i < cfg.max_endpoints_count); + + const st_out = self.st(ep_num, .Out); + assert(st_out.buf.len != 0); + + // Must not be called again until packet received. + assert(!st_out.rx_armed); + + const limit = @min(st_out.buf.len, @as(usize, @intCast(len))); + st_out.rx_limit = @as(u16, @intCast(limit)); + st_out.rx_armed = true; + st_out.rx_last_len = 0; + + uep_max_len(ep_i).write_raw(@as(u16, @intCast(limit))); + + asm volatile ("" ::: .{ .memory = true }); + set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + } + + fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { + const self: *Self = @fieldParentPtr("interface", itf); + + const st_out = self.st(ep_num, .Out); + assert(st_out.buf.len != 0); + + const want: usize = @as(usize, st_out.rx_last_len); + assert(want <= st_out.buf.len); + + // Must be called exactly once per received packet. + // Enforce by clearing rx_last_len after consumption. + defer st_out.rx_last_len = 0; + + var remaining: []align(4) u8 = st_out.buf[0..want]; + var copied: usize = 0; + + for (data) |dst| { + if (remaining.len == 0) break; + const n = @min(dst.len, remaining.len); + @memcpy(dst[0..n], remaining[0..n]); + remaining = @alignCast(remaining[n..]); + copied += n; + } + + // Driver is responsible for re-arming via ep_listen(). + return @as(types.Len, @intCast(copied)); + } + + fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { + const self: *Self = @fieldParentPtr("interface", itf); + assert(vec.len > 0); + + const ep_i: u4 = epn(ep_num); + assert(ep_i < cfg.max_endpoints_count); + + const st_in = self.st(ep_num, .In); + assert(st_in.buf.len != 0); + + if (st_in.tx_busy) { + // Not ready; keep NAK and let upper layer retry. + set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + return 0; + } + + // Copy vector into DMA buffer (up to MPS/buffer size) + var w: usize = 0; + for (vec) |chunk| { + if (w >= st_in.buf.len) break; + const n = @min(chunk.len, st_in.buf.len - w); + @memcpy(st_in.buf[w .. w + n], chunk[0..n]); + w += n; + } + + asm volatile ("" ::: .{ .memory = true }); + + uep_t_len(ep_i).write_raw(@as(u16, @intCast(w))); + + st_in.tx_last_len = @as(u16, @intCast(w)); + st_in.tx_busy = true; + + // Arm IN + set_tx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + + // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. + return @as(types.Len, @intCast(w)); + } + + // ---- HW init --------------------------------------------------------- + + fn usbhd_hw_init() void { + // Reset SIE and clear FIFO + regs().USB_CTRL.write_raw(0); // not sure if writing zero then val is ok? + regs().USB_CTRL.modify(.{ + .RB_UC_CLR_ALL = 1, + .RB_UC_RST_SIE = 1, + }); + // wait 10us, TODO: replace with timer delay + var i: u32 = 0; + while (i < 480) : (i += 1) { + asm volatile ("" ::: .{ .memory = true }); + } + + regs().USB_CTRL.modify(.{ + .RB_UC_RST_SIE = 0, + }); + regs().UHOST_CTRL.write_raw(0); + regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + regs().USB_CTRL.modify(.{ + .RB_UC_DMA_EN = 1, + .RB_UC_INT_BUSY = if (cfg.int_busy) 1 else 0, + .RB_UC_SPEED_TYPE = speed_type(cfg), + }); + + // Enable source interrupts (we poll flags; NVIC off) + regs().USB_INT_EN.modify(.{ + .RB_U_1WIRE_MODE = 1, // actually SETUP_ACT? + .RB_UIE_BUS_RST__RB_UIE_DETECT = 1, + .RB_UIE_TRANSFER = 1, + .RB_UIE_SUSPEND = 1, + .RB_UIE_FIFO_OV = 1, + .RB_UIE_HST_SOF = 1, + }); + + // regs().USB_INT_ST.modify(.{ .RB_UIS_IS_NAK = 1 }); + + // Set some defaults, just in case + // regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = 0 }); + // regs().UEP_CONFIG__UHOST_CTRL.write_raw(0); + // regs().UEP_TYPE.write_raw(0); + // regs().UEP_BUF_MOD.write_raw(0); + } + }; +} + +pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { + std.log.warn("USBHS interrupt handler called", .{}); + regs().USB_INT_FG.raw = 0; // clear all +} From 4c399efbfeb708a47b28480fec21f4b3ef5ebc15 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 18 Jan 2026 13:55:53 +0100 Subject: [PATCH 35/80] add enums to CDC --- core/src/core/usb.zig | 5 ++- core/src/core/usb/descriptor/cdc.zig | 30 ++++++++++++- core/src/core/usb/drivers/cdc.zig | 56 ++++++++++++++++++++----- core/src/core/usb/drivers/example.zig | 2 +- core/src/core/usb/drivers/hid.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 3 +- 6 files changed, 81 insertions(+), 17 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 74efc30ae..e7d318c43 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -385,11 +385,14 @@ pub fn DeviceController(config: Config) type { } fn process_interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { + if (setup.request_type.type != .Class) + log.warn("Non-class ({t}) interface request", .{setup.request_type.type}); + const itf_num: u8 = @truncate(setup.index.into()); switch (itf_num) { inline else => |itf| if (comptime itf < handlers.itf.len) { const drv = handlers.itf[itf]; - return @field(self.driver_data.?, @tagName(drv)).interface_setup(setup); + return @field(self.driver_data.?, @tagName(drv)).class_request(setup); } else { log.warn("Interface index ({}) out of range ({})", .{ itf_num, handlers.itf.len }); return nak; diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index bf6478f59..780fa3eb3 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -26,6 +26,17 @@ pub const Header = extern struct { }; pub const CallManagement = extern struct { + pub const Capabilities = packed struct(u8) { + handles_call_mgmt: bool, + call_mgmt_over_data: bool, + reserved: u6 = 0, + + pub const none: @This() = .{ + .handles_call_mgmt = false, + .call_mgmt_over_data = false, + }; + }; + comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 5); @@ -37,12 +48,27 @@ pub const CallManagement = extern struct { // Subtype of this descriptor, must be `CallManagement`. descriptor_subtype: SubType = .CallManagement, // Capabilities. Should be 0x00 for use as a serial device. - capabilities: u8, + capabilities: Capabilities, // Data interface number. data_interface: u8, }; pub const AbstractControlModel = extern struct { + pub const Capabilities = packed struct(u8) { + /// Device supports the request combination of Set_Comm_Feature, + /// Clear_Comm_Feature, and Get_Comm_Feature + comm_feature: bool, + /// Device supports the request combination of + /// Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, + /// and the notification Serial_State + line_coding: bool, + /// Device supports the request Send_Break + send_break: bool, + /// Device supports the notification Network_Connection + network_connection: bool, + reserved1: u4 = 0, + }; + comptime { assert(@alignOf(@This()) == 1); assert(@sizeOf(@This()) == 4); @@ -54,7 +80,7 @@ pub const AbstractControlModel = extern struct { // Subtype of this descriptor, must be `AbstractControlModel`. descriptor_subtype: SubType = .AbstractControlModel, // Capabilities. Should be 0x02 for use as a serial device. - capabilities: u8, + capabilities: Capabilities, }; pub const Union = extern struct { diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 934419782..484923122 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -4,17 +4,43 @@ const assert = std.debug.assert; const log = std.log.scoped(.usb_cdc); pub const ManagementRequestType = enum(u8) { + SetCommFeature = 0x02, + GetCommFeature = 0x03, + ClearCommFeature = 0x04, + SetAuxLineState = 0x10, + SetHookState = 0x11, + PulseSetup = 0x12, + SendPulse = 0x13, + SetPulseTime = 0x14, + RingAuxJack = 0x15, SetLineCoding = 0x20, GetLineCoding = 0x21, SetControlLineState = 0x22, SendBreak = 0x23, + SetRingerParams = 0x30, + GetRingerParams = 0x31, + SetOperationParams = 0x32, + GetOperationParams = 0x33, + SetLineParams = 0x34, + GetLineParams = 0x35, + DialDigits = 0x36, _, }; pub const LineCoding = extern struct { - bit_rate: u32 align(1), - stop_bits: u8, - parity: u8, + pub const StopBits = enum(u8) { @"1" = 0, @"1.5" = 1, @"2" = 2, _ }; + pub const Parity = enum(u8) { + none = 0, + odd = 1, + even = 2, + mark = 3, + space = 4, + _, + }; + + bit_rate: usb.types.U32_Le, + stop_bits: StopBits, + parity: Parity, data_bits: u8, pub const init: @This() = .{ @@ -68,15 +94,23 @@ pub fn CdcClassDriver(options: Options) type { }, .cdc_header = .{ .bcd_cdc = .from(0x0120) }, .cdc_call_mgmt = .{ - .capabilities = 0, + .capabilities = .none, .data_interface = itf_data, }, - .cdc_acm = .{ .capabilities = 6 }, + .cdc_acm = .{ + .capabilities = .{ + .comm_feature = false, + .send_break = false, + // Line coding requests get sent regardless of this bit + .line_coding = true, + .network_connection = false, + }, + }, .cdc_union = .{ .master_interface = itf_notifi, .slave_interface_0 = itf_data, }, - .ep_notifi = .interrupt(alloc.next_ep(.In), 8, 16), + .ep_notifi = .interrupt(alloc.next_ep(.In), 16, 16), .itf_data = .{ .interface_number = itf_data, .alternate_setting = 0, @@ -171,9 +205,9 @@ pub fn CdcClassDriver(options: Options) type { .device = device, .ep_notifi = desc.ep_notifi.endpoint.num, .line_coding = .{ - .bit_rate = 115200, - .stop_bits = 0, - .parity = 0, + .bit_rate = .from(115200), + .stop_bits = .@"1", + .parity = .none, .data_bits = 8, }, @@ -189,9 +223,9 @@ pub fn CdcClassDriver(options: Options) type { device.ep_listen(desc.ep_out.endpoint.num, options.max_packet_size); } - pub fn interface_setup(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { const mgmt_request: ManagementRequestType = @enumFromInt(setup.request); - log.debug("cdc setup: {t}", .{mgmt_request}); + log.debug("cdc setup: {t} {} {}", .{ mgmt_request, setup.length.into(), setup.value.into() }); return switch (mgmt_request) { .SetLineCoding => usb.ack, // we should handle data phase somehow to read sent line_coding diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index 156f3ba41..70bf49bf3 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -59,7 +59,7 @@ pub const EchoExampleDriver = struct { /// Used for interface configuration through endpoint 0. /// Data returned by this function is sent on endpoint 0. - pub fn interface_setup(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { _ = self; _ = setup; return usb.ack; diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 63529c42d..97d1e8079 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -64,7 +64,7 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }; } - pub fn interface_setup(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { _ = self; switch (setup.request_type.type) { .Standard => { diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 4033f7484..6d7ccb02d 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -236,7 +236,7 @@ pub fn Polled(config: Config) type { peripherals.USB.SIE_CTRL.modify(.{ .PULLUP_EN = 1 }); // Listen for ACKs - self.interface.ep_listen(.ep0, 0); + self.interface.ep_listen(.ep0, max_supported_packet_size); return self; } @@ -410,6 +410,7 @@ pub fn Polled(config: Config) type { .BUFFER_ADDRESS = rp2xxx_buffers.data_offset(ep_hard.data_buffer), }); } + @memset(ep_hard.data_buffer, 0); } fn endpoint_alloc(self: *@This(), desc: *const usb.descriptor.Endpoint) ![]align(64) u8 { From 3f494d030a560ffcd63944da23f582b40a7f1e79 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 18 Jan 2026 14:19:30 +0100 Subject: [PATCH 36/80] cleanup --- core/src/core/usb/descriptor.zig | 8 ++-- core/src/core/usb/descriptor/cdc.zig | 2 +- core/src/core/usb/descriptor/hid.zig | 2 + core/src/core/usb/drivers/cdc.zig | 55 ++++++++++++++------------- core/src/core/usb/drivers/example.zig | 21 +++++----- core/src/core/usb/drivers/hid.zig | 30 ++++++++------- 6 files changed, 64 insertions(+), 54 deletions(-) diff --git a/core/src/core/usb/descriptor.zig b/core/src/core/usb/descriptor.zig index 8baada416..3d6109a1a 100644 --- a/core/src/core/usb/descriptor.zig +++ b/core/src/core/usb/descriptor.zig @@ -152,13 +152,13 @@ pub const String = struct { data: []const u8, - pub fn from_lang(lang: Language) @This() { + pub fn from_lang(comptime lang: Language) @This() { const ret: *const extern struct { length: u8 = @sizeOf(@This()), descriptor_type: Type = .String, lang: types.U16_Le, } = comptime &.{ .lang = .from(@intFromEnum(lang)) }; - return .{ .data = @ptrCast(ret) }; + return .{ .data = std.mem.asBytes(ret) }; } pub fn from_str(comptime string: []const u8) @This() { @@ -294,9 +294,9 @@ pub const BOS = struct { data: []const u8, - pub fn from(objects: []const Object) @This() { + pub fn from(comptime objects: []const Object) @This() { const data: []const u8 = ""; - const header: []const u8 = @ptrCast(&extern struct { + const header: []const u8 = std.mem.asBytes(&extern struct { length: u8 = @sizeOf(@This()), descriptor_type: Type = .BOS, total_length: types.U16_Le = .from(@sizeOf(@This()) + data.len), diff --git a/core/src/core/usb/descriptor/cdc.zig b/core/src/core/usb/descriptor/cdc.zig index 780fa3eb3..1230b29cd 100644 --- a/core/src/core/usb/descriptor/cdc.zig +++ b/core/src/core/usb/descriptor/cdc.zig @@ -66,7 +66,7 @@ pub const AbstractControlModel = extern struct { send_break: bool, /// Device supports the notification Network_Connection network_connection: bool, - reserved1: u4 = 0, + reserved: u4 = 0, }; comptime { diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index cb5995e1f..a7a8ae1a0 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -11,6 +11,7 @@ pub const RequestType = enum(u8) { SetReport = 0x09, SetIdle = 0x0a, SetProtocol = 0x0b, + _, }; /// USB HID descriptor @@ -59,6 +60,7 @@ pub const HID = extern struct { HID = 0x21, Report = 0x22, Physical = 0x23, + _, }; comptime { diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 484923122..b3060dfd2 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -1,6 +1,7 @@ const std = @import("std"); const usb = @import("../../usb.zig"); const assert = std.debug.assert; +const EpNum = usb.types.Endpoint.Num; const log = std.log.scoped(.usb_cdc); pub const ManagementRequestType = enum(u8) { @@ -58,16 +59,18 @@ pub const Options = struct { pub fn CdcClassDriver(options: Options) type { return struct { pub const Descriptor = extern struct { - itf_assoc: usb.descriptor.InterfaceAssociation, - itf_notifi: usb.descriptor.Interface, - cdc_header: usb.descriptor.cdc.Header, - cdc_call_mgmt: usb.descriptor.cdc.CallManagement, - cdc_acm: usb.descriptor.cdc.AbstractControlModel, - cdc_union: usb.descriptor.cdc.Union, - ep_notifi: usb.descriptor.Endpoint, - itf_data: usb.descriptor.Interface, - ep_out: usb.descriptor.Endpoint, - ep_in: usb.descriptor.Endpoint, + const desc = usb.descriptor; + + itf_assoc: desc.InterfaceAssociation, + itf_notifi: desc.Interface, + cdc_header: desc.cdc.Header, + cdc_call_mgmt: desc.cdc.CallManagement, + cdc_acm: desc.cdc.AbstractControlModel, + cdc_union: desc.cdc.Union, + ep_notifi: desc.Endpoint, + itf_data: desc.Interface, + ep_out: desc.Endpoint, + ep_in: desc.Endpoint, pub fn create( alloc: *usb.DescriptorAllocator, @@ -131,19 +134,19 @@ pub fn CdcClassDriver(options: Options) type { }; device: *usb.DeviceInterface, - ep_notifi: usb.types.Endpoint.Num, + ep_notifi: EpNum, line_coding: LineCoding align(4), /// OUT endpoint on which there is data ready to be read, /// or .ep0 when no data is available. - ep_out: usb.types.Endpoint.Num, + ep_out: EpNum, rx_data: [options.max_packet_size]u8, rx_seek: usb.types.Len, rx_end: usb.types.Len, /// IN endpoint where data can be sent, /// or .ep0 when data is being sent. - ep_in: usb.types.Endpoint.Num, + ep_in: EpNum, tx_data: [options.max_packet_size]u8, tx_end: usb.types.Len, @@ -159,11 +162,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.available() > 0) return len; // request more data - const ep_out = @atomicLoad(usb.types.Endpoint.Num, &self.ep_out, .seq_cst); + const ep_out = @atomicLoad(EpNum, &self.ep_out, .seq_cst); if (ep_out != .ep0) { self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); self.rx_seek = 0; - @atomicStore(usb.types.Endpoint.Num, &self.ep_out, .ep0, .seq_cst); + @atomicStore(EpNum, &self.ep_out, .ep0, .seq_cst); self.device.ep_listen(ep_out, options.max_packet_size); } @@ -189,11 +192,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.tx_end == 0) return true; - const ep_in = @atomicLoad(usb.types.Endpoint.Num, &self.ep_in, .seq_cst); + const ep_in = @atomicLoad(EpNum, &self.ep_in, .seq_cst); if (ep_in == .ep0) return false; - @atomicStore(usb.types.Endpoint.Num, &self.ep_in, .ep0, .seq_cst); + @atomicStore(EpNum, &self.ep_in, .ep0, .seq_cst); assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); self.tx_end = 0; @@ -241,19 +244,19 @@ pub fn CdcClassDriver(options: Options) type { }; } - pub fn on_rx(self: *@This(), ep_num: usb.types.Endpoint.Num) void { - assert(.ep0 == @atomicLoad(usb.types.Endpoint.Num, &self.ep_out, .seq_cst)); - @atomicStore(usb.types.Endpoint.Num, &self.ep_out, ep_num, .seq_cst); + pub fn on_rx(self: *@This(), ep_num: EpNum) void { + assert(.ep0 == @atomicLoad(EpNum, &self.ep_out, .seq_cst)); + @atomicStore(EpNum, &self.ep_out, ep_num, .seq_cst); } - pub fn on_tx_ready(self: *@This(), ep_num: usb.types.Endpoint.Num) void { - assert(.ep0 == @atomicLoad(usb.types.Endpoint.Num, &self.ep_in, .seq_cst)); - @atomicStore(usb.types.Endpoint.Num, &self.ep_in, ep_num, .seq_cst); + pub fn on_tx_ready(self: *@This(), ep_num: EpNum) void { + assert(.ep0 == @atomicLoad(EpNum, &self.ep_in, .seq_cst)); + @atomicStore(EpNum, &self.ep_in, ep_num, .seq_cst); } - pub fn on_notifi_ready(self: *@This(), ep_num: usb.types.Endpoint.Num) void { - assert(.ep0 == @atomicLoad(usb.types.Endpoint.Num, &self.ep_notifi, .seq_cst)); - @atomicStore(usb.types.Endpoint.Num, &self.ep_notifi, ep_num, .seq_cst); + pub fn on_notifi_ready(self: *@This(), ep_num: EpNum) void { + assert(.ep0 == @atomicLoad(EpNum, &self.ep_notifi, .seq_cst)); + @atomicStore(EpNum, &self.ep_notifi, ep_num, .seq_cst); } }; } diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index 70bf49bf3..a8caa8054 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -1,14 +1,17 @@ const std = @import("std"); const usb = @import("../../usb.zig"); +const EpNum = usb.types.Endpoint.Num; const log = std.log.scoped(.usb_echo); /// This is an example driver that sends any data received on ep2 to ep1. pub const EchoExampleDriver = struct { /// The descriptors need to have the same memory layout as the sent data. pub const Descriptor = extern struct { - example_interface: usb.descriptor.Interface, - ep_out: usb.descriptor.Endpoint, - ep_in: usb.descriptor.Endpoint, + const desc = usb.descriptor; + + example_interface: desc.Interface, + ep_out: desc.Endpoint, + ep_in: desc.Endpoint, /// This function is used during descriptor creation. If multiple instances /// of a driver are used, a descriptor will be created for each. @@ -42,7 +45,7 @@ pub const EchoExampleDriver = struct { }; device: *usb.DeviceInterface, - ep_tx: usb.types.Endpoint.Num, + ep_tx: EpNum, /// This function is called when the host chooses a configuration /// that contains this driver. `self` points to undefined memory. @@ -68,22 +71,22 @@ pub const EchoExampleDriver = struct { /// Each endpoint (as defined in the descriptor) has its own handler. /// Endpoint number is passed as an argument so that it does not need /// to be stored in the driver. - pub fn on_tx_ready(self: *@This(), ep_tx: usb.types.Endpoint.Num) void { + pub fn on_tx_ready(self: *@This(), ep_tx: EpNum) void { log.info("tx ready", .{}); // Mark transmission as available - @atomicStore(usb.types.Endpoint.Num, &self.ep_tx, ep_tx, .seq_cst); + @atomicStore(EpNum, &self.ep_tx, ep_tx, .seq_cst); } - pub fn on_rx(self: *@This(), ep_rx: usb.types.Endpoint.Num) void { + pub fn on_rx(self: *@This(), ep_rx: EpNum) void { var buf: [64]u8 = undefined; // Read incoming packet into a local buffer const len_rx = self.device.ep_readv(ep_rx, &.{&buf}); log.info("Received: {s}", .{buf[0..len_rx]}); // Check if we can transmit - const ep_tx = @atomicLoad(usb.types.Endpoint.Num, &self.ep_tx, .seq_cst); + const ep_tx = @atomicLoad(EpNum, &self.ep_tx, .seq_cst); if (ep_tx != .ep0) { // Mark transmission as not available - @atomicStore(usb.types.Endpoint.Num, &self.ep_tx, .ep0, .seq_cst); + @atomicStore(EpNum, &self.ep_tx, .ep0, .seq_cst); // Send received packet log.info("Sending {} bytes", .{len_rx}); const len_tx = self.device.ep_writev(ep_tx, &.{buf[0..len_rx]}); diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 97d1e8079..922eac6ea 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -1,6 +1,6 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const assert = std.debug.assert; +const EpNum = usb.types.Endpoint.Num; const log = std.log.scoped(.usb_hid); pub const Options = struct { @@ -11,10 +11,12 @@ pub const Options = struct { pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return struct { pub const Descriptor = extern struct { - interface: usb.descriptor.Interface, - hid: usb.descriptor.hid.HID, - ep_out: usb.descriptor.Endpoint, - ep_in: usb.descriptor.Endpoint, + const desc = usb.descriptor; + + interface: desc.Interface, + hid: desc.hid.HID, + ep_out: desc.Endpoint, + ep_in: desc.Endpoint, pub fn create( alloc: *usb.DescriptorAllocator, @@ -53,8 +55,8 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }; device: *usb.DeviceInterface, - ep_in: usb.types.Endpoint.Num, - ep_out: usb.types.Endpoint.Num, + ep_in: EpNum, + ep_out: EpNum, pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { self.* = .{ @@ -68,16 +70,16 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { _ = self; switch (setup.request_type.type) { .Standard => { - const hid_desc_type = std.meta.intToEnum(usb.descriptor.hid.HID.Type, setup.value.into() >> 8) catch return usb.nak; - const request_code = std.meta.intToEnum(usb.types.SetupRequest, setup.request) catch return usb.nak; + const hid_desc_type: usb.descriptor.hid.HID.Type = @enumFromInt(setup.value.into() >> 8); + const request_code: usb.types.SetupRequest = @enumFromInt(setup.request); if (request_code == .GetDescriptor and hid_desc_type == .HID) - return @as([]const u8, @ptrCast(&hid_descriptor)) + return std.mem.asBytes(&hid_descriptor) else if (request_code == .GetDescriptor and hid_desc_type == .Report) - return @as([]const u8, @ptrCast(&report_descriptor)); + return std.mem.asBytes(&report_descriptor); }, .Class => { - const hid_request_type = std.meta.intToEnum(usb.descriptor.hid.RequestType, setup.request) catch return usb.nak; + const hid_request_type: usb.descriptor.hid.RequestType = @enumFromInt(setup.request); switch (hid_request_type) { .SetIdle => { // TODO: https://github.com/ZigEmbeddedGroup/microzig/issues/454 @@ -118,12 +120,12 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return usb.nak; } - pub fn on_tx_ready(self: *@This(), ep: usb.types.Endpoint.Num) void { + pub fn on_tx_ready(self: *@This(), ep: EpNum) void { _ = self; _ = ep; } - pub fn on_rx(self: *@This(), ep: usb.types.Endpoint.Num) void { + pub fn on_rx(self: *@This(), ep: EpNum) void { _ = self; _ = ep; } From 6474eab39fbace45a2646a71c7c4965d70512e12 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 18 Jan 2026 14:31:36 +0100 Subject: [PATCH 37/80] EpNum -> EP_Num and fix pins in examples --- core/src/core/usb/descriptor/hid.zig | 1 + core/src/core/usb/drivers/cdc.zig | 34 ++++++++++----------- core/src/core/usb/drivers/example.zig | 14 ++++----- core/src/core/usb/drivers/hid.zig | 10 +++--- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 4 +-- examples/raspberrypi/rp2xxx/src/usb_hid.zig | 4 +-- 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/core/src/core/usb/descriptor/hid.zig b/core/src/core/usb/descriptor/hid.zig index a7a8ae1a0..641a715e5 100644 --- a/core/src/core/usb/descriptor/hid.zig +++ b/core/src/core/usb/descriptor/hid.zig @@ -54,6 +54,7 @@ pub const HID = extern struct { Us, Yugoslavia, TurkishF, + _, }; pub const Type = enum(u8) { diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index b3060dfd2..6e4b9bea9 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -1,7 +1,7 @@ const std = @import("std"); const usb = @import("../../usb.zig"); const assert = std.debug.assert; -const EpNum = usb.types.Endpoint.Num; +const EP_Num = usb.types.Endpoint.Num; const log = std.log.scoped(.usb_cdc); pub const ManagementRequestType = enum(u8) { @@ -134,19 +134,19 @@ pub fn CdcClassDriver(options: Options) type { }; device: *usb.DeviceInterface, - ep_notifi: EpNum, + ep_notifi: EP_Num, line_coding: LineCoding align(4), /// OUT endpoint on which there is data ready to be read, /// or .ep0 when no data is available. - ep_out: EpNum, + ep_out: EP_Num, rx_data: [options.max_packet_size]u8, rx_seek: usb.types.Len, rx_end: usb.types.Len, /// IN endpoint where data can be sent, /// or .ep0 when data is being sent. - ep_in: EpNum, + ep_in: EP_Num, tx_data: [options.max_packet_size]u8, tx_end: usb.types.Len, @@ -162,11 +162,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.available() > 0) return len; // request more data - const ep_out = @atomicLoad(EpNum, &self.ep_out, .seq_cst); + const ep_out = @atomicLoad(EP_Num, &self.ep_out, .seq_cst); if (ep_out != .ep0) { self.rx_end = self.device.ep_readv(ep_out, &.{&self.rx_data}); self.rx_seek = 0; - @atomicStore(EpNum, &self.ep_out, .ep0, .seq_cst); + @atomicStore(EP_Num, &self.ep_out, .ep0, .seq_cst); self.device.ep_listen(ep_out, options.max_packet_size); } @@ -192,11 +192,11 @@ pub fn CdcClassDriver(options: Options) type { if (self.tx_end == 0) return true; - const ep_in = @atomicLoad(EpNum, &self.ep_in, .seq_cst); + const ep_in = @atomicLoad(EP_Num, &self.ep_in, .seq_cst); if (ep_in == .ep0) return false; - @atomicStore(EpNum, &self.ep_in, .ep0, .seq_cst); + @atomicStore(EP_Num, &self.ep_in, .ep0, .seq_cst); assert(self.tx_end == self.device.ep_writev(ep_in, &.{self.tx_data[0..self.tx_end]})); self.tx_end = 0; @@ -244,19 +244,19 @@ pub fn CdcClassDriver(options: Options) type { }; } - pub fn on_rx(self: *@This(), ep_num: EpNum) void { - assert(.ep0 == @atomicLoad(EpNum, &self.ep_out, .seq_cst)); - @atomicStore(EpNum, &self.ep_out, ep_num, .seq_cst); + pub fn on_rx(self: *@This(), ep_num: EP_Num) void { + assert(.ep0 == @atomicLoad(EP_Num, &self.ep_out, .seq_cst)); + @atomicStore(EP_Num, &self.ep_out, ep_num, .seq_cst); } - pub fn on_tx_ready(self: *@This(), ep_num: EpNum) void { - assert(.ep0 == @atomicLoad(EpNum, &self.ep_in, .seq_cst)); - @atomicStore(EpNum, &self.ep_in, ep_num, .seq_cst); + pub fn on_tx_ready(self: *@This(), ep_num: EP_Num) void { + assert(.ep0 == @atomicLoad(EP_Num, &self.ep_in, .seq_cst)); + @atomicStore(EP_Num, &self.ep_in, ep_num, .seq_cst); } - pub fn on_notifi_ready(self: *@This(), ep_num: EpNum) void { - assert(.ep0 == @atomicLoad(EpNum, &self.ep_notifi, .seq_cst)); - @atomicStore(EpNum, &self.ep_notifi, ep_num, .seq_cst); + pub fn on_notifi_ready(self: *@This(), ep_num: EP_Num) void { + assert(.ep0 == @atomicLoad(EP_Num, &self.ep_notifi, .seq_cst)); + @atomicStore(EP_Num, &self.ep_notifi, ep_num, .seq_cst); } }; } diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index a8caa8054..59cc59163 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -1,6 +1,6 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const EpNum = usb.types.Endpoint.Num; +const EP_Num = usb.types.Endpoint.Num; const log = std.log.scoped(.usb_echo); /// This is an example driver that sends any data received on ep2 to ep1. @@ -45,7 +45,7 @@ pub const EchoExampleDriver = struct { }; device: *usb.DeviceInterface, - ep_tx: EpNum, + ep_tx: EP_Num, /// This function is called when the host chooses a configuration /// that contains this driver. `self` points to undefined memory. @@ -71,22 +71,22 @@ pub const EchoExampleDriver = struct { /// Each endpoint (as defined in the descriptor) has its own handler. /// Endpoint number is passed as an argument so that it does not need /// to be stored in the driver. - pub fn on_tx_ready(self: *@This(), ep_tx: EpNum) void { + pub fn on_tx_ready(self: *@This(), ep_tx: EP_Num) void { log.info("tx ready", .{}); // Mark transmission as available - @atomicStore(EpNum, &self.ep_tx, ep_tx, .seq_cst); + @atomicStore(EP_Num, &self.ep_tx, ep_tx, .seq_cst); } - pub fn on_rx(self: *@This(), ep_rx: EpNum) void { + pub fn on_rx(self: *@This(), ep_rx: EP_Num) void { var buf: [64]u8 = undefined; // Read incoming packet into a local buffer const len_rx = self.device.ep_readv(ep_rx, &.{&buf}); log.info("Received: {s}", .{buf[0..len_rx]}); // Check if we can transmit - const ep_tx = @atomicLoad(EpNum, &self.ep_tx, .seq_cst); + const ep_tx = @atomicLoad(EP_Num, &self.ep_tx, .seq_cst); if (ep_tx != .ep0) { // Mark transmission as not available - @atomicStore(EpNum, &self.ep_tx, .ep0, .seq_cst); + @atomicStore(EP_Num, &self.ep_tx, .ep0, .seq_cst); // Send received packet log.info("Sending {} bytes", .{len_rx}); const len_tx = self.device.ep_writev(ep_tx, &.{buf[0..len_rx]}); diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 922eac6ea..c8ce11c5b 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -1,6 +1,6 @@ const std = @import("std"); const usb = @import("../../usb.zig"); -const EpNum = usb.types.Endpoint.Num; +const EP_Num = usb.types.Endpoint.Num; const log = std.log.scoped(.usb_hid); pub const Options = struct { @@ -55,8 +55,8 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { }; device: *usb.DeviceInterface, - ep_in: EpNum, - ep_out: EpNum, + ep_in: EP_Num, + ep_out: EP_Num, pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { self.* = .{ @@ -120,12 +120,12 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { return usb.nak; } - pub fn on_tx_ready(self: *@This(), ep: EpNum) void { + pub fn on_tx_ready(self: *@This(), ep: EP_Num) void { _ = self; _ = ep; } - pub fn on_rx(self: *@This(), ep: EpNum) void { + pub fn on_rx(self: *@This(), ep: EP_Num) void { _ = self; _ = ep; } diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 30949bc32..81018ab47 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -61,10 +61,8 @@ const pin_config: rp2xxx.pins.GlobalConfiguration = .{ .GPIO25 = .{ .name = "led", .direction = .out }, }; -const pins = pin_config.pins(); - pub fn main() !void { - pin_config.apply(); + const pins = pin_config.apply(); const uart = rp2xxx.uart.instance.num(0); uart.apply(.{ diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 2105a9b34..03fd4548d 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -64,10 +64,8 @@ const pin_config: rp2xxx.pins.GlobalConfiguration = .{ .GPIO25 = .{ .name = "led", .direction = .out }, }; -const pins = pin_config.pins(); - pub fn main() !void { - pin_config.apply(); + const pins = pin_config.apply(); const uart = rp2xxx.uart.instance.num(0); uart.apply(.{ From 4e008192a5ce30eaa6b575a7463c3273e770e7e3 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 09:10:09 -0500 Subject: [PATCH 38/80] usb periph is alive and responding to host, but not enumerating correctly --- examples/wch/ch32v/build.zig | 23 +++- examples/wch/ch32v/src/usb_cdc.zig | 102 +++++++++++++++- port/wch/ch32v/src/cpus/main.zig | 1 + port/wch/ch32v/src/hals/ch32v30x.zig | 12 ++ port/wch/ch32v/src/hals/clocks.zig | 167 +++++++++++++++++++++++++++ port/wch/ch32v/src/hals/usbhs.zig | 71 ++++++++---- 6 files changed, 344 insertions(+), 32 deletions(-) diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 43d04c258..f2032d19b 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -6,6 +6,11 @@ const MicroBuild = microzig.MicroBuild(.{ }); pub fn build(b: *std.Build) void { + // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. + // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary + // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. + const gnu_objcopy = b.findProgram(&.{"riscv64-unknown-elf-objcopy"}, &.{}) catch null; + const optimize = b.standardOptimizeOption(.{}); const maybe_example = b.option([]const u8, "example", "only build matching examples"); @@ -35,7 +40,7 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, // CH32V30x .{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "empty_ch32v303", .file = "src/empty.zig" }, @@ -43,6 +48,7 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "blinky_systick_ch32v303", .file = "src/blinky_systick.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v305.nano_ch32v305, .name = "nano_ch32v305_blinky", .file = "src/board_blinky.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v307.ch32v307v_r1_1v0, .name = "ch32v307v_r1_1v0_blinky", .file = "src/blinky.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v307.ch32v307v_r1_1v0, .name = "ch32v307v_r1_1v0_usb_cdc", .file = "src/usb_cdc.zig" }, }; for (available_examples) |example| { @@ -67,8 +73,21 @@ pub fn build(b: *std.Build) void { // and allows installing the firmware as a typical firmware file. // // This will also install into `$prefix/firmware` instead of `$prefix/bin`. - mb.install_firmware(fw, .{}); + // Use GNU objcopy to create .bin files (avoids LLVM objcopy 512MB issue) + + if (gnu_objcopy) |objcopy_path| { + const bin_filename = b.fmt("{s}.bin", .{example.name}); + const objcopy_run = b.addSystemCommand(&.{objcopy_path}); + objcopy_run.addArgs(&.{ "-O", "binary" }); + objcopy_run.addArtifactArg(fw.artifact); + const bin_output = objcopy_run.addOutputFileArg(bin_filename); + b.getInstallStep().dependOn(&b.addInstallFileWithDir( + bin_output, + .{ .custom = "firmware" }, + bin_filename, + ).step); + } // For debugging, we also always install the firmware as an ELF file mb.install_firmware(fw, .{ .format = .elf }); } diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 922a20cdc..6b1db955e 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -1,9 +1,12 @@ const std = @import("std"); const microzig = @import("microzig"); + const hal = microzig.hal; const time = hal.time; const gpio = hal.gpio; +pub const usb = hal.usb; + const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; @@ -15,6 +18,44 @@ pub const microzig_options = microzig.Options{ .logFn = hal.usart.log, }; +pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); + +pub var usb_dev: usb.Polled( + microzig.core.usb.Config{ + .device_descriptor = .{ + .bcd_usb = .from(0x0200), + .device_triple = .{ + .class = .Miscellaneous, + .subclass = 2, + .protocol = 1, + }, + .max_packet_size0 = 64, + .vendor = .from(0x2E8A), + .product = .from(0x000A), + .bcd_device = .from(0x0100), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }, + .string_descriptors = &.{ + .from_lang(.English), + .from_str("Raspberry Pi"), + .from_str("Pico Test Device"), + .from_str("someserial"), + .from_str("Board CDC"), + }, + .configurations = &.{.{ + .num = 1, + .configuration_s = 0, + .attributes = .{ .self_powered = true }, + .max_current_ma = 100, + .Drivers = struct { serial: UsbSerial }, + }}, + }, + .{ .prefer_high_speed = true }, +) = undefined; + pub fn main() !void { // Board brings up clocks and time microzig.board.init(); @@ -37,23 +78,72 @@ pub fn main() !void { usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); // Initialize USART2 at 115200 baud - usart.apply(.{ .baud_rate = 115200 }); + usart.apply(.{ .baud_rate = 460800 }); hal.usart.init_logger(usart); std.log.info("UART logging initialized.", .{}); - std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); - microzig.cpu.interrupt.enable_interrupts(); - std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); + std.log.info("Initializing USB device.", .{}); + + usb_dev = .init(); + // microzig.cpu.interrupt.enable(.USBHS); var last = time.get_time_since_boot(); var i: u32 = 0; while (true) : (i += 1) { const now = time.get_time_since_boot(); if (now.diff(last).to_us() > 1000000) { - std.log.info("what {}", .{i}); + // std.log.info("what {}", .{i}); last = now; } - hal.run_usb_demo(); + run_usb(); + } +} + +var usb_tx_buff: [1024]u8 = undefined; + +// Transfer data to host +// NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled +pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype) void { + const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; + + var write_buff = text; + while (write_buff.len > 0) { + write_buff = write_buff[serial.write(write_buff)..]; + usb_dev.poll(); + } +} + +var usb_rx_buff: [1024]u8 = undefined; + +// Receive data from host +// NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation +pub fn usb_cdc_read( + serial: *UsbSerial, +) []const u8 { + var total_read: usize = 0; + var read_buff: []u8 = usb_rx_buff[0..]; + + while (true) { + const len = serial.read(read_buff); + read_buff = read_buff[len..]; + total_read += len; + if (len == 0) break; + } + + return usb_rx_buff[0..total_read]; +} + +pub fn run_usb() void { + if (usb_dev.controller.drivers()) |drivers| { + std.log.info("USB CDC Demo transmitting", .{}); + usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}\r\n", .{"Hello, World!"}); + + // read and print host command if present + const message = usb_cdc_read(&drivers.serial); + if (message.len > 0) { + usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); + } } + usb_dev.poll(); } diff --git a/port/wch/ch32v/src/cpus/main.zig b/port/wch/ch32v/src/cpus/main.zig index 876335a33..c5ac8fce2 100644 --- a/port/wch/ch32v/src/cpus/main.zig +++ b/port/wch/ch32v/src/cpus/main.zig @@ -259,6 +259,7 @@ pub const startup_logic = struct { .mie = 1, .mpie = 1, .fs = if (cpu_name == .@"qingkev4-rv32imafc") .dirty else .off, + .mpp = 0x3, }); // Initialize the system. diff --git a/port/wch/ch32v/src/hals/ch32v30x.zig b/port/wch/ch32v/src/hals/ch32v30x.zig index 4aa858904..dd90de2ec 100644 --- a/port/wch/ch32v/src/hals/ch32v30x.zig +++ b/port/wch/ch32v/src/hals/ch32v30x.zig @@ -5,6 +5,8 @@ pub const clocks = @import("clocks.zig"); pub const time = @import("time.zig"); pub const i2c = @import("i2c.zig"); pub const usart = @import("usart.zig"); +const std = @import("std"); +pub const usb = @import("./usbhs.zig"); /// HSI (High Speed Internal) oscillator frequency /// This is the fixed internal RC oscillator frequency for CH32V30x @@ -13,10 +15,20 @@ pub const hsi_frequency: u32 = 8_000_000; // 8 MHz /// Default interrupt handlers provided by the HAL pub const default_interrupts: microzig.cpu.InterruptOptions = .{ .TIM2 = time.tim2_handler, + .USBHS = usb.usbhs_interrupt_handler, }; /// Initialize HAL subsystems used by default pub fn init() void { // Configure TIM2 timing driver time.init(); + clocks.init(.{ + .hse_frequency = 8_000_000, + .target_frequency = 48_000_000, + .source = .hse, + }); // 8 MHz external crystal + clocks.enable_usbhs_clock(.{ + .ref_source_hz = 8_000_000, + .ref_source = .hse, + }); } diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index ec2b0d62c..f449a3986 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -392,6 +392,173 @@ pub fn get_freqs() ClockSpeeds { }; } +/// USBHS/USBHD clock helper. +/// This configures RCC_CFGR2 fields for the USBHS PHY PLL reference (if present), +/// selects whether the USBHS 48MHz clock comes from the system PLL clock or the USB PHY, +/// and enables the AHB clock gate for USBHS. +/// +/// Note: The SVD you're using names bit31 as `USBFSSRC`, but the reference manual +/// describes it as `USBHSSRC` ("USBHS 48MHz clock source selection"). +/// We write the SVD field, but treat it as USBHS 48MHz source select. +pub const UsbHsClockConfig = struct { + pub const RefSource = enum { hse, hsi }; + /// Desired PHY PLL reference frequency (the USBHSCLK field selects one of these). + /// If null, we'll pick the highest one we can generate exactly: 8MHz, then 5MHz, 4MHz, 3MHz. + pub const RefFreq = enum(u2) { + mhz3 = 0b00, + mhz4 = 0b01, + mhz8 = 0b10, + mhz5 = 0b11, + }; + /// Some parts have the USBHS controller but *no* built-in HS PHY. + /// If false, we will not enable the PHY internal PLL and will select 48MHz from PLL CLK. + has_hs_phy: bool = true, + + /// If true (and has_hs_phy), select USB PHY as the 48MHz source and enable the PHY internal PLL. + /// If false, select PLL CLK as the 48MHz source (you must ensure a valid 48MHz PLL clock exists). + use_phy_48mhz: bool = true, + + /// PHY PLL reference source (only used when has_hs_phy && use_phy_48mhz). + ref_source: RefSource = .hse, + + /// Frequency of the chosen ref_source, in Hz. + /// Typical: HSE crystal (e.g. 8_000_000, 12_000_000, 24_000_000) or HSI (often 8_000_000). + ref_source_hz: u32, + + ref_freq: ?RefFreq = null, +}; + +fn div_to_usbhsdiv(div: u32) u3 { + // RCC_CFGR2 USBHSDIV encoding: + // 000: /1, 001: /2, ... 111: /8 + return @as(u3, @intCast(div - 1)); +} + +fn hz_for_ref(ref: UsbHsClockConfig.RefFreq) u32 { + return switch (ref) { + .mhz3 => 3_000_000, + .mhz4 => 4_000_000, + .mhz5 => 5_000_000, + .mhz8 => 8_000_000, + }; +} + +const HSPLLSRC = enum(u2) { + hse = 0, + hsi = 1, +}; + +/// Enable + configure USBHS clocks. +/// +/// This does *not* reconfigure the system PLL to 48MHz; it only selects between: +/// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or +/// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true and cfg.has_hs_phy = true), +/// in which case it also configures the PHY PLL reference (USBHSCLK/USBHSPLLSRC/USBHSDIV) +/// and enables the PHY internal PLL (USBHSPLL). +pub fn enable_usbhs_clock(comptime cfg: UsbHsClockConfig) void { + // Turn on the AHB clock gate for the USBHS peripheral block. + + // If there's no PHY (or caller prefers PLL CLK), force PLL CLK selection and keep PHY PLL off. + if (!cfg.has_hs_phy or !cfg.use_phy_48mhz) { + RCC.CFGR2.modify(.{ + // SVD name mismatch: USBFSSRC field == USBHS 48MHz source select per RM. + .USBFSSRC = 0, // 0: PLL CLK (per RM: "USBHS 48MHz clock source selection") + .USBHSPLL = 0, // PHY internal PLL disabled + }); + return; + } + + // Ensure the selected oscillator is on (best-effort; usually already enabled by your system clock init). + switch (cfg.ref_source) { + .hse => { + if (RCC.CTLR.read().HSEON == 0) RCC.CTLR.modify(.{ .HSEON = 1 }); + while (RCC.CTLR.read().HSERDY == 0) {} + }, + .hsi => { + if (RCC.CTLR.read().HSION == 0) RCC.CTLR.modify(.{ .HSION = 1 }); + while (RCC.CTLR.read().HSIRDY == 0) {} + }, + } + + // Choose a reference frequency and divider that is exactly achievable. + const candidates = [_]UsbHsClockConfig.RefFreq{ .mhz8, .mhz5, .mhz4, .mhz3 }; + + comptime var chosen_ref: UsbHsClockConfig.RefFreq = undefined; + comptime var chosen_div: u32 = 0; + comptime { + if (cfg.ref_freq) |forced| { + const want = hz_for_ref(forced); + var found = false; + for (1..9) |div| { + if (cfg.ref_source_hz % div == 0 and (cfg.ref_source_hz / div) == want) { + chosen_ref = forced; + chosen_div = div; + found = true; + break; + } + } + if (!found) { + @compileError("USBHS PHY PLL ref cannot be generated exactly from ref_source_hz with /1..8 prescaler."); + } + } else { + var found = false; + for (candidates) |ref| { + const want = hz_for_ref(ref); + for (1..9) |div| { + if (cfg.ref_source_hz % div == 0 and (cfg.ref_source_hz / div) == want) { + chosen_ref = ref; + chosen_div = div; + found = true; + break; + } + } + if (found) break; + } + if (!found) { + @compileError("USBHS PHY PLL ref cannot be generated: need 3/4/5/8MHz from ref_source_hz using /1..8 prescaler."); + } + } + } + + // RCC_USBCLK48MConfig( RCC_USBCLK48MCLKSource_USBPHY ); // bit 31 = 1 + // RCC_USBHSPLLCLKConfig( RCC_HSBHSPLLCLKSource_HSE ); // bit 27 = 0 + // RCC_USBHSConfig( RCC_USBPLL_Div2 ); // bit 24 = 1 + // RCC_USBHSPLLCKREFCLKConfig( RCC_USBHSPLLCKREFCLK_4M );// bit 28 = 1 + // RCC_USBHSPHYPLLALIVEcmd( ENABLE ); // bit 30 = 1 + // RCC_AHBPeriphClockCmd( RCC_AHBPeriph_USBHS, ENABLE ); + + // Program CFGR2: + // - USBHSPLLSRC: 0=HSE, 1=HSI + // - USBHSDIV: prescaler (/1..8) + // - USBHSCLK: selects which ref freq the PHY PLL expects (3/4/8/5 MHz) + // - USBHSPLL: enable PHY internal PLL + // - USBHSSRC (SVD calls it USBFSSRC): 1=USB PHY as 48MHz source + RCC.CFGR2.modify(.{ + .USBHSPLLSRC = switch (cfg.ref_source) { + .hse => 0, + .hsi => 1, + }, + .USBHSDIV = div_to_usbhsdiv(chosen_div), + .USBHSCLK = @as(u2, @intFromEnum(chosen_ref)), + .USBHSPLL = 1, + .USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY + }); + + // RCC.CFGR2.raw = (1 << 31 | 1 << 24 | 1 << 28 | 1 << 30); + + // RCC.CFGR2.modify(.{ + // .USBHSPLLSRC = switch (cfg.ref_source) { + // .hse => 0, + // .hsi => 1, + // }, + // .USBHSDIV = 1, + // .USBHSCLK = 1, + // .USBHSPLL = 1, + // .USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY + // }); + RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); +} + // ============================================================================ // Convenience Functions for HAL Modules // ============================================================================ diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 84acf5209..fac571300 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -149,14 +149,14 @@ fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { v |= @as(u8, res); v |= @as(u8, tog) << 3; if (auto) v |= 1 << 5; - uep_tx_ctrl(ep).write_raw(v); + uep_tx_ctrl(ep).raw = v; } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { var v: u8 = 0; v |= @as(u8, res); v |= @as(u8, tog) << 3; if (auto) v |= 1 << 5; - uep_rx_ctrl(ep).write_raw(v); + uep_rx_ctrl(ep).raw = v; } /// Polled USBHD device backend for microzig core USB controller. @@ -193,8 +193,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { .interface = .{ .vtable = &vtable }, }; @memset(std.mem.asBytes(&self.endpoints), 0); + @memset(pool[0..64], 0x7e); + @memset(pool[64..], 0); - self.buffer_pool = @alignCast(pool[0..]); + self.buffer_pool = @alignCast(pool[64..]); usbhd_hw_init(); @@ -218,6 +220,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Connect pull-up to signal device ready regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); + self.interface.ep_listen(.ep0, 0); + + regs().USB_CTRL.modify(.{ + .RB_UC_CLR_ALL = 0, + }); + return self; } @@ -265,6 +273,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ep0 = self.st(.ep0, .Out); assert(ep0.buf.len >= 8); const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); + std.log.info("USB: SETUP packet data: 0x{x} 0x{x}", .{ words_ptr[0], words_ptr[1] }); return @bitCast(words_ptr.*); } @@ -301,7 +310,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if ((fg & UIF_BUS_RST) != 0) { std.log.info("USB: bus reset", .{}); // clear - regs().USB_INT_FG.write_raw(UIF_BUS_RST); + regs().USB_INT_FG.raw = UIF_BUS_RST; // address back to 0 set_address(&self.interface, 0); @@ -314,8 +323,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if ((fg & UIF_SETUP_ACT) != 0) { std.log.info("USB: SETUP received", .{}); // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. - regs().USB_INT_FG.write_raw(UIF_SETUP_ACT); - if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.write_raw(UIF_TRANSFER); + regs().USB_INT_FG.raw = UIF_SETUP_ACT; + if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; const setup = self.read_setup_from_ep0(); @@ -333,14 +342,18 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); // clear transfer - regs().USB_INT_FG.write_raw(UIF_TRANSFER); + regs().USB_INT_FG.raw = UIF_TRANSFER; self.handle_transfer(ep, token); continue; } // Clear anything else we don't handle explicitly - regs().USB_INT_FG.write_raw(fg); + regs().USB_INT_FG.raw = fg; + // 0x4 => SUSPEND + // 0x8 => HST_SOF + // 0x10 => FIFO_OV + // 0x40 => ISO_ACT } } @@ -413,6 +426,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + std.log.info("USB: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); // Notify controller/drivers of IN completion. self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); @@ -428,13 +442,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { - std.log.info("USB: ep_open called", .{}); const self: *Self = @fieldParentPtr("interface", itf); const desc = desc_ptr.*; const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); + std.log.info("USB: ep_open called for ep{}", .{ep_i}); const mps: u16 = desc.max_packet_size.into(); assert(mps > 0 and mps <= 2047); @@ -444,7 +458,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const out_st = self.st(.ep0, .Out); const in_st = self.st(.ep0, .In); if (ep0_dma().raw == 0) { - std.log.warn("USBHS: EP0 DMA not initialized yet!", .{}); + std.log.warn("USBHS: EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { // this should probably be fixed at 64 bytes for EP0? @@ -452,8 +466,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { out_st.buf = buf; in_st.buf = buf; - ep0_dma().write_raw(@as(u32, @intCast(@intFromPtr(buf.ptr)))); - uep_max_len(0).write_raw(@intCast(64)); + const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); + std.log.info("USBHS: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + ep0_dma().raw = ptr_val; + uep_max_len(0).raw = @intCast(64); } else { // Ensure both directions point at the same backing buffer. if (out_st.buf.len == 0) out_st.buf = in_st.buf; @@ -470,29 +486,31 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); if (e.dir == .Out) { - uep_rx_dma(ep_i).write_raw(ptr_val); - uep_max_len(ep_i).write_raw(mps); + uep_rx_dma(ep_i).raw = ptr_val; + uep_max_len(ep_i).raw = mps; set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); } else { - uep_tx_dma(ep_i).write_raw(ptr_val); + uep_tx_dma(ep_i).raw = ptr_val; set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); } } + uep_t_len(ep_i).raw = 0; + // Enable endpoint direction in UEP_CONFIG bitmaps. var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; if (e.num != .ep0) { // if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); } - regs().UEP_CONFIG__UHOST_CTRL.write_raw(cfg_raw); + regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; // Endpoint type ISO marking (optional, only if you use ISO). if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { var type_raw: u32 = regs().UEP_TYPE.raw; // if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); - regs().UEP_TYPE.write_raw(type_raw); + regs().UEP_TYPE.raw = type_raw; } // EP0 OUT always ACK @@ -515,6 +533,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { + std.log.info("USB: ep_listen called for ep{} len={}", .{ ep_num, len }); const self: *Self = @fieldParentPtr("interface", itf); // EP0 OUT is always armed; ignore listen semantics here. @@ -539,13 +558,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_out.rx_armed = true; st_out.rx_last_len = 0; - uep_max_len(ep_i).write_raw(@as(u16, @intCast(limit))); + uep_max_len(ep_i).raw = @as(u16, @intCast(limit)); asm volatile ("" ::: .{ .memory = true }); set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { + std.log.info("USB: ep_readv called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); const st_out = self.st(ep_num, .Out); @@ -574,6 +594,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { + std.log.info("USB: ep_writev called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -600,7 +621,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { asm volatile ("" ::: .{ .memory = true }); - uep_t_len(ep_i).write_raw(@as(u16, @intCast(w))); + uep_t_len(ep_i).raw = @as(u16, @intCast(w)); st_in.tx_last_len = @as(u16, @intCast(w)); st_in.tx_busy = true; @@ -616,7 +637,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn usbhd_hw_init() void { // Reset SIE and clear FIFO - regs().USB_CTRL.write_raw(0); // not sure if writing zero then val is ok? + regs().UHOST_CTRL.raw = 0; + regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + regs().USB_CTRL.raw = 0; // not sure if writing zero then val is ok? regs().USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 1, .RB_UC_RST_SIE = 1, @@ -630,8 +653,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_CTRL.modify(.{ .RB_UC_RST_SIE = 0, }); - regs().UHOST_CTRL.write_raw(0); - regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + regs().USB_CTRL.modify(.{ .RB_UC_DMA_EN = 1, .RB_UC_INT_BUSY = if (cfg.int_busy) 1 else 0, @@ -660,6 +682,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { - std.log.warn("USBHS interrupt handler called", .{}); - regs().USB_INT_FG.raw = 0; // clear all + const fg = regs().USB_INT_FG.raw; + std.log.warn("USBHS interrupt handler called, flags: {}", .{fg}); + regs().USB_INT_FG.raw = fg; // clear all } From 94c4e647a1c1524c353c3827f5f9770e8b24af7b Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 09:11:56 -0500 Subject: [PATCH 39/80] change to work with v307 evt --- examples/wch/ch32v/src/uart_log.zig | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/wch/ch32v/src/uart_log.zig b/examples/wch/ch32v/src/uart_log.zig index d99db5a1c..0a0252b60 100644 --- a/examples/wch/ch32v/src/uart_log.zig +++ b/examples/wch/ch32v/src/uart_log.zig @@ -7,8 +7,10 @@ const gpio = hal.gpio; const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +// TODO: Get usart from board config + +const usart = hal.usart.instance.USART1; +const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 pub const microzig_options = microzig.Options{ .log_level = .debug, @@ -18,19 +20,18 @@ pub const microzig_options = microzig.Options{ pub fn main() !void { // Board brings up clocks and time microzig.board.init(); + microzig.hal.init(); // Enable peripheral clocks for USART2 and GPIOA RCC.APB2PCENR.modify(.{ .IOPAEN = 1, // Enable GPIOA clock .AFIOEN = 1, // Enable AFIO clock + .USART1EN = 1, // Enable USART1 clock }); RCC.APB1PCENR.modify(.{ .USART2EN = 1, // Enable USART2 clock }); - // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) - AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); - // Configure PA2 as alternate function push-pull for USART2 TX usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); @@ -38,10 +39,14 @@ pub fn main() !void { usart.apply(.{ .baud_rate = 115200 }); hal.usart.init_logger(usart); - + std.log.info("UART logging initialized.", .{}); + var last = time.get_time_since_boot(); var i: u32 = 0; while (true) : (i += 1) { - std.log.info("what {}", .{i}); - time.sleep_ms(1000); + const now = time.get_time_since_boot(); + if (now.diff(last).to_us() > 500000) { + std.log.info("what {}", .{i}); + last = now; + } } } From 32feeb725eaca062741bfaea84d938b46ab0e7f3 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 10:55:05 -0500 Subject: [PATCH 40/80] Logging updates and call on_buffer at end of setup flag block --- examples/wch/ch32v/src/usb_cdc.zig | 1 + port/wch/ch32v/src/hals/usbhs.zig | 49 +++++++++++++++++++----------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 6b1db955e..b2269ee9a 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -52,6 +52,7 @@ pub var usb_dev: usb.Polled( .max_current_ma = 100, .Drivers = struct { serial: UsbSerial }, }}, + .debug = true, }, .{ .prefer_high_speed = true }, ) = undefined; diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index fac571300..0588760cd 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -159,6 +159,10 @@ fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { uep_rx_ctrl(ep).raw = v; } +fn fmt_slice(dest: []u8, msg: []const u8) []const u8 { + return std.fmt.bufPrint(dest, "{x}", .{msg}) catch &.{}; +} + /// Polled USBHD device backend for microzig core USB controller. pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { comptime { @@ -220,7 +224,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Connect pull-up to signal device ready regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); - self.interface.ep_listen(.ep0, 0); regs().USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 0, @@ -273,7 +276,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ep0 = self.st(.ep0, .Out); assert(ep0.buf.len >= 8); const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); - std.log.info("USB: SETUP packet data: 0x{x} 0x{x}", .{ words_ptr[0], words_ptr[1] }); return @bitCast(words_ptr.*); } @@ -300,15 +302,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- pub fn poll(self: *Self) void { - // std.log.debug("USBHS: poll start", .{}); while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - std.log.debug("USBHS: INT_FG={x}", .{fg}); + std.log.debug("ch32: INT_FG={x}", .{fg}); if ((fg & UIF_BUS_RST) != 0) { - std.log.info("USB: bus reset", .{}); + std.log.info("ch32: bus reset", .{}); // clear regs().USB_INT_FG.raw = UIF_BUS_RST; @@ -321,22 +322,26 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } if ((fg & UIF_SETUP_ACT) != 0) { - std.log.info("USB: SETUP received", .{}); + std.log.info("ch32: SETUP received", .{}); // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; + const stv = regs().USB_INT_ST.read(); + std.log.debug("ch32: INT_ST={any}", .{stv}); const setup = self.read_setup_from_ep0(); + std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); + self.call_on_buffer(.In, 0, self.st(.ep0, .In).buf[0..8]); // consume SETUP continue; } if ((fg & UIF_TRANSFER) != 0) { - std.log.info("USB: TRANSFER event", .{}); + std.log.info("ch32: TRANSFER event", .{}); const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); @@ -351,6 +356,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Clear anything else we don't handle explicitly regs().USB_INT_FG.raw = fg; // 0x4 => SUSPEND + // 0x5 => SUSPEND | RESET // 0x8 => HST_SOF // 0x10 => FIFO_OV // 0x40 => ISO_ACT @@ -366,7 +372,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { TOKEN_SETUP => { // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. if (ep == 0) { - const setup = self.read_setup_from_ep0(); + const setup: types.SetupPacket = self.read_setup_from_ep0(); + std.log.debug("ch32: Setup: {any}", .{setup}); set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); } @@ -377,7 +384,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_out(self: *Self, ep: u4) void { const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; - std.log.info("USB: EP{} OUT received {} bytes", .{ ep, len }); + std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { asm volatile ("" ::: .{ .memory = true }); @@ -426,7 +433,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); - std.log.info("USB: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); + std.log.info("ch32: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); + var buf: [256]u8 = undefined; + // var str = &buf[0..]; + const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); + std.log.debug("ch32: EP{} IN data: {s}", .{ ep, str }); // Notify controller/drivers of IN completion. self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); @@ -437,7 +448,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- VTable functions ------------------------------------------------ fn set_address(_: *usb.DeviceInterface, addr: u7) void { - std.log.info("USB: set_address {}", .{addr}); + std.log.info("ch32: set_address {}", .{addr}); regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); } @@ -448,7 +459,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); - std.log.info("USB: ep_open called for ep{}", .{ep_i}); + std.log.info("ch32: ep_open called for ep{}", .{ep_i}); const mps: u16 = desc.max_packet_size.into(); assert(mps > 0 and mps <= 2047); @@ -458,7 +469,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const out_st = self.st(.ep0, .Out); const in_st = self.st(.ep0, .In); if (ep0_dma().raw == 0) { - std.log.warn("USBHS: EP0 DMA is null!", .{}); + std.log.warn("ch32: EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { // this should probably be fixed at 64 bytes for EP0? @@ -467,7 +478,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { in_st.buf = buf; const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); - std.log.info("USBHS: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + std.log.info("ch32: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); ep0_dma().raw = ptr_val; uep_max_len(0).raw = @intCast(64); } else { @@ -533,7 +544,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { - std.log.info("USB: ep_listen called for ep{} len={}", .{ ep_num, len }); + std.log.info("ch32: ep_listen called for ep{} len={}", .{ ep_num, len }); const self: *Self = @fieldParentPtr("interface", itf); // EP0 OUT is always armed; ignore listen semantics here. @@ -565,7 +576,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { - std.log.info("USB: ep_readv called for ep{}", .{ep_num}); + std.log.info("ch32: ep_readv called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); const st_out = self.st(ep_num, .Out); @@ -594,7 +605,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { - std.log.info("USB: ep_writev called for ep{}", .{ep_num}); + std.log.info("ch32: ep_writev called for {}", .{ep_num}); + for (vec) |chunk| { + std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); + std.log.debug("ch32: chunk: {x}", .{fmt_slice(&[_]u8{}, chunk)}); + } const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); From 5fff85cc855a9ca48f81efd18ffee508e6f38fc6 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 11:45:13 -0500 Subject: [PATCH 41/80] add fifo overflow warning and reduce SOF spam --- port/wch/ch32v/src/hals/usbhs.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 0588760cd..2c7c9a93c 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -306,7 +306,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - std.log.debug("ch32: INT_FG={x}", .{fg}); + std.log.debug("ch32: INT_FG = 0x{x}", .{fg}); + + if (fg & UIF_FIFO_OV != 0) { + std.log.warn("ch32: FIFO overflow!", .{}); + regs().USB_INT_FG.raw = UIF_FIFO_OV; + continue; + } if ((fg & UIF_BUS_RST) != 0) { std.log.info("ch32: bus reset", .{}); @@ -326,6 +332,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; + const stv = regs().USB_INT_ST.read(); std.log.debug("ch32: INT_ST={any}", .{stv}); @@ -341,10 +348,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } if ((fg & UIF_TRANSFER) != 0) { - std.log.info("ch32: TRANSFER event", .{}); const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + if (token != TOKEN_SOF) { // reduce spam from SOF events + std.log.info("ch32: TRANSFER event", .{}); + } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -415,6 +424,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.call_on_buffer(.Out, ep, st_out.buf[0..n]); } + // IN => into host from device fn handle_in(self: *Self, ep: u4) void { const num: types.Endpoint.Num = @enumFromInt(ep); const st_in = self.st(num, .In); From efefeaef7a9df9d6e7353bdacf7af11c8b330462 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 13:06:19 -0500 Subject: [PATCH 42/80] logging swirl --- port/wch/ch32v/src/hals/usbhs.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 2c7c9a93c..7f2586103 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -302,9 +302,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- pub fn poll(self: *Self) void { + var did_work = false; while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; + did_work = true; std.log.debug("ch32: INT_FG = 0x{x}", .{fg}); @@ -333,14 +335,15 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; - const stv = regs().USB_INT_ST.read(); - std.log.debug("ch32: INT_ST={any}", .{stv}); + // const stv = regs().USB_INT_ST.read(); + // std.log.debug("ch32: INT_ST={any}", .{stv}); const setup = self.read_setup_from_ep0(); - std.log.debug("ch32: Setup: {any}", .{setup}); + // std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); self.call_on_buffer(.In, 0, self.st(.ep0, .In).buf[0..8]); // consume SETUP @@ -351,9 +354,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - if (token != TOKEN_SOF) { // reduce spam from SOF events - std.log.info("ch32: TRANSFER event", .{}); - } + std.log.info("ch32: TRANSFER event", .{}); // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -370,13 +371,17 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // 0x10 => FIFO_OV // 0x40 => ISO_ACT } + if (did_work) { + std.log.info("", .{}); + } } fn handle_transfer(self: *Self, ep: u4, token: u2) void { if (ep >= cfg.max_endpoints_count) return; - + std.log.debug("ch32: token={}", .{token}); switch (token) { TOKEN_OUT => self.handle_out(ep), + TOKEN_SOF => {}, TOKEN_IN => self.handle_in(ep), TOKEN_SETUP => { // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. @@ -387,7 +392,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.controller.on_setup_req(&self.interface, &setup); } }, - TOKEN_SOF => {}, } } From 6f6ba02a5db50e38c25c9e17c83cd848d321cd6e Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Sun, 18 Jan 2026 22:12:01 +0100 Subject: [PATCH 43/80] implement GetStatus --- core/src/core/usb.zig | 16 +++++++++------- core/src/core/usb/drivers/cdc.zig | 2 +- core/src/core/usb/types.zig | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index e7d318c43..e76298d60 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -356,8 +356,13 @@ pub fn DeviceController(config: Config) type { switch (setup.request_type.type) { .Standard => { const request: types.SetupRequest = @enumFromInt(setup.request); - log.debug("Device setup: {t}", .{request}); + log.debug("Device setup: {any}", .{request}); switch (request) { + .GetStatus => { + const attr = config_descriptor.__configuration_descriptor.attributes; + const status: types.DeviceStatus = comptime .create(attr.self_powered, false); + return std.mem.asBytes(&status); + }, .SetAddress => self.new_address = @truncate(setup.value.into()), .SetConfiguration => self.process_set_config(device_itf, setup.value.into()), .GetDescriptor => return get_descriptor(setup.value.into()), @@ -370,7 +375,7 @@ pub fn DeviceController(config: Config) type { else => return nak, } }, - _ => { + else => { log.warn("Unsupported standard request: {}", .{setup.request}); return nak; }, @@ -378,16 +383,13 @@ pub fn DeviceController(config: Config) type { return ack; }, else => |t| { - log.warn("Unhandled device setup request: {t}", .{t}); + log.warn("Unhandled device setup request: {any}", .{t}); return nak; }, } } fn process_interface_setup(self: *@This(), setup: *const types.SetupPacket) ?[]const u8 { - if (setup.request_type.type != .Class) - log.warn("Non-class ({t}) interface request", .{setup.request_type.type}); - const itf_num: u8 = @truncate(setup.index.into()); switch (itf_num) { inline else => |itf| if (comptime itf < handlers.itf.len) { @@ -404,7 +406,7 @@ pub fn DeviceController(config: Config) type { const asBytes = std.mem.asBytes; const desc_type: descriptor.Type = @enumFromInt(value >> 8); const desc_idx: u8 = @truncate(value); - log.debug("Request for {t} descriptor {}", .{ desc_type, desc_idx }); + log.debug("Request for {any} descriptor {}", .{ desc_type, desc_idx }); return switch (desc_type) { .Device => asBytes(&config.device_descriptor), .DeviceQualifier => asBytes(comptime &config.device_descriptor.qualifier()), diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 6e4b9bea9..ecbc70a05 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -228,7 +228,7 @@ pub fn CdcClassDriver(options: Options) type { pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { const mgmt_request: ManagementRequestType = @enumFromInt(setup.request); - log.debug("cdc setup: {t} {} {}", .{ mgmt_request, setup.length.into(), setup.value.into() }); + log.debug("cdc setup: {any} {} {}", .{ mgmt_request, setup.length.into(), setup.value.into() }); return switch (mgmt_request) { .SetLineCoding => usb.ack, // we should handle data phase somehow to read sent line_coding diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index 7a53ede4a..bfcd04ba0 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -293,13 +293,32 @@ pub const TransferType = enum(u2) { /// The types of USB SETUP requests that we understand. pub const SetupRequest = enum(u8) { + GetStatus = 0x00, + ClearFeature = 0x02, SetFeature = 0x03, SetAddress = 0x05, GetDescriptor = 0x06, + SetDescriptor = 0x07, + GetConfiguration = 0x08, SetConfiguration = 0x09, _, }; +pub const DeviceStatus = extern struct { + const Flags = packed struct(u8) { + self_powered: bool, + remote_wakeup: bool, + reserved: u6 = 0, + }; + + flags: Flags, + reserved: u8 = 0, + + pub fn create(self_powered: bool, remote_wakeup: bool) @This() { + return .{ .flags = .{ .self_powered = self_powered, .remote_wakeup = remote_wakeup } }; + } +}; + pub const FeatureSelector = enum(u8) { EndpointHalt = 0x00, DeviceRemoteWakeup = 0x01, From c2338510787dbedce23ed7ccb7e6d0efabe4f47a Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 17:59:31 -0500 Subject: [PATCH 44/80] make it to config descriptor but fail to send 2nd packet. --- port/wch/ch32v/src/hals/usbhs.zig | 71 ++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 7f2586103..531756e14 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -73,6 +73,13 @@ fn speed_type(comptime cfg: Config) u2 { } // --- USBHD token encodings --- +const Token = enum(u2) { + Out = 0, + Sof = 1, + In = 2, + Setup = 3, +}; + const TOKEN_OUT: u2 = 0; const TOKEN_SOF: u2 = 1; const TOKEN_IN: u2 = 2; @@ -136,7 +143,7 @@ fn uep_t_len(ep: u4) *volatile RegU16 { return mmio_u16(0xD8 + (@as(usize, ep) * 4)); } // TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 -fn uep_tx_ctrl(ep: u4) *volatile RegU8 { +pub fn uep_tx_ctrl(ep: u4) *volatile RegU8 { return mmio_u8(0xDA + (@as(usize, ep) * 4)); } fn uep_rx_ctrl(ep: u4) *volatile RegU8 { @@ -145,6 +152,7 @@ fn uep_rx_ctrl(ep: u4) *volatile RegU8 { fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { // [1:0]=RES, [4:3]=TOG, [5]=AUTO + std.log.debug("ch32: set_tx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); var v: u8 = 0; v |= @as(u8, res); v |= @as(u8, tog) << 3; @@ -152,6 +160,7 @@ fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { uep_tx_ctrl(ep).raw = v; } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { + std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); var v: u8 = 0; v |= @as(u8, res); v |= @as(u8, tog) << 3; @@ -246,10 +255,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn arm_ep0_out_always(self: *Self) void { _ = self; + std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); // EP0 OUT is always ACK with auto-toggle. - set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + set_tx_ctrl(0, RES_NAK, TOG_DATA0, true); } fn on_bus_reset_local(self: *Self) void { @@ -306,9 +316,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - did_work = true; - std.log.debug("ch32: INT_FG = 0x{x}", .{fg}); + const stv = regs().USB_INT_ST.read(); + const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); + const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + if (token != TOKEN_SOF) { + std.log.debug("ch32: INT_FG = 0x{x}, token: {}", .{ fg, @as(Token, @enumFromInt(token)) }); + did_work = true; + } if (fg & UIF_FIFO_OV != 0) { std.log.warn("ch32: FIFO overflow!", .{}); @@ -317,7 +332,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } if ((fg & UIF_BUS_RST) != 0) { - std.log.info("ch32: bus reset", .{}); + std.log.info("ch32: bus reset\n\n\n", .{}); // clear regs().USB_INT_FG.raw = UIF_BUS_RST; @@ -338,24 +353,21 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // const stv = regs().USB_INT_ST.read(); // std.log.debug("ch32: INT_ST={any}", .{stv}); - const setup = self.read_setup_from_ep0(); - // std.log.debug("ch32: Setup: {any}", .{setup}); + const setup: types.SetupPacket = self.read_setup_from_ep0(); + std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); - self.call_on_buffer(.In, 0, self.st(.ep0, .In).buf[0..8]); // consume SETUP continue; } if ((fg & UIF_TRANSFER) != 0) { - const stv = regs().USB_INT_ST.read(); - const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); - const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - std.log.info("ch32: TRANSFER event", .{}); - + if (token != TOKEN_SOF) { + std.log.info("ch32: TRANSFER event", .{}); + } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -378,7 +390,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_transfer(self: *Self, ep: u4, token: u2) void { if (ep >= cfg.max_endpoints_count) return; - std.log.debug("ch32: token={}", .{token}); + if (token == TOKEN_SOF) return; + std.log.debug("ch32: handle_transfer ep={} token={}", .{ ep, @as(Token, @enumFromInt(token)) }); + // std.log.debug("ch32: token={}", .{token}); switch (token) { TOKEN_OUT => self.handle_out(ep), TOKEN_SOF => {}, @@ -445,7 +459,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = false; st_in.tx_last_len = 0; - set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + // set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); std.log.info("ch32: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); var buf: [256]u8 = undefined; @@ -453,10 +467,15 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); std.log.debug("ch32: EP{} IN data: {s}", .{ ep, str }); // Notify controller/drivers of IN completion. + std.log.debug("tx_ctrl before on_buffer: {any}", .{uep_tx_ctrl(ep)}); + // set_tx_ctrl(ep, RES_NAK, TOG_DATA1, true); + self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); // EP0 OUT must remain ACK - if (ep == 0) self.arm_ep0_out_always(); + if (self.controller.tx_slice == null) { + if (ep == 0) self.arm_ep0_out_always(); + } } // ---- VTable functions ------------------------------------------------ @@ -565,7 +584,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (ep_num == .ep0) { const st0 = self.st(.ep0, .Out); st0.rx_limit = @as(u16, @intCast(len)); - set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); return; } @@ -622,8 +641,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { std.log.info("ch32: ep_writev called for {}", .{ep_num}); for (vec) |chunk| { std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); - std.log.debug("ch32: chunk: {x}", .{fmt_slice(&[_]u8{}, chunk)}); + std.log.debug("ch32: chunk: {any}", .{chunk}); } + const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -633,8 +653,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(ep_num, .In); assert(st_in.buf.len != 0); + std.log.debug("tx_ctrl before write: {any}", .{uep_tx_ctrl(ep_i)}); + if (st_in.tx_busy) { - // Not ready; keep NAK and let upper layer retry. + // do I want to set anything in this case? + std.log.warn("ch32: ep_writev called while IN endpoint busy, returning 0", .{}); set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); return 0; } @@ -647,6 +670,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { @memcpy(st_in.buf[w .. w + n], chunk[0..n]); w += n; } + std.log.debug("ch32: Writing chunk of len={}", .{w}); asm volatile ("" ::: .{ .memory = true }); @@ -656,7 +680,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = true; // Arm IN - set_tx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + if (w < vec[0].len) { + set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + } else { + set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + } + std.log.debug("tx_ctrl after write: {any}", .{uep_tx_ctrl(ep_i)}); // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); From 872e79466c92e4aa179447890e4ba95021ef49c7 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 19 Jan 2026 01:05:19 +0100 Subject: [PATCH 45/80] driver handler type safety --- core/src/core/usb.zig | 110 ++++++++++++++++++++------ core/src/core/usb/drivers/cdc.zig | 8 +- core/src/core/usb/drivers/example.zig | 6 +- core/src/core/usb/drivers/hid.zig | 6 +- 4 files changed, 95 insertions(+), 35 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index e76298d60..b4ff02188 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -91,6 +91,30 @@ pub const DeviceInterface = struct { } }; +pub fn DriverHadlers(Driver: type) type { + const EndpointHandler = fn (*Driver, types.Endpoint.Num) void; + + const Field = std.builtin.Type.StructField; + var ret_fields: []const Field = &.{}; + const desc_fields = @typeInfo(Driver.Descriptor).@"struct".fields; + for (desc_fields) |fld| switch (fld.type) { + descriptor.Endpoint => ret_fields = ret_fields ++ &[1]Field{.{ + .alignment = @alignOf(usize), + .default_value_ptr = null, + .is_comptime = false, + .name = fld.name, + .type = EndpointHandler, + }}, + else => {}, + }; + return @Type(.{ .@"struct" = .{ + .decls = &.{}, + .fields = ret_fields, + .is_tuple = false, + .layout = .auto, + } }); +} + pub const Config = struct { pub const Configuration = struct { num: u8, @@ -208,17 +232,32 @@ pub fn DeviceController(config: Config) type { }; const handlers = blk: { - const Handler = struct { - driver: []const u8, - function: []const u8, - }; + @setEvalBranchQuota(10000); + + const Field = std.builtin.Type.StructField; + var drivers_ep: struct { + In: [16][]const u8 = @splat(""), + Out: [16][]const u8 = @splat(""), + } = .{}; + var handlers_ep: struct { + const default_field: Field = .{ + .alignment = 1, + .default_value_ptr = null, + .is_comptime = false, + .name = "", + .type = void, + }; + In: [16]Field = @splat(default_field), + Out: [16]Field = @splat(default_field), + } = .{}; + var itf_handlers: []const DriverEnum = &.{}; + + for (0..16) |i| { + const name = std.fmt.comptimePrint("{}", .{i}); + handlers_ep.In[i].name = name; + handlers_ep.Out[i].name = name; + } - var ret: struct { In: [16]Handler, Out: [16]Handler, itf: []const DriverEnum } = .{ - .In = @splat(.{ .driver = "", .function = "" }), - .Out = @splat(.{ .driver = "", .function = "" }), - .itf = &.{}, - }; - var itf_handlers = ret.itf; for (driver_fields) |fld_drv| { const cfg = @field(config_descriptor, fld_drv.name); const fields = @typeInfo(@TypeOf(cfg)).@"struct".fields; @@ -237,22 +276,43 @@ pub fn DeviceController(config: Config) type { for (fields) |fld| { if (fld.type != descriptor.Endpoint) continue; const desc: descriptor.Endpoint = @field(cfg, fld.name); + const tag = @tagName(desc.endpoint.dir); const ep_num = @intFromEnum(desc.endpoint.num); - const handler = &@field(ret, @tagName(desc.endpoint.dir))[ep_num]; - const function = @field(fld_drv.type.handlers, fld.name); - if (handler.driver.len != 0 or handler.function.len != 0) + + const driver = &@field(drivers_ep, tag)[ep_num]; + if (driver.*.len != 0) @compileError(std.fmt.comptimePrint( - "ep{} {t}: multiple handlers: {s}.{s} and {s}.{s}", - .{ ep_num, desc.endpoint.dir, handler.driver, handler.function, fld_drv.name, function }, + "ep{} {t}: multiple handlers: {s} and {s}", + .{ ep_num, desc.endpoint.dir, driver.*, fld_drv.name }, )); - handler.* = .{ - .driver = fld_drv.name, - .function = function, - }; + + driver.* = fld_drv.name; + const func = @field(fld_drv.type.handlers, fld.name); + const handler = &@field(handlers_ep, tag)[ep_num]; + handler.alignment = @alignOf(@TypeOf(func)); + handler.default_value_ptr = &func; + handler.type = @TypeOf(func); + handler.is_comptime = true; } } - ret.itf = itf_handlers; - break :blk ret; + break :blk .{ + .ep_drv = drivers_ep, + .ep_han = .{ + .In = @Type(.{ .@"struct" = .{ + .decls = &.{}, + .fields = &handlers_ep.In, + .is_tuple = true, + .layout = .auto, + } }){}, + .Out = @Type(.{ .@"struct" = .{ + .decls = &.{}, + .fields = &handlers_ep.Out, + .is_tuple = true, + .layout = .auto, + } }){}, + }, + .itf = itf_handlers, + }; }; /// If not zero, change the device address at the next opportunity. @@ -305,7 +365,8 @@ pub fn DeviceController(config: Config) type { pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint) void { log.debug("on_buffer {t} {t}", .{ ep.num, ep.dir }); - const handler = comptime @field(handlers, @tagName(ep.dir))[@intFromEnum(ep.num)]; + const driver = comptime @field(handlers.ep_drv, @tagName(ep.dir))[@intFromEnum(ep.num)]; + const function = comptime @field(handlers.ep_han, @tagName(ep.dir))[@intFromEnum(ep.num)]; if (comptime ep == types.Endpoint.in(.ep0)) { // We use this opportunity to finish the delayed @@ -334,9 +395,8 @@ pub fn DeviceController(config: Config) type { } else if (comptime ep == types.Endpoint.out(.ep0)) log.warn("Unhandled packet on ep0 Out", .{}); - if (comptime handler.driver.len != 0) { - const drv = &@field(self.driver_data.?, handler.driver); - @field(@FieldType(config0.Drivers, handler.driver), handler.function)(drv, ep.num); + if (comptime driver.len != 0) { + function(&@field(self.driver_data.?, driver), ep.num); } } diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index ecbc70a05..001a229b2 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -127,10 +127,10 @@ pub fn CdcClassDriver(options: Options) type { } }; - pub const handlers = .{ - .ep_notifi = "on_notifi_ready", - .ep_out = "on_rx", - .ep_in = "on_tx_ready", + pub const handlers: usb.DriverHadlers(@This()) = .{ + .ep_notifi = on_notifi_ready, + .ep_out = on_rx, + .ep_in = on_tx_ready, }; device: *usb.DeviceInterface, diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig index 59cc59163..67cfd5dee 100644 --- a/core/src/core/usb/drivers/example.zig +++ b/core/src/core/usb/drivers/example.zig @@ -39,9 +39,9 @@ pub const EchoExampleDriver = struct { /// This is a mapping from endpoint descriptor field names to handler /// function names. Counterintuitively, usb devices send data on 'in' /// endpoints and receive on 'out' endpoints. - pub const handlers = .{ - .ep_in = "on_tx_ready", - .ep_out = "on_rx", + pub const handlers: usb.DriverHadlers(@This()) = .{ + .ep_in = on_tx_ready, + .ep_out = on_rx, }; device: *usb.DeviceInterface, diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index c8ce11c5b..8f6a0d734 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -49,9 +49,9 @@ pub fn HidClassDriver(options: Options, report_descriptor: anytype) type { .report_length = .from(@sizeOf(@TypeOf(report_descriptor))), }; - pub const handlers = .{ - .ep_out = "on_rx", - .ep_in = "on_tx_ready", + pub const handlers: usb.DriverHadlers(@This()) = .{ + .ep_out = on_rx, + .ep_in = on_tx_ready, }; device: *usb.DeviceInterface, From 52396d6c669472f69cca88f346394e3ed20d9a6d Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 21:26:11 -0500 Subject: [PATCH 46/80] successful enumeration! Started replacing constants with enums, changed the reset state for ep0 tx/rx ctrl from DATA0 to DATA1, I think that did it. --- examples/wch/ch32v/src/usb_cdc.zig | 6 ++-- port/wch/ch32v/src/hals/usbhs.zig | 45 ++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index b2269ee9a..a7fcbb2cc 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -40,8 +40,8 @@ pub var usb_dev: usb.Polled( }, .string_descriptors = &.{ .from_lang(.English), - .from_str("Raspberry Pi"), - .from_str("Pico Test Device"), + .from_str("MicroZig"), + .from_str("ch32v307 Test Device"), .from_str("someserial"), .from_str("Board CDC"), }, @@ -52,7 +52,7 @@ pub var usb_dev: usb.Polled( .max_current_ma = 100, .Drivers = struct { serial: UsbSerial }, }}, - .debug = true, + .debug = false, }, .{ .prefer_high_speed = true }, ) = undefined; diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 531756e14..a022ffbdb 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -99,13 +99,27 @@ const RES_ACK: u2 = 0; const RES_NAK: u2 = 2; const RES_STALL: u2 = 3; +const Res = enum(u2) { + ACK = 0, + NAK = 2, + STALL = 3, +}; + const TOG_DATA0: u2 = 0; const TOG_DATA1: u2 = 1; +const Tog = enum(u2) { + DATA0 = 0, + DATA1 = 0b1, + DATA2 = 0b10, + MDATA = 0b11, +}; + // We use offset-based access for per-EP regs to keep helpers compact. const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); +pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { _reserved2: u2 = 0, AUTO: bool = false, TOG: u2 = 0, _reserved0: u1 = 0, RES: u2 = 0 }); fn baseAddr() usize { return @intFromPtr(peripherals.USBHS); @@ -119,6 +133,12 @@ fn mmio_u16(off: usize) *volatile RegU16 { fn mmio_u8(off: usize) *volatile RegU8 { return @ptrFromInt(baseAddr() + off); } +fn mmio_tx_ctrl(off: usize) *volatile RegCtrl { + return @ptrFromInt(baseAddr() + off); +} +fn mmio_rx_ctrl(off: usize) *volatile RegCtrl { + return @ptrFromInt(baseAddr() + off); +} // RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) // EP0 has its own dedicated DMA reg. @@ -143,21 +163,24 @@ fn uep_t_len(ep: u4) *volatile RegU16 { return mmio_u16(0xD8 + (@as(usize, ep) * 4)); } // TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 -pub fn uep_tx_ctrl(ep: u4) *volatile RegU8 { - return mmio_u8(0xDA + (@as(usize, ep) * 4)); +pub fn uep_tx_ctrl(ep: u4) *volatile RegCtrl { + const ret: *volatile RegCtrl = mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); + return ret; } -fn uep_rx_ctrl(ep: u4) *volatile RegU8 { - return mmio_u8(0xDB + (@as(usize, ep) * 4)); + +fn uep_rx_ctrl(ep: u4) *volatile RegCtrl { + return mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); } fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { // [1:0]=RES, [4:3]=TOG, [5]=AUTO std.log.debug("ch32: set_tx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); - var v: u8 = 0; - v |= @as(u8, res); - v |= @as(u8, tog) << 3; - if (auto) v |= 1 << 5; - uep_tx_ctrl(ep).raw = v; + const tx_ctrl: *volatile RegCtrl = uep_tx_ctrl(ep); + tx_ctrl.write(.{ + .RES = res, + .TOG = tog, + .AUTO = auto, + }); } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); @@ -257,9 +280,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { _ = self; std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); // EP0 OUT is always ACK with auto-toggle. - set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); + set_rx_ctrl(0, RES_ACK, TOG_DATA1, true); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA0, true); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); } fn on_bus_reset_local(self: *Self) void { From aa8034d6a6a80946e7e359a8ae89fdcb97e5a04e Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 19 Jan 2026 17:55:40 +0100 Subject: [PATCH 47/80] inline get_setup_packet() --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 33 +++++++------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 6d7ccb02d..b2bb29d7b 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -106,11 +106,17 @@ pub fn Polled(config: Config) type { // Setup request received? if (ints.SETUP_REQ != 0) { // Reset PID to 1 for EP0 IN. Every DATA packet we send in response - // to an IN on EP0 needs to use PID DATA1, and this line will ensure - // that. + // to an IN on EP0 needs to use PID DATA1. buffer_control[0].in.modify(.{ .PID_0 = 0 }); - const setup = get_setup_packet(); + // Clear the status flag (write-one-to-clear) + peripherals.USB.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); + + // The PAC models this buffer as two 32-bit registers. + const setup: usb.types.SetupPacket = @bitCast([2]u32{ + peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw, + peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw, + }); log.debug("setup {any}", .{setup}); controller.on_setup_req(&self.interface, &setup); @@ -349,27 +355,6 @@ pub fn Polled(config: Config) type { bufctrl_ptr.write(bufctrl); } - /// Returns a received USB setup packet - /// - /// Side effect: The setup request status flag will be cleared - /// - /// One can assume that this function is only called if the - /// setup request flag is set. - fn get_setup_packet() usb.types.SetupPacket { - // Clear the status flag (write-one-to-clear) - peripherals.USB.SIE_STATUS.modify(.{ .SETUP_REC = 1 }); - - // This assumes that the setup packet is arriving on EP0, our - // control endpoint. Which it should be. We don't have any other - // Control endpoints. - - // The PAC models this buffer as two 32-bit registers. - return @bitCast([2]u32{ - peripherals.USB_DPRAM.SETUP_PACKET_LOW.raw, - peripherals.USB_DPRAM.SETUP_PACKET_HIGH.raw, - }); - } - fn set_address(_: *usb.DeviceInterface, addr: u7) void { log.debug("set addr {}", .{addr}); From 7667ea7226bfe50cf87c162f514a15410cb608d3 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Mon, 19 Jan 2026 18:19:06 +0100 Subject: [PATCH 48/80] fix ep_writev only using the first buffer --- port/raspberrypi/rp2xxx/src/hal/usb.zig | 52 ++++++++++++++++--------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index b2bb29d7b..acd867145 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -263,20 +263,16 @@ pub fn Polled(config: Config) type { const bufctrl_ptr = &buffer_control[@intFromEnum(ep_num)].in; const ep = self.hardware_endpoint_get_by_address(.in(ep_num)); + var hw_buf: []align(1) u8 = ep.data_buffer; - const len = @min(data[0].len, ep.data_buffer.len); - switch (chip) { - .RP2040 => @memcpy(ep.data_buffer[0..len], data[0][0..len]), - .RP2350 => { - const dst: [*]align(4) u32 = @ptrCast(ep.data_buffer.ptr); - const src: [*]align(1) const u32 = @ptrCast(data[0].ptr); - for (0..len / 4) |i| - dst[i] = src[i]; - for (0..len % 4) |i| - ep.data_buffer[len - i - 1] = data[0][len - i - 1]; - }, + for (data) |src| { + const len = @min(src.len, hw_buf.len); + dpram_memcpy(hw_buf[0..len], src[0..len]); + hw_buf = hw_buf[len..]; } + const len: usb.types.Len = @intCast(ep.data_buffer.len - hw_buf.len); + var bufctrl = bufctrl_ptr.read(); assert(bufctrl.AVAILABLE_0 == 0); // Write the buffer information to the buffer control register @@ -296,32 +292,36 @@ pub fn Polled(config: Config) type { bufctrl.AVAILABLE_0 = 1; bufctrl_ptr.write(bufctrl); - return @intCast(len); + return len; } + /// Copies the last sent packet from USB SRAM into the provided buffer. + /// Slices in `data` must collectively be long enough to store the full packet. fn ep_readv( itf: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, data: []const []u8, ) usb.types.Len { - log.debug("readv {t} {}: {any}", .{ ep_num, data[0].len, data[0] }); + var total_len: usize = data[0].len; + for (data[1..]) |d| total_len += d.len; + log.debug("readv {t} {}", .{ ep_num, total_len }); const self: *@This() = @fieldParentPtr("interface", itf); assert(data.len > 0); - const bufctrl = &buffer_control[@intFromEnum(ep_num)].out.read(); + const bufctrl = buffer_control[@intFromEnum(ep_num)].out.read(); const ep = self.hardware_endpoint_get_by_address(.out(ep_num)); var hw_buf: []align(1) u8 = ep.data_buffer[0..bufctrl.LENGTH_0]; for (data) |dst| { const len = @min(dst.len, hw_buf.len); - // make sure reads from device memory of size 1 - for (dst[0..len], hw_buf[0..len]) |*d, *s| - @atomicStore(u8, d, @atomicLoad(u8, s, .unordered), .unordered); + dpram_memcpy(dst[0..len], hw_buf[0..len]); + hw_buf = hw_buf[len..]; if (hw_buf.len == 0) return @intCast(hw_buf.ptr - ep.data_buffer.ptr); } - unreachable; + log.warn("discarding rx data on ep {t}, {} bytes received", .{ ep_num, bufctrl.LENGTH_0 }); + return @intCast(total_len); } fn ep_listen( @@ -361,6 +361,22 @@ pub fn Polled(config: Config) type { peripherals.USB.ADDR_ENDP.write(.{ .ADDRESS = addr }); } + fn dpram_memcpy(dst: []u8, src: []const u8) void { + assert(dst.len == src.len); + switch (chip) { + .RP2040 => @memcpy(dst, src), + .RP2350 => { + // Could be optimized for aligned data, for now just copy + // byte by byte. Atomic operations are used so that + // the compiler does not try to optimize this. + for (dst, src) |*d, *s| { + const tmp = @atomicLoad(u8, s, .unordered); + @atomicStore(u8, d, tmp, .unordered); + } + }, + } + } + fn hardware_endpoint_get_by_address(self: *@This(), ep: usb.types.Endpoint) *HardwareEndpointData { return &self.endpoints[@intFromEnum(ep.num)][@intFromEnum(ep.dir)]; } From 1a1aab8d48e81b12a072c20ca9da2a6c302c2192 Mon Sep 17 00:00:00 2001 From: Piotr Fila Date: Wed, 21 Jan 2026 20:09:47 +0100 Subject: [PATCH 49/80] add rp2xxx reset interface --- examples/raspberrypi/rp2xxx/src/usb_cdc.zig | 2 +- examples/raspberrypi/rp2xxx/src/usb_hid.zig | 2 +- port/raspberrypi/rp2xxx/src/hal/usb.zig | 53 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig index 81018ab47..4ce452b5d 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_cdc.zig @@ -35,7 +35,7 @@ var usb_controller: usb.DeviceController(.{ .configuration_s = 0, .attributes = .{ .self_powered = false }, .max_current_ma = 50, - .Drivers = struct { serial: USB_Serial }, + .Drivers = struct { serial: USB_Serial, reset: rp2xxx.usb.ResetDriver(null, 0) }, }}, .max_supported_packet_size = USB_Device.max_supported_packet_size, }) = .init; diff --git a/examples/raspberrypi/rp2xxx/src/usb_hid.zig b/examples/raspberrypi/rp2xxx/src/usb_hid.zig index 03fd4548d..de89a1119 100644 --- a/examples/raspberrypi/rp2xxx/src/usb_hid.zig +++ b/examples/raspberrypi/rp2xxx/src/usb_hid.zig @@ -38,7 +38,7 @@ var usb_controller: usb.DeviceController(.{ .configuration_s = 0, .attributes = .{ .self_powered = false }, .max_current_ma = 50, - .Drivers = struct { hid: HID_Driver }, + .Drivers = struct { hid: HID_Driver, reset: rp2xxx.usb.ResetDriver(null, 0) }, }}, .max_supported_packet_size = USB_Device.max_supported_packet_size, }) = .init; diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index acd867145..730a73fac 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -428,3 +428,56 @@ pub fn Polled(config: Config) type { } }; } + +pub fn ResetDriver(bootsel_activity_led: ?u5, interface_disable_mask: u32) type { + return struct { + const log_drv = std.log.scoped(.pico_reset); + + pub const Descriptor = extern struct { + reset_interface: usb.descriptor.Interface, + + pub fn create(alloc: *usb.DescriptorAllocator, _: u8, _: usb.types.Len) @This() { + return .{ .reset_interface = .{ + .interface_number = alloc.next_itf(), + .alternate_setting = 0, + .num_endpoints = 0, + .interface_triple = .from( + .VendorSpecific, + @enumFromInt(0x00), + @enumFromInt(0x01), + ), + .interface_s = 0, + } }; + } + }; + + pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { + _ = desc; + _ = device; + self.* = .{}; + } + + pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { + _ = self; + switch (setup.request) { + 0x01 => { + const value = setup.value.into(); + const mask = @as(u32, 1) << if (value & 0x100 != 0) + @intCast(value >> 9) + else + bootsel_activity_led orelse 0; + const itf_disable = (value & 0x7F) | interface_disable_mask; + log_drv.debug("Resetting to bootsel. Mask: {} Itf disable: {}", .{ mask, itf_disable }); + microzig.hal.rom.reset_to_usb_boot(); + }, + 0x02 => { + log_drv.debug("Resetting to flash", .{}); + // Not implemented yet + microzig.hal.rom.reset_to_usb_boot(); + }, + else => return usb.nak, + } + return usb.ack; + } + }; +} From 6c89d64bce514be87af7c8716476bb7e3f43780e Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 11 Jan 2026 12:04:35 -0500 Subject: [PATCH 50/80] start at ch32v usb --- port/wch/ch32v/src/hals/usb.zig | 153 ++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 port/wch/ch32v/src/hals/usb.zig diff --git a/port/wch/ch32v/src/hals/usb.zig b/port/wch/ch32v/src/hals/usb.zig new file mode 100644 index 000000000..81503c046 --- /dev/null +++ b/port/wch/ch32v/src/hals/usb.zig @@ -0,0 +1,153 @@ +//! USB device implementation +//! +//! + +const std = @import("std"); +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 PHY = enum { + device, + host_device, +}; + +pub const HardwareConfig = struct { + max_endpoints_count: comptime_int = 3, // datasheet text says 16 but only has 8 registers? + max_interfaces_count: comptime_int = 2, // not sure on this + num_can_active: u2 = 0, // 0, 1 or 2, changes amount of shared SRAM + phy: PHY = .device, + buffer_size: u32 = 64, +}; + +const BufferDescription = packed struct { + const Count = packed struct { + val: u10, + res: u6, + res2: u16, + }; + address: u32, + count: Count, +}; + +const SHARED_SRAM_SIZE: comptime_int = 512; // bytes +const SHARED_SRAM_ADDR: comptime_int = 0x40006000; +pub fn Polled( + controller_config: usb.Config, + config: HardwareConfig, +) type { + const buffer_desc_size = 2 * config.max_endpoints_count * @sizeOf(BufferDescription); + // seems like we must have 2 buffers per endpoint + // they're either double buffered or tx/rx? + const buffer_size = 2 * config.max_endpoints_count * config.buffer_size; + const used_sram = buffer_desc_size + buffer_size; + comptime { + // Do hardware config validity checks here + const can_sram_size: u32 = 128 * @as(u32, @intCast(config.num_can_active)); + const available_sram = SHARED_SRAM_SIZE - can_sram_size; + + if (used_sram > available_sram) { + @compileLog("USB Buffer configuration overflows available sram"); + } + } + _ = controller_config; + // _ = config; + const USB = peripherals.USB; + const EXT = peripherals.EXTEND; + + // always putting the buffer descriptor table at the start of shared sram + const buffer_descriptor_table: *align(2) [2 * config.max_endpoints_count]BufferDescription = @ptrFromInt(SHARED_SRAM_ADDR); + const ep_buffers: *align(2) [2 * config.max_endpoints_count][config.buffer_size]u8 = @ptrFromInt(SHARED_SRAM_ADDR + buffer_desc_size); + + return struct { + const vtable: usb.DeviceInterface.VTable = .{ + .ep_writev = start_tx, + .ep_readv = start_rx, + .set_address = set_address, + .ep_open = ep_open, + }; + + pub fn poll() void { + // do the recurring work here + } + pub fn init() @This() { + // setup hardware here + // first check for 48MHz clock then enable the USB Clock in RCC_CFGR0 + const RCC = peripherals.RCC; + // RCC.CFGR0 // USBPRE + RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); + RCC.APB1PCENR.modify(.{ .USBDEN = 1 }); + USB.CNTR.modify(.{ .FRES = 1 }); // reset the peripheral if it's not already + + init_shared_sram(); + // endpoint config + + //packet buffer description table + + USB.DADDR.modify(.{ .ADD = 0, .EF = 1 }); + EXT.EXTEND_CTR.modify(.{ .USBDPU = 1 }); // enable pull up + USB.CNTR.modify(.{ .FRES = 0 }); // bring device out of reset + USB.ISTR.raw = 0; // reset all interrupt bits + // enable interrupts here if needed + USB.CNTR.modify(.{ .ESOFM = 0, .SOFM = 0, .RESETM = 0, .SUSPM = 0, .WKUPM = 0, .ERRM = 0, .PMAOVRM = 0, .CTRM = 0 }); + + return .{}; + } + + fn init_shared_sram() void { + // put table at start of sram + USB.BTABLE.modify(.{ .BTABLE = 0 }); + for (buffer_descriptor_table, ep_buffers) |*desc, *buf| { + desc.*.address = @intFromPtr(buf); + desc.*.count.val = 64; // is EP0 64 bytes? + } + } + + pub fn start_tx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, buffer: []const u8) void { + _ = self; + _ = ep_num; + _ = buffer; + } + pub fn start_rx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usize) void { + _ = self; + _ = ep_num; + _ = len; + } + pub fn ep_open(self: *@This(), desc: *const usb.descriptor.Endpoint) void { + _ = self; + // how to tell if IN or OUT endpoint? + + const epr = get_epr_reg(desc.endpoint.num); + epr.modify(.{ + .EP_TYPE = switch (desc.attributes.transfer_type) { + .Bulk => 0b00, + .Control => 0b01, + .Isochronous => 0b10, + .Interrupt => 0b11, + }, + .EP_KIND = 0, // no double buffering for now + + }); + } + pub fn set_address(self: *usb.DeviceInterface, addr: u7) void { + _ = self; + _ = addr; + } + fn get_epr_reg(ep_num: usb.types.Endpoint.Num) @TypeOf(USB.EPR0) { + return switch (ep_num) { + .ep0 => USB.EPR0, + .ep1 => USB.EPR1, + .ep2 => USB.EPR2, + .ep3 => USB.EPR3, + .ep4 => USB.EPR4, + .ep5 => USB.EPR5, + .ep6 => USB.EPR6, + .ep7 => USB.EPR7, + else => @compileError("Unsupported endpoint number"), + }; + } + }; +} From 64d16cee17a1d63b787da9e452a0fe00ddd76832 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 17 Jan 2026 00:40:10 -0500 Subject: [PATCH 51/80] fix startup --- port/wch/ch32v/src/cpus/main.zig | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/port/wch/ch32v/src/cpus/main.zig b/port/wch/ch32v/src/cpus/main.zig index 593ac98d2..876335a33 100644 --- a/port/wch/ch32v/src/cpus/main.zig +++ b/port/wch/ch32v/src/cpus/main.zig @@ -314,8 +314,13 @@ pub const startup_logic = struct { cpu_impl.system_init(microzig.chip); } - export fn _reset_vector() linksection("microzig_flash_start") callconv(.naked) void { - asm volatile ("j _start"); + comptime { + asm ( + \\.section microzig_flash_start, "ax" + \\.global _reset_vector + \\_reset_vector: + \\j _start + ); } }; From e8c6a40b01c8dcc4891cf61b9e0073b7f0183b37 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 17 Jan 2026 17:51:46 -0500 Subject: [PATCH 52/80] add USBHD/USBHS device backend implementation --- examples/wch/ch32v/src/usb_cdc.zig | 59 +++ port/wch/ch32v/src/hals/usbhs.zig | 665 +++++++++++++++++++++++++++++ 2 files changed, 724 insertions(+) create mode 100644 examples/wch/ch32v/src/usb_cdc.zig create mode 100644 port/wch/ch32v/src/hals/usbhs.zig diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig new file mode 100644 index 000000000..922a20cdc --- /dev/null +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -0,0 +1,59 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const hal = microzig.hal; +const time = hal.time; +const gpio = hal.gpio; + +const RCC = microzig.chip.peripherals.RCC; +const AFIO = microzig.chip.peripherals.AFIO; + +const usart = hal.usart.instance.USART1; +const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 + +pub const microzig_options = microzig.Options{ + .log_level = .debug, + .logFn = hal.usart.log, +}; + +pub fn main() !void { + // Board brings up clocks and time + microzig.board.init(); + microzig.hal.init(); + + // Enable peripheral clocks for USART2 and GPIOA + RCC.APB2PCENR.modify(.{ + .IOPAEN = 1, // Enable GPIOA clock + .AFIOEN = 1, // Enable AFIO clock + .USART1EN = 1, // Enable USART1 clock + }); + RCC.APB1PCENR.modify(.{ + .USART2EN = 1, // Enable USART2 clock + }); + + // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) + AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); + + // Configure PA2 as alternate function push-pull for USART2 TX + usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); + + // Initialize USART2 at 115200 baud + usart.apply(.{ .baud_rate = 115200 }); + + hal.usart.init_logger(usart); + std.log.info("UART logging initialized.", .{}); + + std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); + microzig.cpu.interrupt.enable_interrupts(); + std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); + + var last = time.get_time_since_boot(); + var i: u32 = 0; + while (true) : (i += 1) { + const now = time.get_time_since_boot(); + if (now.diff(last).to_us() > 1000000) { + std.log.info("what {}", .{i}); + last = now; + } + hal.run_usb_demo(); + } +} diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig new file mode 100644 index 000000000..84acf5209 --- /dev/null +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -0,0 +1,665 @@ +//! WCH USBHD/USBHS device backend (polled) +//! +//! Key semantics (per DeviceController in usb.zig): +//! - Must call controller.on_buffer() on IN completion (especially EP0 IN). +//! - Must call controller.on_buffer() on OUT receipt; driver will call ep_readv() exactly once. +//! - EP0 OUT must always accept status-stage OUT ZLP (controller does not always re-arm EP0 OUT). +//! +//! Ownership safety: +//! - OUT buffer is only read after UIF_TRANSFER OUT event; endpoint set to NAK immediately after. +//! - IN buffer is only written when not tx_busy; endpoint set to NAK immediately after IN complete. + +const std = @import("std"); +const assert = std.debug.assert; + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const usb = microzig.core.usb; +const types = usb.types; +const descriptor = usb.descriptor; + +pub const USBHD_MAX_ENDPOINTS_COUNT = 16; + +var pool: [2048]u8 = undefined; + +pub const Config = struct { + max_endpoints_count: comptime_int = USBHD_MAX_ENDPOINTS_COUNT, + + /// Some chips have USBHD regs but no HS PHY. Force FS in that case. + has_hs_phy: bool = true, + prefer_high_speed: bool = true, + + /// Static buffer pool in SRAM, bump-allocated. + buffer_bytes: comptime_int = 2048, + + /// Enable SIE "int busy" behavior: pauses during UIF_TRANSFER so polling won't lose INT_ST context. + int_busy: bool = true, + + /// Future seam only; not implemented in this initial version. + use_interrupts: bool = false, +}; + +const Regs = @TypeOf(peripherals.USBHS); +fn regs() Regs { + return peripherals.USBHS; +} + +const EpState = struct { + buf: []align(4) u8 = &[_]u8{}, + // OUT: + rx_armed: bool = false, + rx_limit: u16 = 0, + rx_last_len: u16 = 0, // valid until ep_readv() consumes it + // IN: + tx_busy: bool = false, + tx_last_len: u16 = 0, +}; + +fn PerEndpointArray(comptime N: comptime_int) type { + return [N][2]EpState; // [ep][dir] +} + +fn epn(ep: types.Endpoint.Num) u4 { + return @as(u4, @intCast(@intFromEnum(ep))); +} +fn dir_index(d: types.Dir) u1 { + return @as(u1, @intCast(@intFromEnum(d))); +} + +fn speed_type(comptime cfg: Config) u2 { + if (!cfg.has_hs_phy) return 0; // FS + if (!cfg.prefer_high_speed) return 0; // FS + return 1; // HS (per USBHS.zig field comment) +} + +// --- USBHD token encodings --- +const TOKEN_OUT: u2 = 0; +const TOKEN_SOF: u2 = 1; +const TOKEN_IN: u2 = 2; +const TOKEN_SETUP: u2 = 3; + +// --- INT_FG raw bits (match USBHS.zig packed order) --- +const UIF_BUS_RST: u8 = 1 << 0; +const UIF_TRANSFER: u8 = 1 << 1; +const UIF_SUSPEND: u8 = 1 << 2; +const UIF_HST_SOF: u8 = 1 << 3; +const UIF_FIFO_OV: u8 = 1 << 4; +const UIF_SETUP_ACT: u8 = 1 << 5; +const UIF_ISO_ACT: u8 = 1 << 6; + +// --- endpoint response encodings (WCH style) --- +const RES_ACK: u2 = 0; +const RES_NAK: u2 = 2; +const RES_STALL: u2 = 3; + +const TOG_DATA0: u2 = 0; +const TOG_DATA1: u2 = 1; + +// We use offset-based access for per-EP regs to keep helpers compact. +const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); +const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); +const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); + +fn baseAddr() usize { + return @intFromPtr(peripherals.USBHS); +} +fn mmio_u32(off: usize) *volatile RegU32 { + return @ptrFromInt(baseAddr() + off); +} +fn mmio_u16(off: usize) *volatile RegU16 { + return @ptrFromInt(baseAddr() + off); +} +fn mmio_u8(off: usize) *volatile RegU8 { + return @ptrFromInt(baseAddr() + off); +} + +// RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) +// EP0 has its own dedicated DMA reg. +// URGENT TODO: FIX EP0 DMA handling! +fn ep0_dma() *volatile RegU32 { + return mmio_u32(0x1C); +} +fn uep_rx_dma(ep: u4) *volatile RegU32 { + return mmio_u32(0x20 + (@as(usize, ep - 1) * 4)); +} +// TX DMA: 0x5C + (ep-1)*4 (EP1..EP15) +fn uep_tx_dma(ep: u4) *volatile RegU32 { + return mmio_u32(0x5C + (@as(usize, ep - 1) * 4)); +} + +// MAX_LEN: EP0..EP15 at 0x98 + ep*4 +fn uep_max_len(ep: u4) *volatile RegU16 { + return mmio_u16(0x98 + (@as(usize, ep) * 4)); +} +// T_LEN: EP0..EP15 at 0xD8 + ep*4 +fn uep_t_len(ep: u4) *volatile RegU16 { + return mmio_u16(0xD8 + (@as(usize, ep) * 4)); +} +// TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 +fn uep_tx_ctrl(ep: u4) *volatile RegU8 { + return mmio_u8(0xDA + (@as(usize, ep) * 4)); +} +fn uep_rx_ctrl(ep: u4) *volatile RegU8 { + return mmio_u8(0xDB + (@as(usize, ep) * 4)); +} + +fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { + // [1:0]=RES, [4:3]=TOG, [5]=AUTO + var v: u8 = 0; + v |= @as(u8, res); + v |= @as(u8, tog) << 3; + if (auto) v |= 1 << 5; + uep_tx_ctrl(ep).write_raw(v); +} +fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { + var v: u8 = 0; + v |= @as(u8, res); + v |= @as(u8, tog) << 3; + if (auto) v |= 1 << 5; + uep_rx_ctrl(ep).write_raw(v); +} + +/// Polled USBHD device backend for microzig core USB controller. +pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { + comptime { + if (cfg.max_endpoints_count > USBHD_MAX_ENDPOINTS_COUNT) + @compileError("USBHD max_endpoints_count cannot exceed 16"); + if (cfg.buffer_bytes < 64) + @compileError("USBHD buffer_bytes must be at least 64"); + } + + return struct { + const Self = @This(); + + const vtable: usb.DeviceInterface.VTable = .{ + .ep_writev = ep_writev, + .ep_readv = ep_readv, + .ep_listen = ep_listen, + .ep_open = ep_open, + .set_address = set_address, + }; + + endpoints: PerEndpointArray(cfg.max_endpoints_count), + buffer_pool: []align(4) u8, + + controller: usb.DeviceController(controller_config), + interface: usb.DeviceInterface, + + pub fn init() Self { + var self: Self = .{ + .endpoints = undefined, + .buffer_pool = undefined, + .controller = .init, + .interface = .{ .vtable = &vtable }, + }; + @memset(std.mem.asBytes(&self.endpoints), 0); + + self.buffer_pool = @alignCast(pool[0..]); + + usbhd_hw_init(); + + // EP0 is required; open OUT then IN (or vice versa), we will share buffer. + self.interface.ep_open(&.{ + .endpoint = .out(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + self.interface.ep_open(&.{ + .endpoint = .in(.ep0), + .max_packet_size = .from(64), + .attributes = .{ .transfer_type = .Control, .usage = .data }, + .interval = 0, + }); + + // EP0 OUT should always be able to accept status-stage OUT ZLP. + // So keep EP0 RX in ACK state permanently. + self.arm_ep0_out_always(); + + // Connect pull-up to signal device ready + regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); + return self; + } + + // TODO: replace with fixedbuffer allocator + fn endpoint_alloc(self: *Self, size: usize) []align(4) u8 { + assert(self.buffer_pool.len >= size); + const out = self.buffer_pool[0..size]; + self.buffer_pool = @alignCast(self.buffer_pool[size..]); + return out; + } + + fn st(self: *Self, ep_num: types.Endpoint.Num, dir: types.Dir) *EpState { + return &self.endpoints[@intFromEnum(ep_num)][@intFromEnum(dir)]; + } + + fn arm_ep0_out_always(self: *Self) void { + _ = self; + // EP0 OUT is always ACK with auto-toggle. + set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + // EP0 IN remains NAK until data is queued. + set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + } + + fn on_bus_reset_local(self: *Self) void { + // Clear state + inline for (0..cfg.max_endpoints_count) |i| { + self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; + self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; + self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; + self.endpoints[i][@intFromEnum(types.Dir.In)].tx_last_len = 0; + } + + // Default: NAK all non-EP0 endpoints. + for (1..cfg.max_endpoints_count) |i| { + const e: u4 = @as(u4, @intCast(i)); + set_rx_ctrl(e, RES_NAK, TOG_DATA0, true); + set_tx_ctrl(e, RES_NAK, TOG_DATA0, true); + } + + // EP0 special. + self.arm_ep0_out_always(); + } + + fn read_setup_from_ep0(self: *Self) types.SetupPacket { + const ep0 = self.st(.ep0, .Out); + assert(ep0.buf.len >= 8); + const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); + return @bitCast(words_ptr.*); + } + + // ---- comptime dispatch helpers (required by DeviceController API) ---- + + fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, buf: []u8) void { + // controller.on_buffer requires comptime ep parameter + switch (dir) { + .In => switch (ep) { + inline 0...15 => |i| { + const num: types.Endpoint.Num = @enumFromInt(i); + self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }, buf); + }, + }, + .Out => switch (ep) { + inline 0...15 => |i| { + const num: types.Endpoint.Num = @enumFromInt(i); + self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }, buf); + }, + }, + } + } + + // ---- Poll loop ------------------------------------------------------- + + pub fn poll(self: *Self) void { + // std.log.debug("USBHS: poll start", .{}); + while (true) { + const fg: u8 = regs().USB_INT_FG.raw; + if (fg == 0) break; + + std.log.debug("USBHS: INT_FG={x}", .{fg}); + + if ((fg & UIF_BUS_RST) != 0) { + std.log.info("USB: bus reset", .{}); + // clear + regs().USB_INT_FG.write_raw(UIF_BUS_RST); + + // address back to 0 + set_address(&self.interface, 0); + + self.on_bus_reset_local(); + self.controller.on_bus_reset(); + continue; + } + + if ((fg & UIF_SETUP_ACT) != 0) { + std.log.info("USB: SETUP received", .{}); + // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. + regs().USB_INT_FG.write_raw(UIF_SETUP_ACT); + if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.write_raw(UIF_TRANSFER); + + const setup = self.read_setup_from_ep0(); + + // After SETUP, EP0 IN data stage starts with DATA1. + set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + + self.controller.on_setup_req(&self.interface, &setup); + continue; + } + + if ((fg & UIF_TRANSFER) != 0) { + std.log.info("USB: TRANSFER event", .{}); + const stv = regs().USB_INT_ST.read(); + const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); + const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + + // clear transfer + regs().USB_INT_FG.write_raw(UIF_TRANSFER); + + self.handle_transfer(ep, token); + continue; + } + + // Clear anything else we don't handle explicitly + regs().USB_INT_FG.write_raw(fg); + } + } + + fn handle_transfer(self: *Self, ep: u4, token: u2) void { + if (ep >= cfg.max_endpoints_count) return; + + switch (token) { + TOKEN_OUT => self.handle_out(ep), + TOKEN_IN => self.handle_in(ep), + TOKEN_SETUP => { + // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. + if (ep == 0) { + const setup = self.read_setup_from_ep0(); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + self.controller.on_setup_req(&self.interface, &setup); + } + }, + TOKEN_SOF => {}, + } + } + + fn handle_out(self: *Self, ep: u4) void { + const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; + std.log.info("USB: EP{} OUT received {} bytes", .{ ep, len }); + // EP0 OUT is always armed; accept status ZLP. + if (ep == 0) { + asm volatile ("" ::: .{ .memory = true }); + const st_out = self.st(.ep0, .Out); + st_out.rx_last_len = len; + // stay ACK + self.call_on_buffer(.Out, 0, st_out.buf[0..@min(@as(usize, len), st_out.buf.len)]); + return; + } + + const num: types.Endpoint.Num = @enumFromInt(ep); + const st_out = self.st(num, .Out); + + // Only read if previously armed (ep_listen) + if (!st_out.rx_armed) { + set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + return; + } + + asm volatile ("" ::: .{ .memory = true }); + // Disarm immediately (NAK) to regain ownership. + st_out.rx_armed = false; + set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + + const n = @min(@as(usize, len), st_out.buf.len); + st_out.rx_last_len = @as(u16, @intCast(n)); + + self.call_on_buffer(.Out, ep, st_out.buf[0..n]); + } + + fn handle_in(self: *Self, ep: u4) void { + const num: types.Endpoint.Num = @enumFromInt(ep); + const st_in = self.st(num, .In); + + if (!st_in.tx_busy) { + set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + return; + } + + asm volatile ("" ::: .{ .memory = true }); + + // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. + const sent_len = st_in.tx_last_len; + st_in.tx_busy = false; + st_in.tx_last_len = 0; + + set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + + // Notify controller/drivers of IN completion. + self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); + + // EP0 OUT must remain ACK + if (ep == 0) self.arm_ep0_out_always(); + } + + // ---- VTable functions ------------------------------------------------ + + fn set_address(_: *usb.DeviceInterface, addr: u7) void { + std.log.info("USB: set_address {}", .{addr}); + regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); + } + + fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { + std.log.info("USB: ep_open called", .{}); + const self: *Self = @fieldParentPtr("interface", itf); + const desc = desc_ptr.*; + + const e = desc.endpoint; + const ep_i: u4 = epn(e.num); + assert(ep_i < cfg.max_endpoints_count); + + const mps: u16 = desc.max_packet_size.into(); + assert(mps > 0 and mps <= 2047); + + // EP0 shares a single DMA buffer for both directions. + if (e.num == .ep0) { + const out_st = self.st(.ep0, .Out); + const in_st = self.st(.ep0, .In); + if (ep0_dma().raw == 0) { + std.log.warn("USBHS: EP0 DMA not initialized yet!", .{}); + } + if (out_st.buf.len == 0 and in_st.buf.len == 0) { + // this should probably be fixed at 64 bytes for EP0? + const buf = self.endpoint_alloc(64); + out_st.buf = buf; + in_st.buf = buf; + + ep0_dma().write_raw(@as(u32, @intCast(@intFromPtr(buf.ptr)))); + uep_max_len(0).write_raw(@intCast(64)); + } else { + // Ensure both directions point at the same backing buffer. + if (out_st.buf.len == 0) out_st.buf = in_st.buf; + if (in_st.buf.len == 0) in_st.buf = out_st.buf; + } + } else { + // Non-EP0: separate DMA buffers per direction + const st_ep = self.st(e.num, e.dir); + + if (st_ep.buf.len == 0) { + st_ep.buf = self.endpoint_alloc(@as(usize, mps)); + } + + const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); + + if (e.dir == .Out) { + uep_rx_dma(ep_i).write_raw(ptr_val); + uep_max_len(ep_i).write_raw(mps); + set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + } else { + uep_tx_dma(ep_i).write_raw(ptr_val); + set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + } + } + + // Enable endpoint direction in UEP_CONFIG bitmaps. + var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; + if (e.num != .ep0) { + // if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + ep_i)); + if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); + } + regs().UEP_CONFIG__UHOST_CTRL.write_raw(cfg_raw); + + // Endpoint type ISO marking (optional, only if you use ISO). + if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { + var type_raw: u32 = regs().UEP_TYPE.raw; + // if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + ep_i)); + if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); + regs().UEP_TYPE.write_raw(type_raw); + } + + // EP0 OUT always ACK + if (e.num == .ep0 and e.dir == .Out) { + self.arm_ep0_out_always(); + } + + // IMPORTANT per usb.zig comment: + // For IN endpoints, ep_open may call controller.on_buffer so drivers can prime initial data. + // Must be comptime-dispatched. + if (e.dir == .In and e.num != .ep0) { + // Empty slice -> “buffer is available / endpoint opened” + switch (e.num) { + inline else => |num_ct| { + const st_in = self.st(num_ct, .In); + self.controller.on_buffer(&self.interface, .{ .num = num_ct, .dir = .In }, st_in.buf[0..0]); + }, + } + } + } + + fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { + const self: *Self = @fieldParentPtr("interface", itf); + + // EP0 OUT is always armed; ignore listen semantics here. + if (ep_num == .ep0) { + const st0 = self.st(.ep0, .Out); + st0.rx_limit = @as(u16, @intCast(len)); + set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + return; + } + + const ep_i: u4 = epn(ep_num); + assert(ep_i < cfg.max_endpoints_count); + + const st_out = self.st(ep_num, .Out); + assert(st_out.buf.len != 0); + + // Must not be called again until packet received. + assert(!st_out.rx_armed); + + const limit = @min(st_out.buf.len, @as(usize, @intCast(len))); + st_out.rx_limit = @as(u16, @intCast(limit)); + st_out.rx_armed = true; + st_out.rx_last_len = 0; + + uep_max_len(ep_i).write_raw(@as(u16, @intCast(limit))); + + asm volatile ("" ::: .{ .memory = true }); + set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + } + + fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { + const self: *Self = @fieldParentPtr("interface", itf); + + const st_out = self.st(ep_num, .Out); + assert(st_out.buf.len != 0); + + const want: usize = @as(usize, st_out.rx_last_len); + assert(want <= st_out.buf.len); + + // Must be called exactly once per received packet. + // Enforce by clearing rx_last_len after consumption. + defer st_out.rx_last_len = 0; + + var remaining: []align(4) u8 = st_out.buf[0..want]; + var copied: usize = 0; + + for (data) |dst| { + if (remaining.len == 0) break; + const n = @min(dst.len, remaining.len); + @memcpy(dst[0..n], remaining[0..n]); + remaining = @alignCast(remaining[n..]); + copied += n; + } + + // Driver is responsible for re-arming via ep_listen(). + return @as(types.Len, @intCast(copied)); + } + + fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { + const self: *Self = @fieldParentPtr("interface", itf); + assert(vec.len > 0); + + const ep_i: u4 = epn(ep_num); + assert(ep_i < cfg.max_endpoints_count); + + const st_in = self.st(ep_num, .In); + assert(st_in.buf.len != 0); + + if (st_in.tx_busy) { + // Not ready; keep NAK and let upper layer retry. + set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + return 0; + } + + // Copy vector into DMA buffer (up to MPS/buffer size) + var w: usize = 0; + for (vec) |chunk| { + if (w >= st_in.buf.len) break; + const n = @min(chunk.len, st_in.buf.len - w); + @memcpy(st_in.buf[w .. w + n], chunk[0..n]); + w += n; + } + + asm volatile ("" ::: .{ .memory = true }); + + uep_t_len(ep_i).write_raw(@as(u16, @intCast(w))); + + st_in.tx_last_len = @as(u16, @intCast(w)); + st_in.tx_busy = true; + + // Arm IN + set_tx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + + // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. + return @as(types.Len, @intCast(w)); + } + + // ---- HW init --------------------------------------------------------- + + fn usbhd_hw_init() void { + // Reset SIE and clear FIFO + regs().USB_CTRL.write_raw(0); // not sure if writing zero then val is ok? + regs().USB_CTRL.modify(.{ + .RB_UC_CLR_ALL = 1, + .RB_UC_RST_SIE = 1, + }); + // wait 10us, TODO: replace with timer delay + var i: u32 = 0; + while (i < 480) : (i += 1) { + asm volatile ("" ::: .{ .memory = true }); + } + + regs().USB_CTRL.modify(.{ + .RB_UC_RST_SIE = 0, + }); + regs().UHOST_CTRL.write_raw(0); + regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + regs().USB_CTRL.modify(.{ + .RB_UC_DMA_EN = 1, + .RB_UC_INT_BUSY = if (cfg.int_busy) 1 else 0, + .RB_UC_SPEED_TYPE = speed_type(cfg), + }); + + // Enable source interrupts (we poll flags; NVIC off) + regs().USB_INT_EN.modify(.{ + .RB_U_1WIRE_MODE = 1, // actually SETUP_ACT? + .RB_UIE_BUS_RST__RB_UIE_DETECT = 1, + .RB_UIE_TRANSFER = 1, + .RB_UIE_SUSPEND = 1, + .RB_UIE_FIFO_OV = 1, + .RB_UIE_HST_SOF = 1, + }); + + // regs().USB_INT_ST.modify(.{ .RB_UIS_IS_NAK = 1 }); + + // Set some defaults, just in case + // regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = 0 }); + // regs().UEP_CONFIG__UHOST_CTRL.write_raw(0); + // regs().UEP_TYPE.write_raw(0); + // regs().UEP_BUF_MOD.write_raw(0); + } + }; +} + +pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { + std.log.warn("USBHS interrupt handler called", .{}); + regs().USB_INT_FG.raw = 0; // clear all +} From 8f4fdcc5581e1e64720fa7967df8bbcf227de074 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 09:10:09 -0500 Subject: [PATCH 53/80] usb periph is alive and responding to host, but not enumerating correctly --- examples/wch/ch32v/build.zig | 22 ++++ examples/wch/ch32v/src/usb_cdc.zig | 102 +++++++++++++++- port/wch/ch32v/src/cpus/main.zig | 1 + port/wch/ch32v/src/hals/ch32v30x.zig | 12 ++ port/wch/ch32v/src/hals/clocks.zig | 167 +++++++++++++++++++++++++++ port/wch/ch32v/src/hals/usbhs.zig | 71 ++++++++---- 6 files changed, 345 insertions(+), 30 deletions(-) diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 0b4007532..8ec179d03 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -6,6 +6,11 @@ const MicroBuild = microzig.MicroBuild(.{ }); pub fn build(b: *std.Build) void { + // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. + // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary + // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. + const gnu_objcopy = b.findProgram(&.{"riscv64-unknown-elf-objcopy"}, &.{}) catch null; + const optimize = b.standardOptimizeOption(.{}); const maybe_example = b.option([]const u8, "example", "only build matching examples"); @@ -53,6 +58,7 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "blinky_systick_ch32v303", .file = "src/blinky_systick.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v305.nano_ch32v305, .name = "nano_ch32v305_blinky", .file = "src/board_blinky.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v307.ch32v307v_r1_1v0, .name = "ch32v307v_r1_1v0_blinky", .file = "src/blinky.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v307.ch32v307v_r1_1v0, .name = "ch32v307v_r1_1v0_usb_cdc", .file = "src/usb_cdc.zig" }, }; for (available_examples) |example| { @@ -77,6 +83,22 @@ pub fn build(b: *std.Build) void { // and allows installing the firmware as a typical firmware file. // // This will also install into `$prefix/firmware` instead of `$prefix/bin`. + + // Use GNU objcopy to create .bin files (avoids LLVM objcopy 512MB issue) + + if (gnu_objcopy) |objcopy_path| { + const bin_filename = b.fmt("{s}.bin", .{example.name}); + const objcopy_run = b.addSystemCommand(&.{objcopy_path}); + objcopy_run.addArgs(&.{ "-O", "binary" }); + objcopy_run.addArtifactArg(fw.artifact); + const bin_output = objcopy_run.addOutputFileArg(bin_filename); + b.getInstallStep().dependOn(&b.addInstallFileWithDir( + bin_output, + .{ .custom = "firmware" }, + bin_filename, + ).step); + } + // For debugging, we also always install the firmware as an ELF file mb.install_firmware(fw, .{ .format = .elf }); // Use GNU objcopy to create .bin files (avoids LLVM objcopy 512MB issue) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 922a20cdc..6b1db955e 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -1,9 +1,12 @@ const std = @import("std"); const microzig = @import("microzig"); + const hal = microzig.hal; const time = hal.time; const gpio = hal.gpio; +pub const usb = hal.usb; + const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; @@ -15,6 +18,44 @@ pub const microzig_options = microzig.Options{ .logFn = hal.usart.log, }; +pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); + +pub var usb_dev: usb.Polled( + microzig.core.usb.Config{ + .device_descriptor = .{ + .bcd_usb = .from(0x0200), + .device_triple = .{ + .class = .Miscellaneous, + .subclass = 2, + .protocol = 1, + }, + .max_packet_size0 = 64, + .vendor = .from(0x2E8A), + .product = .from(0x000A), + .bcd_device = .from(0x0100), + .manufacturer_s = 1, + .product_s = 2, + .serial_s = 3, + .num_configurations = 1, + }, + .string_descriptors = &.{ + .from_lang(.English), + .from_str("Raspberry Pi"), + .from_str("Pico Test Device"), + .from_str("someserial"), + .from_str("Board CDC"), + }, + .configurations = &.{.{ + .num = 1, + .configuration_s = 0, + .attributes = .{ .self_powered = true }, + .max_current_ma = 100, + .Drivers = struct { serial: UsbSerial }, + }}, + }, + .{ .prefer_high_speed = true }, +) = undefined; + pub fn main() !void { // Board brings up clocks and time microzig.board.init(); @@ -37,23 +78,72 @@ pub fn main() !void { usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); // Initialize USART2 at 115200 baud - usart.apply(.{ .baud_rate = 115200 }); + usart.apply(.{ .baud_rate = 460800 }); hal.usart.init_logger(usart); std.log.info("UART logging initialized.", .{}); - std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); - microzig.cpu.interrupt.enable_interrupts(); - std.log.info("Interrupt status: {}", .{microzig.cpu.interrupt.globally_enabled()}); + std.log.info("Initializing USB device.", .{}); + + usb_dev = .init(); + // microzig.cpu.interrupt.enable(.USBHS); var last = time.get_time_since_boot(); var i: u32 = 0; while (true) : (i += 1) { const now = time.get_time_since_boot(); if (now.diff(last).to_us() > 1000000) { - std.log.info("what {}", .{i}); + // std.log.info("what {}", .{i}); last = now; } - hal.run_usb_demo(); + run_usb(); + } +} + +var usb_tx_buff: [1024]u8 = undefined; + +// Transfer data to host +// NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled +pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype) void { + const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; + + var write_buff = text; + while (write_buff.len > 0) { + write_buff = write_buff[serial.write(write_buff)..]; + usb_dev.poll(); + } +} + +var usb_rx_buff: [1024]u8 = undefined; + +// Receive data from host +// NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation +pub fn usb_cdc_read( + serial: *UsbSerial, +) []const u8 { + var total_read: usize = 0; + var read_buff: []u8 = usb_rx_buff[0..]; + + while (true) { + const len = serial.read(read_buff); + read_buff = read_buff[len..]; + total_read += len; + if (len == 0) break; + } + + return usb_rx_buff[0..total_read]; +} + +pub fn run_usb() void { + if (usb_dev.controller.drivers()) |drivers| { + std.log.info("USB CDC Demo transmitting", .{}); + usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}\r\n", .{"Hello, World!"}); + + // read and print host command if present + const message = usb_cdc_read(&drivers.serial); + if (message.len > 0) { + usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); + } } + usb_dev.poll(); } diff --git a/port/wch/ch32v/src/cpus/main.zig b/port/wch/ch32v/src/cpus/main.zig index 876335a33..c5ac8fce2 100644 --- a/port/wch/ch32v/src/cpus/main.zig +++ b/port/wch/ch32v/src/cpus/main.zig @@ -259,6 +259,7 @@ pub const startup_logic = struct { .mie = 1, .mpie = 1, .fs = if (cpu_name == .@"qingkev4-rv32imafc") .dirty else .off, + .mpp = 0x3, }); // Initialize the system. diff --git a/port/wch/ch32v/src/hals/ch32v30x.zig b/port/wch/ch32v/src/hals/ch32v30x.zig index 4aa858904..dd90de2ec 100644 --- a/port/wch/ch32v/src/hals/ch32v30x.zig +++ b/port/wch/ch32v/src/hals/ch32v30x.zig @@ -5,6 +5,8 @@ pub const clocks = @import("clocks.zig"); pub const time = @import("time.zig"); pub const i2c = @import("i2c.zig"); pub const usart = @import("usart.zig"); +const std = @import("std"); +pub const usb = @import("./usbhs.zig"); /// HSI (High Speed Internal) oscillator frequency /// This is the fixed internal RC oscillator frequency for CH32V30x @@ -13,10 +15,20 @@ pub const hsi_frequency: u32 = 8_000_000; // 8 MHz /// Default interrupt handlers provided by the HAL pub const default_interrupts: microzig.cpu.InterruptOptions = .{ .TIM2 = time.tim2_handler, + .USBHS = usb.usbhs_interrupt_handler, }; /// Initialize HAL subsystems used by default pub fn init() void { // Configure TIM2 timing driver time.init(); + clocks.init(.{ + .hse_frequency = 8_000_000, + .target_frequency = 48_000_000, + .source = .hse, + }); // 8 MHz external crystal + clocks.enable_usbhs_clock(.{ + .ref_source_hz = 8_000_000, + .ref_source = .hse, + }); } diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index 8efa45fc8..6a9158817 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -413,6 +413,173 @@ pub fn get_freqs() ClockSpeeds { }; } +/// USBHS/USBHD clock helper. +/// This configures RCC_CFGR2 fields for the USBHS PHY PLL reference (if present), +/// selects whether the USBHS 48MHz clock comes from the system PLL clock or the USB PHY, +/// and enables the AHB clock gate for USBHS. +/// +/// Note: The SVD you're using names bit31 as `USBFSSRC`, but the reference manual +/// describes it as `USBHSSRC` ("USBHS 48MHz clock source selection"). +/// We write the SVD field, but treat it as USBHS 48MHz source select. +pub const UsbHsClockConfig = struct { + pub const RefSource = enum { hse, hsi }; + /// Desired PHY PLL reference frequency (the USBHSCLK field selects one of these). + /// If null, we'll pick the highest one we can generate exactly: 8MHz, then 5MHz, 4MHz, 3MHz. + pub const RefFreq = enum(u2) { + mhz3 = 0b00, + mhz4 = 0b01, + mhz8 = 0b10, + mhz5 = 0b11, + }; + /// Some parts have the USBHS controller but *no* built-in HS PHY. + /// If false, we will not enable the PHY internal PLL and will select 48MHz from PLL CLK. + has_hs_phy: bool = true, + + /// If true (and has_hs_phy), select USB PHY as the 48MHz source and enable the PHY internal PLL. + /// If false, select PLL CLK as the 48MHz source (you must ensure a valid 48MHz PLL clock exists). + use_phy_48mhz: bool = true, + + /// PHY PLL reference source (only used when has_hs_phy && use_phy_48mhz). + ref_source: RefSource = .hse, + + /// Frequency of the chosen ref_source, in Hz. + /// Typical: HSE crystal (e.g. 8_000_000, 12_000_000, 24_000_000) or HSI (often 8_000_000). + ref_source_hz: u32, + + ref_freq: ?RefFreq = null, +}; + +fn div_to_usbhsdiv(div: u32) u3 { + // RCC_CFGR2 USBHSDIV encoding: + // 000: /1, 001: /2, ... 111: /8 + return @as(u3, @intCast(div - 1)); +} + +fn hz_for_ref(ref: UsbHsClockConfig.RefFreq) u32 { + return switch (ref) { + .mhz3 => 3_000_000, + .mhz4 => 4_000_000, + .mhz5 => 5_000_000, + .mhz8 => 8_000_000, + }; +} + +const HSPLLSRC = enum(u2) { + hse = 0, + hsi = 1, +}; + +/// Enable + configure USBHS clocks. +/// +/// This does *not* reconfigure the system PLL to 48MHz; it only selects between: +/// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or +/// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true and cfg.has_hs_phy = true), +/// in which case it also configures the PHY PLL reference (USBHSCLK/USBHSPLLSRC/USBHSDIV) +/// and enables the PHY internal PLL (USBHSPLL). +pub fn enable_usbhs_clock(comptime cfg: UsbHsClockConfig) void { + // Turn on the AHB clock gate for the USBHS peripheral block. + + // If there's no PHY (or caller prefers PLL CLK), force PLL CLK selection and keep PHY PLL off. + if (!cfg.has_hs_phy or !cfg.use_phy_48mhz) { + RCC.CFGR2.modify(.{ + // SVD name mismatch: USBFSSRC field == USBHS 48MHz source select per RM. + .USBFSSRC = 0, // 0: PLL CLK (per RM: "USBHS 48MHz clock source selection") + .USBHSPLL = 0, // PHY internal PLL disabled + }); + return; + } + + // Ensure the selected oscillator is on (best-effort; usually already enabled by your system clock init). + switch (cfg.ref_source) { + .hse => { + if (RCC.CTLR.read().HSEON == 0) RCC.CTLR.modify(.{ .HSEON = 1 }); + while (RCC.CTLR.read().HSERDY == 0) {} + }, + .hsi => { + if (RCC.CTLR.read().HSION == 0) RCC.CTLR.modify(.{ .HSION = 1 }); + while (RCC.CTLR.read().HSIRDY == 0) {} + }, + } + + // Choose a reference frequency and divider that is exactly achievable. + const candidates = [_]UsbHsClockConfig.RefFreq{ .mhz8, .mhz5, .mhz4, .mhz3 }; + + comptime var chosen_ref: UsbHsClockConfig.RefFreq = undefined; + comptime var chosen_div: u32 = 0; + comptime { + if (cfg.ref_freq) |forced| { + const want = hz_for_ref(forced); + var found = false; + for (1..9) |div| { + if (cfg.ref_source_hz % div == 0 and (cfg.ref_source_hz / div) == want) { + chosen_ref = forced; + chosen_div = div; + found = true; + break; + } + } + if (!found) { + @compileError("USBHS PHY PLL ref cannot be generated exactly from ref_source_hz with /1..8 prescaler."); + } + } else { + var found = false; + for (candidates) |ref| { + const want = hz_for_ref(ref); + for (1..9) |div| { + if (cfg.ref_source_hz % div == 0 and (cfg.ref_source_hz / div) == want) { + chosen_ref = ref; + chosen_div = div; + found = true; + break; + } + } + if (found) break; + } + if (!found) { + @compileError("USBHS PHY PLL ref cannot be generated: need 3/4/5/8MHz from ref_source_hz using /1..8 prescaler."); + } + } + } + + // RCC_USBCLK48MConfig( RCC_USBCLK48MCLKSource_USBPHY ); // bit 31 = 1 + // RCC_USBHSPLLCLKConfig( RCC_HSBHSPLLCLKSource_HSE ); // bit 27 = 0 + // RCC_USBHSConfig( RCC_USBPLL_Div2 ); // bit 24 = 1 + // RCC_USBHSPLLCKREFCLKConfig( RCC_USBHSPLLCKREFCLK_4M );// bit 28 = 1 + // RCC_USBHSPHYPLLALIVEcmd( ENABLE ); // bit 30 = 1 + // RCC_AHBPeriphClockCmd( RCC_AHBPeriph_USBHS, ENABLE ); + + // Program CFGR2: + // - USBHSPLLSRC: 0=HSE, 1=HSI + // - USBHSDIV: prescaler (/1..8) + // - USBHSCLK: selects which ref freq the PHY PLL expects (3/4/8/5 MHz) + // - USBHSPLL: enable PHY internal PLL + // - USBHSSRC (SVD calls it USBFSSRC): 1=USB PHY as 48MHz source + RCC.CFGR2.modify(.{ + .USBHSPLLSRC = switch (cfg.ref_source) { + .hse => 0, + .hsi => 1, + }, + .USBHSDIV = div_to_usbhsdiv(chosen_div), + .USBHSCLK = @as(u2, @intFromEnum(chosen_ref)), + .USBHSPLL = 1, + .USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY + }); + + // RCC.CFGR2.raw = (1 << 31 | 1 << 24 | 1 << 28 | 1 << 30); + + // RCC.CFGR2.modify(.{ + // .USBHSPLLSRC = switch (cfg.ref_source) { + // .hse => 0, + // .hsi => 1, + // }, + // .USBHSDIV = 1, + // .USBHSCLK = 1, + // .USBHSPLL = 1, + // .USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY + // }); + RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); +} + // ============================================================================ // Convenience Functions for HAL Modules // ============================================================================ diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 84acf5209..fac571300 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -149,14 +149,14 @@ fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { v |= @as(u8, res); v |= @as(u8, tog) << 3; if (auto) v |= 1 << 5; - uep_tx_ctrl(ep).write_raw(v); + uep_tx_ctrl(ep).raw = v; } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { var v: u8 = 0; v |= @as(u8, res); v |= @as(u8, tog) << 3; if (auto) v |= 1 << 5; - uep_rx_ctrl(ep).write_raw(v); + uep_rx_ctrl(ep).raw = v; } /// Polled USBHD device backend for microzig core USB controller. @@ -193,8 +193,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { .interface = .{ .vtable = &vtable }, }; @memset(std.mem.asBytes(&self.endpoints), 0); + @memset(pool[0..64], 0x7e); + @memset(pool[64..], 0); - self.buffer_pool = @alignCast(pool[0..]); + self.buffer_pool = @alignCast(pool[64..]); usbhd_hw_init(); @@ -218,6 +220,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Connect pull-up to signal device ready regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); + self.interface.ep_listen(.ep0, 0); + + regs().USB_CTRL.modify(.{ + .RB_UC_CLR_ALL = 0, + }); + return self; } @@ -265,6 +273,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ep0 = self.st(.ep0, .Out); assert(ep0.buf.len >= 8); const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); + std.log.info("USB: SETUP packet data: 0x{x} 0x{x}", .{ words_ptr[0], words_ptr[1] }); return @bitCast(words_ptr.*); } @@ -301,7 +310,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if ((fg & UIF_BUS_RST) != 0) { std.log.info("USB: bus reset", .{}); // clear - regs().USB_INT_FG.write_raw(UIF_BUS_RST); + regs().USB_INT_FG.raw = UIF_BUS_RST; // address back to 0 set_address(&self.interface, 0); @@ -314,8 +323,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if ((fg & UIF_SETUP_ACT) != 0) { std.log.info("USB: SETUP received", .{}); // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. - regs().USB_INT_FG.write_raw(UIF_SETUP_ACT); - if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.write_raw(UIF_TRANSFER); + regs().USB_INT_FG.raw = UIF_SETUP_ACT; + if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; const setup = self.read_setup_from_ep0(); @@ -333,14 +342,18 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); // clear transfer - regs().USB_INT_FG.write_raw(UIF_TRANSFER); + regs().USB_INT_FG.raw = UIF_TRANSFER; self.handle_transfer(ep, token); continue; } // Clear anything else we don't handle explicitly - regs().USB_INT_FG.write_raw(fg); + regs().USB_INT_FG.raw = fg; + // 0x4 => SUSPEND + // 0x8 => HST_SOF + // 0x10 => FIFO_OV + // 0x40 => ISO_ACT } } @@ -413,6 +426,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + std.log.info("USB: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); // Notify controller/drivers of IN completion. self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); @@ -428,13 +442,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { - std.log.info("USB: ep_open called", .{}); const self: *Self = @fieldParentPtr("interface", itf); const desc = desc_ptr.*; const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); + std.log.info("USB: ep_open called for ep{}", .{ep_i}); const mps: u16 = desc.max_packet_size.into(); assert(mps > 0 and mps <= 2047); @@ -444,7 +458,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const out_st = self.st(.ep0, .Out); const in_st = self.st(.ep0, .In); if (ep0_dma().raw == 0) { - std.log.warn("USBHS: EP0 DMA not initialized yet!", .{}); + std.log.warn("USBHS: EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { // this should probably be fixed at 64 bytes for EP0? @@ -452,8 +466,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { out_st.buf = buf; in_st.buf = buf; - ep0_dma().write_raw(@as(u32, @intCast(@intFromPtr(buf.ptr)))); - uep_max_len(0).write_raw(@intCast(64)); + const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); + std.log.info("USBHS: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + ep0_dma().raw = ptr_val; + uep_max_len(0).raw = @intCast(64); } else { // Ensure both directions point at the same backing buffer. if (out_st.buf.len == 0) out_st.buf = in_st.buf; @@ -470,29 +486,31 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); if (e.dir == .Out) { - uep_rx_dma(ep_i).write_raw(ptr_val); - uep_max_len(ep_i).write_raw(mps); + uep_rx_dma(ep_i).raw = ptr_val; + uep_max_len(ep_i).raw = mps; set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); } else { - uep_tx_dma(ep_i).write_raw(ptr_val); + uep_tx_dma(ep_i).raw = ptr_val; set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); } } + uep_t_len(ep_i).raw = 0; + // Enable endpoint direction in UEP_CONFIG bitmaps. var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; if (e.num != .ep0) { // if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); } - regs().UEP_CONFIG__UHOST_CTRL.write_raw(cfg_raw); + regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; // Endpoint type ISO marking (optional, only if you use ISO). if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { var type_raw: u32 = regs().UEP_TYPE.raw; // if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); - regs().UEP_TYPE.write_raw(type_raw); + regs().UEP_TYPE.raw = type_raw; } // EP0 OUT always ACK @@ -515,6 +533,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { + std.log.info("USB: ep_listen called for ep{} len={}", .{ ep_num, len }); const self: *Self = @fieldParentPtr("interface", itf); // EP0 OUT is always armed; ignore listen semantics here. @@ -539,13 +558,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_out.rx_armed = true; st_out.rx_last_len = 0; - uep_max_len(ep_i).write_raw(@as(u16, @intCast(limit))); + uep_max_len(ep_i).raw = @as(u16, @intCast(limit)); asm volatile ("" ::: .{ .memory = true }); set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { + std.log.info("USB: ep_readv called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); const st_out = self.st(ep_num, .Out); @@ -574,6 +594,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { + std.log.info("USB: ep_writev called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -600,7 +621,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { asm volatile ("" ::: .{ .memory = true }); - uep_t_len(ep_i).write_raw(@as(u16, @intCast(w))); + uep_t_len(ep_i).raw = @as(u16, @intCast(w)); st_in.tx_last_len = @as(u16, @intCast(w)); st_in.tx_busy = true; @@ -616,7 +637,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn usbhd_hw_init() void { // Reset SIE and clear FIFO - regs().USB_CTRL.write_raw(0); // not sure if writing zero then val is ok? + regs().UHOST_CTRL.raw = 0; + regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + regs().USB_CTRL.raw = 0; // not sure if writing zero then val is ok? regs().USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 1, .RB_UC_RST_SIE = 1, @@ -630,8 +653,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_CTRL.modify(.{ .RB_UC_RST_SIE = 0, }); - regs().UHOST_CTRL.write_raw(0); - regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + regs().USB_CTRL.modify(.{ .RB_UC_DMA_EN = 1, .RB_UC_INT_BUSY = if (cfg.int_busy) 1 else 0, @@ -660,6 +682,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { - std.log.warn("USBHS interrupt handler called", .{}); - regs().USB_INT_FG.raw = 0; // clear all + const fg = regs().USB_INT_FG.raw; + std.log.warn("USBHS interrupt handler called, flags: {}", .{fg}); + regs().USB_INT_FG.raw = fg; // clear all } From d36e85cf1d118a2f7491540c73fe56bf98d08bba Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 09:11:56 -0500 Subject: [PATCH 54/80] change to work with v307 evt --- examples/wch/ch32v/src/uart_log.zig | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/examples/wch/ch32v/src/uart_log.zig b/examples/wch/ch32v/src/uart_log.zig index d99db5a1c..0a0252b60 100644 --- a/examples/wch/ch32v/src/uart_log.zig +++ b/examples/wch/ch32v/src/uart_log.zig @@ -7,8 +7,10 @@ const gpio = hal.gpio; const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; -const usart = hal.usart.instance.USART2; -const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 +// TODO: Get usart from board config + +const usart = hal.usart.instance.USART1; +const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 pub const microzig_options = microzig.Options{ .log_level = .debug, @@ -18,19 +20,18 @@ pub const microzig_options = microzig.Options{ pub fn main() !void { // Board brings up clocks and time microzig.board.init(); + microzig.hal.init(); // Enable peripheral clocks for USART2 and GPIOA RCC.APB2PCENR.modify(.{ .IOPAEN = 1, // Enable GPIOA clock .AFIOEN = 1, // Enable AFIO clock + .USART1EN = 1, // Enable USART1 clock }); RCC.APB1PCENR.modify(.{ .USART2EN = 1, // Enable USART2 clock }); - // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) - AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); - // Configure PA2 as alternate function push-pull for USART2 TX usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); @@ -38,10 +39,14 @@ pub fn main() !void { usart.apply(.{ .baud_rate = 115200 }); hal.usart.init_logger(usart); - + std.log.info("UART logging initialized.", .{}); + var last = time.get_time_since_boot(); var i: u32 = 0; while (true) : (i += 1) { - std.log.info("what {}", .{i}); - time.sleep_ms(1000); + const now = time.get_time_since_boot(); + if (now.diff(last).to_us() > 500000) { + std.log.info("what {}", .{i}); + last = now; + } } } From a3673d8a6f83eb4938b7a3b82c1ba42e585eddf8 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 10:55:05 -0500 Subject: [PATCH 55/80] Logging updates and call on_buffer at end of setup flag block --- examples/wch/ch32v/src/usb_cdc.zig | 1 + port/wch/ch32v/src/hals/usbhs.zig | 49 +++++++++++++++++++----------- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 6b1db955e..b2269ee9a 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -52,6 +52,7 @@ pub var usb_dev: usb.Polled( .max_current_ma = 100, .Drivers = struct { serial: UsbSerial }, }}, + .debug = true, }, .{ .prefer_high_speed = true }, ) = undefined; diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index fac571300..0588760cd 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -159,6 +159,10 @@ fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { uep_rx_ctrl(ep).raw = v; } +fn fmt_slice(dest: []u8, msg: []const u8) []const u8 { + return std.fmt.bufPrint(dest, "{x}", .{msg}) catch &.{}; +} + /// Polled USBHD device backend for microzig core USB controller. pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { comptime { @@ -220,7 +224,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Connect pull-up to signal device ready regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); - self.interface.ep_listen(.ep0, 0); regs().USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 0, @@ -273,7 +276,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ep0 = self.st(.ep0, .Out); assert(ep0.buf.len >= 8); const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); - std.log.info("USB: SETUP packet data: 0x{x} 0x{x}", .{ words_ptr[0], words_ptr[1] }); return @bitCast(words_ptr.*); } @@ -300,15 +302,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- pub fn poll(self: *Self) void { - // std.log.debug("USBHS: poll start", .{}); while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - std.log.debug("USBHS: INT_FG={x}", .{fg}); + std.log.debug("ch32: INT_FG={x}", .{fg}); if ((fg & UIF_BUS_RST) != 0) { - std.log.info("USB: bus reset", .{}); + std.log.info("ch32: bus reset", .{}); // clear regs().USB_INT_FG.raw = UIF_BUS_RST; @@ -321,22 +322,26 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } if ((fg & UIF_SETUP_ACT) != 0) { - std.log.info("USB: SETUP received", .{}); + std.log.info("ch32: SETUP received", .{}); // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; + const stv = regs().USB_INT_ST.read(); + std.log.debug("ch32: INT_ST={any}", .{stv}); const setup = self.read_setup_from_ep0(); + std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); + self.call_on_buffer(.In, 0, self.st(.ep0, .In).buf[0..8]); // consume SETUP continue; } if ((fg & UIF_TRANSFER) != 0) { - std.log.info("USB: TRANSFER event", .{}); + std.log.info("ch32: TRANSFER event", .{}); const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); @@ -351,6 +356,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Clear anything else we don't handle explicitly regs().USB_INT_FG.raw = fg; // 0x4 => SUSPEND + // 0x5 => SUSPEND | RESET // 0x8 => HST_SOF // 0x10 => FIFO_OV // 0x40 => ISO_ACT @@ -366,7 +372,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { TOKEN_SETUP => { // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. if (ep == 0) { - const setup = self.read_setup_from_ep0(); + const setup: types.SetupPacket = self.read_setup_from_ep0(); + std.log.debug("ch32: Setup: {any}", .{setup}); set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); } @@ -377,7 +384,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_out(self: *Self, ep: u4) void { const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; - std.log.info("USB: EP{} OUT received {} bytes", .{ ep, len }); + std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { asm volatile ("" ::: .{ .memory = true }); @@ -426,7 +433,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); - std.log.info("USB: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); + std.log.info("ch32: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); + var buf: [256]u8 = undefined; + // var str = &buf[0..]; + const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); + std.log.debug("ch32: EP{} IN data: {s}", .{ ep, str }); // Notify controller/drivers of IN completion. self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); @@ -437,7 +448,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- VTable functions ------------------------------------------------ fn set_address(_: *usb.DeviceInterface, addr: u7) void { - std.log.info("USB: set_address {}", .{addr}); + std.log.info("ch32: set_address {}", .{addr}); regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); } @@ -448,7 +459,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); - std.log.info("USB: ep_open called for ep{}", .{ep_i}); + std.log.info("ch32: ep_open called for ep{}", .{ep_i}); const mps: u16 = desc.max_packet_size.into(); assert(mps > 0 and mps <= 2047); @@ -458,7 +469,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const out_st = self.st(.ep0, .Out); const in_st = self.st(.ep0, .In); if (ep0_dma().raw == 0) { - std.log.warn("USBHS: EP0 DMA is null!", .{}); + std.log.warn("ch32: EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { // this should probably be fixed at 64 bytes for EP0? @@ -467,7 +478,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { in_st.buf = buf; const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); - std.log.info("USBHS: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + std.log.info("ch32: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); ep0_dma().raw = ptr_val; uep_max_len(0).raw = @intCast(64); } else { @@ -533,7 +544,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { - std.log.info("USB: ep_listen called for ep{} len={}", .{ ep_num, len }); + std.log.info("ch32: ep_listen called for ep{} len={}", .{ ep_num, len }); const self: *Self = @fieldParentPtr("interface", itf); // EP0 OUT is always armed; ignore listen semantics here. @@ -565,7 +576,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { - std.log.info("USB: ep_readv called for ep{}", .{ep_num}); + std.log.info("ch32: ep_readv called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); const st_out = self.st(ep_num, .Out); @@ -594,7 +605,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { - std.log.info("USB: ep_writev called for ep{}", .{ep_num}); + std.log.info("ch32: ep_writev called for {}", .{ep_num}); + for (vec) |chunk| { + std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); + std.log.debug("ch32: chunk: {x}", .{fmt_slice(&[_]u8{}, chunk)}); + } const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); From 321c3176547fbe81f7c69c7557cfb42215fdc374 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 11:45:13 -0500 Subject: [PATCH 56/80] add fifo overflow warning and reduce SOF spam --- port/wch/ch32v/src/hals/usbhs.zig | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 0588760cd..2c7c9a93c 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -306,7 +306,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - std.log.debug("ch32: INT_FG={x}", .{fg}); + std.log.debug("ch32: INT_FG = 0x{x}", .{fg}); + + if (fg & UIF_FIFO_OV != 0) { + std.log.warn("ch32: FIFO overflow!", .{}); + regs().USB_INT_FG.raw = UIF_FIFO_OV; + continue; + } if ((fg & UIF_BUS_RST) != 0) { std.log.info("ch32: bus reset", .{}); @@ -326,6 +332,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; + const stv = regs().USB_INT_ST.read(); std.log.debug("ch32: INT_ST={any}", .{stv}); @@ -341,10 +348,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } if ((fg & UIF_TRANSFER) != 0) { - std.log.info("ch32: TRANSFER event", .{}); const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + if (token != TOKEN_SOF) { // reduce spam from SOF events + std.log.info("ch32: TRANSFER event", .{}); + } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -415,6 +424,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.call_on_buffer(.Out, ep, st_out.buf[0..n]); } + // IN => into host from device fn handle_in(self: *Self, ep: u4) void { const num: types.Endpoint.Num = @enumFromInt(ep); const st_in = self.st(num, .In); From 59450b4253b212ab535ef21b1829c76e72078927 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 13:06:19 -0500 Subject: [PATCH 57/80] logging swirl --- port/wch/ch32v/src/hals/usbhs.zig | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 2c7c9a93c..7f2586103 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -302,9 +302,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- pub fn poll(self: *Self) void { + var did_work = false; while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; + did_work = true; std.log.debug("ch32: INT_FG = 0x{x}", .{fg}); @@ -333,14 +335,15 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; - const stv = regs().USB_INT_ST.read(); - std.log.debug("ch32: INT_ST={any}", .{stv}); + // const stv = regs().USB_INT_ST.read(); + // std.log.debug("ch32: INT_ST={any}", .{stv}); const setup = self.read_setup_from_ep0(); - std.log.debug("ch32: Setup: {any}", .{setup}); + // std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); self.call_on_buffer(.In, 0, self.st(.ep0, .In).buf[0..8]); // consume SETUP @@ -351,9 +354,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - if (token != TOKEN_SOF) { // reduce spam from SOF events - std.log.info("ch32: TRANSFER event", .{}); - } + std.log.info("ch32: TRANSFER event", .{}); // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -370,13 +371,17 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // 0x10 => FIFO_OV // 0x40 => ISO_ACT } + if (did_work) { + std.log.info("", .{}); + } } fn handle_transfer(self: *Self, ep: u4, token: u2) void { if (ep >= cfg.max_endpoints_count) return; - + std.log.debug("ch32: token={}", .{token}); switch (token) { TOKEN_OUT => self.handle_out(ep), + TOKEN_SOF => {}, TOKEN_IN => self.handle_in(ep), TOKEN_SETUP => { // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. @@ -387,7 +392,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.controller.on_setup_req(&self.interface, &setup); } }, - TOKEN_SOF => {}, } } From 07bfc32823c6c31d7fec3fab258d65a561c06650 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 17:59:31 -0500 Subject: [PATCH 58/80] make it to config descriptor but fail to send 2nd packet. --- port/wch/ch32v/src/hals/usbhs.zig | 71 ++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 7f2586103..531756e14 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -73,6 +73,13 @@ fn speed_type(comptime cfg: Config) u2 { } // --- USBHD token encodings --- +const Token = enum(u2) { + Out = 0, + Sof = 1, + In = 2, + Setup = 3, +}; + const TOKEN_OUT: u2 = 0; const TOKEN_SOF: u2 = 1; const TOKEN_IN: u2 = 2; @@ -136,7 +143,7 @@ fn uep_t_len(ep: u4) *volatile RegU16 { return mmio_u16(0xD8 + (@as(usize, ep) * 4)); } // TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 -fn uep_tx_ctrl(ep: u4) *volatile RegU8 { +pub fn uep_tx_ctrl(ep: u4) *volatile RegU8 { return mmio_u8(0xDA + (@as(usize, ep) * 4)); } fn uep_rx_ctrl(ep: u4) *volatile RegU8 { @@ -145,6 +152,7 @@ fn uep_rx_ctrl(ep: u4) *volatile RegU8 { fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { // [1:0]=RES, [4:3]=TOG, [5]=AUTO + std.log.debug("ch32: set_tx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); var v: u8 = 0; v |= @as(u8, res); v |= @as(u8, tog) << 3; @@ -152,6 +160,7 @@ fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { uep_tx_ctrl(ep).raw = v; } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { + std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); var v: u8 = 0; v |= @as(u8, res); v |= @as(u8, tog) << 3; @@ -246,10 +255,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn arm_ep0_out_always(self: *Self) void { _ = self; + std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); // EP0 OUT is always ACK with auto-toggle. - set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + set_tx_ctrl(0, RES_NAK, TOG_DATA0, true); } fn on_bus_reset_local(self: *Self) void { @@ -306,9 +316,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - did_work = true; - std.log.debug("ch32: INT_FG = 0x{x}", .{fg}); + const stv = regs().USB_INT_ST.read(); + const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); + const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + if (token != TOKEN_SOF) { + std.log.debug("ch32: INT_FG = 0x{x}, token: {}", .{ fg, @as(Token, @enumFromInt(token)) }); + did_work = true; + } if (fg & UIF_FIFO_OV != 0) { std.log.warn("ch32: FIFO overflow!", .{}); @@ -317,7 +332,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } if ((fg & UIF_BUS_RST) != 0) { - std.log.info("ch32: bus reset", .{}); + std.log.info("ch32: bus reset\n\n\n", .{}); // clear regs().USB_INT_FG.raw = UIF_BUS_RST; @@ -338,24 +353,21 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // const stv = regs().USB_INT_ST.read(); // std.log.debug("ch32: INT_ST={any}", .{stv}); - const setup = self.read_setup_from_ep0(); - // std.log.debug("ch32: Setup: {any}", .{setup}); + const setup: types.SetupPacket = self.read_setup_from_ep0(); + std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); - self.call_on_buffer(.In, 0, self.st(.ep0, .In).buf[0..8]); // consume SETUP continue; } if ((fg & UIF_TRANSFER) != 0) { - const stv = regs().USB_INT_ST.read(); - const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); - const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - std.log.info("ch32: TRANSFER event", .{}); - + if (token != TOKEN_SOF) { + std.log.info("ch32: TRANSFER event", .{}); + } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -378,7 +390,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_transfer(self: *Self, ep: u4, token: u2) void { if (ep >= cfg.max_endpoints_count) return; - std.log.debug("ch32: token={}", .{token}); + if (token == TOKEN_SOF) return; + std.log.debug("ch32: handle_transfer ep={} token={}", .{ ep, @as(Token, @enumFromInt(token)) }); + // std.log.debug("ch32: token={}", .{token}); switch (token) { TOKEN_OUT => self.handle_out(ep), TOKEN_SOF => {}, @@ -445,7 +459,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = false; st_in.tx_last_len = 0; - set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + // set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); std.log.info("ch32: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); var buf: [256]u8 = undefined; @@ -453,10 +467,15 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); std.log.debug("ch32: EP{} IN data: {s}", .{ ep, str }); // Notify controller/drivers of IN completion. + std.log.debug("tx_ctrl before on_buffer: {any}", .{uep_tx_ctrl(ep)}); + // set_tx_ctrl(ep, RES_NAK, TOG_DATA1, true); + self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); // EP0 OUT must remain ACK - if (ep == 0) self.arm_ep0_out_always(); + if (self.controller.tx_slice == null) { + if (ep == 0) self.arm_ep0_out_always(); + } } // ---- VTable functions ------------------------------------------------ @@ -565,7 +584,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (ep_num == .ep0) { const st0 = self.st(.ep0, .Out); st0.rx_limit = @as(u16, @intCast(len)); - set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); return; } @@ -622,8 +641,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { std.log.info("ch32: ep_writev called for {}", .{ep_num}); for (vec) |chunk| { std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); - std.log.debug("ch32: chunk: {x}", .{fmt_slice(&[_]u8{}, chunk)}); + std.log.debug("ch32: chunk: {any}", .{chunk}); } + const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -633,8 +653,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(ep_num, .In); assert(st_in.buf.len != 0); + std.log.debug("tx_ctrl before write: {any}", .{uep_tx_ctrl(ep_i)}); + if (st_in.tx_busy) { - // Not ready; keep NAK and let upper layer retry. + // do I want to set anything in this case? + std.log.warn("ch32: ep_writev called while IN endpoint busy, returning 0", .{}); set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); return 0; } @@ -647,6 +670,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { @memcpy(st_in.buf[w .. w + n], chunk[0..n]); w += n; } + std.log.debug("ch32: Writing chunk of len={}", .{w}); asm volatile ("" ::: .{ .memory = true }); @@ -656,7 +680,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = true; // Arm IN - set_tx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + if (w < vec[0].len) { + set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + } else { + set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + } + std.log.debug("tx_ctrl after write: {any}", .{uep_tx_ctrl(ep_i)}); // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); From 2bbaf5b5f27d441e6c72f9aef25fcbdf509987d4 Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 18 Jan 2026 21:26:11 -0500 Subject: [PATCH 59/80] successful enumeration! Started replacing constants with enums, changed the reset state for ep0 tx/rx ctrl from DATA0 to DATA1, I think that did it. --- examples/wch/ch32v/src/usb_cdc.zig | 6 ++-- port/wch/ch32v/src/hals/usbhs.zig | 45 ++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index b2269ee9a..a7fcbb2cc 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -40,8 +40,8 @@ pub var usb_dev: usb.Polled( }, .string_descriptors = &.{ .from_lang(.English), - .from_str("Raspberry Pi"), - .from_str("Pico Test Device"), + .from_str("MicroZig"), + .from_str("ch32v307 Test Device"), .from_str("someserial"), .from_str("Board CDC"), }, @@ -52,7 +52,7 @@ pub var usb_dev: usb.Polled( .max_current_ma = 100, .Drivers = struct { serial: UsbSerial }, }}, - .debug = true, + .debug = false, }, .{ .prefer_high_speed = true }, ) = undefined; diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 531756e14..a022ffbdb 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -99,13 +99,27 @@ const RES_ACK: u2 = 0; const RES_NAK: u2 = 2; const RES_STALL: u2 = 3; +const Res = enum(u2) { + ACK = 0, + NAK = 2, + STALL = 3, +}; + const TOG_DATA0: u2 = 0; const TOG_DATA1: u2 = 1; +const Tog = enum(u2) { + DATA0 = 0, + DATA1 = 0b1, + DATA2 = 0b10, + MDATA = 0b11, +}; + // We use offset-based access for per-EP regs to keep helpers compact. const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); +pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { _reserved2: u2 = 0, AUTO: bool = false, TOG: u2 = 0, _reserved0: u1 = 0, RES: u2 = 0 }); fn baseAddr() usize { return @intFromPtr(peripherals.USBHS); @@ -119,6 +133,12 @@ fn mmio_u16(off: usize) *volatile RegU16 { fn mmio_u8(off: usize) *volatile RegU8 { return @ptrFromInt(baseAddr() + off); } +fn mmio_tx_ctrl(off: usize) *volatile RegCtrl { + return @ptrFromInt(baseAddr() + off); +} +fn mmio_rx_ctrl(off: usize) *volatile RegCtrl { + return @ptrFromInt(baseAddr() + off); +} // RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) // EP0 has its own dedicated DMA reg. @@ -143,21 +163,24 @@ fn uep_t_len(ep: u4) *volatile RegU16 { return mmio_u16(0xD8 + (@as(usize, ep) * 4)); } // TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 -pub fn uep_tx_ctrl(ep: u4) *volatile RegU8 { - return mmio_u8(0xDA + (@as(usize, ep) * 4)); +pub fn uep_tx_ctrl(ep: u4) *volatile RegCtrl { + const ret: *volatile RegCtrl = mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); + return ret; } -fn uep_rx_ctrl(ep: u4) *volatile RegU8 { - return mmio_u8(0xDB + (@as(usize, ep) * 4)); + +fn uep_rx_ctrl(ep: u4) *volatile RegCtrl { + return mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); } fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { // [1:0]=RES, [4:3]=TOG, [5]=AUTO std.log.debug("ch32: set_tx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); - var v: u8 = 0; - v |= @as(u8, res); - v |= @as(u8, tog) << 3; - if (auto) v |= 1 << 5; - uep_tx_ctrl(ep).raw = v; + const tx_ctrl: *volatile RegCtrl = uep_tx_ctrl(ep); + tx_ctrl.write(.{ + .RES = res, + .TOG = tog, + .AUTO = auto, + }); } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); @@ -257,9 +280,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { _ = self; std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); // EP0 OUT is always ACK with auto-toggle. - set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); + set_rx_ctrl(0, RES_ACK, TOG_DATA1, true); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA0, true); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); } fn on_bus_reset_local(self: *Self) void { From 66e9c3267173b5afb810f2165d16cdef2f783d1f Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 21 Jan 2026 22:25:21 -0500 Subject: [PATCH 60/80] minor changes while hunting down why cdc transfers don't work. --- core/src/core/usb.zig | 3 +- examples/wch/ch32v/build.zig | 2 +- examples/wch/ch32v/src/usb_cdc.zig | 8 +- .../hals => examples/wch/ch32v/src}/usbhs.zig | 109 ++++++++---------- port/wch/ch32v/src/hals/ch32v30x.zig | 4 +- 5 files changed, 61 insertions(+), 65 deletions(-) rename {port/wch/ch32v/src/hals => examples/wch/ch32v/src}/usbhs.zig (89%) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index b4ff02188..0279f5416 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -511,7 +511,7 @@ pub fn DeviceController(config: Config) type { device_itf.ep_open(desc); } - @field(self.driver_data.?, fld_drv.name).init(&cfg, device_itf); + // @field(self.driver_data.?, fld_drv.name).init(&cfg, device_itf); // Open IN endpoint last so that callbacks can happen inline for (desc_fields) |fld| { @@ -519,6 +519,7 @@ pub fn DeviceController(config: Config) type { if (comptime fld.type == descriptor.Endpoint and desc.endpoint.dir == .In) device_itf.ep_open(desc); } + @field(self.driver_data.?, fld_drv.name) = .init(&cfg, device_itf); } } }; diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 8ec179d03..bd63759f1 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -5,7 +5,7 @@ const MicroBuild = microzig.MicroBuild(.{ .ch32v = true, }); -pub fn build(b: *std.Build) void { +pub fn build(b: *std.Build) void { // $ls root_id 24 // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index a7fcbb2cc..a87e6b0a9 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -5,7 +5,7 @@ const hal = microzig.hal; const time = hal.time; const gpio = hal.gpio; -pub const usb = hal.usb; +pub const usb = @import("usbhs.zig"); const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; @@ -18,7 +18,7 @@ pub const microzig_options = microzig.Options{ .logFn = hal.usart.log, }; -pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 64 }); +pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 512 }); pub var usb_dev: usb.Polled( microzig.core.usb.Config{ @@ -79,7 +79,7 @@ pub fn main() !void { usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); // Initialize USART2 at 115200 baud - usart.apply(.{ .baud_rate = 460800 }); + usart.apply(.{ .baud_rate = 921600 }); hal.usart.init_logger(usart); std.log.info("UART logging initialized.", .{}); @@ -95,6 +95,7 @@ pub fn main() !void { const now = time.get_time_since_boot(); if (now.diff(last).to_us() > 1000000) { // std.log.info("what {}", .{i}); + std.log.debug("usb drivers available?: {}", .{usb_dev.controller.drivers() != null}); last = now; } run_usb(); @@ -111,6 +112,7 @@ pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype var write_buff = text; while (write_buff.len > 0) { write_buff = write_buff[serial.write(write_buff)..]; + // _ = serial.flush(); usb_dev.poll(); } } diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig similarity index 89% rename from port/wch/ch32v/src/hals/usbhs.zig rename to examples/wch/ch32v/src/usbhs.zig index a022ffbdb..223ab0ef7 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -18,9 +18,11 @@ const usb = microzig.core.usb; const types = usb.types; const descriptor = usb.descriptor; -pub const USBHD_MAX_ENDPOINTS_COUNT = 16; +pub const USBHD_MAX_ENDPOINTS_COUNT = 6; -var pool: [2048]u8 = undefined; +const max_buffer_pool_size = USBHD_MAX_ENDPOINTS_COUNT * 2 * 512 + 64; + +var pool: [max_buffer_pool_size]u8 = undefined; pub const Config = struct { max_endpoints_count: comptime_int = USBHD_MAX_ENDPOINTS_COUNT, @@ -30,7 +32,7 @@ pub const Config = struct { prefer_high_speed: bool = true, /// Static buffer pool in SRAM, bump-allocated. - buffer_bytes: comptime_int = 2048, + buffer_bytes: comptime_int = max_buffer_pool_size, /// Enable SIE "int busy" behavior: pauses during UIF_TRANSFER so polling won't lose INT_ST context. int_busy: bool = true, @@ -174,7 +176,6 @@ fn uep_rx_ctrl(ep: u4) *volatile RegCtrl { fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { // [1:0]=RES, [4:3]=TOG, [5]=AUTO - std.log.debug("ch32: set_tx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); const tx_ctrl: *volatile RegCtrl = uep_tx_ctrl(ep); tx_ctrl.write(.{ .RES = res, @@ -183,12 +184,13 @@ fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { }); } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { - std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); - var v: u8 = 0; - v |= @as(u8, res); - v |= @as(u8, tog) << 3; - if (auto) v |= 1 << 5; - uep_rx_ctrl(ep).raw = v; + // std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); + const ctrl: *volatile RegCtrl = uep_rx_ctrl(ep); + ctrl.write(.{ + .RES = res, + .TOG = tog, + .AUTO = auto, + }); } fn fmt_slice(dest: []u8, msg: []const u8) []const u8 { @@ -278,7 +280,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn arm_ep0_out_always(self: *Self) void { _ = self; - std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); + // std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); // EP0 OUT is always ACK with auto-toggle. set_rx_ctrl(0, RES_ACK, TOG_DATA1, true); // EP0 IN remains NAK until data is queued. @@ -377,7 +379,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // std.log.debug("ch32: INT_ST={any}", .{stv}); const setup: types.SetupPacket = self.read_setup_from_ep0(); - std.log.debug("ch32: Setup: {any}", .{setup}); + // std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); @@ -437,7 +439,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { - asm volatile ("" ::: .{ .memory = true }); const st_out = self.st(.ep0, .Out); st_out.rx_last_len = len; // stay ACK @@ -454,7 +455,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { return; } - asm volatile ("" ::: .{ .memory = true }); // Disarm immediately (NAK) to regain ownership. st_out.rx_armed = false; set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); @@ -471,27 +471,22 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(num, .In); if (!st_in.tx_busy) { - set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + std.log.warn("ch32: EP{} IN but not busy, spurious?", .{ep}); + // @panic("IN but not busy"); + uep_tx_ctrl(ep).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0 }); return; } - asm volatile ("" ::: .{ .memory = true }); - // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. const sent_len = st_in.tx_last_len; st_in.tx_busy = false; st_in.tx_last_len = 0; - // set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); - - std.log.info("ch32: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); - var buf: [256]u8 = undefined; - // var str = &buf[0..]; - const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); - std.log.debug("ch32: EP{} IN data: {s}", .{ ep, str }); + // std.log.info("ch32: EP{} IN completed, sent {} bytes, TOG={}", .{ ep, sent_len, uep_tx_ctrl(ep).read().TOG }); + // var buf: [256]u8 = undefined; + // const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); + // std.log.debug("ch32: EP{} IN data: {any}", .{ ep, st_in.buf[0..sent_len] }); // Notify controller/drivers of IN completion. - std.log.debug("tx_ctrl before on_buffer: {any}", .{uep_tx_ctrl(ep)}); - // set_tx_ctrl(ep, RES_NAK, TOG_DATA1, true); self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); @@ -543,11 +538,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (in_st.buf.len == 0) in_st.buf = out_st.buf; } } else { + std.log.debug("ch32: ep_open non-EP0 ep{} dir={}", .{ ep_i, e.dir }); // Non-EP0: separate DMA buffers per direction const st_ep = self.st(e.num, e.dir); if (st_ep.buf.len == 0) { - st_ep.buf = self.endpoint_alloc(@as(usize, mps)); + st_ep.buf = self.endpoint_alloc(@as(usize, 512)); } const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); @@ -561,6 +557,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); } } + std.log.debug("allocation and DMA setup done for ep{}", .{ep_i}); uep_t_len(ep_i).raw = 0; @@ -585,18 +582,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.arm_ep0_out_always(); } - // IMPORTANT per usb.zig comment: - // For IN endpoints, ep_open may call controller.on_buffer so drivers can prime initial data. - // Must be comptime-dispatched. - if (e.dir == .In and e.num != .ep0) { - // Empty slice -> “buffer is available / endpoint opened” - switch (e.num) { - inline else => |num_ct| { - const st_in = self.st(num_ct, .In); - self.controller.on_buffer(&self.interface, .{ .num = num_ct, .dir = .In }, st_in.buf[0..0]); - }, - } - } + std.log.debug("ch32: ep_open completed for ep{}", .{ep_i}); } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { @@ -612,13 +598,17 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } const ep_i: u4 = epn(ep_num); - assert(ep_i < cfg.max_endpoints_count); + if (ep_i > cfg.max_endpoints_count) + @panic("ep_listen called for invalid endpoint"); const st_out = self.st(ep_num, .Out); - assert(st_out.buf.len != 0); + // std.log.debug("ep_listen: st_out buf len={}, st_out.rx_armed={}", .{ st_out.buf.len, st_out.rx_armed }); + if (st_out.buf.len == 0) + @panic("ep_listen called for endpoint with no buffer allocated"); // Must not be called again until packet received. - assert(!st_out.rx_armed); + if (st_out.rx_armed) + @panic("ep_listen called while OUT endpoint already armed"); const limit = @min(st_out.buf.len, @as(usize, @intCast(len))); st_out.rx_limit = @as(u16, @intCast(limit)); @@ -627,8 +617,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { uep_max_len(ep_i).raw = @as(u16, @intCast(limit)); - asm volatile ("" ::: .{ .memory = true }); + asm volatile (""); set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + std.log.debug("finished ep_listen for ep{}", .{ep_num}); } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { @@ -662,10 +653,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { std.log.info("ch32: ep_writev called for {}", .{ep_num}); - for (vec) |chunk| { - std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); - std.log.debug("ch32: chunk: {any}", .{chunk}); - } + // for (vec) |chunk| { + // std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); + // std.log.debug("ch32: chunk: {any}", .{chunk}); + // } const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -676,12 +667,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(ep_num, .In); assert(st_in.buf.len != 0); - std.log.debug("tx_ctrl before write: {any}", .{uep_tx_ctrl(ep_i)}); - if (st_in.tx_busy) { // do I want to set anything in this case? - std.log.warn("ch32: ep_writev called while IN endpoint busy, returning 0", .{}); - set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + std.log.warn("ch32: ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); + // set_tx_ctrl(ep_i, RES_NAK, TOG_DATA1, true); + uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK }); return 0; } @@ -693,9 +683,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { @memcpy(st_in.buf[w .. w + n], chunk[0..n]); w += n; } - std.log.debug("ch32: Writing chunk of len={}", .{w}); + // std.log.debug("ch32: Writing chunk of len={}", .{w}); - asm volatile ("" ::: .{ .memory = true }); + asm volatile (""); uep_t_len(ep_i).raw = @as(u16, @intCast(w)); @@ -703,12 +693,15 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = true; // Arm IN - if (w < vec[0].len) { - set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + if (ep_i == 0) { + // EP0 IN starts with DATA1 after SETUP + // set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); } else { - set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); + // set_tx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); } - std.log.debug("tx_ctrl after write: {any}", .{uep_tx_ctrl(ep_i)}); + // set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); @@ -728,7 +721,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // wait 10us, TODO: replace with timer delay var i: u32 = 0; while (i < 480) : (i += 1) { - asm volatile ("" ::: .{ .memory = true }); + asm volatile (""); } regs().USB_CTRL.modify(.{ diff --git a/port/wch/ch32v/src/hals/ch32v30x.zig b/port/wch/ch32v/src/hals/ch32v30x.zig index dd90de2ec..74587548c 100644 --- a/port/wch/ch32v/src/hals/ch32v30x.zig +++ b/port/wch/ch32v/src/hals/ch32v30x.zig @@ -6,7 +6,7 @@ pub const time = @import("time.zig"); pub const i2c = @import("i2c.zig"); pub const usart = @import("usart.zig"); const std = @import("std"); -pub const usb = @import("./usbhs.zig"); +// pub const usb = @import("./usbhs.zig"); /// HSI (High Speed Internal) oscillator frequency /// This is the fixed internal RC oscillator frequency for CH32V30x @@ -15,7 +15,7 @@ pub const hsi_frequency: u32 = 8_000_000; // 8 MHz /// Default interrupt handlers provided by the HAL pub const default_interrupts: microzig.cpu.InterruptOptions = .{ .TIM2 = time.tim2_handler, - .USBHS = usb.usbhs_interrupt_handler, + // .USBHS = usb.usbhs_interrupt_handler, }; /// Initialize HAL subsystems used by default From 00fe33bc122d89b0f72825625d9d8fe90ed5bd61 Mon Sep 17 00:00:00 2001 From: Bob Date: Wed, 21 Jan 2026 23:55:23 -0500 Subject: [PATCH 61/80] Fix data toggle config --- core/src/core/usb.zig | 6 +++--- examples/wch/ch32v/src/usb_cdc.zig | 8 +++++--- examples/wch/ch32v/src/usbhs.zig | 32 ++++++++++-------------------- 3 files changed, 19 insertions(+), 27 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 0279f5416..a54a2c07d 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -159,7 +159,7 @@ pub fn DeviceController(config: Config) type { const DriverEnum = std.meta.FieldEnum(config0.Drivers); const config_descriptor = blk: { const max_psize = config.max_supported_packet_size; - assert(max_psize == 8 or max_psize == 16 or max_psize == 32 or max_psize == 64); + assert(max_psize == 8 or max_psize == 16 or max_psize == 32 or max_psize == 64 or max_psize == 512); var alloc: DescriptorAllocator = .init(config.unique_endpoints); var next_string = 4; @@ -511,7 +511,7 @@ pub fn DeviceController(config: Config) type { device_itf.ep_open(desc); } - // @field(self.driver_data.?, fld_drv.name).init(&cfg, device_itf); + @field(self.driver_data.?, fld_drv.name).init(&cfg, device_itf); // Open IN endpoint last so that callbacks can happen inline for (desc_fields) |fld| { @@ -519,7 +519,7 @@ pub fn DeviceController(config: Config) type { if (comptime fld.type == descriptor.Endpoint and desc.endpoint.dir == .In) device_itf.ep_open(desc); } - @field(self.driver_data.?, fld_drv.name) = .init(&cfg, device_itf); + // @field(self.driver_data.?, fld_drv.name) = .init(&cfg, &config_descriptor, device_itf); } } }; diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index a87e6b0a9..3e2bc41e5 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -52,7 +52,7 @@ pub var usb_dev: usb.Polled( .max_current_ma = 100, .Drivers = struct { serial: UsbSerial }, }}, - .debug = false, + .max_supported_packet_size = 512, }, .{ .prefer_high_speed = true }, ) = undefined; @@ -96,9 +96,10 @@ pub fn main() !void { if (now.diff(last).to_us() > 1000000) { // std.log.info("what {}", .{i}); std.log.debug("usb drivers available?: {}", .{usb_dev.controller.drivers() != null}); + run_usb(); last = now; } - run_usb(); + usb_dev.poll(); } } @@ -111,8 +112,9 @@ pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype var write_buff = text; while (write_buff.len > 0) { + // std.log.info("USB CDC Demo writing", .{}); write_buff = write_buff[serial.write(write_buff)..]; - // _ = serial.flush(); + _ = serial.flush(); usb_dev.poll(); } } diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index 223ab0ef7..4dc3a2324 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -144,7 +144,6 @@ fn mmio_rx_ctrl(off: usize) *volatile RegCtrl { // RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) // EP0 has its own dedicated DMA reg. -// URGENT TODO: FIX EP0 DMA handling! fn ep0_dma() *volatile RegU32 { return mmio_u32(0x1C); } @@ -316,19 +315,19 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- comptime dispatch helpers (required by DeviceController API) ---- - fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, buf: []u8) void { + fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4) void { // controller.on_buffer requires comptime ep parameter switch (dir) { .In => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @enumFromInt(i); - self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }, buf); + self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); }, }, .Out => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @enumFromInt(i); - self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }, buf); + self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); }, }, } @@ -365,7 +364,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_address(&self.interface, 0); self.on_bus_reset_local(); - self.controller.on_bus_reset(); + self.controller.on_bus_reset(&self.interface); continue; } @@ -375,14 +374,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_INT_FG.raw = UIF_SETUP_ACT; if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; - // const stv = regs().USB_INT_ST.read(); - // std.log.debug("ch32: INT_ST={any}", .{stv}); - const setup: types.SetupPacket = self.read_setup_from_ep0(); - // std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + uep_tx_ctrl(0).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA1, .AUTO = true }); set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); self.controller.on_setup_req(&self.interface, &setup); @@ -427,7 +422,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (ep == 0) { const setup: types.SetupPacket = self.read_setup_from_ep0(); std.log.debug("ch32: Setup: {any}", .{setup}); - set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + uep_tx_ctrl(0).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA1 }); self.controller.on_setup_req(&self.interface, &setup); } }, @@ -442,7 +437,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_out = self.st(.ep0, .Out); st_out.rx_last_len = len; // stay ACK - self.call_on_buffer(.Out, 0, st_out.buf[0..@min(@as(usize, len), st_out.buf.len)]); + self.call_on_buffer(.Out, 0); return; } @@ -462,7 +457,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const n = @min(@as(usize, len), st_out.buf.len); st_out.rx_last_len = @as(u16, @intCast(n)); - self.call_on_buffer(.Out, ep, st_out.buf[0..n]); + self.call_on_buffer(.Out, ep); } // IN => into host from device @@ -478,7 +473,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. - const sent_len = st_in.tx_last_len; + // const sent_len = st_in.tx_last_len; st_in.tx_busy = false; st_in.tx_last_len = 0; @@ -488,7 +483,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // std.log.debug("ch32: EP{} IN data: {any}", .{ ep, st_in.buf[0..sent_len] }); // Notify controller/drivers of IN completion. - self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); + self.call_on_buffer(.In, ep); // EP0 OUT must remain ACK if (self.controller.tx_slice == null) { @@ -554,7 +549,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); } else { uep_tx_dma(ep_i).raw = ptr_val; - set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA1, .AUTO = true }); } } std.log.debug("allocation and DMA setup done for ep{}", .{ep_i}); @@ -670,7 +665,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (st_in.tx_busy) { // do I want to set anything in this case? std.log.warn("ch32: ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); - // set_tx_ctrl(ep_i, RES_NAK, TOG_DATA1, true); uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK }); return 0; } @@ -694,14 +688,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Arm IN if (ep_i == 0) { - // EP0 IN starts with DATA1 after SETUP - // set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); } else { - // set_tx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); } - // set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); From 20115903a538420718ba0e7fb6034fc4e6f24567 Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 22 Jan 2026 00:28:18 -0500 Subject: [PATCH 62/80] Fixed data toggles, CDC Data flows! --- core/src/core/usb/drivers/cdc.zig | 11 ++- examples/wch/ch32v/build.zig | 33 +++---- examples/wch/ch32v/src/usb_cdc.zig | 12 +-- examples/wch/ch32v/src/usbhs.zig | 6 +- port/wch/ch32v/src/hals/usb.zig | 153 ----------------------------- 5 files changed, 32 insertions(+), 183 deletions(-) delete mode 100644 port/wch/ch32v/src/hals/usb.zig diff --git a/core/src/core/usb/drivers/cdc.zig b/core/src/core/usb/drivers/cdc.zig index 001a229b2..f1cbfc5e7 100644 --- a/core/src/core/usb/drivers/cdc.zig +++ b/core/src/core/usb/drivers/cdc.zig @@ -174,9 +174,16 @@ pub fn CdcClassDriver(options: Options) type { } pub fn write(self: *@This(), data: []const u8) usize { - const len = @min(self.tx_data.len - self.tx_end, data.len); + const free_space = self.tx_data.len - self.tx_end; + if (free_space == 0) { + _ = self.flush(); + return 0; + } + const len = @min(free_space, data.len); - if (len == 0) return 0; + if (len == 0) { + return 0; + } @memcpy(self.tx_data[self.tx_end..][0..len], data[0..len]); self.tx_end += @intCast(len); diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index bd63759f1..f02010de9 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -17,11 +17,6 @@ pub fn build(b: *std.Build) void { // $ls root_id 24 const mz_dep = b.dependency("microzig", .{}); const mb = MicroBuild.init(b, mz_dep) orelse return; - // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. - // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary - // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. - const gnu_objcopy = b.findProgram(&.{"riscv64-unknown-elf-objcopy"}, &.{}) catch null; - const available_examples = [_]Example{ // CH32V003 .{ .target = mb.ports.ch32v.chips.ch32v003x4, .name = "empty_ch32v003", .file = "src/empty.zig" }, @@ -37,20 +32,20 @@ pub fn build(b: *std.Build) void { // $ls root_id 24 .{ .target = mb.ports.ch32v.boards.ch32v103.ch32v103r_r1_1v1, .name = "ch32v103r_r1_1v1_empty", .file = "src/empty.zig" }, // CH32V203 - .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "empty_ch32v203", .file = "src/empty.zig" }, - .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_ch32v203", .file = "src/blinky.zig" }, - .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_systick_ch32v203", .file = "src/blinky_systick.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_blinky", .file = "src/board_blinky.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_dma", .file = "src/dma.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_eeprom", .file = "src/i2c_eeprom.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_loopback", .file = "src/spi_loopback.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_flash_w25q", .file = "src/spi_flash_w25q.zig" }, - .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_sharp_niceview", .file = "src/sharp_niceview.zig" }, + // .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "empty_ch32v203", .file = "src/empty.zig" }, + // .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_ch32v203", .file = "src/blinky.zig" }, + // .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_systick_ch32v203", .file = "src/blinky_systick.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_blinky", .file = "src/board_blinky.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_dma", .file = "src/dma.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_eeprom", .file = "src/i2c_eeprom.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_loopback", .file = "src/spi_loopback.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_flash_w25q", .file = "src/spi_flash_w25q.zig" }, + // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_sharp_niceview", .file = "src/sharp_niceview.zig" }, // CH32V30x .{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "empty_ch32v303", .file = "src/empty.zig" }, diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 3e2bc41e5..93c6374dc 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -93,11 +93,11 @@ pub fn main() !void { var i: u32 = 0; while (true) : (i += 1) { const now = time.get_time_since_boot(); - if (now.diff(last).to_us() > 1000000) { + if (now.diff(last).to_us() > 10000) { // std.log.info("what {}", .{i}); - std.log.debug("usb drivers available?: {}", .{usb_dev.controller.drivers() != null}); - run_usb(); + // std.log.debug("usb drivers available?: {}", .{usb_dev.controller.drivers() != null}); last = now; + run_usb(i); } usb_dev.poll(); } @@ -114,7 +114,7 @@ pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype while (write_buff.len > 0) { // std.log.info("USB CDC Demo writing", .{}); write_buff = write_buff[serial.write(write_buff)..]; - _ = serial.flush(); + // _ = serial.flush(); usb_dev.poll(); } } @@ -139,10 +139,10 @@ pub fn usb_cdc_read( return usb_rx_buff[0..total_read]; } -pub fn run_usb() void { +pub fn run_usb(i: u32) void { if (usb_dev.controller.drivers()) |drivers| { std.log.info("USB CDC Demo transmitting", .{}); - usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}\r\n", .{"Hello, World!"}); + usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}, {}\r\n", .{ "Hello, World!", i }); // read and print host command if present const message = usb_cdc_read(&drivers.serial); diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index 4dc3a2324..6d5e51b06 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -549,7 +549,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); } else { uep_tx_dma(ep_i).raw = ptr_val; - uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA1, .AUTO = true }); + uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0, .AUTO = true }); } } std.log.debug("allocation and DMA setup done for ep{}", .{ep_i}); @@ -663,9 +663,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { assert(st_in.buf.len != 0); if (st_in.tx_busy) { - // do I want to set anything in this case? std.log.warn("ch32: ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); - uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK }); return 0; } @@ -687,7 +685,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = true; // Arm IN + // uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); if (ep_i == 0) { + // EP0 IN starts with DATA1 after SETUP uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); } else { uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); diff --git a/port/wch/ch32v/src/hals/usb.zig b/port/wch/ch32v/src/hals/usb.zig deleted file mode 100644 index 81503c046..000000000 --- a/port/wch/ch32v/src/hals/usb.zig +++ /dev/null @@ -1,153 +0,0 @@ -//! USB device implementation -//! -//! - -const std = @import("std"); -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 PHY = enum { - device, - host_device, -}; - -pub const HardwareConfig = struct { - max_endpoints_count: comptime_int = 3, // datasheet text says 16 but only has 8 registers? - max_interfaces_count: comptime_int = 2, // not sure on this - num_can_active: u2 = 0, // 0, 1 or 2, changes amount of shared SRAM - phy: PHY = .device, - buffer_size: u32 = 64, -}; - -const BufferDescription = packed struct { - const Count = packed struct { - val: u10, - res: u6, - res2: u16, - }; - address: u32, - count: Count, -}; - -const SHARED_SRAM_SIZE: comptime_int = 512; // bytes -const SHARED_SRAM_ADDR: comptime_int = 0x40006000; -pub fn Polled( - controller_config: usb.Config, - config: HardwareConfig, -) type { - const buffer_desc_size = 2 * config.max_endpoints_count * @sizeOf(BufferDescription); - // seems like we must have 2 buffers per endpoint - // they're either double buffered or tx/rx? - const buffer_size = 2 * config.max_endpoints_count * config.buffer_size; - const used_sram = buffer_desc_size + buffer_size; - comptime { - // Do hardware config validity checks here - const can_sram_size: u32 = 128 * @as(u32, @intCast(config.num_can_active)); - const available_sram = SHARED_SRAM_SIZE - can_sram_size; - - if (used_sram > available_sram) { - @compileLog("USB Buffer configuration overflows available sram"); - } - } - _ = controller_config; - // _ = config; - const USB = peripherals.USB; - const EXT = peripherals.EXTEND; - - // always putting the buffer descriptor table at the start of shared sram - const buffer_descriptor_table: *align(2) [2 * config.max_endpoints_count]BufferDescription = @ptrFromInt(SHARED_SRAM_ADDR); - const ep_buffers: *align(2) [2 * config.max_endpoints_count][config.buffer_size]u8 = @ptrFromInt(SHARED_SRAM_ADDR + buffer_desc_size); - - return struct { - const vtable: usb.DeviceInterface.VTable = .{ - .ep_writev = start_tx, - .ep_readv = start_rx, - .set_address = set_address, - .ep_open = ep_open, - }; - - pub fn poll() void { - // do the recurring work here - } - pub fn init() @This() { - // setup hardware here - // first check for 48MHz clock then enable the USB Clock in RCC_CFGR0 - const RCC = peripherals.RCC; - // RCC.CFGR0 // USBPRE - RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); - RCC.APB1PCENR.modify(.{ .USBDEN = 1 }); - USB.CNTR.modify(.{ .FRES = 1 }); // reset the peripheral if it's not already - - init_shared_sram(); - // endpoint config - - //packet buffer description table - - USB.DADDR.modify(.{ .ADD = 0, .EF = 1 }); - EXT.EXTEND_CTR.modify(.{ .USBDPU = 1 }); // enable pull up - USB.CNTR.modify(.{ .FRES = 0 }); // bring device out of reset - USB.ISTR.raw = 0; // reset all interrupt bits - // enable interrupts here if needed - USB.CNTR.modify(.{ .ESOFM = 0, .SOFM = 0, .RESETM = 0, .SUSPM = 0, .WKUPM = 0, .ERRM = 0, .PMAOVRM = 0, .CTRM = 0 }); - - return .{}; - } - - fn init_shared_sram() void { - // put table at start of sram - USB.BTABLE.modify(.{ .BTABLE = 0 }); - for (buffer_descriptor_table, ep_buffers) |*desc, *buf| { - desc.*.address = @intFromPtr(buf); - desc.*.count.val = 64; // is EP0 64 bytes? - } - } - - pub fn start_tx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, buffer: []const u8) void { - _ = self; - _ = ep_num; - _ = buffer; - } - pub fn start_rx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usize) void { - _ = self; - _ = ep_num; - _ = len; - } - pub fn ep_open(self: *@This(), desc: *const usb.descriptor.Endpoint) void { - _ = self; - // how to tell if IN or OUT endpoint? - - const epr = get_epr_reg(desc.endpoint.num); - epr.modify(.{ - .EP_TYPE = switch (desc.attributes.transfer_type) { - .Bulk => 0b00, - .Control => 0b01, - .Isochronous => 0b10, - .Interrupt => 0b11, - }, - .EP_KIND = 0, // no double buffering for now - - }); - } - pub fn set_address(self: *usb.DeviceInterface, addr: u7) void { - _ = self; - _ = addr; - } - fn get_epr_reg(ep_num: usb.types.Endpoint.Num) @TypeOf(USB.EPR0) { - return switch (ep_num) { - .ep0 => USB.EPR0, - .ep1 => USB.EPR1, - .ep2 => USB.EPR2, - .ep3 => USB.EPR3, - .ep4 => USB.EPR4, - .ep5 => USB.EPR5, - .ep6 => USB.EPR6, - .ep7 => USB.EPR7, - else => @compileError("Unsupported endpoint number"), - }; - } - }; -} From 9865fe3385b4afe05513ea1a65e0e9e7aced522c Mon Sep 17 00:00:00 2001 From: Bob Date: Thu, 22 Jan 2026 00:49:19 -0500 Subject: [PATCH 63/80] comment out some logging --- examples/wch/ch32v/src/usb_cdc.zig | 9 ++++----- examples/wch/ch32v/src/usbhs.zig | 11 +++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 93c6374dc..aed825e61 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -93,11 +93,10 @@ pub fn main() !void { var i: u32 = 0; while (true) : (i += 1) { const now = time.get_time_since_boot(); - if (now.diff(last).to_us() > 10000) { + if (now.diff(last).to_us() > 500000) { // std.log.info("what {}", .{i}); - // std.log.debug("usb drivers available?: {}", .{usb_dev.controller.drivers() != null}); - last = now; run_usb(i); + last = now; } usb_dev.poll(); } @@ -141,7 +140,7 @@ pub fn usb_cdc_read( pub fn run_usb(i: u32) void { if (usb_dev.controller.drivers()) |drivers| { - std.log.info("USB CDC Demo transmitting", .{}); + // std.log.info("USB CDC Demo transmitting", .{}); usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}, {}\r\n", .{ "Hello, World!", i }); // read and print host command if present @@ -150,5 +149,5 @@ pub fn run_usb(i: u32) void { usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); } } - usb_dev.poll(); + // usb_dev.poll(); } diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index 6d5e51b06..fe6566640 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -345,7 +345,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); if (token != TOKEN_SOF) { - std.log.debug("ch32: INT_FG = 0x{x}, token: {}", .{ fg, @as(Token, @enumFromInt(token)) }); + // std.log.debug("ch32: INT_FG = 0x{x}, token: {}", .{ fg, @as(Token, @enumFromInt(token)) }); did_work = true; } @@ -386,7 +386,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if ((fg & UIF_TRANSFER) != 0) { if (token != TOKEN_SOF) { - std.log.info("ch32: TRANSFER event", .{}); + // std.log.info("ch32: TRANSFER event", .{}); } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; @@ -404,14 +404,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // 0x40 => ISO_ACT } if (did_work) { - std.log.info("", .{}); + // std.log.info("", .{}); } } fn handle_transfer(self: *Self, ep: u4, token: u2) void { if (ep >= cfg.max_endpoints_count) return; if (token == TOKEN_SOF) return; - std.log.debug("ch32: handle_transfer ep={} token={}", .{ ep, @as(Token, @enumFromInt(token)) }); + // std.log.debug("ch32: handle_transfer ep={} token={}", .{ ep, @as(Token, @enumFromInt(token)) }); // std.log.debug("ch32: token={}", .{token}); switch (token) { TOKEN_OUT => self.handle_out(ep), @@ -466,8 +466,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(num, .In); if (!st_in.tx_busy) { - std.log.warn("ch32: EP{} IN but not busy, spurious?", .{ep}); - // @panic("IN but not busy"); + // std.log.warn("ch32: EP{} IN but not busy, no data to send", .{ep}); uep_tx_ctrl(ep).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0 }); return; } From c3371463a6fa1cbd8b41e333fc557b68f2eb9798 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 24 Jan 2026 20:58:50 -0500 Subject: [PATCH 64/80] Refactor ch32 USB logging and enhance clock configuration for CH32V boards. ch32 USBHS still sends packets twice. --- core/src/core/usb.zig | 2 +- examples/wch/ch32v/build.zig | 2 +- examples/wch/ch32v/src/usb_cdc.zig | 53 +++++++- examples/wch/ch32v/src/usbhs.zig | 47 ++----- .../wch/ch32v/src/boards/CH32V307V-R1-1v0.zig | 7 +- port/wch/ch32v/src/hals/ch32v30x.zig | 2 +- port/wch/ch32v/src/hals/clocks.zig | 126 +++++++++++++++--- port/wch/ch32v/src/hals/usart.zig | 34 +++++ 8 files changed, 204 insertions(+), 69 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index a54a2c07d..429f53e15 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -363,7 +363,7 @@ pub fn DeviceController(config: Config) type { /// Called by the device implementation when a packet has been sent or received. pub fn on_buffer(self: *@This(), device_itf: *DeviceInterface, comptime ep: types.Endpoint) void { - log.debug("on_buffer {t} {t}", .{ ep.num, ep.dir }); + // log.debug("on_buffer {t} {t}", .{ ep.num, ep.dir }); const driver = comptime @field(handlers.ep_drv, @tagName(ep.dir))[@intFromEnum(ep.num)]; const function = comptime @field(handlers.ep_han, @tagName(ep.dir))[@intFromEnum(ep.num)]; diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index f02010de9..3bc977530 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -5,7 +5,7 @@ const MicroBuild = microzig.MicroBuild(.{ .ch32v = true, }); -pub fn build(b: *std.Build) void { // $ls root_id 24 +pub fn build(b: *std.Build) void { // $ls root_id 16 // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 7d04f93e0..43a6ee04c 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -13,9 +13,17 @@ const AFIO = microzig.chip.peripherals.AFIO; const usart = hal.usart.instance.USART1; const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 +const mco_pin = gpio.Pin.init(0, 8); // PA9 + +pub const my_interrupts: microzig.cpu.InterruptOptions = .{ + // .TIM2 = time.tim2_handler, + .USBHS = usb.usbhs_interrupt_handler, +}; + pub const microzig_options = microzig.Options{ .log_level = .debug, .logFn = hal.usart.log, + .interrupts = my_interrupts, }; pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 512 }); @@ -61,16 +69,13 @@ pub fn main() !void { // Board brings up clocks and time microzig.board.init(); microzig.hal.init(); - + // RCC.CFGR0.modify(.{ .MCO = 0b0100 }); // Enable peripheral clocks for USART2 and GPIOA RCC.APB2PCENR.modify(.{ .IOPAEN = 1, // Enable GPIOA clock .AFIOEN = 1, // Enable AFIO clock .USART1EN = 1, // Enable USART1 clock }); - RCC.APB1PCENR.modify(.{ - .USART2EN = 1, // Enable USART2 clock - }); // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); @@ -78,8 +83,19 @@ pub fn main() !void { // Configure PA2 as alternate function push-pull for USART2 TX usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); + // mco_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); + // Initialize USART2 at 115200 baud usart.apply(.{ .baud_rate = 921600 }); + comptime { + const sysclk: comptime_float = 144_000_000; + const hb: comptime_float = 1.0; + const pb2: comptime_float = 1.0; + const pclk = (sysclk / hb) / pb2; + if (!usart.validate_baudrate(921600, pclk, 1.0)) { + @compileError("Bad baud rate"); + } + } hal.usart.init_logger(usart); std.log.info("UART logging initialized.", .{}); @@ -91,11 +107,12 @@ pub fn main() !void { var last = time.get_time_since_boot(); var i: u32 = 0; - while (true) : (i += 1) { + while (true) { const now = time.get_time_since_boot(); - if (now.diff(last).to_us() > 500000) { + if (now.diff(last).to_us() > 100000) { // std.log.info("what {}", .{i}); run_usb(i); + i += 1; last = now; } usb_dev.poll(); @@ -136,16 +153,38 @@ pub fn usb_cdc_read( return usb_rx_buff[0..total_read]; } +fn intToHexChar(i: u4) u8 { + // Ensure the input is within the 0-15 range + std.debug.assert(i <= 15); + + const base: u8 = if (i < 10) '0' else 'A'; + const offset: u8 = if (i < 10) 0 else 10; + return @intCast(@as(u8, @intCast(base)) + @as(u8, @intCast(i)) - offset); +} + pub fn run_usb(i: u32) void { if (usb_dev.controller.drivers()) |drivers| { // std.log.info("USB CDC Demo transmitting", .{}); - usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}, {}\r\n", .{ "Hello, World!", i }); + // var buf: [512]u8 = undefined; + // for (buf[0..511], 0..) |*b, idx| { + // const char = @as(u8, @intCast(idx % 26)) + 'A'; + // b.* = char; + // } + // buf[0] = intToHexChar(@as(u4, @intCast(i % 16))); + // buf[1] = ' '; + // buf[30] = '\r'; + // buf[31] = '\n'; + // usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}, {}\r\n", .{ "Hello, World!", i }); + // usb_cdc_write(&drivers.serial, "{s}", .{buf[0..32]}); + const freqs = hal.clocks.get_freqs(); + usb_cdc_write(&drivers.serial, "what {}: sysclk {}, hclk {}, pclk1 {}, pclk2 {}\r\n", .{ i, freqs.sysclk, freqs.hclk, freqs.pclk1, freqs.pclk2 }); // read and print host command if present const message = usb_cdc_read(&drivers.serial); if (message.len > 0) { usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); } + _ = drivers.*.serial.flush(); } // usb_dev.poll(); } diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index fe6566640..077e2d934 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -431,7 +431,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_out(self: *Self, ep: u4) void { const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; - std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); + // std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { const st_out = self.st(.ep0, .Out); @@ -446,13 +446,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Only read if previously armed (ep_listen) if (!st_out.rx_armed) { - set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + // set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); return; } // Disarm immediately (NAK) to regain ownership. st_out.rx_armed = false; - set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + // set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + uep_tx_ctrl(ep).modify(.{ .RES = RES_NAK }); const n = @min(@as(usize, len), st_out.buf.len); st_out.rx_last_len = @as(u16, @intCast(n)); @@ -466,28 +467,16 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(num, .In); if (!st_in.tx_busy) { - // std.log.warn("ch32: EP{} IN but not busy, no data to send", .{ep}); uep_tx_ctrl(ep).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0 }); return; } // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. - // const sent_len = st_in.tx_last_len; st_in.tx_busy = false; st_in.tx_last_len = 0; - // std.log.info("ch32: EP{} IN completed, sent {} bytes, TOG={}", .{ ep, sent_len, uep_tx_ctrl(ep).read().TOG }); - // var buf: [256]u8 = undefined; - // const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); - // std.log.debug("ch32: EP{} IN data: {any}", .{ ep, st_in.buf[0..sent_len] }); // Notify controller/drivers of IN completion. - self.call_on_buffer(.In, ep); - - // EP0 OUT must remain ACK - if (self.controller.tx_slice == null) { - if (ep == 0) self.arm_ep0_out_always(); - } } // ---- VTable functions ------------------------------------------------ @@ -517,7 +506,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { std.log.warn("ch32: EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { - // this should probably be fixed at 64 bytes for EP0? + // this should probably be fixed at 64 bytes for EP0 for HS? const buf = self.endpoint_alloc(64); out_st.buf = buf; in_st.buf = buf; @@ -545,7 +534,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (e.dir == .Out) { uep_rx_dma(ep_i).raw = ptr_val; uep_max_len(ep_i).raw = mps; - set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + set_rx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); } else { uep_tx_dma(ep_i).raw = ptr_val; uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0, .AUTO = true }); @@ -558,7 +547,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Enable endpoint direction in UEP_CONFIG bitmaps. var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; if (e.num != .ep0) { - // if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); } regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; @@ -566,7 +554,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Endpoint type ISO marking (optional, only if you use ISO). if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { var type_raw: u32 = regs().UEP_TYPE.raw; - // if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); regs().UEP_TYPE.raw = type_raw; } @@ -587,7 +574,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (ep_num == .ep0) { const st0 = self.st(.ep0, .Out); st0.rx_limit = @as(u16, @intCast(len)); - set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); + // set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); return; } @@ -596,7 +583,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { @panic("ep_listen called for invalid endpoint"); const st_out = self.st(ep_num, .Out); - // std.log.debug("ep_listen: st_out buf len={}, st_out.rx_armed={}", .{ st_out.buf.len, st_out.rx_armed }); if (st_out.buf.len == 0) @panic("ep_listen called for endpoint with no buffer allocated"); @@ -646,12 +632,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { - std.log.info("ch32: ep_writev called for {}", .{ep_num}); - // for (vec) |chunk| { - // std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); - // std.log.debug("ch32: chunk: {any}", .{chunk}); - // } - const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -674,23 +654,14 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { @memcpy(st_in.buf[w .. w + n], chunk[0..n]); w += n; } - // std.log.debug("ch32: Writing chunk of len={}", .{w}); - - asm volatile (""); uep_t_len(ep_i).raw = @as(u16, @intCast(w)); st_in.tx_last_len = @as(u16, @intCast(w)); st_in.tx_busy = true; - // Arm IN - // uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); - if (ep_i == 0) { - // EP0 IN starts with DATA1 after SETUP - uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); - } else { - uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); - } + + uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); diff --git a/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig b/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig index ff7d27d4a..3aad5ef43 100644 --- a/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig +++ b/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig @@ -6,14 +6,15 @@ const ch32v = microzig.hal; /// Clock configuration for this board pub const clock_config: ch32v.clocks.Config = .{ - .source = .hsi, - .target_frequency = 48_000_000, + .source = .hse, + .hse_frequency = 8_000_000, + .target_frequency = 144_000_000, }; /// CPU frequency is derived from clock config pub const cpu_frequency = clock_config.target_frequency; -/// Board-specific init: set 48 MHz clock, enable SysTick time +/// Board-specific init: set 144 MHz clock, enable SysTick time pub fn init() void { ch32v.clocks.init(clock_config); ch32v.time.init(); diff --git a/port/wch/ch32v/src/hals/ch32v30x.zig b/port/wch/ch32v/src/hals/ch32v30x.zig index 74587548c..af730614b 100644 --- a/port/wch/ch32v/src/hals/ch32v30x.zig +++ b/port/wch/ch32v/src/hals/ch32v30x.zig @@ -24,7 +24,7 @@ pub fn init() void { time.init(); clocks.init(.{ .hse_frequency = 8_000_000, - .target_frequency = 48_000_000, + .target_frequency = 144_000_000, .source = .hse, }); // 8 MHz external crystal clocks.enable_usbhs_clock(.{ diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index 6a9158817..269bd9788 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -86,6 +86,73 @@ pub fn enable_afio_clock() void { RCC.APB2PCENR.modify(.{ .AFIOEN = 1 }); } +const PLL2MUL = enum(u4) { + PLL2_MUL_2_5 = 0, + PLL2_MUL_12_5 = 0b0001, + PLL2_MUL_4 = 0b0010, + PLL2_MUL_5 = 0b0011, + PLL2_MUL_6 = 0b0100, + PLL2_MUL_7 = 0b0101, + PLL2_MUL_8 = 0b0110, + PLL2_MUL_9 = 0b0111, + PLL2_MUL_10 = 0b1000, + PLL2_MUL_11 = 0b1001, + PLL2_MUL_12 = 0b1010, + PLL2_MUL_13 = 0b1011, + PLL2_MUL_14 = 0b1100, + PLL2_MUL_15 = 0b1101, + PLL2_MUL_16 = 0b1110, + PLL2_MUL_20 = 0b1111, +}; + +fn prediv(div: u4) u4 { + return div - 1; +} + +const PREDIV = enum(u4) { + DIV1 = 0b0000, + DIV2 = 0b0001, + DIV3 = 0b0010, + DIV4 = 0b0011, + DIV5 = 0b0100, + DIV6 = 0b0101, + DIV7 = 0b0110, + DIV8 = 0b0111, + DIV9 = 0b1000, + DIV10 = 0b1001, + DIV11 = 0b1010, + DIV12 = 0b1011, + DIV13 = 0b1100, + DIV14 = 0b1101, + DIV15 = 0b1110, + DIV16 = 0b1111, +}; + +const PLL = enum { + PLL1, // + PLL2, + PLL3, +}; + +/// Start a PLL and wait for it to stabilize +/// +pub fn start_pll(pll: PLL) void { + switch (pll) { + .PLL1 => { + RCC.CTLR.modify(.{ .PLLON = 1 }); + while (RCC.CTLR.read().PLLRDY == 0) {} + }, + .PLL2 => { + RCC.CTLR.modify(.{ .PLL2ON = 1 }); + while (RCC.CTLR.read().PLL2RDY == 0) {} + }, + .PLL3 => { + RCC.CTLR.modify(.{ .PLL3ON = 1 }); + while (RCC.CTLR.read().PLL3RDY == 0) {} + }, + } +} + // ============================================================================ // Clock Configuration System // ============================================================================ @@ -232,7 +299,7 @@ fn init_from_hsi(target_freq: u32) void { /// Initialize clocks from HSE to reach target frequency /// Assumes hse_freq and target_freq have been validated at compile time -fn init_from_hse(hse_freq: u32, target_freq: u32) void { +fn init_from_hse(hse_freq: u32, comptime target_freq: u32) void { // Enable HSE RCC.CTLR.modify(.{ .HSEON = 1 }); @@ -267,15 +334,48 @@ fn init_from_hse(hse_freq: u32, target_freq: u32) void { RCC.CFGR0.modify(.{ .SW = 2 }); while (RCC.CFGR0.read().SWS != 2) {} } else if (target_freq == 144_000_000) { + // TODO: Handle the chip specific clock config for PLLMUL, this is for the v307 RCC.CFGR0.modify(.{ .HPRE = 0, // AHB prescaler = 1 .PPRE2 = 0, // APB2 prescaler = 1 - .PPRE1 = 4, // APB1 prescaler = 2 + .PPRE1 = 0b101, // APB1 prescaler = 2 .PLLSRC = 1, // PLL source = HSE .PLLXTPRE = 0, // HSE not divided before PLL - .PLLMUL = 15, // PLL multiplier = 18 + .PLLMUL = 0, // PLL multiplier = 18 }); + // // // coefficients, not reg vals + // const prediv2: u32 = 2; + // const pll2mul: u32 = 9; + // const prediv1: u32 = 1; + // const pllmul: u32 = 8; + + // const sysclk = ((8_000_000 / prediv2) * pll2mul / prediv1) * pllmul; + + // if (sysclk != target_freq) { + // // const std = @import("std"); + // // @compileError(std.fmt.comptimePrint("Warning: Target frequency {} Hz does not match calculated system clock of {} Hz\n", .{ target_freq, sysclk })); + // // _ = sysclk; + // } + + // RCC.CFGR0.modify(.{ + // .HPRE = 1, // AHB prescaler = 1 + // .PPRE2 = 0, // APB2 prescaler = 1 + // .PPRE1 = 0b101, // APB1 prescaler = 2 + // .PLLSRC = 1, // PLL source = HSE + // .PLLMUL = 0b0000, + // }); + + // RCC.CFGR2.modify(.{ + // .PLL2MUL = @intFromEnum(@as(PLL2MUL, .PLL2_MUL_9)), // PLL2MUL = 9 + // .PREDIV2 = prediv(prediv2), // PREDIV2 = 4 + // .PREDIV1 = @intFromEnum(@as(PREDIV, .DIV1)), // HSE not divided before PLL + // }); + // RCC.CTLR.modify(.{ .PLL2ON = 1 }); + // while (RCC.CTLR.read().PLL2RDY == 0) {} + + // RCC.CFGR2.modify(.{ .PREDIV1SRC = 1 }); + // Enable PLL RCC.CTLR.modify(.{ .PLLON = 1 }); while (RCC.CTLR.read().PLLRDY == 0) {} @@ -354,7 +454,9 @@ pub fn get_freqs() ClockSpeeds { // PLL multiplication factor: PLLMUL bits + 2 // Special case: if result is 17, it's actually 18 var pllmul: u32 = @as(u32, pllmul_bits) + 2; - if (pllmul == 17) pllmul = 18; + // TODO: Handle chip specific clock config + // if (pllmul == 17) pllmul = 18; + if (@as(u32, pllmul_bits) == 0) pllmul = 18; if (pllsrc == 0) { // PLL source is HSI @@ -418,7 +520,7 @@ pub fn get_freqs() ClockSpeeds { /// selects whether the USBHS 48MHz clock comes from the system PLL clock or the USB PHY, /// and enables the AHB clock gate for USBHS. /// -/// Note: The SVD you're using names bit31 as `USBFSSRC`, but the reference manual +/// Note: The SVD names bit31 as `USBFSSRC`, but the reference manual /// describes it as `USBHSSRC` ("USBHS 48MHz clock source selection"). /// We write the SVD field, but treat it as USBHS 48MHz source select. pub const UsbHsClockConfig = struct { @@ -471,7 +573,7 @@ const HSPLLSRC = enum(u2) { /// Enable + configure USBHS clocks. /// -/// This does *not* reconfigure the system PLL to 48MHz; it only selects between: +/// Selects USBHS Clock source, options are: /// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or /// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true and cfg.has_hs_phy = true), /// in which case it also configures the PHY PLL reference (USBHSCLK/USBHSPLLSRC/USBHSDIV) @@ -565,18 +667,6 @@ pub fn enable_usbhs_clock(comptime cfg: UsbHsClockConfig) void { .USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY }); - // RCC.CFGR2.raw = (1 << 31 | 1 << 24 | 1 << 28 | 1 << 30); - - // RCC.CFGR2.modify(.{ - // .USBHSPLLSRC = switch (cfg.ref_source) { - // .hse => 0, - // .hsi => 1, - // }, - // .USBHSDIV = 1, - // .USBHSCLK = 1, - // .USBHSPLL = 1, - // .USBFSSRC = 1, // RM: USBHS 48MHz clock source = USB PHY - // }); RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); } diff --git a/port/wch/ch32v/src/hals/usart.zig b/port/wch/ch32v/src/hals/usart.zig index f71655363..3a37b7aae 100644 --- a/port/wch/ch32v/src/hals/usart.zig +++ b/port/wch/ch32v/src/hals/usart.zig @@ -245,6 +245,40 @@ pub const USART = enum(u2) { regs.BRR.write_raw(@intCast(brr_value)); } + pub fn calc_usartdiv(baud_rate: u32, pclk: u32) u32 { + // Calculate baud rate divisor for 16x oversampling + const oversample = 16; + // Formula: BRR = (25 * PCLK) / (4 * baud_rate) + const integerdivider = (25 * pclk) / (4 * baud_rate); + + const mantissa = integerdivider / 100; + const fraction_part = integerdivider - (100 * mantissa); + + const fraction = ((fraction_part * oversample + 50) / 100) & 0x0F; + const brr_value = (mantissa << 4) | fraction; + return brr_value; + } + pub fn validate_baudrate(self: @This(), comptime baud_rate: u32, comptime pclk: comptime_float, comptime tol: comptime_float) bool { + _ = self; + // const oversample: comptime_float = 16; + const baud_rate_f: comptime_float = @floatFromInt(baud_rate); + const brr = calc_usartdiv(baud_rate, pclk); + const div: comptime_float = @as(comptime_float, @floatFromInt(brr)); + if (div == 0) { + @compileError("how it zero?"); + } + const baud_rate_actual = pclk / div; + + const err = 100.0 * ((baud_rate_actual - baud_rate_f) / baud_rate_f); + if (@abs(err) >= tol) { + @compileLog(std.fmt.comptimePrint("pclk = {}, baud_rate = {}, brr = {}, div = {}, err = {}", .{ pclk, baud_rate, brr, div, err })); + @compileLog(std.fmt.comptimePrint("Baud rate error too high for the given system clock", .{})); + @compileLog(std.fmt.comptimePrint("Desired: {}", .{baud_rate})); + @compileLog(std.fmt.comptimePrint("Actual: {}", .{baud_rate_actual})); + @compileLog(std.fmt.comptimePrint("Error: {}", .{err})); + } + return @abs(err) < tol; + } /// Check if transmit data register is empty (can write) pub inline fn is_writeable(usart: USART) bool { return usart.get_regs().STATR.read().TXE == 1; From baed7994e9c62dcfc90485070404bfaf11a1997c Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 25 Jan 2026 00:10:46 -0500 Subject: [PATCH 65/80] Refactor USBHS interrupt handling and switch toggle management to manual mode. CDC Works without extra packets! --- examples/wch/ch32v/src/usb_cdc.zig | 23 ++++++-- examples/wch/ch32v/src/usbhs.zig | 85 ++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 21 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 43a6ee04c..c1ed2a387 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -17,7 +17,7 @@ const mco_pin = gpio.Pin.init(0, 8); // PA9 pub const my_interrupts: microzig.cpu.InterruptOptions = .{ // .TIM2 = time.tim2_handler, - .USBHS = usb.usbhs_interrupt_handler, + .USBHS = usbhs_interrupt_handler, }; pub const microzig_options = microzig.Options{ @@ -26,6 +26,11 @@ pub const microzig_options = microzig.Options{ .interrupts = my_interrupts, }; +pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { + usb_dev.poll(); + // std.log.debug("usb isr called", .{}); +} + pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 512 }); pub var usb_dev: usb.Polled( @@ -103,7 +108,7 @@ pub fn main() !void { std.log.info("Initializing USB device.", .{}); usb_dev = .init(); - // microzig.cpu.interrupt.enable(.USBHS); + microzig.cpu.interrupt.enable(.USBHS); var last = time.get_time_since_boot(); var i: u32 = 0; @@ -115,7 +120,9 @@ pub fn main() !void { i += 1; last = now; } + microzig.cpu.interrupt.disable(.USBHS); usb_dev.poll(); + microzig.cpu.interrupt.enable(.USBHS); } } @@ -125,11 +132,14 @@ var usb_tx_buff: [1024]u8 = undefined; // NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype) void { const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; + std.log.debug("usb_cdc_write len={}", .{text.len}); var write_buff = text; while (write_buff.len > 0) { write_buff = write_buff[serial.write(write_buff)..]; - usb_dev.poll(); + // microzig.cpu.interrupt.disable(.USBHS); + // usb_dev.poll(); + // microzig.cpu.interrupt.enable(.USBHS); } } @@ -184,7 +194,12 @@ pub fn run_usb(i: u32) void { if (message.len > 0) { usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); } - _ = drivers.*.serial.flush(); + const flushed = drivers.*.serial.flush(); + if (!flushed) { + std.log.debug("cdc flush blocked (ep_in busy)", .{}); + } } + // microzig.cpu.interrupt.disable(.USBHS); // usb_dev.poll(); + // microzig.cpu.interrupt.enable(.USBHS); } diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index 077e2d934..93cc398ec 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -117,11 +117,21 @@ const Tog = enum(u2) { MDATA = 0b11, }; +fn toggle_next(tog: u2) u2 { + return if (tog == TOG_DATA0) TOG_DATA1 else TOG_DATA0; +} + // We use offset-based access for per-EP regs to keep helpers compact. const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); -pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { _reserved2: u2 = 0, AUTO: bool = false, TOG: u2 = 0, _reserved0: u1 = 0, RES: u2 = 0 }); +pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { + RES: u2 = 0, + _reserved0: u1 = 0, + TOG: u2 = 0, + AUTO: bool = false, + _reserved1: u2 = 0, +}); fn baseAddr() usize { return @intFromPtr(peripherals.USBHS); @@ -170,7 +180,7 @@ pub fn uep_tx_ctrl(ep: u4) *volatile RegCtrl { } fn uep_rx_ctrl(ep: u4) *volatile RegCtrl { - return mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); + return mmio_rx_ctrl(0xDB + (@as(usize, ep) * 4)); } fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { @@ -192,6 +202,14 @@ fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { }); } +fn current_rx_tog(ep: u4) u2 { + return uep_rx_ctrl(ep).read().TOG; +} + +fn current_tx_tog(ep: u4) u2 { + return uep_tx_ctrl(ep).read().TOG; +} + fn fmt_slice(dest: []u8, msg: []const u8) []const u8 { return std.fmt.bufPrint(dest, "{x}", .{msg}) catch &.{}; } @@ -280,10 +298,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn arm_ep0_out_always(self: *Self) void { _ = self; // std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); - // EP0 OUT is always ACK with auto-toggle. - set_rx_ctrl(0, RES_ACK, TOG_DATA1, true); + // EP0 OUT is always ACK. + set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + set_tx_ctrl(0, RES_NAK, TOG_DATA0, false); } fn on_bus_reset_local(self: *Self) void { @@ -298,8 +316,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Default: NAK all non-EP0 endpoints. for (1..cfg.max_endpoints_count) |i| { const e: u4 = @as(u4, @intCast(i)); - set_rx_ctrl(e, RES_NAK, TOG_DATA0, true); - set_tx_ctrl(e, RES_NAK, TOG_DATA0, true); + set_rx_ctrl(e, RES_NAK, TOG_DATA0, false); + set_tx_ctrl(e, RES_NAK, TOG_DATA0, false); } // EP0 special. @@ -377,8 +395,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const setup: types.SetupPacket = self.read_setup_from_ep0(); // After SETUP, EP0 IN data stage starts with DATA1. - uep_tx_ctrl(0).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA1, .AUTO = true }); - set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); self.controller.on_setup_req(&self.interface, &setup); continue; @@ -422,7 +440,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (ep == 0) { const setup: types.SetupPacket = self.read_setup_from_ep0(); std.log.debug("ch32: Setup: {any}", .{setup}); - uep_tx_ctrl(0).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA1 }); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); self.controller.on_setup_req(&self.interface, &setup); } }, @@ -431,11 +450,19 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_out(self: *Self, ep: u4) void { const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; + const stv = regs().USB_INT_ST.read(); + const rx_ctrl = uep_rx_ctrl(ep).read(); + std.log.debug( + "ch32: OUT ep{} len={} tog_ok={} rx_res={} rx_tog={}", + .{ ep, len, stv.RB_UIS_TOG_OK, rx_ctrl.RES, rx_ctrl.TOG }, + ); // std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { const st_out = self.st(.ep0, .Out); st_out.rx_last_len = len; + const next = toggle_next(current_rx_tog(0)); + set_rx_ctrl(0, RES_ACK, next, false); // stay ACK self.call_on_buffer(.Out, 0); return; @@ -452,8 +479,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Disarm immediately (NAK) to regain ownership. st_out.rx_armed = false; - // set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); - uep_tx_ctrl(ep).modify(.{ .RES = RES_NAK }); + const next = toggle_next(current_rx_tog(ep)); + set_rx_ctrl(ep, RES_NAK, next, false); const n = @min(@as(usize, len), st_out.buf.len); st_out.rx_last_len = @as(u16, @intCast(n)); @@ -465,15 +492,23 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn handle_in(self: *Self, ep: u4) void { const num: types.Endpoint.Num = @enumFromInt(ep); const st_in = self.st(num, .In); + const stv = regs().USB_INT_ST.read(); + const tx_ctrl = uep_tx_ctrl(ep).read(); + std.log.debug( + "ch32: IN ep{} busy={} tog_ok={} tx_res={} tx_tog={}", + .{ ep, st_in.tx_busy, stv.RB_UIS_TOG_OK, tx_ctrl.RES, tx_ctrl.TOG }, + ); if (!st_in.tx_busy) { - uep_tx_ctrl(ep).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0 }); + set_tx_ctrl(ep, RES_NAK, current_tx_tog(ep), false); return; } // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. st_in.tx_busy = false; st_in.tx_last_len = 0; + const next = toggle_next(current_tx_tog(ep)); + set_tx_ctrl(ep, RES_NAK, next, false); // Notify controller/drivers of IN completion. self.call_on_buffer(.In, ep); @@ -534,10 +569,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (e.dir == .Out) { uep_rx_dma(ep_i).raw = ptr_val; uep_max_len(ep_i).raw = mps; - set_rx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + set_rx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); } else { uep_tx_dma(ep_i).raw = ptr_val; - uep_tx_ctrl(ep_i).modify(.{ .RES = RES_NAK, .TOG = TOG_DATA0, .AUTO = true }); + set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); } } std.log.debug("allocation and DMA setup done for ep{}", .{ep_i}); @@ -598,7 +633,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { uep_max_len(ep_i).raw = @as(u16, @intCast(limit)); asm volatile (""); - set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + set_rx_ctrl(ep_i, RES_ACK, current_rx_tog(ep_i), false); std.log.debug("finished ep_listen for ep{}", .{ep_num}); } @@ -661,7 +696,23 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = true; // Arm IN - uep_tx_ctrl(ep_i).modify(.{ .RES = RES_ACK, .TOG = TOG_DATA1 }); + // if (ep_num != .ep0) { + const tx_before = uep_tx_ctrl(ep_i).read(); + std.log.debug( + "ch32: ep_writev ep{} len={} tx_res={} tx_tog={}", + .{ ep_i, w, tx_before.RES, tx_before.TOG }, + ); + // } + + set_tx_ctrl(ep_i, RES_ACK, current_tx_tog(ep_i), false); + + // if (ep_num != .ep0) { + const tx_after = uep_tx_ctrl(ep_i).read(); + std.log.debug( + "ch32: ep_writev ep{} armed tx_res={} tx_tog={}", + .{ ep_i, tx_after.RES, tx_after.TOG }, + ); + // } // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); From df4e1815acaaf0a6048ac2f0a944ee1861c3bc2c Mon Sep 17 00:00:00 2001 From: Bob Date: Sun, 25 Jan 2026 00:34:46 -0500 Subject: [PATCH 66/80] Refactor USB polling logic into a separate function and clean up unused code in usb_cdc.zig --- examples/wch/ch32v/src/usb_cdc.zig | 38 +++++++++++------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index c1ed2a387..9ea4a82bd 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -31,6 +31,13 @@ pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) // std.log.debug("usb isr called", .{}); } +fn usb_poll() void { + // Polled backend: keep this as a safety net for missed IRQ edges. + microzig.cpu.interrupt.disable(.USBHS); + usb_dev.poll(); + microzig.cpu.interrupt.enable(.USBHS); +} + pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 512 }); pub var usb_dev: usb.Polled( @@ -120,9 +127,7 @@ pub fn main() !void { i += 1; last = now; } - microzig.cpu.interrupt.disable(.USBHS); - usb_dev.poll(); - microzig.cpu.interrupt.enable(.USBHS); + usb_poll(); } } @@ -137,9 +142,7 @@ pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype var write_buff = text; while (write_buff.len > 0) { write_buff = write_buff[serial.write(write_buff)..]; - // microzig.cpu.interrupt.disable(.USBHS); - // usb_dev.poll(); - // microzig.cpu.interrupt.enable(.USBHS); + usb_poll(); } } @@ -175,17 +178,7 @@ fn intToHexChar(i: u4) u8 { pub fn run_usb(i: u32) void { if (usb_dev.controller.drivers()) |drivers| { // std.log.info("USB CDC Demo transmitting", .{}); - // var buf: [512]u8 = undefined; - // for (buf[0..511], 0..) |*b, idx| { - // const char = @as(u8, @intCast(idx % 26)) + 'A'; - // b.* = char; - // } - // buf[0] = intToHexChar(@as(u4, @intCast(i % 16))); - // buf[1] = ' '; - // buf[30] = '\r'; - // buf[31] = '\n'; - // usb_cdc_write(&drivers.serial, "This is very very long text sent from ch32 by USB CDC to your device: {s}, {}\r\n", .{ "Hello, World!", i }); - // usb_cdc_write(&drivers.serial, "{s}", .{buf[0..32]}); + const freqs = hal.clocks.get_freqs(); usb_cdc_write(&drivers.serial, "what {}: sysclk {}, hclk {}, pclk1 {}, pclk2 {}\r\n", .{ i, freqs.sysclk, freqs.hclk, freqs.pclk1, freqs.pclk2 }); @@ -194,12 +187,9 @@ pub fn run_usb(i: u32) void { if (message.len > 0) { usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); } - const flushed = drivers.*.serial.flush(); - if (!flushed) { - std.log.debug("cdc flush blocked (ep_in busy)", .{}); - } + // const flushed = drivers.*.serial.flush(); + // if (!flushed) { + // std.log.debug("cdc flush blocked (ep_in busy)", .{}); + // } } - // microzig.cpu.interrupt.disable(.USBHS); - // usb_dev.poll(); - // microzig.cpu.interrupt.enable(.USBHS); } From ad686c3c4bda10d20ab103412891fc82ee866131 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 13:53:50 -0500 Subject: [PATCH 67/80] update for latest usb core changes --- core/src/core/usb.zig | 2 +- core/src/core/usb/drivers/hid.zig | 5 - examples/wch/ch32v/src/usb_cdc.zig | 89 ++++++-------- examples/wch/ch32v/src/usbhs.zig | 187 ++++++++++------------------- 4 files changed, 99 insertions(+), 184 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index e1cd56409..ef5a559ee 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -274,7 +274,7 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type { const desc_device: descriptor.Device = .{ .bcd_usb = config.bcd_usb, .device_triple = config.device_triple, - .max_packet_size0 = @max(config.max_supported_packet_size, 64), + .max_packet_size0 = @min(config.max_supported_packet_size, 64), .vendor = .from(config.vendor.id), .product = .from(config.product.id), .bcd_device = config.bcd_device, diff --git a/core/src/core/usb/drivers/hid.zig b/core/src/core/usb/drivers/hid.zig index 8a557a3e0..c9548e9f4 100644 --- a/core/src/core/usb/drivers/hid.zig +++ b/core/src/core/usb/drivers/hid.zig @@ -374,11 +374,6 @@ pub fn InterruptDriver(options: InterruptDriverOptions) type { .ep_out = on_rx, }; - pub const handlers: usb.DriverHadlers(@This()) = .{ - .ep_out = on_rx, - .ep_in = on_tx_ready, - }; - device: *usb.DeviceInterface, descriptor: *const Descriptor, tx_ready: std.atomic.Value(bool), diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index 9ea4a82bd..dd69ec611 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -7,8 +7,11 @@ const gpio = hal.gpio; pub const usb = @import("usbhs.zig"); +const USB_Serial = microzig.core.usb.drivers.CDC; + const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; +const PFIC = microzig.chip.peripherals.PFIC; const usart = hal.usart.instance.USART1; const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 @@ -27,56 +30,39 @@ pub const microzig_options = microzig.Options{ }; pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { - usb_dev.poll(); + // usb_dev.poll(true); // std.log.debug("usb isr called", .{}); } fn usb_poll() void { - // Polled backend: keep this as a safety net for missed IRQ edges. - microzig.cpu.interrupt.disable(.USBHS); - usb_dev.poll(); - microzig.cpu.interrupt.enable(.USBHS); + // microzig.cpu.interrupt.disable(.USBHS); + usb_dev.poll(false, &usb_controller); + // microzig.cpu.interrupt.enable(.USBHS); } -pub const UsbSerial = microzig.core.usb.drivers.cdc.CdcClassDriver(.{ .max_packet_size = 512 }); +const USBController = microzig.core.usb.DeviceController(.{ + .bcd_usb = .v2_00, + .device_triple = .unspecified, + .vendor = .{ .id = 0x2E8A, .str = "MicroZig" }, + .product = .{ .id = 0x000A, .str = "ch32v307 Test Device" }, + .bcd_device = .v1_00, + .serial = "someserial", + .max_supported_packet_size = 512, + .configurations = &.{.{ + .attributes = .{ .self_powered = false }, + .max_current_ma = 50, + .Drivers = struct { serial: USB_Serial }, + }}, +}, .{.{ + .serial = .{ .itf_notifi = "Board CDC", .itf_data = "Board CDC Data" }, +}}); pub var usb_dev: usb.Polled( - microzig.core.usb.Config{ - .device_descriptor = .{ - .bcd_usb = .from(0x0200), - .device_triple = .{ - .class = .Miscellaneous, - .subclass = 2, - .protocol = 1, - }, - .max_packet_size0 = 64, - .vendor = .from(0x2E8A), - .product = .from(0x000A), - .bcd_device = .from(0x0100), - .manufacturer_s = 1, - .product_s = 2, - .serial_s = 3, - .num_configurations = 1, - }, - .string_descriptors = &.{ - .from_lang(.English), - .from_str("MicroZig"), - .from_str("ch32v307 Test Device"), - .from_str("someserial"), - .from_str("Board CDC"), - }, - .configurations = &.{.{ - .num = 1, - .configuration_s = 0, - .attributes = .{ .self_powered = true }, - .max_current_ma = 100, - .Drivers = struct { serial: UsbSerial }, - }}, - .max_supported_packet_size = 512, - }, .{ .prefer_high_speed = true }, ) = undefined; +var usb_controller: USBController = .init; + pub fn main() !void { // Board brings up clocks and time microzig.board.init(); @@ -115,14 +101,14 @@ pub fn main() !void { std.log.info("Initializing USB device.", .{}); usb_dev = .init(); - microzig.cpu.interrupt.enable(.USBHS); - + // microzig.cpu.interrupt.enable(.USBHS); + PFIC.IPRIOR70 = 255; var last = time.get_time_since_boot(); var i: u32 = 0; + while (true) { const now = time.get_time_since_boot(); - if (now.diff(last).to_us() > 100000) { - // std.log.info("what {}", .{i}); + if (now.diff(last).to_us() > 100_000) { run_usb(i); i += 1; last = now; @@ -135,10 +121,8 @@ var usb_tx_buff: [1024]u8 = undefined; // Transfer data to host // NOTE: After each USB chunk transfer, we have to call the USB task so that bus TX events can be handled -pub fn usb_cdc_write(serial: *UsbSerial, comptime fmt: []const u8, args: anytype) void { +pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytype) void { const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; - std.log.debug("usb_cdc_write len={}", .{text.len}); - var write_buff = text; while (write_buff.len > 0) { write_buff = write_buff[serial.write(write_buff)..]; @@ -151,7 +135,7 @@ var usb_rx_buff: [1024]u8 = undefined; // Receive data from host // NOTE: Read code was not tested extensively. In case of issues, try to call USB task before every read operation pub fn usb_cdc_read( - serial: *UsbSerial, + serial: *USB_Serial, ) []const u8 { var total_read: usize = 0; var read_buff: []u8 = usb_rx_buff[0..]; @@ -176,20 +160,15 @@ fn intToHexChar(i: u4) u8 { } pub fn run_usb(i: u32) void { - if (usb_dev.controller.drivers()) |drivers| { - // std.log.info("USB CDC Demo transmitting", .{}); - + // _ = i; + if (usb_controller.drivers()) |drivers| { const freqs = hal.clocks.get_freqs(); - usb_cdc_write(&drivers.serial, "what {}: sysclk {}, hclk {}, pclk1 {}, pclk2 {}\r\n", .{ i, freqs.sysclk, freqs.hclk, freqs.pclk1, freqs.pclk2 }); + usb_cdc_write(&drivers.serial, "what {}: sysclk {}, hclk {}, pclk1 {}, PFIC.IPRIOR70 {}\r\n", .{ i, freqs.sysclk, freqs.hclk, freqs.pclk1, PFIC.IPRIOR70 }); // read and print host command if present const message = usb_cdc_read(&drivers.serial); if (message.len > 0) { usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); } - // const flushed = drivers.*.serial.flush(); - // if (!flushed) { - // std.log.debug("cdc flush blocked (ep_in busy)", .{}); - // } } } diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index 93cc398ec..16321d985 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -1,16 +1,9 @@ -//! WCH USBHD/USBHS device backend (polled) +//! WCH USBHD/USBHS device backend //! -//! Key semantics (per DeviceController in usb.zig): -//! - Must call controller.on_buffer() on IN completion (especially EP0 IN). -//! - Must call controller.on_buffer() on OUT receipt; driver will call ep_readv() exactly once. -//! - EP0 OUT must always accept status-stage OUT ZLP (controller does not always re-arm EP0 OUT). -//! -//! Ownership safety: -//! - OUT buffer is only read after UIF_TRANSFER OUT event; endpoint set to NAK immediately after. -//! - IN buffer is only written when not tx_busy; endpoint set to NAK immediately after IN complete. const std = @import("std"); const assert = std.debug.assert; +const log = std.log.scoped(.ch32_usbhs); const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; @@ -18,31 +11,24 @@ const usb = microzig.core.usb; const types = usb.types; const descriptor = usb.descriptor; -pub const USBHD_MAX_ENDPOINTS_COUNT = 6; +pub const USBHD_MAX_ENDPOINTS_COUNT = 16; const max_buffer_pool_size = USBHD_MAX_ENDPOINTS_COUNT * 2 * 512 + 64; -var pool: [max_buffer_pool_size]u8 = undefined; - pub const Config = struct { max_endpoints_count: comptime_int = USBHD_MAX_ENDPOINTS_COUNT, - /// Some chips have USBHD regs but no HS PHY. Force FS in that case. - has_hs_phy: bool = true, prefer_high_speed: bool = true, /// Static buffer pool in SRAM, bump-allocated. buffer_bytes: comptime_int = max_buffer_pool_size, - /// Enable SIE "int busy" behavior: pauses during UIF_TRANSFER so polling won't lose INT_ST context. - int_busy: bool = true, - /// Future seam only; not implemented in this initial version. use_interrupts: bool = false, }; const Regs = @TypeOf(peripherals.USBHS); -fn regs() Regs { +pub fn regs() Regs { return peripherals.USBHS; } @@ -64,14 +50,9 @@ fn PerEndpointArray(comptime N: comptime_int) type { fn epn(ep: types.Endpoint.Num) u4 { return @as(u4, @intCast(@intFromEnum(ep))); } -fn dir_index(d: types.Dir) u1 { - return @as(u1, @intCast(@intFromEnum(d))); -} - fn speed_type(comptime cfg: Config) u2 { - if (!cfg.has_hs_phy) return 0; // FS if (!cfg.prefer_high_speed) return 0; // FS - return 1; // HS (per USBHS.zig field comment) + return 1; } // --- USBHD token encodings --- @@ -142,9 +123,6 @@ fn mmio_u32(off: usize) *volatile RegU32 { fn mmio_u16(off: usize) *volatile RegU16 { return @ptrFromInt(baseAddr() + off); } -fn mmio_u8(off: usize) *volatile RegU8 { - return @ptrFromInt(baseAddr() + off); -} fn mmio_tx_ctrl(off: usize) *volatile RegCtrl { return @ptrFromInt(baseAddr() + off); } @@ -164,7 +142,6 @@ fn uep_rx_dma(ep: u4) *volatile RegU32 { fn uep_tx_dma(ep: u4) *volatile RegU32 { return mmio_u32(0x5C + (@as(usize, ep - 1) * 4)); } - // MAX_LEN: EP0..EP15 at 0x98 + ep*4 fn uep_max_len(ep: u4) *volatile RegU16 { return mmio_u16(0x98 + (@as(usize, ep) * 4)); @@ -205,17 +182,13 @@ fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { fn current_rx_tog(ep: u4) u2 { return uep_rx_ctrl(ep).read().TOG; } - fn current_tx_tog(ep: u4) u2 { return uep_tx_ctrl(ep).read().TOG; } -fn fmt_slice(dest: []u8, msg: []const u8) []const u8 { - return std.fmt.bufPrint(dest, "{x}", .{msg}) catch &.{}; -} - /// Polled USBHD device backend for microzig core USB controller. -pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { +/// maybe we should pass a list of valid controller types, for validation +pub fn Polled(comptime cfg: Config) type { comptime { if (cfg.max_endpoints_count > USBHD_MAX_ENDPOINTS_COUNT) @compileError("USBHD max_endpoints_count cannot exceed 16"); @@ -235,23 +208,23 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { }; endpoints: PerEndpointArray(cfg.max_endpoints_count), + pool: [cfg.buffer_bytes]u8 = undefined, + buffer_pool: []align(4) u8, - controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, pub fn init() Self { var self: Self = .{ .endpoints = undefined, .buffer_pool = undefined, - .controller = .init, .interface = .{ .vtable = &vtable }, }; @memset(std.mem.asBytes(&self.endpoints), 0); - @memset(pool[0..64], 0x7e); - @memset(pool[64..], 0); + @memset(self.pool[0..64], 0x7e); + @memset(self.pool[64..], 0); - self.buffer_pool = @alignCast(pool[64..]); + self.buffer_pool = @alignCast(self.pool[64..]); usbhd_hw_init(); @@ -283,7 +256,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { return self; } - // TODO: replace with fixedbuffer allocator + // TODO: replace with fixedbuffer allocator? fn endpoint_alloc(self: *Self, size: usize) []align(4) u8 { assert(self.buffer_pool.len >= size); const out = self.buffer_pool[0..size]; @@ -297,11 +270,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn arm_ep0_out_always(self: *Self) void { _ = self; - // std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); // EP0 OUT is always ACK. set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA0, false); + // set_tx_ctrl(0, RES_NAK, TOG_DATA0, false); } fn on_bus_reset_local(self: *Self) void { @@ -333,19 +305,19 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- comptime dispatch helpers (required by DeviceController API) ---- - fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4) void { + fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, controller: anytype) void { // controller.on_buffer requires comptime ep parameter switch (dir) { .In => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @enumFromInt(i); - self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); }, }, .Out => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @enumFromInt(i); - self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); }, }, } @@ -353,28 +325,34 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- - pub fn poll(self: *Self) void { - var did_work = false; + pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; + if ((fg & UIF_HST_SOF) != 0) { + // acknowledge SOF but ignore + regs().USB_INT_FG.raw = UIF_HST_SOF; + } + if ((fg & UIF_SUSPEND) != 0) { + // acknowledge SUSPEND but ignore + regs().USB_INT_FG.raw = UIF_SUSPEND; + } const stv = regs().USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); if (token != TOKEN_SOF) { - // std.log.debug("ch32: INT_FG = 0x{x}, token: {}", .{ fg, @as(Token, @enumFromInt(token)) }); - did_work = true; + if (false) + log.debug("in isr: {}, fg: {}, EN: {}", .{ in_isr, fg, regs().USB_INT_EN.raw }); } if (fg & UIF_FIFO_OV != 0) { - std.log.warn("ch32: FIFO overflow!", .{}); + log.warn("FIFO overflow!", .{}); regs().USB_INT_FG.raw = UIF_FIFO_OV; - continue; } if ((fg & UIF_BUS_RST) != 0) { - std.log.info("ch32: bus reset\n\n\n", .{}); + log.info("bus reset\n\n\n", .{}); // clear regs().USB_INT_FG.raw = UIF_BUS_RST; @@ -382,15 +360,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_address(&self.interface, 0); self.on_bus_reset_local(); - self.controller.on_bus_reset(&self.interface); - continue; + controller.on_bus_reset(&self.interface); } if ((fg & UIF_SETUP_ACT) != 0) { - std.log.info("ch32: SETUP received", .{}); - // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. + log.info("SETUP received", .{}); regs().USB_INT_FG.raw = UIF_SETUP_ACT; - if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; const setup: types.SetupPacket = self.read_setup_from_ep0(); @@ -398,65 +373,52 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); - self.controller.on_setup_req(&self.interface, &setup); - continue; + controller.on_setup_req(&self.interface, &setup); } if ((fg & UIF_TRANSFER) != 0) { - if (token != TOKEN_SOF) { - // std.log.info("ch32: TRANSFER event", .{}); - } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; - self.handle_transfer(ep, token); - continue; + self.handle_transfer(ep, token, controller); } - // Clear anything else we don't handle explicitly - regs().USB_INT_FG.raw = fg; // 0x4 => SUSPEND // 0x5 => SUSPEND | RESET // 0x8 => HST_SOF // 0x10 => FIFO_OV // 0x40 => ISO_ACT } - if (did_work) { - // std.log.info("", .{}); - } } - fn handle_transfer(self: *Self, ep: u4, token: u2) void { + fn handle_transfer(self: *Self, ep: u4, token: u2, controller: anytype) void { if (ep >= cfg.max_endpoints_count) return; if (token == TOKEN_SOF) return; - // std.log.debug("ch32: handle_transfer ep={} token={}", .{ ep, @as(Token, @enumFromInt(token)) }); - // std.log.debug("ch32: token={}", .{token}); switch (token) { - TOKEN_OUT => self.handle_out(ep), + TOKEN_OUT => self.handle_out(ep, controller), TOKEN_SOF => {}, - TOKEN_IN => self.handle_in(ep), + TOKEN_IN => self.handle_in(ep, controller), TOKEN_SETUP => { // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. if (ep == 0) { const setup: types.SetupPacket = self.read_setup_from_ep0(); - std.log.debug("ch32: Setup: {any}", .{setup}); + log.debug("Setup: {any}", .{setup}); set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); - self.controller.on_setup_req(&self.interface, &setup); + controller.on_setup_req(&self.interface, &setup); } }, } } - fn handle_out(self: *Self, ep: u4) void { + fn handle_out(self: *Self, ep: u4, controller: anytype) void { const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; const stv = regs().USB_INT_ST.read(); const rx_ctrl = uep_rx_ctrl(ep).read(); - std.log.debug( - "ch32: OUT ep{} len={} tog_ok={} rx_res={} rx_tog={}", + log.debug( + "OUT ep{} len={} tog_ok={} rx_res={} rx_tog={}", .{ ep, len, stv.RB_UIS_TOG_OK, rx_ctrl.RES, rx_ctrl.TOG }, ); - // std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { const st_out = self.st(.ep0, .Out); @@ -464,7 +426,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const next = toggle_next(current_rx_tog(0)); set_rx_ctrl(0, RES_ACK, next, false); // stay ACK - self.call_on_buffer(.Out, 0); + self.call_on_buffer(.Out, 0, controller); return; } @@ -485,19 +447,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const n = @min(@as(usize, len), st_out.buf.len); st_out.rx_last_len = @as(u16, @intCast(n)); - self.call_on_buffer(.Out, ep); + self.call_on_buffer(.Out, ep, controller); } // IN => into host from device - fn handle_in(self: *Self, ep: u4) void { + fn handle_in(self: *Self, ep: u4, controller: anytype) void { const num: types.Endpoint.Num = @enumFromInt(ep); const st_in = self.st(num, .In); - const stv = regs().USB_INT_ST.read(); - const tx_ctrl = uep_tx_ctrl(ep).read(); - std.log.debug( - "ch32: IN ep{} busy={} tog_ok={} tx_res={} tx_tog={}", - .{ ep, st_in.tx_busy, stv.RB_UIS_TOG_OK, tx_ctrl.RES, tx_ctrl.TOG }, - ); if (!st_in.tx_busy) { set_tx_ctrl(ep, RES_NAK, current_tx_tog(ep), false); @@ -511,13 +467,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep, RES_NAK, next, false); // Notify controller/drivers of IN completion. - self.call_on_buffer(.In, ep); + self.call_on_buffer(.In, ep, controller); } // ---- VTable functions ------------------------------------------------ fn set_address(_: *usb.DeviceInterface, addr: u7) void { - std.log.info("ch32: set_address {}", .{addr}); + log.info("set_address {}", .{addr}); regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); } @@ -528,7 +484,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); - std.log.info("ch32: ep_open called for ep{}", .{ep_i}); + log.info("ep_open called for ep{}", .{ep_i}); const mps: u16 = desc.max_packet_size.into(); assert(mps > 0 and mps <= 2047); @@ -538,7 +494,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const out_st = self.st(.ep0, .Out); const in_st = self.st(.ep0, .In); if (ep0_dma().raw == 0) { - std.log.warn("ch32: EP0 DMA is null!", .{}); + log.warn("EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { // this should probably be fixed at 64 bytes for EP0 for HS? @@ -547,7 +503,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { in_st.buf = buf; const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); - std.log.info("ch32: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + log.info("Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); ep0_dma().raw = ptr_val; uep_max_len(0).raw = @intCast(64); } else { @@ -556,7 +512,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (in_st.buf.len == 0) in_st.buf = out_st.buf; } } else { - std.log.debug("ch32: ep_open non-EP0 ep{} dir={}", .{ ep_i, e.dir }); + log.debug("ep_open ep{} dir={}", .{ ep_i, e.dir }); // Non-EP0: separate DMA buffers per direction const st_ep = self.st(e.num, e.dir); @@ -575,18 +531,19 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); } } - std.log.debug("allocation and DMA setup done for ep{}", .{ep_i}); + log.debug("allocation and DMA setup done for ep{}", .{ep_i}); uep_t_len(ep_i).raw = 0; // Enable endpoint direction in UEP_CONFIG bitmaps. + // TODO: make this a function, too ugly here var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; if (e.num != .ep0) { if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); } regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; - // Endpoint type ISO marking (optional, only if you use ISO). + // Endpoint type ISO marking (only for ISO endpoints). if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { var type_raw: u32 = regs().UEP_TYPE.raw; if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); @@ -598,11 +555,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.arm_ep0_out_always(); } - std.log.debug("ch32: ep_open completed for ep{}", .{ep_i}); + log.debug("ep_open completed for ep{}", .{ep_i}); } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { - std.log.info("ch32: ep_listen called for ep{} len={}", .{ ep_num, len }); + log.info("ep_listen called for ep{} len={}", .{ ep_num, len }); const self: *Self = @fieldParentPtr("interface", itf); // EP0 OUT is always armed; ignore listen semantics here. @@ -634,11 +591,9 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { asm volatile (""); set_rx_ctrl(ep_i, RES_ACK, current_rx_tog(ep_i), false); - std.log.debug("finished ep_listen for ep{}", .{ep_num}); } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { - std.log.info("ch32: ep_readv called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); const st_out = self.st(ep_num, .Out); @@ -677,7 +632,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { assert(st_in.buf.len != 0); if (st_in.tx_busy) { - std.log.warn("ch32: ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); + log.warn("ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); return 0; } @@ -696,24 +651,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { st_in.tx_busy = true; // Arm IN - // if (ep_num != .ep0) { - const tx_before = uep_tx_ctrl(ep_i).read(); - std.log.debug( - "ch32: ep_writev ep{} len={} tx_res={} tx_tog={}", - .{ ep_i, w, tx_before.RES, tx_before.TOG }, - ); - // } - set_tx_ctrl(ep_i, RES_ACK, current_tx_tog(ep_i), false); - // if (ep_num != .ep0) { - const tx_after = uep_tx_ctrl(ep_i).read(); - std.log.debug( - "ch32: ep_writev ep{} armed tx_res={} tx_tog={}", - .{ ep_i, tx_after.RES, tx_after.TOG }, - ); - // } - // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); } @@ -728,11 +667,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 1, .RB_UC_RST_SIE = 1, + .RB_UC_INT_BUSY = 1, }); // wait 10us, TODO: replace with timer delay var i: u32 = 0; - while (i < 480) : (i += 1) { - asm volatile (""); + while (i < 1440) : (i += 1) { + asm volatile ("nop"); } regs().USB_CTRL.modify(.{ @@ -741,7 +681,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_CTRL.modify(.{ .RB_UC_DMA_EN = 1, - .RB_UC_INT_BUSY = if (cfg.int_busy) 1 else 0, + .RB_UC_INT_BUSY = 1, .RB_UC_SPEED_TYPE = speed_type(cfg), }); @@ -752,7 +692,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { .RB_UIE_TRANSFER = 1, .RB_UIE_SUSPEND = 1, .RB_UIE_FIFO_OV = 1, - .RB_UIE_HST_SOF = 1, + // .RB_UIE_HST_SOF = 1, + .RB_UIE_DEV_NAK = 1, }); // regs().USB_INT_ST.modify(.{ .RB_UIS_IS_NAK = 1 }); @@ -768,6 +709,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { const fg = regs().USB_INT_FG.raw; - std.log.warn("USBHS interrupt handler called, flags: {}", .{fg}); + // log.warn("USBHS interrupt handler called, flags: {}", .{fg}); regs().USB_INT_FG.raw = fg; // clear all } From fce66464675abdcac310b6e45e5e723784c6c1ef Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 13:59:08 -0500 Subject: [PATCH 68/80] remove unfinished usbd driver --- port/wch/ch32v/src/hals/usb.zig | 153 -------------------------------- 1 file changed, 153 deletions(-) delete mode 100644 port/wch/ch32v/src/hals/usb.zig diff --git a/port/wch/ch32v/src/hals/usb.zig b/port/wch/ch32v/src/hals/usb.zig deleted file mode 100644 index 81503c046..000000000 --- a/port/wch/ch32v/src/hals/usb.zig +++ /dev/null @@ -1,153 +0,0 @@ -//! USB device implementation -//! -//! - -const std = @import("std"); -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 PHY = enum { - device, - host_device, -}; - -pub const HardwareConfig = struct { - max_endpoints_count: comptime_int = 3, // datasheet text says 16 but only has 8 registers? - max_interfaces_count: comptime_int = 2, // not sure on this - num_can_active: u2 = 0, // 0, 1 or 2, changes amount of shared SRAM - phy: PHY = .device, - buffer_size: u32 = 64, -}; - -const BufferDescription = packed struct { - const Count = packed struct { - val: u10, - res: u6, - res2: u16, - }; - address: u32, - count: Count, -}; - -const SHARED_SRAM_SIZE: comptime_int = 512; // bytes -const SHARED_SRAM_ADDR: comptime_int = 0x40006000; -pub fn Polled( - controller_config: usb.Config, - config: HardwareConfig, -) type { - const buffer_desc_size = 2 * config.max_endpoints_count * @sizeOf(BufferDescription); - // seems like we must have 2 buffers per endpoint - // they're either double buffered or tx/rx? - const buffer_size = 2 * config.max_endpoints_count * config.buffer_size; - const used_sram = buffer_desc_size + buffer_size; - comptime { - // Do hardware config validity checks here - const can_sram_size: u32 = 128 * @as(u32, @intCast(config.num_can_active)); - const available_sram = SHARED_SRAM_SIZE - can_sram_size; - - if (used_sram > available_sram) { - @compileLog("USB Buffer configuration overflows available sram"); - } - } - _ = controller_config; - // _ = config; - const USB = peripherals.USB; - const EXT = peripherals.EXTEND; - - // always putting the buffer descriptor table at the start of shared sram - const buffer_descriptor_table: *align(2) [2 * config.max_endpoints_count]BufferDescription = @ptrFromInt(SHARED_SRAM_ADDR); - const ep_buffers: *align(2) [2 * config.max_endpoints_count][config.buffer_size]u8 = @ptrFromInt(SHARED_SRAM_ADDR + buffer_desc_size); - - return struct { - const vtable: usb.DeviceInterface.VTable = .{ - .ep_writev = start_tx, - .ep_readv = start_rx, - .set_address = set_address, - .ep_open = ep_open, - }; - - pub fn poll() void { - // do the recurring work here - } - pub fn init() @This() { - // setup hardware here - // first check for 48MHz clock then enable the USB Clock in RCC_CFGR0 - const RCC = peripherals.RCC; - // RCC.CFGR0 // USBPRE - RCC.AHBPCENR.modify(.{ .USBHS_EN = 1 }); - RCC.APB1PCENR.modify(.{ .USBDEN = 1 }); - USB.CNTR.modify(.{ .FRES = 1 }); // reset the peripheral if it's not already - - init_shared_sram(); - // endpoint config - - //packet buffer description table - - USB.DADDR.modify(.{ .ADD = 0, .EF = 1 }); - EXT.EXTEND_CTR.modify(.{ .USBDPU = 1 }); // enable pull up - USB.CNTR.modify(.{ .FRES = 0 }); // bring device out of reset - USB.ISTR.raw = 0; // reset all interrupt bits - // enable interrupts here if needed - USB.CNTR.modify(.{ .ESOFM = 0, .SOFM = 0, .RESETM = 0, .SUSPM = 0, .WKUPM = 0, .ERRM = 0, .PMAOVRM = 0, .CTRM = 0 }); - - return .{}; - } - - fn init_shared_sram() void { - // put table at start of sram - USB.BTABLE.modify(.{ .BTABLE = 0 }); - for (buffer_descriptor_table, ep_buffers) |*desc, *buf| { - desc.*.address = @intFromPtr(buf); - desc.*.count.val = 64; // is EP0 64 bytes? - } - } - - pub fn start_tx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, buffer: []const u8) void { - _ = self; - _ = ep_num; - _ = buffer; - } - pub fn start_rx(self: *usb.DeviceInterface, ep_num: usb.types.Endpoint.Num, len: usize) void { - _ = self; - _ = ep_num; - _ = len; - } - pub fn ep_open(self: *@This(), desc: *const usb.descriptor.Endpoint) void { - _ = self; - // how to tell if IN or OUT endpoint? - - const epr = get_epr_reg(desc.endpoint.num); - epr.modify(.{ - .EP_TYPE = switch (desc.attributes.transfer_type) { - .Bulk => 0b00, - .Control => 0b01, - .Isochronous => 0b10, - .Interrupt => 0b11, - }, - .EP_KIND = 0, // no double buffering for now - - }); - } - pub fn set_address(self: *usb.DeviceInterface, addr: u7) void { - _ = self; - _ = addr; - } - fn get_epr_reg(ep_num: usb.types.Endpoint.Num) @TypeOf(USB.EPR0) { - return switch (ep_num) { - .ep0 => USB.EPR0, - .ep1 => USB.EPR1, - .ep2 => USB.EPR2, - .ep3 => USB.EPR3, - .ep4 => USB.EPR4, - .ep5 => USB.EPR5, - .ep6 => USB.EPR6, - .ep7 => USB.EPR7, - else => @compileError("Unsupported endpoint number"), - }; - } - }; -} From 8a0e7525e1425181528005867d411b902e9031aa Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:04:39 -0500 Subject: [PATCH 69/80] revert to upstream with minimal updates for usbhs --- examples/wch/ch32v/build.zig | 56 +++++++++++++----------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 3bc977530..c26e44a19 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -5,18 +5,18 @@ const MicroBuild = microzig.MicroBuild(.{ .ch32v = true, }); -pub fn build(b: *std.Build) void { // $ls root_id 16 - // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. - // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary - // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. - const gnu_objcopy = b.findProgram(&.{"riscv64-unknown-elf-objcopy"}, &.{}) catch null; - +pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const maybe_example = b.option([]const u8, "example", "only build matching examples"); const mz_dep = b.dependency("microzig", .{}); const mb = MicroBuild.init(b, mz_dep) orelse return; + // Use GNU objcopy instead of LLVM objcopy to avoid 512MB binary issue. + // LLVM objcopy includes LOAD segments for NOLOAD sections, causing the binary + // to span from flash (0x0) to RAM (0x20000000) = 512MB of zeros. + const gnu_objcopy = b.findProgram(&.{"riscv64-unknown-elf-objcopy"}, &.{}) catch null; + const available_examples = [_]Example{ // CH32V003 .{ .target = mb.ports.ch32v.chips.ch32v003x4, .name = "empty_ch32v003", .file = "src/empty.zig" }, @@ -32,20 +32,20 @@ pub fn build(b: *std.Build) void { // $ls root_id 16 .{ .target = mb.ports.ch32v.boards.ch32v103.ch32v103r_r1_1v1, .name = "ch32v103r_r1_1v1_empty", .file = "src/empty.zig" }, // CH32V203 - // .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "empty_ch32v203", .file = "src/empty.zig" }, - // .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_ch32v203", .file = "src/blinky.zig" }, - // .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_systick_ch32v203", .file = "src/blinky_systick.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_blinky", .file = "src/board_blinky.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_dma", .file = "src/dma.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_eeprom", .file = "src/i2c_eeprom.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_loopback", .file = "src/spi_loopback.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_flash_w25q", .file = "src/spi_flash_w25q.zig" }, - // .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_sharp_niceview", .file = "src/sharp_niceview.zig" }, + .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "empty_ch32v203", .file = "src/empty.zig" }, + .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_ch32v203", .file = "src/blinky.zig" }, + .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_systick_ch32v203", .file = "src/blinky_systick.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_blinky", .file = "src/board_blinky.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_dma", .file = "src/dma.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_uart_log", .file = "src/uart_log.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_bus_scan", .file = "src/i2c_bus_scan.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_eeprom", .file = "src/i2c_eeprom.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_i2c_position_sensor", .file = "src/i2c_position_sensor.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_loopback", .file = "src/spi_loopback.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_spi_flash_w25q", .file = "src/spi_flash_w25q.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_sharp_niceview", .file = "src/sharp_niceview.zig" }, // CH32V30x .{ .target = mb.ports.ch32v.chips.ch32v303xb, .name = "empty_ch32v303", .file = "src/empty.zig" }, @@ -78,22 +78,6 @@ pub fn build(b: *std.Build) void { // $ls root_id 16 // and allows installing the firmware as a typical firmware file. // // This will also install into `$prefix/firmware` instead of `$prefix/bin`. - - // Use GNU objcopy to create .bin files (avoids LLVM objcopy 512MB issue) - - if (gnu_objcopy) |objcopy_path| { - const bin_filename = b.fmt("{s}.bin", .{example.name}); - const objcopy_run = b.addSystemCommand(&.{objcopy_path}); - objcopy_run.addArgs(&.{ "-O", "binary" }); - objcopy_run.addArtifactArg(fw.artifact); - const bin_output = objcopy_run.addOutputFileArg(bin_filename); - b.getInstallStep().dependOn(&b.addInstallFileWithDir( - bin_output, - .{ .custom = "firmware" }, - bin_filename, - ).step); - } - // For debugging, we also always install the firmware as an ELF file mb.install_firmware(fw, .{ .format = .elf }); // Use GNU objcopy to create .bin files (avoids LLVM objcopy 512MB issue) From 57f498f53b2c191e77d1eb1e36ef2d5b77d1421e Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:17:25 -0500 Subject: [PATCH 70/80] revert changes to uart_log --- examples/wch/ch32v/src/uart_log.zig | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/wch/ch32v/src/uart_log.zig b/examples/wch/ch32v/src/uart_log.zig index 0a0252b60..d99db5a1c 100644 --- a/examples/wch/ch32v/src/uart_log.zig +++ b/examples/wch/ch32v/src/uart_log.zig @@ -7,10 +7,8 @@ const gpio = hal.gpio; const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; -// TODO: Get usart from board config - -const usart = hal.usart.instance.USART1; -const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 +const usart = hal.usart.instance.USART2; +const usart_tx_pin = gpio.Pin.init(0, 2); // PA2 pub const microzig_options = microzig.Options{ .log_level = .debug, @@ -20,18 +18,19 @@ pub const microzig_options = microzig.Options{ pub fn main() !void { // Board brings up clocks and time microzig.board.init(); - microzig.hal.init(); // Enable peripheral clocks for USART2 and GPIOA RCC.APB2PCENR.modify(.{ .IOPAEN = 1, // Enable GPIOA clock .AFIOEN = 1, // Enable AFIO clock - .USART1EN = 1, // Enable USART1 clock }); RCC.APB1PCENR.modify(.{ .USART2EN = 1, // Enable USART2 clock }); + // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) + AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); + // Configure PA2 as alternate function push-pull for USART2 TX usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); @@ -39,14 +38,10 @@ pub fn main() !void { usart.apply(.{ .baud_rate = 115200 }); hal.usart.init_logger(usart); - std.log.info("UART logging initialized.", .{}); - var last = time.get_time_since_boot(); + var i: u32 = 0; while (true) : (i += 1) { - const now = time.get_time_since_boot(); - if (now.diff(last).to_us() > 500000) { - std.log.info("what {}", .{i}); - last = now; - } + std.log.info("what {}", .{i}); + time.sleep_ms(1000); } } From 8b9704cf900c5ea497ff5adb46d270fd13aae0c4 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:18:17 -0500 Subject: [PATCH 71/80] remove old example driver --- core/src/core/usb/drivers/example.zig | 99 --------------------------- 1 file changed, 99 deletions(-) delete mode 100644 core/src/core/usb/drivers/example.zig diff --git a/core/src/core/usb/drivers/example.zig b/core/src/core/usb/drivers/example.zig deleted file mode 100644 index 67cfd5dee..000000000 --- a/core/src/core/usb/drivers/example.zig +++ /dev/null @@ -1,99 +0,0 @@ -const std = @import("std"); -const usb = @import("../../usb.zig"); -const EP_Num = usb.types.Endpoint.Num; -const log = std.log.scoped(.usb_echo); - -/// This is an example driver that sends any data received on ep2 to ep1. -pub const EchoExampleDriver = struct { - /// The descriptors need to have the same memory layout as the sent data. - pub const Descriptor = extern struct { - const desc = usb.descriptor; - - example_interface: desc.Interface, - ep_out: desc.Endpoint, - ep_in: desc.Endpoint, - - /// This function is used during descriptor creation. If multiple instances - /// of a driver are used, a descriptor will be created for each. - /// Endpoint numbers are allocated automatically, this function should - /// use placeholder .ep0 values on all endpoints. - pub fn create( - alloc: *usb.DescriptorAllocator, - first_string: u8, - max_supported_packet_size: usb.types.Len, - ) @This() { - return .{ - .example_interface = .{ - .interface_number = alloc.next_itf(), - .alternate_setting = 0, - .num_endpoints = 2, - .interface_triple = .vendor_specific, - .interface_s = first_string, - }, - .ep_out = .bulk(alloc.next_ep(.Out), max_supported_packet_size), - .ep_in = .bulk(alloc.next_ep(.In), max_supported_packet_size), - }; - } - }; - - /// This is a mapping from endpoint descriptor field names to handler - /// function names. Counterintuitively, usb devices send data on 'in' - /// endpoints and receive on 'out' endpoints. - pub const handlers: usb.DriverHadlers(@This()) = .{ - .ep_in = on_tx_ready, - .ep_out = on_rx, - }; - - device: *usb.DeviceInterface, - ep_tx: EP_Num, - - /// This function is called when the host chooses a configuration - /// that contains this driver. `self` points to undefined memory. - pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface) void { - self.* = .{ - .device = device, - .ep_tx = desc.ep_in.endpoint.num, - }; - device.ep_listen( - desc.ep_out.endpoint.num, - @intCast(desc.ep_out.max_packet_size.into()), - ); - } - - /// Used for interface configuration through endpoint 0. - /// Data returned by this function is sent on endpoint 0. - pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 { - _ = self; - _ = setup; - return usb.ack; - } - - /// Each endpoint (as defined in the descriptor) has its own handler. - /// Endpoint number is passed as an argument so that it does not need - /// to be stored in the driver. - pub fn on_tx_ready(self: *@This(), ep_tx: EP_Num) void { - log.info("tx ready", .{}); - // Mark transmission as available - @atomicStore(EP_Num, &self.ep_tx, ep_tx, .seq_cst); - } - - pub fn on_rx(self: *@This(), ep_rx: EP_Num) void { - var buf: [64]u8 = undefined; - // Read incoming packet into a local buffer - const len_rx = self.device.ep_readv(ep_rx, &.{&buf}); - log.info("Received: {s}", .{buf[0..len_rx]}); - // Check if we can transmit - const ep_tx = @atomicLoad(EP_Num, &self.ep_tx, .seq_cst); - if (ep_tx != .ep0) { - // Mark transmission as not available - @atomicStore(EP_Num, &self.ep_tx, .ep0, .seq_cst); - // Send received packet - log.info("Sending {} bytes", .{len_rx}); - const len_tx = self.device.ep_writev(ep_tx, &.{buf[0..len_rx]}); - if (len_tx != len_rx) - log.err("Only sent {} bytes", .{len_tx}); - } - // Listen for next packet - self.device.ep_listen(ep_rx, 64); - } -}; From 0049e034a15c7ad9a2f6c664216ed3c81435dea1 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:22:55 -0500 Subject: [PATCH 72/80] cleanup --- examples/wch/ch32v/src/usbhs.zig | 37 +++++++------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig index 16321d985..588b7e0ed 100644 --- a/examples/wch/ch32v/src/usbhs.zig +++ b/examples/wch/ch32v/src/usbhs.zig @@ -40,7 +40,6 @@ const EpState = struct { rx_last_len: u16 = 0, // valid until ep_readv() consumes it // IN: tx_busy: bool = false, - tx_last_len: u16 = 0, }; fn PerEndpointArray(comptime N: comptime_int) type { @@ -282,7 +281,6 @@ pub fn Polled(comptime cfg: Config) type { self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; - self.endpoints[i][@intFromEnum(types.Dir.In)].tx_last_len = 0; } // Default: NAK all non-EP0 endpoints. @@ -326,6 +324,7 @@ pub fn Polled(comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { + _ = in_isr; while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; @@ -338,13 +337,6 @@ pub fn Polled(comptime cfg: Config) type { // acknowledge SUSPEND but ignore regs().USB_INT_FG.raw = UIF_SUSPEND; } - const stv = regs().USB_INT_ST.read(); - const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); - const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - if (token != TOKEN_SOF) { - if (false) - log.debug("in isr: {}, fg: {}, EN: {}", .{ in_isr, fg, regs().USB_INT_EN.raw }); - } if (fg & UIF_FIFO_OV != 0) { log.warn("FIFO overflow!", .{}); @@ -379,15 +371,11 @@ pub fn Polled(comptime cfg: Config) type { if ((fg & UIF_TRANSFER) != 0) { // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; - + const stv = regs().USB_INT_ST.read(); + const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); + const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); self.handle_transfer(ep, token, controller); } - - // 0x4 => SUSPEND - // 0x5 => SUSPEND | RESET - // 0x8 => HST_SOF - // 0x10 => FIFO_OV - // 0x40 => ISO_ACT } } @@ -462,7 +450,6 @@ pub fn Polled(comptime cfg: Config) type { // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. st_in.tx_busy = false; - st_in.tx_last_len = 0; const next = toggle_next(current_tx_tog(ep)); set_tx_ctrl(ep, RES_NAK, next, false); @@ -647,7 +634,6 @@ pub fn Polled(comptime cfg: Config) type { uep_t_len(ep_i).raw = @as(u16, @intCast(w)); - st_in.tx_last_len = @as(u16, @intCast(w)); st_in.tx_busy = true; // Arm IN @@ -685,7 +671,7 @@ pub fn Polled(comptime cfg: Config) type { .RB_UC_SPEED_TYPE = speed_type(cfg), }); - // Enable source interrupts (we poll flags; NVIC off) + // Enable source interrupts (we poll these flags, interrupt disabled) regs().USB_INT_EN.modify(.{ .RB_U_1WIRE_MODE = 1, // actually SETUP_ACT? .RB_UIE_BUS_RST__RB_UIE_DETECT = 1, @@ -695,20 +681,13 @@ pub fn Polled(comptime cfg: Config) type { // .RB_UIE_HST_SOF = 1, .RB_UIE_DEV_NAK = 1, }); - - // regs().USB_INT_ST.modify(.{ .RB_UIS_IS_NAK = 1 }); - - // Set some defaults, just in case - // regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = 0 }); - // regs().UEP_CONFIG__UHOST_CTRL.write_raw(0); - // regs().UEP_TYPE.write_raw(0); - // regs().UEP_BUF_MOD.write_raw(0); } }; } +///! Skeleton ISR pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { const fg = regs().USB_INT_FG.raw; - // log.warn("USBHS interrupt handler called, flags: {}", .{fg}); - regs().USB_INT_FG.raw = fg; // clear all + regs().USB_INT_FG.raw = fg; + @panic("Don't Enable USBHS Interrupt, Not yet supported!"); } From 01624dc97155574258b7374ca88dd22a02d8e5c8 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:31:09 -0500 Subject: [PATCH 73/80] cleanup usbhs clock setup --- port/wch/ch32v/src/hals/clocks.zig | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index 269bd9788..72a3aacdc 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -522,7 +522,6 @@ pub fn get_freqs() ClockSpeeds { /// /// Note: The SVD names bit31 as `USBFSSRC`, but the reference manual /// describes it as `USBHSSRC` ("USBHS 48MHz clock source selection"). -/// We write the SVD field, but treat it as USBHS 48MHz source select. pub const UsbHsClockConfig = struct { pub const RefSource = enum { hse, hsi }; /// Desired PHY PLL reference frequency (the USBHSCLK field selects one of these). @@ -533,15 +532,12 @@ pub const UsbHsClockConfig = struct { mhz8 = 0b10, mhz5 = 0b11, }; - /// Some parts have the USBHS controller but *no* built-in HS PHY. - /// If false, we will not enable the PHY internal PLL and will select 48MHz from PLL CLK. - has_hs_phy: bool = true, - /// If true (and has_hs_phy), select USB PHY as the 48MHz source and enable the PHY internal PLL. + /// If true select USB PHY as the 48MHz source and enable the PHY internal PLL. /// If false, select PLL CLK as the 48MHz source (you must ensure a valid 48MHz PLL clock exists). use_phy_48mhz: bool = true, - /// PHY PLL reference source (only used when has_hs_phy && use_phy_48mhz). + /// PHY PLL reference source (only used when use_phy_48mhz). ref_source: RefSource = .hse, /// Frequency of the chosen ref_source, in Hz. @@ -575,23 +571,23 @@ const HSPLLSRC = enum(u2) { /// /// Selects USBHS Clock source, options are: /// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or -/// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true and cfg.has_hs_phy = true), +/// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true), /// in which case it also configures the PHY PLL reference (USBHSCLK/USBHSPLLSRC/USBHSDIV) /// and enables the PHY internal PLL (USBHSPLL). pub fn enable_usbhs_clock(comptime cfg: UsbHsClockConfig) void { // Turn on the AHB clock gate for the USBHS peripheral block. - // If there's no PHY (or caller prefers PLL CLK), force PLL CLK selection and keep PHY PLL off. - if (!cfg.has_hs_phy or !cfg.use_phy_48mhz) { + // If caller prefers PLL CLK, set PLL CLK selection and keep PHY PLL off. + if (!cfg.use_phy_48mhz) { RCC.CFGR2.modify(.{ // SVD name mismatch: USBFSSRC field == USBHS 48MHz source select per RM. - .USBFSSRC = 0, // 0: PLL CLK (per RM: "USBHS 48MHz clock source selection") + .USBFSSRC = 0, // 0: PLL CLK .USBHSPLL = 0, // PHY internal PLL disabled }); return; } - // Ensure the selected oscillator is on (best-effort; usually already enabled by your system clock init). + // Ensure the selected oscillator is on (best-effort; usually already enabled by system clock init). switch (cfg.ref_source) { .hse => { if (RCC.CTLR.read().HSEON == 0) RCC.CTLR.modify(.{ .HSEON = 1 }); @@ -642,14 +638,6 @@ pub fn enable_usbhs_clock(comptime cfg: UsbHsClockConfig) void { } } } - - // RCC_USBCLK48MConfig( RCC_USBCLK48MCLKSource_USBPHY ); // bit 31 = 1 - // RCC_USBHSPLLCLKConfig( RCC_HSBHSPLLCLKSource_HSE ); // bit 27 = 0 - // RCC_USBHSConfig( RCC_USBPLL_Div2 ); // bit 24 = 1 - // RCC_USBHSPLLCKREFCLKConfig( RCC_USBHSPLLCKREFCLK_4M );// bit 28 = 1 - // RCC_USBHSPHYPLLALIVEcmd( ENABLE ); // bit 30 = 1 - // RCC_AHBPeriphClockCmd( RCC_AHBPeriph_USBHS, ENABLE ); - // Program CFGR2: // - USBHSPLLSRC: 0=HSE, 1=HSI // - USBHSDIV: prescaler (/1..8) From 19cc7f3cbd365e8558d62c00a87592633ca4db43 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:49:46 -0500 Subject: [PATCH 74/80] more cleanup --- examples/wch/ch32v/src/usb_cdc.zig | 51 +- examples/wch/ch32v/src/usbhs.zig | 693 --------------------------- port/wch/ch32v/src/hals/ch32v30x.zig | 2 +- port/wch/ch32v/src/hals/usbhs.zig | 332 +++++-------- 4 files changed, 149 insertions(+), 929 deletions(-) delete mode 100644 examples/wch/ch32v/src/usbhs.zig diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index dd69ec611..bbf85d2a5 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -5,7 +5,7 @@ const hal = microzig.hal; const time = hal.time; const gpio = hal.gpio; -pub const usb = @import("usbhs.zig"); +pub const usb = hal.usbhs; const USB_Serial = microzig.core.usb.drivers.CDC; @@ -103,15 +103,26 @@ pub fn main() !void { usb_dev = .init(); // microzig.cpu.interrupt.enable(.USBHS); PFIC.IPRIOR70 = 255; - var last = time.get_time_since_boot(); var i: u32 = 0; + var old: u64 = time.get_time_since_boot().to_us(); + var new: u64 = 0; while (true) { - const now = time.get_time_since_boot(); - if (now.diff(last).to_us() > 100_000) { - run_usb(i); - i += 1; - last = now; + if (usb_controller.drivers()) |drivers| { + new = time.get_time_since_boot().to_us(); + if (new - old > 500000) { + old = new; + i += 1; + std.log.info("cdc test: {}", .{i}); + + usb_cdc_write(&drivers.serial, "This is very very very very very very very very long text sent from ch32v30x by USB CDC to your device: {}\r\n", .{i}); + } + + // read and print host command if present + const message = usb_cdc_read(&drivers.serial); + if (message.len > 0) { + usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); + } } usb_poll(); } @@ -126,7 +137,8 @@ pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytyp var write_buff = text; while (write_buff.len > 0) { write_buff = write_buff[serial.write(write_buff)..]; - usb_poll(); + while (!serial.flush()) + usb_poll(); } } @@ -149,26 +161,3 @@ pub fn usb_cdc_read( return usb_rx_buff[0..total_read]; } - -fn intToHexChar(i: u4) u8 { - // Ensure the input is within the 0-15 range - std.debug.assert(i <= 15); - - const base: u8 = if (i < 10) '0' else 'A'; - const offset: u8 = if (i < 10) 0 else 10; - return @intCast(@as(u8, @intCast(base)) + @as(u8, @intCast(i)) - offset); -} - -pub fn run_usb(i: u32) void { - // _ = i; - if (usb_controller.drivers()) |drivers| { - const freqs = hal.clocks.get_freqs(); - usb_cdc_write(&drivers.serial, "what {}: sysclk {}, hclk {}, pclk1 {}, PFIC.IPRIOR70 {}\r\n", .{ i, freqs.sysclk, freqs.hclk, freqs.pclk1, PFIC.IPRIOR70 }); - - // read and print host command if present - const message = usb_cdc_read(&drivers.serial); - if (message.len > 0) { - usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); - } - } -} diff --git a/examples/wch/ch32v/src/usbhs.zig b/examples/wch/ch32v/src/usbhs.zig deleted file mode 100644 index 588b7e0ed..000000000 --- a/examples/wch/ch32v/src/usbhs.zig +++ /dev/null @@ -1,693 +0,0 @@ -//! WCH USBHD/USBHS device backend -//! - -const std = @import("std"); -const assert = std.debug.assert; -const log = std.log.scoped(.ch32_usbhs); - -const microzig = @import("microzig"); -const peripherals = microzig.chip.peripherals; -const usb = microzig.core.usb; -const types = usb.types; -const descriptor = usb.descriptor; - -pub const USBHD_MAX_ENDPOINTS_COUNT = 16; - -const max_buffer_pool_size = USBHD_MAX_ENDPOINTS_COUNT * 2 * 512 + 64; - -pub const Config = struct { - max_endpoints_count: comptime_int = USBHD_MAX_ENDPOINTS_COUNT, - - prefer_high_speed: bool = true, - - /// Static buffer pool in SRAM, bump-allocated. - buffer_bytes: comptime_int = max_buffer_pool_size, - - /// Future seam only; not implemented in this initial version. - use_interrupts: bool = false, -}; - -const Regs = @TypeOf(peripherals.USBHS); -pub fn regs() Regs { - return peripherals.USBHS; -} - -const EpState = struct { - buf: []align(4) u8 = &[_]u8{}, - // OUT: - rx_armed: bool = false, - rx_limit: u16 = 0, - rx_last_len: u16 = 0, // valid until ep_readv() consumes it - // IN: - tx_busy: bool = false, -}; - -fn PerEndpointArray(comptime N: comptime_int) type { - return [N][2]EpState; // [ep][dir] -} - -fn epn(ep: types.Endpoint.Num) u4 { - return @as(u4, @intCast(@intFromEnum(ep))); -} -fn speed_type(comptime cfg: Config) u2 { - if (!cfg.prefer_high_speed) return 0; // FS - return 1; -} - -// --- USBHD token encodings --- -const Token = enum(u2) { - Out = 0, - Sof = 1, - In = 2, - Setup = 3, -}; - -const TOKEN_OUT: u2 = 0; -const TOKEN_SOF: u2 = 1; -const TOKEN_IN: u2 = 2; -const TOKEN_SETUP: u2 = 3; - -// --- INT_FG raw bits (match USBHS.zig packed order) --- -const UIF_BUS_RST: u8 = 1 << 0; -const UIF_TRANSFER: u8 = 1 << 1; -const UIF_SUSPEND: u8 = 1 << 2; -const UIF_HST_SOF: u8 = 1 << 3; -const UIF_FIFO_OV: u8 = 1 << 4; -const UIF_SETUP_ACT: u8 = 1 << 5; -const UIF_ISO_ACT: u8 = 1 << 6; - -// --- endpoint response encodings (WCH style) --- -const RES_ACK: u2 = 0; -const RES_NAK: u2 = 2; -const RES_STALL: u2 = 3; - -const Res = enum(u2) { - ACK = 0, - NAK = 2, - STALL = 3, -}; - -const TOG_DATA0: u2 = 0; -const TOG_DATA1: u2 = 1; - -const Tog = enum(u2) { - DATA0 = 0, - DATA1 = 0b1, - DATA2 = 0b10, - MDATA = 0b11, -}; - -fn toggle_next(tog: u2) u2 { - return if (tog == TOG_DATA0) TOG_DATA1 else TOG_DATA0; -} - -// We use offset-based access for per-EP regs to keep helpers compact. -const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); -const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); -const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); -pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { - RES: u2 = 0, - _reserved0: u1 = 0, - TOG: u2 = 0, - AUTO: bool = false, - _reserved1: u2 = 0, -}); - -fn baseAddr() usize { - return @intFromPtr(peripherals.USBHS); -} -fn mmio_u32(off: usize) *volatile RegU32 { - return @ptrFromInt(baseAddr() + off); -} -fn mmio_u16(off: usize) *volatile RegU16 { - return @ptrFromInt(baseAddr() + off); -} -fn mmio_tx_ctrl(off: usize) *volatile RegCtrl { - return @ptrFromInt(baseAddr() + off); -} -fn mmio_rx_ctrl(off: usize) *volatile RegCtrl { - return @ptrFromInt(baseAddr() + off); -} - -// RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) -// EP0 has its own dedicated DMA reg. -fn ep0_dma() *volatile RegU32 { - return mmio_u32(0x1C); -} -fn uep_rx_dma(ep: u4) *volatile RegU32 { - return mmio_u32(0x20 + (@as(usize, ep - 1) * 4)); -} -// TX DMA: 0x5C + (ep-1)*4 (EP1..EP15) -fn uep_tx_dma(ep: u4) *volatile RegU32 { - return mmio_u32(0x5C + (@as(usize, ep - 1) * 4)); -} -// MAX_LEN: EP0..EP15 at 0x98 + ep*4 -fn uep_max_len(ep: u4) *volatile RegU16 { - return mmio_u16(0x98 + (@as(usize, ep) * 4)); -} -// T_LEN: EP0..EP15 at 0xD8 + ep*4 -fn uep_t_len(ep: u4) *volatile RegU16 { - return mmio_u16(0xD8 + (@as(usize, ep) * 4)); -} -// TX_CTRL: 0xDA + ep*4, RX_CTRL: 0xDB + ep*4 -pub fn uep_tx_ctrl(ep: u4) *volatile RegCtrl { - const ret: *volatile RegCtrl = mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); - return ret; -} - -fn uep_rx_ctrl(ep: u4) *volatile RegCtrl { - return mmio_rx_ctrl(0xDB + (@as(usize, ep) * 4)); -} - -fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { - // [1:0]=RES, [4:3]=TOG, [5]=AUTO - const tx_ctrl: *volatile RegCtrl = uep_tx_ctrl(ep); - tx_ctrl.write(.{ - .RES = res, - .TOG = tog, - .AUTO = auto, - }); -} -fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { - // std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); - const ctrl: *volatile RegCtrl = uep_rx_ctrl(ep); - ctrl.write(.{ - .RES = res, - .TOG = tog, - .AUTO = auto, - }); -} - -fn current_rx_tog(ep: u4) u2 { - return uep_rx_ctrl(ep).read().TOG; -} -fn current_tx_tog(ep: u4) u2 { - return uep_tx_ctrl(ep).read().TOG; -} - -/// Polled USBHD device backend for microzig core USB controller. -/// maybe we should pass a list of valid controller types, for validation -pub fn Polled(comptime cfg: Config) type { - comptime { - if (cfg.max_endpoints_count > USBHD_MAX_ENDPOINTS_COUNT) - @compileError("USBHD max_endpoints_count cannot exceed 16"); - if (cfg.buffer_bytes < 64) - @compileError("USBHD buffer_bytes must be at least 64"); - } - - return struct { - const Self = @This(); - - const vtable: usb.DeviceInterface.VTable = .{ - .ep_writev = ep_writev, - .ep_readv = ep_readv, - .ep_listen = ep_listen, - .ep_open = ep_open, - .set_address = set_address, - }; - - endpoints: PerEndpointArray(cfg.max_endpoints_count), - pool: [cfg.buffer_bytes]u8 = undefined, - - buffer_pool: []align(4) u8, - - interface: usb.DeviceInterface, - - pub fn init() Self { - var self: Self = .{ - .endpoints = undefined, - .buffer_pool = undefined, - .interface = .{ .vtable = &vtable }, - }; - @memset(std.mem.asBytes(&self.endpoints), 0); - @memset(self.pool[0..64], 0x7e); - @memset(self.pool[64..], 0); - - self.buffer_pool = @alignCast(self.pool[64..]); - - usbhd_hw_init(); - - // EP0 is required; open OUT then IN (or vice versa), we will share buffer. - self.interface.ep_open(&.{ - .endpoint = .out(.ep0), - .max_packet_size = .from(64), - .attributes = .{ .transfer_type = .Control, .usage = .data }, - .interval = 0, - }); - self.interface.ep_open(&.{ - .endpoint = .in(.ep0), - .max_packet_size = .from(64), - .attributes = .{ .transfer_type = .Control, .usage = .data }, - .interval = 0, - }); - - // EP0 OUT should always be able to accept status-stage OUT ZLP. - // So keep EP0 RX in ACK state permanently. - self.arm_ep0_out_always(); - - // Connect pull-up to signal device ready - regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); - - regs().USB_CTRL.modify(.{ - .RB_UC_CLR_ALL = 0, - }); - - return self; - } - - // TODO: replace with fixedbuffer allocator? - fn endpoint_alloc(self: *Self, size: usize) []align(4) u8 { - assert(self.buffer_pool.len >= size); - const out = self.buffer_pool[0..size]; - self.buffer_pool = @alignCast(self.buffer_pool[size..]); - return out; - } - - fn st(self: *Self, ep_num: types.Endpoint.Num, dir: types.Dir) *EpState { - return &self.endpoints[@intFromEnum(ep_num)][@intFromEnum(dir)]; - } - - fn arm_ep0_out_always(self: *Self) void { - _ = self; - // EP0 OUT is always ACK. - set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); - // EP0 IN remains NAK until data is queued. - // set_tx_ctrl(0, RES_NAK, TOG_DATA0, false); - } - - fn on_bus_reset_local(self: *Self) void { - // Clear state - inline for (0..cfg.max_endpoints_count) |i| { - self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; - self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; - self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; - } - - // Default: NAK all non-EP0 endpoints. - for (1..cfg.max_endpoints_count) |i| { - const e: u4 = @as(u4, @intCast(i)); - set_rx_ctrl(e, RES_NAK, TOG_DATA0, false); - set_tx_ctrl(e, RES_NAK, TOG_DATA0, false); - } - - // EP0 special. - self.arm_ep0_out_always(); - } - - fn read_setup_from_ep0(self: *Self) types.SetupPacket { - const ep0 = self.st(.ep0, .Out); - assert(ep0.buf.len >= 8); - const words_ptr: *align(4) const [2]u32 = @ptrCast(ep0.buf.ptr); - return @bitCast(words_ptr.*); - } - - // ---- comptime dispatch helpers (required by DeviceController API) ---- - - fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, controller: anytype) void { - // controller.on_buffer requires comptime ep parameter - switch (dir) { - .In => switch (ep) { - inline 0...15 => |i| { - const num: types.Endpoint.Num = @enumFromInt(i); - controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); - }, - }, - .Out => switch (ep) { - inline 0...15 => |i| { - const num: types.Endpoint.Num = @enumFromInt(i); - controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); - }, - }, - } - } - - // ---- Poll loop ------------------------------------------------------- - - pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { - _ = in_isr; - while (true) { - const fg: u8 = regs().USB_INT_FG.raw; - if (fg == 0) break; - - if ((fg & UIF_HST_SOF) != 0) { - // acknowledge SOF but ignore - regs().USB_INT_FG.raw = UIF_HST_SOF; - } - if ((fg & UIF_SUSPEND) != 0) { - // acknowledge SUSPEND but ignore - regs().USB_INT_FG.raw = UIF_SUSPEND; - } - - if (fg & UIF_FIFO_OV != 0) { - log.warn("FIFO overflow!", .{}); - regs().USB_INT_FG.raw = UIF_FIFO_OV; - } - - if ((fg & UIF_BUS_RST) != 0) { - log.info("bus reset\n\n\n", .{}); - // clear - regs().USB_INT_FG.raw = UIF_BUS_RST; - - // address back to 0 - set_address(&self.interface, 0); - - self.on_bus_reset_local(); - controller.on_bus_reset(&self.interface); - } - - if ((fg & UIF_SETUP_ACT) != 0) { - log.info("SETUP received", .{}); - regs().USB_INT_FG.raw = UIF_SETUP_ACT; - - const setup: types.SetupPacket = self.read_setup_from_ep0(); - - // After SETUP, EP0 IN data stage starts with DATA1. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); - set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); - - controller.on_setup_req(&self.interface, &setup); - } - - if ((fg & UIF_TRANSFER) != 0) { - // clear transfer - regs().USB_INT_FG.raw = UIF_TRANSFER; - const stv = regs().USB_INT_ST.read(); - const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); - const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - self.handle_transfer(ep, token, controller); - } - } - } - - fn handle_transfer(self: *Self, ep: u4, token: u2, controller: anytype) void { - if (ep >= cfg.max_endpoints_count) return; - if (token == TOKEN_SOF) return; - switch (token) { - TOKEN_OUT => self.handle_out(ep, controller), - TOKEN_SOF => {}, - TOKEN_IN => self.handle_in(ep, controller), - TOKEN_SETUP => { - // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. - if (ep == 0) { - const setup: types.SetupPacket = self.read_setup_from_ep0(); - log.debug("Setup: {any}", .{setup}); - set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); - set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); - controller.on_setup_req(&self.interface, &setup); - } - }, - } - } - - fn handle_out(self: *Self, ep: u4, controller: anytype) void { - const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; - const stv = regs().USB_INT_ST.read(); - const rx_ctrl = uep_rx_ctrl(ep).read(); - log.debug( - "OUT ep{} len={} tog_ok={} rx_res={} rx_tog={}", - .{ ep, len, stv.RB_UIS_TOG_OK, rx_ctrl.RES, rx_ctrl.TOG }, - ); - // EP0 OUT is always armed; accept status ZLP. - if (ep == 0) { - const st_out = self.st(.ep0, .Out); - st_out.rx_last_len = len; - const next = toggle_next(current_rx_tog(0)); - set_rx_ctrl(0, RES_ACK, next, false); - // stay ACK - self.call_on_buffer(.Out, 0, controller); - return; - } - - const num: types.Endpoint.Num = @enumFromInt(ep); - const st_out = self.st(num, .Out); - - // Only read if previously armed (ep_listen) - if (!st_out.rx_armed) { - // set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); - return; - } - - // Disarm immediately (NAK) to regain ownership. - st_out.rx_armed = false; - const next = toggle_next(current_rx_tog(ep)); - set_rx_ctrl(ep, RES_NAK, next, false); - - const n = @min(@as(usize, len), st_out.buf.len); - st_out.rx_last_len = @as(u16, @intCast(n)); - - self.call_on_buffer(.Out, ep, controller); - } - - // IN => into host from device - fn handle_in(self: *Self, ep: u4, controller: anytype) void { - const num: types.Endpoint.Num = @enumFromInt(ep); - const st_in = self.st(num, .In); - - if (!st_in.tx_busy) { - set_tx_ctrl(ep, RES_NAK, current_tx_tog(ep), false); - return; - } - - // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. - st_in.tx_busy = false; - const next = toggle_next(current_tx_tog(ep)); - set_tx_ctrl(ep, RES_NAK, next, false); - - // Notify controller/drivers of IN completion. - self.call_on_buffer(.In, ep, controller); - } - - // ---- VTable functions ------------------------------------------------ - - fn set_address(_: *usb.DeviceInterface, addr: u7) void { - log.info("set_address {}", .{addr}); - regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); - } - - fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { - const self: *Self = @fieldParentPtr("interface", itf); - const desc = desc_ptr.*; - - const e = desc.endpoint; - const ep_i: u4 = epn(e.num); - assert(ep_i < cfg.max_endpoints_count); - log.info("ep_open called for ep{}", .{ep_i}); - - const mps: u16 = desc.max_packet_size.into(); - assert(mps > 0 and mps <= 2047); - - // EP0 shares a single DMA buffer for both directions. - if (e.num == .ep0) { - const out_st = self.st(.ep0, .Out); - const in_st = self.st(.ep0, .In); - if (ep0_dma().raw == 0) { - log.warn("EP0 DMA is null!", .{}); - } - if (out_st.buf.len == 0 and in_st.buf.len == 0) { - // this should probably be fixed at 64 bytes for EP0 for HS? - const buf = self.endpoint_alloc(64); - out_st.buf = buf; - in_st.buf = buf; - - const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); - log.info("Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); - ep0_dma().raw = ptr_val; - uep_max_len(0).raw = @intCast(64); - } else { - // Ensure both directions point at the same backing buffer. - if (out_st.buf.len == 0) out_st.buf = in_st.buf; - if (in_st.buf.len == 0) in_st.buf = out_st.buf; - } - } else { - log.debug("ep_open ep{} dir={}", .{ ep_i, e.dir }); - // Non-EP0: separate DMA buffers per direction - const st_ep = self.st(e.num, e.dir); - - if (st_ep.buf.len == 0) { - st_ep.buf = self.endpoint_alloc(@as(usize, 512)); - } - - const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); - - if (e.dir == .Out) { - uep_rx_dma(ep_i).raw = ptr_val; - uep_max_len(ep_i).raw = mps; - set_rx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); - } else { - uep_tx_dma(ep_i).raw = ptr_val; - set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); - } - } - log.debug("allocation and DMA setup done for ep{}", .{ep_i}); - - uep_t_len(ep_i).raw = 0; - - // Enable endpoint direction in UEP_CONFIG bitmaps. - // TODO: make this a function, too ugly here - var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; - if (e.num != .ep0) { - if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); - } - regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; - - // Endpoint type ISO marking (only for ISO endpoints). - if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { - var type_raw: u32 = regs().UEP_TYPE.raw; - if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); - regs().UEP_TYPE.raw = type_raw; - } - - // EP0 OUT always ACK - if (e.num == .ep0 and e.dir == .Out) { - self.arm_ep0_out_always(); - } - - log.debug("ep_open completed for ep{}", .{ep_i}); - } - - fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { - log.info("ep_listen called for ep{} len={}", .{ ep_num, len }); - const self: *Self = @fieldParentPtr("interface", itf); - - // EP0 OUT is always armed; ignore listen semantics here. - if (ep_num == .ep0) { - const st0 = self.st(.ep0, .Out); - st0.rx_limit = @as(u16, @intCast(len)); - // set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); - return; - } - - const ep_i: u4 = epn(ep_num); - if (ep_i > cfg.max_endpoints_count) - @panic("ep_listen called for invalid endpoint"); - - const st_out = self.st(ep_num, .Out); - if (st_out.buf.len == 0) - @panic("ep_listen called for endpoint with no buffer allocated"); - - // Must not be called again until packet received. - if (st_out.rx_armed) - @panic("ep_listen called while OUT endpoint already armed"); - - const limit = @min(st_out.buf.len, @as(usize, @intCast(len))); - st_out.rx_limit = @as(u16, @intCast(limit)); - st_out.rx_armed = true; - st_out.rx_last_len = 0; - - uep_max_len(ep_i).raw = @as(u16, @intCast(limit)); - - asm volatile (""); - set_rx_ctrl(ep_i, RES_ACK, current_rx_tog(ep_i), false); - } - - fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { - const self: *Self = @fieldParentPtr("interface", itf); - - const st_out = self.st(ep_num, .Out); - assert(st_out.buf.len != 0); - - const want: usize = @as(usize, st_out.rx_last_len); - assert(want <= st_out.buf.len); - - // Must be called exactly once per received packet. - // Enforce by clearing rx_last_len after consumption. - defer st_out.rx_last_len = 0; - - var remaining: []align(4) u8 = st_out.buf[0..want]; - var copied: usize = 0; - - for (data) |dst| { - if (remaining.len == 0) break; - const n = @min(dst.len, remaining.len); - @memcpy(dst[0..n], remaining[0..n]); - remaining = @alignCast(remaining[n..]); - copied += n; - } - - // Driver is responsible for re-arming via ep_listen(). - return @as(types.Len, @intCast(copied)); - } - - fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { - const self: *Self = @fieldParentPtr("interface", itf); - assert(vec.len > 0); - - const ep_i: u4 = epn(ep_num); - assert(ep_i < cfg.max_endpoints_count); - - const st_in = self.st(ep_num, .In); - assert(st_in.buf.len != 0); - - if (st_in.tx_busy) { - log.warn("ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); - return 0; - } - - // Copy vector into DMA buffer (up to MPS/buffer size) - var w: usize = 0; - for (vec) |chunk| { - if (w >= st_in.buf.len) break; - const n = @min(chunk.len, st_in.buf.len - w); - @memcpy(st_in.buf[w .. w + n], chunk[0..n]); - w += n; - } - - uep_t_len(ep_i).raw = @as(u16, @intCast(w)); - - st_in.tx_busy = true; - // Arm IN - - set_tx_ctrl(ep_i, RES_ACK, current_tx_tog(ep_i), false); - - // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. - return @as(types.Len, @intCast(w)); - } - - // ---- HW init --------------------------------------------------------- - - fn usbhd_hw_init() void { - // Reset SIE and clear FIFO - regs().UHOST_CTRL.raw = 0; - regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); - regs().USB_CTRL.raw = 0; // not sure if writing zero then val is ok? - regs().USB_CTRL.modify(.{ - .RB_UC_CLR_ALL = 1, - .RB_UC_RST_SIE = 1, - .RB_UC_INT_BUSY = 1, - }); - // wait 10us, TODO: replace with timer delay - var i: u32 = 0; - while (i < 1440) : (i += 1) { - asm volatile ("nop"); - } - - regs().USB_CTRL.modify(.{ - .RB_UC_RST_SIE = 0, - }); - - regs().USB_CTRL.modify(.{ - .RB_UC_DMA_EN = 1, - .RB_UC_INT_BUSY = 1, - .RB_UC_SPEED_TYPE = speed_type(cfg), - }); - - // Enable source interrupts (we poll these flags, interrupt disabled) - regs().USB_INT_EN.modify(.{ - .RB_U_1WIRE_MODE = 1, // actually SETUP_ACT? - .RB_UIE_BUS_RST__RB_UIE_DETECT = 1, - .RB_UIE_TRANSFER = 1, - .RB_UIE_SUSPEND = 1, - .RB_UIE_FIFO_OV = 1, - // .RB_UIE_HST_SOF = 1, - .RB_UIE_DEV_NAK = 1, - }); - } - }; -} - -///! Skeleton ISR -pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { - const fg = regs().USB_INT_FG.raw; - regs().USB_INT_FG.raw = fg; - @panic("Don't Enable USBHS Interrupt, Not yet supported!"); -} diff --git a/port/wch/ch32v/src/hals/ch32v30x.zig b/port/wch/ch32v/src/hals/ch32v30x.zig index af730614b..2b0ad04e6 100644 --- a/port/wch/ch32v/src/hals/ch32v30x.zig +++ b/port/wch/ch32v/src/hals/ch32v30x.zig @@ -6,7 +6,7 @@ pub const time = @import("time.zig"); pub const i2c = @import("i2c.zig"); pub const usart = @import("usart.zig"); const std = @import("std"); -// pub const usb = @import("./usbhs.zig"); +pub const usbhs = @import("./usbhs.zig"); /// HSI (High Speed Internal) oscillator frequency /// This is the fixed internal RC oscillator frequency for CH32V30x diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index a022ffbdb..588b7e0ed 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -1,16 +1,9 @@ -//! WCH USBHD/USBHS device backend (polled) +//! WCH USBHD/USBHS device backend //! -//! Key semantics (per DeviceController in usb.zig): -//! - Must call controller.on_buffer() on IN completion (especially EP0 IN). -//! - Must call controller.on_buffer() on OUT receipt; driver will call ep_readv() exactly once. -//! - EP0 OUT must always accept status-stage OUT ZLP (controller does not always re-arm EP0 OUT). -//! -//! Ownership safety: -//! - OUT buffer is only read after UIF_TRANSFER OUT event; endpoint set to NAK immediately after. -//! - IN buffer is only written when not tx_busy; endpoint set to NAK immediately after IN complete. const std = @import("std"); const assert = std.debug.assert; +const log = std.log.scoped(.ch32_usbhs); const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; @@ -20,27 +13,22 @@ const descriptor = usb.descriptor; pub const USBHD_MAX_ENDPOINTS_COUNT = 16; -var pool: [2048]u8 = undefined; +const max_buffer_pool_size = USBHD_MAX_ENDPOINTS_COUNT * 2 * 512 + 64; pub const Config = struct { max_endpoints_count: comptime_int = USBHD_MAX_ENDPOINTS_COUNT, - /// Some chips have USBHD regs but no HS PHY. Force FS in that case. - has_hs_phy: bool = true, prefer_high_speed: bool = true, /// Static buffer pool in SRAM, bump-allocated. - buffer_bytes: comptime_int = 2048, - - /// Enable SIE "int busy" behavior: pauses during UIF_TRANSFER so polling won't lose INT_ST context. - int_busy: bool = true, + buffer_bytes: comptime_int = max_buffer_pool_size, /// Future seam only; not implemented in this initial version. use_interrupts: bool = false, }; const Regs = @TypeOf(peripherals.USBHS); -fn regs() Regs { +pub fn regs() Regs { return peripherals.USBHS; } @@ -52,7 +40,6 @@ const EpState = struct { rx_last_len: u16 = 0, // valid until ep_readv() consumes it // IN: tx_busy: bool = false, - tx_last_len: u16 = 0, }; fn PerEndpointArray(comptime N: comptime_int) type { @@ -62,14 +49,9 @@ fn PerEndpointArray(comptime N: comptime_int) type { fn epn(ep: types.Endpoint.Num) u4 { return @as(u4, @intCast(@intFromEnum(ep))); } -fn dir_index(d: types.Dir) u1 { - return @as(u1, @intCast(@intFromEnum(d))); -} - fn speed_type(comptime cfg: Config) u2 { - if (!cfg.has_hs_phy) return 0; // FS if (!cfg.prefer_high_speed) return 0; // FS - return 1; // HS (per USBHS.zig field comment) + return 1; } // --- USBHD token encodings --- @@ -115,11 +97,21 @@ const Tog = enum(u2) { MDATA = 0b11, }; +fn toggle_next(tog: u2) u2 { + return if (tog == TOG_DATA0) TOG_DATA1 else TOG_DATA0; +} + // We use offset-based access for per-EP regs to keep helpers compact. const RegU32 = microzig.mmio.Mmio(packed struct(u32) { v: u32 = 0 }); const RegU16 = microzig.mmio.Mmio(packed struct(u16) { v: u16 = 0 }); const RegU8 = microzig.mmio.Mmio(packed struct(u8) { v: u8 = 0 }); -pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { _reserved2: u2 = 0, AUTO: bool = false, TOG: u2 = 0, _reserved0: u1 = 0, RES: u2 = 0 }); +pub const RegCtrl = microzig.mmio.Mmio(packed struct(u8) { + RES: u2 = 0, + _reserved0: u1 = 0, + TOG: u2 = 0, + AUTO: bool = false, + _reserved1: u2 = 0, +}); fn baseAddr() usize { return @intFromPtr(peripherals.USBHS); @@ -130,9 +122,6 @@ fn mmio_u32(off: usize) *volatile RegU32 { fn mmio_u16(off: usize) *volatile RegU16 { return @ptrFromInt(baseAddr() + off); } -fn mmio_u8(off: usize) *volatile RegU8 { - return @ptrFromInt(baseAddr() + off); -} fn mmio_tx_ctrl(off: usize) *volatile RegCtrl { return @ptrFromInt(baseAddr() + off); } @@ -142,7 +131,6 @@ fn mmio_rx_ctrl(off: usize) *volatile RegCtrl { // RX DMA: 0x20 + (ep-1)*4 (EP1..EP15) // EP0 has its own dedicated DMA reg. -// URGENT TODO: FIX EP0 DMA handling! fn ep0_dma() *volatile RegU32 { return mmio_u32(0x1C); } @@ -153,7 +141,6 @@ fn uep_rx_dma(ep: u4) *volatile RegU32 { fn uep_tx_dma(ep: u4) *volatile RegU32 { return mmio_u32(0x5C + (@as(usize, ep - 1) * 4)); } - // MAX_LEN: EP0..EP15 at 0x98 + ep*4 fn uep_max_len(ep: u4) *volatile RegU16 { return mmio_u16(0x98 + (@as(usize, ep) * 4)); @@ -169,12 +156,11 @@ pub fn uep_tx_ctrl(ep: u4) *volatile RegCtrl { } fn uep_rx_ctrl(ep: u4) *volatile RegCtrl { - return mmio_tx_ctrl(0xDA + (@as(usize, ep) * 4)); + return mmio_rx_ctrl(0xDB + (@as(usize, ep) * 4)); } fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { // [1:0]=RES, [4:3]=TOG, [5]=AUTO - std.log.debug("ch32: set_tx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); const tx_ctrl: *volatile RegCtrl = uep_tx_ctrl(ep); tx_ctrl.write(.{ .RES = res, @@ -183,20 +169,25 @@ fn set_tx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { }); } fn set_rx_ctrl(ep: u4, res: u2, tog: u2, auto: bool) void { - std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); - var v: u8 = 0; - v |= @as(u8, res); - v |= @as(u8, tog) << 3; - if (auto) v |= 1 << 5; - uep_rx_ctrl(ep).raw = v; + // std.log.debug("ch32: set_rx_ctrl ep={} res={} tog={} auto={}", .{ ep, res, tog, auto }); + const ctrl: *volatile RegCtrl = uep_rx_ctrl(ep); + ctrl.write(.{ + .RES = res, + .TOG = tog, + .AUTO = auto, + }); } -fn fmt_slice(dest: []u8, msg: []const u8) []const u8 { - return std.fmt.bufPrint(dest, "{x}", .{msg}) catch &.{}; +fn current_rx_tog(ep: u4) u2 { + return uep_rx_ctrl(ep).read().TOG; +} +fn current_tx_tog(ep: u4) u2 { + return uep_tx_ctrl(ep).read().TOG; } /// Polled USBHD device backend for microzig core USB controller. -pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { +/// maybe we should pass a list of valid controller types, for validation +pub fn Polled(comptime cfg: Config) type { comptime { if (cfg.max_endpoints_count > USBHD_MAX_ENDPOINTS_COUNT) @compileError("USBHD max_endpoints_count cannot exceed 16"); @@ -216,23 +207,23 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { }; endpoints: PerEndpointArray(cfg.max_endpoints_count), + pool: [cfg.buffer_bytes]u8 = undefined, + buffer_pool: []align(4) u8, - controller: usb.DeviceController(controller_config), interface: usb.DeviceInterface, pub fn init() Self { var self: Self = .{ .endpoints = undefined, .buffer_pool = undefined, - .controller = .init, .interface = .{ .vtable = &vtable }, }; @memset(std.mem.asBytes(&self.endpoints), 0); - @memset(pool[0..64], 0x7e); - @memset(pool[64..], 0); + @memset(self.pool[0..64], 0x7e); + @memset(self.pool[64..], 0); - self.buffer_pool = @alignCast(pool[64..]); + self.buffer_pool = @alignCast(self.pool[64..]); usbhd_hw_init(); @@ -264,7 +255,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { return self; } - // TODO: replace with fixedbuffer allocator + // TODO: replace with fixedbuffer allocator? fn endpoint_alloc(self: *Self, size: usize) []align(4) u8 { assert(self.buffer_pool.len >= size); const out = self.buffer_pool[0..size]; @@ -278,11 +269,10 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { fn arm_ep0_out_always(self: *Self) void { _ = self; - std.log.debug("ch32: arm_ep0, ACK OUT, NAK IN", .{}); - // EP0 OUT is always ACK with auto-toggle. - set_rx_ctrl(0, RES_ACK, TOG_DATA1, true); + // EP0 OUT is always ACK. + set_rx_ctrl(0, RES_ACK, TOG_DATA0, false); // EP0 IN remains NAK until data is queued. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); + // set_tx_ctrl(0, RES_NAK, TOG_DATA0, false); } fn on_bus_reset_local(self: *Self) void { @@ -291,14 +281,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_armed = false; self.endpoints[i][@intFromEnum(types.Dir.Out)].rx_last_len = 0; self.endpoints[i][@intFromEnum(types.Dir.In)].tx_busy = false; - self.endpoints[i][@intFromEnum(types.Dir.In)].tx_last_len = 0; } // Default: NAK all non-EP0 endpoints. for (1..cfg.max_endpoints_count) |i| { const e: u4 = @as(u4, @intCast(i)); - set_rx_ctrl(e, RES_NAK, TOG_DATA0, true); - set_tx_ctrl(e, RES_NAK, TOG_DATA0, true); + set_rx_ctrl(e, RES_NAK, TOG_DATA0, false); + set_tx_ctrl(e, RES_NAK, TOG_DATA0, false); } // EP0 special. @@ -314,19 +303,19 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- comptime dispatch helpers (required by DeviceController API) ---- - fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, buf: []u8) void { + fn call_on_buffer(self: *Self, dir: types.Dir, ep: u4, controller: anytype) void { // controller.on_buffer requires comptime ep parameter switch (dir) { .In => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @enumFromInt(i); - self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }, buf); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .In }); }, }, .Out => switch (ep) { inline 0...15 => |i| { const num: types.Endpoint.Num = @enumFromInt(i); - self.controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }, buf); + controller.on_buffer(&self.interface, .{ .num = num, .dir = .Out }); }, }, } @@ -334,28 +323,28 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // ---- Poll loop ------------------------------------------------------- - pub fn poll(self: *Self) void { - var did_work = false; + pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { + _ = in_isr; while (true) { const fg: u8 = regs().USB_INT_FG.raw; if (fg == 0) break; - const stv = regs().USB_INT_ST.read(); - const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); - const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); - if (token != TOKEN_SOF) { - std.log.debug("ch32: INT_FG = 0x{x}, token: {}", .{ fg, @as(Token, @enumFromInt(token)) }); - did_work = true; + if ((fg & UIF_HST_SOF) != 0) { + // acknowledge SOF but ignore + regs().USB_INT_FG.raw = UIF_HST_SOF; + } + if ((fg & UIF_SUSPEND) != 0) { + // acknowledge SUSPEND but ignore + regs().USB_INT_FG.raw = UIF_SUSPEND; } if (fg & UIF_FIFO_OV != 0) { - std.log.warn("ch32: FIFO overflow!", .{}); + log.warn("FIFO overflow!", .{}); regs().USB_INT_FG.raw = UIF_FIFO_OV; - continue; } if ((fg & UIF_BUS_RST) != 0) { - std.log.info("ch32: bus reset\n\n\n", .{}); + log.info("bus reset\n\n\n", .{}); // clear regs().USB_INT_FG.raw = UIF_BUS_RST; @@ -363,85 +352,69 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { set_address(&self.interface, 0); self.on_bus_reset_local(); - self.controller.on_bus_reset(); - continue; + controller.on_bus_reset(&self.interface); } if ((fg & UIF_SETUP_ACT) != 0) { - std.log.info("ch32: SETUP received", .{}); - // Some parts also assert UIF_TRANSFER w/ TOKEN_SETUP; clear both to avoid double-processing. + log.info("SETUP received", .{}); regs().USB_INT_FG.raw = UIF_SETUP_ACT; - if ((fg & UIF_TRANSFER) != 0) regs().USB_INT_FG.raw = UIF_TRANSFER; - - // const stv = regs().USB_INT_ST.read(); - // std.log.debug("ch32: INT_ST={any}", .{stv}); const setup: types.SetupPacket = self.read_setup_from_ep0(); - std.log.debug("ch32: Setup: {any}", .{setup}); // After SETUP, EP0 IN data stage starts with DATA1. - set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); - set_rx_ctrl(0, RES_NAK, TOG_DATA1, true); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); - self.controller.on_setup_req(&self.interface, &setup); - continue; + controller.on_setup_req(&self.interface, &setup); } if ((fg & UIF_TRANSFER) != 0) { - if (token != TOKEN_SOF) { - std.log.info("ch32: TRANSFER event", .{}); - } // clear transfer regs().USB_INT_FG.raw = UIF_TRANSFER; - - self.handle_transfer(ep, token); - continue; + const stv = regs().USB_INT_ST.read(); + const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); + const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); + self.handle_transfer(ep, token, controller); } - - // Clear anything else we don't handle explicitly - regs().USB_INT_FG.raw = fg; - // 0x4 => SUSPEND - // 0x5 => SUSPEND | RESET - // 0x8 => HST_SOF - // 0x10 => FIFO_OV - // 0x40 => ISO_ACT - } - if (did_work) { - std.log.info("", .{}); } } - fn handle_transfer(self: *Self, ep: u4, token: u2) void { + fn handle_transfer(self: *Self, ep: u4, token: u2, controller: anytype) void { if (ep >= cfg.max_endpoints_count) return; if (token == TOKEN_SOF) return; - std.log.debug("ch32: handle_transfer ep={} token={}", .{ ep, @as(Token, @enumFromInt(token)) }); - // std.log.debug("ch32: token={}", .{token}); switch (token) { - TOKEN_OUT => self.handle_out(ep), + TOKEN_OUT => self.handle_out(ep, controller), TOKEN_SOF => {}, - TOKEN_IN => self.handle_in(ep), + TOKEN_IN => self.handle_in(ep, controller), TOKEN_SETUP => { // If UIF_SETUP_ACT isn't present, handle SETUP via TRANSFER. if (ep == 0) { const setup: types.SetupPacket = self.read_setup_from_ep0(); - std.log.debug("ch32: Setup: {any}", .{setup}); - set_tx_ctrl(0, RES_NAK, TOG_DATA1, true); - self.controller.on_setup_req(&self.interface, &setup); + log.debug("Setup: {any}", .{setup}); + set_tx_ctrl(0, RES_NAK, TOG_DATA1, false); + set_rx_ctrl(0, RES_ACK, TOG_DATA1, false); + controller.on_setup_req(&self.interface, &setup); } }, } } - fn handle_out(self: *Self, ep: u4) void { + fn handle_out(self: *Self, ep: u4, controller: anytype) void { const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; - std.log.info("ch32: EP{} OUT received {} bytes", .{ ep, len }); + const stv = regs().USB_INT_ST.read(); + const rx_ctrl = uep_rx_ctrl(ep).read(); + log.debug( + "OUT ep{} len={} tog_ok={} rx_res={} rx_tog={}", + .{ ep, len, stv.RB_UIS_TOG_OK, rx_ctrl.RES, rx_ctrl.TOG }, + ); // EP0 OUT is always armed; accept status ZLP. if (ep == 0) { - asm volatile ("" ::: .{ .memory = true }); const st_out = self.st(.ep0, .Out); st_out.rx_last_len = len; + const next = toggle_next(current_rx_tog(0)); + set_rx_ctrl(0, RES_ACK, next, false); // stay ACK - self.call_on_buffer(.Out, 0, st_out.buf[0..@min(@as(usize, len), st_out.buf.len)]); + self.call_on_buffer(.Out, 0, controller); return; } @@ -450,61 +423,44 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { // Only read if previously armed (ep_listen) if (!st_out.rx_armed) { - set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + // set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); return; } - asm volatile ("" ::: .{ .memory = true }); // Disarm immediately (NAK) to regain ownership. st_out.rx_armed = false; - set_rx_ctrl(ep, RES_NAK, TOG_DATA0, true); + const next = toggle_next(current_rx_tog(ep)); + set_rx_ctrl(ep, RES_NAK, next, false); const n = @min(@as(usize, len), st_out.buf.len); st_out.rx_last_len = @as(u16, @intCast(n)); - self.call_on_buffer(.Out, ep, st_out.buf[0..n]); + self.call_on_buffer(.Out, ep, controller); } // IN => into host from device - fn handle_in(self: *Self, ep: u4) void { + fn handle_in(self: *Self, ep: u4, controller: anytype) void { const num: types.Endpoint.Num = @enumFromInt(ep); const st_in = self.st(num, .In); if (!st_in.tx_busy) { - set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + set_tx_ctrl(ep, RES_NAK, current_tx_tog(ep), false); return; } - asm volatile ("" ::: .{ .memory = true }); - // Mark free before calling on_buffer(), so EP0_IN logic can immediately queue next chunk. - const sent_len = st_in.tx_last_len; st_in.tx_busy = false; - st_in.tx_last_len = 0; - - // set_tx_ctrl(ep, RES_NAK, TOG_DATA0, true); + const next = toggle_next(current_tx_tog(ep)); + set_tx_ctrl(ep, RES_NAK, next, false); - std.log.info("ch32: EP{} IN completed, sent {} bytes", .{ ep, sent_len }); - var buf: [256]u8 = undefined; - // var str = &buf[0..]; - const str = fmt_slice(buf[0..], st_in.buf[0..sent_len]); - std.log.debug("ch32: EP{} IN data: {s}", .{ ep, str }); // Notify controller/drivers of IN completion. - std.log.debug("tx_ctrl before on_buffer: {any}", .{uep_tx_ctrl(ep)}); - // set_tx_ctrl(ep, RES_NAK, TOG_DATA1, true); - - self.call_on_buffer(.In, ep, st_in.buf[0..sent_len]); - - // EP0 OUT must remain ACK - if (self.controller.tx_slice == null) { - if (ep == 0) self.arm_ep0_out_always(); - } + self.call_on_buffer(.In, ep, controller); } // ---- VTable functions ------------------------------------------------ fn set_address(_: *usb.DeviceInterface, addr: u7) void { - std.log.info("ch32: set_address {}", .{addr}); + log.info("set_address {}", .{addr}); regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); } @@ -515,7 +471,7 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const e = desc.endpoint; const ep_i: u4 = epn(e.num); assert(ep_i < cfg.max_endpoints_count); - std.log.info("ch32: ep_open called for ep{}", .{ep_i}); + log.info("ep_open called for ep{}", .{ep_i}); const mps: u16 = desc.max_packet_size.into(); assert(mps > 0 and mps <= 2047); @@ -525,16 +481,16 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const out_st = self.st(.ep0, .Out); const in_st = self.st(.ep0, .In); if (ep0_dma().raw == 0) { - std.log.warn("ch32: EP0 DMA is null!", .{}); + log.warn("EP0 DMA is null!", .{}); } if (out_st.buf.len == 0 and in_st.buf.len == 0) { - // this should probably be fixed at 64 bytes for EP0? + // this should probably be fixed at 64 bytes for EP0 for HS? const buf = self.endpoint_alloc(64); out_st.buf = buf; in_st.buf = buf; const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); - std.log.info("ch32: Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + log.info("Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); ep0_dma().raw = ptr_val; uep_max_len(0).raw = @intCast(64); } else { @@ -543,11 +499,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (in_st.buf.len == 0) in_st.buf = out_st.buf; } } else { + log.debug("ep_open ep{} dir={}", .{ ep_i, e.dir }); // Non-EP0: separate DMA buffers per direction const st_ep = self.st(e.num, e.dir); if (st_ep.buf.len == 0) { - st_ep.buf = self.endpoint_alloc(@as(usize, mps)); + st_ep.buf = self.endpoint_alloc(@as(usize, 512)); } const ptr_val: u32 = @as(u32, @intCast(@intFromPtr(st_ep.buf.ptr))); @@ -555,27 +512,27 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { if (e.dir == .Out) { uep_rx_dma(ep_i).raw = ptr_val; uep_max_len(ep_i).raw = mps; - set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + set_rx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); } else { uep_tx_dma(ep_i).raw = ptr_val; - set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, false); } } + log.debug("allocation and DMA setup done for ep{}", .{ep_i}); uep_t_len(ep_i).raw = 0; // Enable endpoint direction in UEP_CONFIG bitmaps. + // TODO: make this a function, too ugly here var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; if (e.num != .ep0) { - // if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); } regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; - // Endpoint type ISO marking (optional, only if you use ISO). + // Endpoint type ISO marking (only for ISO endpoints). if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { var type_raw: u32 = regs().UEP_TYPE.raw; - // if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + ep_i)); if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); regs().UEP_TYPE.raw = type_raw; } @@ -585,40 +542,32 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { self.arm_ep0_out_always(); } - // IMPORTANT per usb.zig comment: - // For IN endpoints, ep_open may call controller.on_buffer so drivers can prime initial data. - // Must be comptime-dispatched. - if (e.dir == .In and e.num != .ep0) { - // Empty slice -> “buffer is available / endpoint opened” - switch (e.num) { - inline else => |num_ct| { - const st_in = self.st(num_ct, .In); - self.controller.on_buffer(&self.interface, .{ .num = num_ct, .dir = .In }, st_in.buf[0..0]); - }, - } - } + log.debug("ep_open completed for ep{}", .{ep_i}); } fn ep_listen(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, len: types.Len) void { - std.log.info("ch32: ep_listen called for ep{} len={}", .{ ep_num, len }); + log.info("ep_listen called for ep{} len={}", .{ ep_num, len }); const self: *Self = @fieldParentPtr("interface", itf); // EP0 OUT is always armed; ignore listen semantics here. if (ep_num == .ep0) { const st0 = self.st(.ep0, .Out); st0.rx_limit = @as(u16, @intCast(len)); - set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); + // set_rx_ctrl(0, RES_ACK, TOG_DATA0, true); return; } const ep_i: u4 = epn(ep_num); - assert(ep_i < cfg.max_endpoints_count); + if (ep_i > cfg.max_endpoints_count) + @panic("ep_listen called for invalid endpoint"); const st_out = self.st(ep_num, .Out); - assert(st_out.buf.len != 0); + if (st_out.buf.len == 0) + @panic("ep_listen called for endpoint with no buffer allocated"); // Must not be called again until packet received. - assert(!st_out.rx_armed); + if (st_out.rx_armed) + @panic("ep_listen called while OUT endpoint already armed"); const limit = @min(st_out.buf.len, @as(usize, @intCast(len))); st_out.rx_limit = @as(u16, @intCast(limit)); @@ -627,12 +576,11 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { uep_max_len(ep_i).raw = @as(u16, @intCast(limit)); - asm volatile ("" ::: .{ .memory = true }); - set_rx_ctrl(ep_i, RES_ACK, TOG_DATA0, true); + asm volatile (""); + set_rx_ctrl(ep_i, RES_ACK, current_rx_tog(ep_i), false); } fn ep_readv(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, data: []const []u8) types.Len { - std.log.info("ch32: ep_readv called for ep{}", .{ep_num}); const self: *Self = @fieldParentPtr("interface", itf); const st_out = self.st(ep_num, .Out); @@ -661,12 +609,6 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { } fn ep_writev(itf: *usb.DeviceInterface, ep_num: types.Endpoint.Num, vec: []const []const u8) types.Len { - std.log.info("ch32: ep_writev called for {}", .{ep_num}); - for (vec) |chunk| { - std.log.debug("ch32: ep_writev data chunk len={}", .{chunk.len}); - std.log.debug("ch32: chunk: {any}", .{chunk}); - } - const self: *Self = @fieldParentPtr("interface", itf); assert(vec.len > 0); @@ -676,12 +618,8 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { const st_in = self.st(ep_num, .In); assert(st_in.buf.len != 0); - std.log.debug("tx_ctrl before write: {any}", .{uep_tx_ctrl(ep_i)}); - if (st_in.tx_busy) { - // do I want to set anything in this case? - std.log.warn("ch32: ep_writev called while IN endpoint busy, returning 0", .{}); - set_tx_ctrl(ep_i, RES_NAK, TOG_DATA0, true); + log.warn("ep_writev called while {} IN endpoint busy, returning 0", .{ep_num}); return 0; } @@ -693,22 +631,13 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { @memcpy(st_in.buf[w .. w + n], chunk[0..n]); w += n; } - std.log.debug("ch32: Writing chunk of len={}", .{w}); - - asm volatile ("" ::: .{ .memory = true }); uep_t_len(ep_i).raw = @as(u16, @intCast(w)); - st_in.tx_last_len = @as(u16, @intCast(w)); st_in.tx_busy = true; - // Arm IN - if (w < vec[0].len) { - set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); - } else { - set_tx_ctrl(ep_i, RES_ACK, TOG_DATA1, true); - } - std.log.debug("tx_ctrl after write: {any}", .{uep_tx_ctrl(ep_i)}); + + set_tx_ctrl(ep_i, RES_ACK, current_tx_tog(ep_i), false); // For ZLP ACK, usb.DeviceInterface.ep_ack() expects ep_writev() returns 0. return @as(types.Len, @intCast(w)); @@ -724,11 +653,12 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 1, .RB_UC_RST_SIE = 1, + .RB_UC_INT_BUSY = 1, }); // wait 10us, TODO: replace with timer delay var i: u32 = 0; - while (i < 480) : (i += 1) { - asm volatile ("" ::: .{ .memory = true }); + while (i < 1440) : (i += 1) { + asm volatile ("nop"); } regs().USB_CTRL.modify(.{ @@ -737,33 +667,27 @@ pub fn Polled(controller_config: usb.Config, comptime cfg: Config) type { regs().USB_CTRL.modify(.{ .RB_UC_DMA_EN = 1, - .RB_UC_INT_BUSY = if (cfg.int_busy) 1 else 0, + .RB_UC_INT_BUSY = 1, .RB_UC_SPEED_TYPE = speed_type(cfg), }); - // Enable source interrupts (we poll flags; NVIC off) + // Enable source interrupts (we poll these flags, interrupt disabled) regs().USB_INT_EN.modify(.{ .RB_U_1WIRE_MODE = 1, // actually SETUP_ACT? .RB_UIE_BUS_RST__RB_UIE_DETECT = 1, .RB_UIE_TRANSFER = 1, .RB_UIE_SUSPEND = 1, .RB_UIE_FIFO_OV = 1, - .RB_UIE_HST_SOF = 1, + // .RB_UIE_HST_SOF = 1, + .RB_UIE_DEV_NAK = 1, }); - - // regs().USB_INT_ST.modify(.{ .RB_UIS_IS_NAK = 1 }); - - // Set some defaults, just in case - // regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = 0 }); - // regs().UEP_CONFIG__UHOST_CTRL.write_raw(0); - // regs().UEP_TYPE.write_raw(0); - // regs().UEP_BUF_MOD.write_raw(0); } }; } +///! Skeleton ISR pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { const fg = regs().USB_INT_FG.raw; - std.log.warn("USBHS interrupt handler called, flags: {}", .{fg}); - regs().USB_INT_FG.raw = fg; // clear all + regs().USB_INT_FG.raw = fg; + @panic("Don't Enable USBHS Interrupt, Not yet supported!"); } From 493c041c7a3e00c8a71149f40b14d1fa6c51dc71 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 14:55:58 -0500 Subject: [PATCH 75/80] revert board clock setup to one that works on current upstream. --- port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig | 4 ++-- port/wch/ch32v/src/hals/ch32v30x.zig | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig b/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig index 3aad5ef43..84cfd20f8 100644 --- a/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig +++ b/port/wch/ch32v/src/boards/CH32V307V-R1-1v0.zig @@ -8,13 +8,13 @@ const ch32v = microzig.hal; pub const clock_config: ch32v.clocks.Config = .{ .source = .hse, .hse_frequency = 8_000_000, - .target_frequency = 144_000_000, + .target_frequency = 48_000_000, }; /// CPU frequency is derived from clock config pub const cpu_frequency = clock_config.target_frequency; -/// Board-specific init: set 144 MHz clock, enable SysTick time +/// Board-specific init: set 48 MHz clock, enable SysTick time pub fn init() void { ch32v.clocks.init(clock_config); ch32v.time.init(); diff --git a/port/wch/ch32v/src/hals/ch32v30x.zig b/port/wch/ch32v/src/hals/ch32v30x.zig index 2b0ad04e6..4ac0510c3 100644 --- a/port/wch/ch32v/src/hals/ch32v30x.zig +++ b/port/wch/ch32v/src/hals/ch32v30x.zig @@ -11,6 +11,7 @@ pub const usbhs = @import("./usbhs.zig"); /// HSI (High Speed Internal) oscillator frequency /// This is the fixed internal RC oscillator frequency for CH32V30x pub const hsi_frequency: u32 = 8_000_000; // 8 MHz +pub const hse_frequency: u32 = 8_000_000; // 8 MHz /// Default interrupt handlers provided by the HAL pub const default_interrupts: microzig.cpu.InterruptOptions = .{ @@ -23,12 +24,12 @@ pub fn init() void { // Configure TIM2 timing driver time.init(); clocks.init(.{ - .hse_frequency = 8_000_000, - .target_frequency = 144_000_000, .source = .hse, + .hse_frequency = hse_frequency, + .target_frequency = 48_000_000, }); // 8 MHz external crystal clocks.enable_usbhs_clock(.{ - .ref_source_hz = 8_000_000, + .ref_source_hz = hse_frequency, .ref_source = .hse, }); } From 02ce566e9b39d1c915f79ccf8ace2aa931436a82 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 15:02:04 -0500 Subject: [PATCH 76/80] revert most of clocks.zig to upstream. --- port/wch/ch32v/src/hals/clocks.zig | 116 ++--------------------------- 1 file changed, 8 insertions(+), 108 deletions(-) diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index 72a3aacdc..a59fed27d 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -86,73 +86,6 @@ pub fn enable_afio_clock() void { RCC.APB2PCENR.modify(.{ .AFIOEN = 1 }); } -const PLL2MUL = enum(u4) { - PLL2_MUL_2_5 = 0, - PLL2_MUL_12_5 = 0b0001, - PLL2_MUL_4 = 0b0010, - PLL2_MUL_5 = 0b0011, - PLL2_MUL_6 = 0b0100, - PLL2_MUL_7 = 0b0101, - PLL2_MUL_8 = 0b0110, - PLL2_MUL_9 = 0b0111, - PLL2_MUL_10 = 0b1000, - PLL2_MUL_11 = 0b1001, - PLL2_MUL_12 = 0b1010, - PLL2_MUL_13 = 0b1011, - PLL2_MUL_14 = 0b1100, - PLL2_MUL_15 = 0b1101, - PLL2_MUL_16 = 0b1110, - PLL2_MUL_20 = 0b1111, -}; - -fn prediv(div: u4) u4 { - return div - 1; -} - -const PREDIV = enum(u4) { - DIV1 = 0b0000, - DIV2 = 0b0001, - DIV3 = 0b0010, - DIV4 = 0b0011, - DIV5 = 0b0100, - DIV6 = 0b0101, - DIV7 = 0b0110, - DIV8 = 0b0111, - DIV9 = 0b1000, - DIV10 = 0b1001, - DIV11 = 0b1010, - DIV12 = 0b1011, - DIV13 = 0b1100, - DIV14 = 0b1101, - DIV15 = 0b1110, - DIV16 = 0b1111, -}; - -const PLL = enum { - PLL1, // - PLL2, - PLL3, -}; - -/// Start a PLL and wait for it to stabilize -/// -pub fn start_pll(pll: PLL) void { - switch (pll) { - .PLL1 => { - RCC.CTLR.modify(.{ .PLLON = 1 }); - while (RCC.CTLR.read().PLLRDY == 0) {} - }, - .PLL2 => { - RCC.CTLR.modify(.{ .PLL2ON = 1 }); - while (RCC.CTLR.read().PLL2RDY == 0) {} - }, - .PLL3 => { - RCC.CTLR.modify(.{ .PLL3ON = 1 }); - while (RCC.CTLR.read().PLL3RDY == 0) {} - }, - } -} - // ============================================================================ // Clock Configuration System // ============================================================================ @@ -299,7 +232,7 @@ fn init_from_hsi(target_freq: u32) void { /// Initialize clocks from HSE to reach target frequency /// Assumes hse_freq and target_freq have been validated at compile time -fn init_from_hse(hse_freq: u32, comptime target_freq: u32) void { +fn init_from_hse(hse_freq: u32, target_freq: u32) void { // Enable HSE RCC.CTLR.modify(.{ .HSEON = 1 }); @@ -334,48 +267,15 @@ fn init_from_hse(hse_freq: u32, comptime target_freq: u32) void { RCC.CFGR0.modify(.{ .SW = 2 }); while (RCC.CFGR0.read().SWS != 2) {} } else if (target_freq == 144_000_000) { - // TODO: Handle the chip specific clock config for PLLMUL, this is for the v307 RCC.CFGR0.modify(.{ .HPRE = 0, // AHB prescaler = 1 .PPRE2 = 0, // APB2 prescaler = 1 - .PPRE1 = 0b101, // APB1 prescaler = 2 + .PPRE1 = 4, // APB1 prescaler = 2 .PLLSRC = 1, // PLL source = HSE .PLLXTPRE = 0, // HSE not divided before PLL - .PLLMUL = 0, // PLL multiplier = 18 + .PLLMUL = 15, // PLL multiplier = 18 }); - // // // coefficients, not reg vals - // const prediv2: u32 = 2; - // const pll2mul: u32 = 9; - // const prediv1: u32 = 1; - // const pllmul: u32 = 8; - - // const sysclk = ((8_000_000 / prediv2) * pll2mul / prediv1) * pllmul; - - // if (sysclk != target_freq) { - // // const std = @import("std"); - // // @compileError(std.fmt.comptimePrint("Warning: Target frequency {} Hz does not match calculated system clock of {} Hz\n", .{ target_freq, sysclk })); - // // _ = sysclk; - // } - - // RCC.CFGR0.modify(.{ - // .HPRE = 1, // AHB prescaler = 1 - // .PPRE2 = 0, // APB2 prescaler = 1 - // .PPRE1 = 0b101, // APB1 prescaler = 2 - // .PLLSRC = 1, // PLL source = HSE - // .PLLMUL = 0b0000, - // }); - - // RCC.CFGR2.modify(.{ - // .PLL2MUL = @intFromEnum(@as(PLL2MUL, .PLL2_MUL_9)), // PLL2MUL = 9 - // .PREDIV2 = prediv(prediv2), // PREDIV2 = 4 - // .PREDIV1 = @intFromEnum(@as(PREDIV, .DIV1)), // HSE not divided before PLL - // }); - // RCC.CTLR.modify(.{ .PLL2ON = 1 }); - // while (RCC.CTLR.read().PLL2RDY == 0) {} - - // RCC.CFGR2.modify(.{ .PREDIV1SRC = 1 }); - // Enable PLL RCC.CTLR.modify(.{ .PLLON = 1 }); while (RCC.CTLR.read().PLLRDY == 0) {} @@ -454,9 +354,7 @@ pub fn get_freqs() ClockSpeeds { // PLL multiplication factor: PLLMUL bits + 2 // Special case: if result is 17, it's actually 18 var pllmul: u32 = @as(u32, pllmul_bits) + 2; - // TODO: Handle chip specific clock config - // if (pllmul == 17) pllmul = 18; - if (@as(u32, pllmul_bits) == 0) pllmul = 18; + if (pllmul == 17) pllmul = 18; if (pllsrc == 0) { // PLL source is HSI @@ -567,8 +465,10 @@ const HSPLLSRC = enum(u2) { hsi = 1, }; -/// Enable + configure USBHS clocks. -/// +// ============================================================================ +// Enable + configure USBHS clocks. +// ============================================================================ + /// Selects USBHS Clock source, options are: /// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or /// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true), From 349fb6a67cf11abb0305638491118c92e2a22c9a Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 15:06:14 -0500 Subject: [PATCH 77/80] mirror rp2xxx better --- examples/wch/ch32v/src/usb_cdc.zig | 15 ++++++++------- port/wch/ch32v/src/hals/usbhs.zig | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index bbf85d2a5..ecc464bb0 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -14,25 +14,26 @@ const AFIO = microzig.chip.peripherals.AFIO; const PFIC = microzig.chip.peripherals.PFIC; const usart = hal.usart.instance.USART1; -const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 +const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 const mco_pin = gpio.Pin.init(0, 8); // PA9 pub const my_interrupts: microzig.cpu.InterruptOptions = .{ - // .TIM2 = time.tim2_handler, .USBHS = usbhs_interrupt_handler, }; pub const microzig_options = microzig.Options{ - .log_level = .debug, .logFn = hal.usart.log, + .log_level = .debug, + .log_scope_levels = &.{ + .{ .scope = .usb_dev, .level = .warn }, + .{ .scope = .usb_ctrl, .level = .warn }, + .{ .scope = .usb_cdc, .level = .warn }, + }, .interrupts = my_interrupts, }; -pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { - // usb_dev.poll(true); - // std.log.debug("usb isr called", .{}); -} +pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void {} fn usb_poll() void { // microzig.cpu.interrupt.disable(.USBHS); diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index 588b7e0ed..e445e50dc 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -3,7 +3,7 @@ const std = @import("std"); const assert = std.debug.assert; -const log = std.log.scoped(.ch32_usbhs); +const log = std.log.scoped(.usb_dev); const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; From e0cef4696f442d4cd1b38d3398e4b65da0be2067 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 15:16:42 -0500 Subject: [PATCH 78/80] revert usart changes, cleanup example to match rp2xxx better --- examples/wch/ch32v/src/usb_cdc.zig | 54 +++++++----------------------- port/wch/ch32v/src/hals/usart.zig | 34 ------------------- 2 files changed, 12 insertions(+), 76 deletions(-) diff --git a/examples/wch/ch32v/src/usb_cdc.zig b/examples/wch/ch32v/src/usb_cdc.zig index ecc464bb0..6b0aba7f4 100644 --- a/examples/wch/ch32v/src/usb_cdc.zig +++ b/examples/wch/ch32v/src/usb_cdc.zig @@ -4,10 +4,9 @@ const microzig = @import("microzig"); const hal = microzig.hal; const time = hal.time; const gpio = hal.gpio; +const usb = microzig.core.usb; -pub const usb = hal.usbhs; - -const USB_Serial = microzig.core.usb.drivers.CDC; +const USB_Serial = usb.drivers.CDC; const RCC = microzig.chip.peripherals.RCC; const AFIO = microzig.chip.peripherals.AFIO; @@ -16,11 +15,6 @@ const PFIC = microzig.chip.peripherals.PFIC; const usart = hal.usart.instance.USART1; const usart_tx_pin = gpio.Pin.init(0, 9); // PA9 -const mco_pin = gpio.Pin.init(0, 8); // PA9 - -pub const my_interrupts: microzig.cpu.InterruptOptions = .{ - .USBHS = usbhs_interrupt_handler, -}; pub const microzig_options = microzig.Options{ .logFn = hal.usart.log, @@ -30,18 +24,9 @@ pub const microzig_options = microzig.Options{ .{ .scope = .usb_ctrl, .level = .warn }, .{ .scope = .usb_cdc, .level = .warn }, }, - .interrupts = my_interrupts, }; -pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void {} - -fn usb_poll() void { - // microzig.cpu.interrupt.disable(.USBHS); - usb_dev.poll(false, &usb_controller); - // microzig.cpu.interrupt.enable(.USBHS); -} - -const USBController = microzig.core.usb.DeviceController(.{ +const USBController = usb.DeviceController(.{ .bcd_usb = .v2_00, .device_triple = .unspecified, .vendor = .{ .id = 0x2E8A, .str = "MicroZig" }, @@ -58,7 +43,7 @@ const USBController = microzig.core.usb.DeviceController(.{ .serial = .{ .itf_notifi = "Board CDC", .itf_data = "Board CDC Data" }, }}); -pub var usb_dev: usb.Polled( +pub var usb_dev: hal.usbhs.Polled( .{ .prefer_high_speed = true }, ) = undefined; @@ -68,33 +53,19 @@ pub fn main() !void { // Board brings up clocks and time microzig.board.init(); microzig.hal.init(); - // RCC.CFGR0.modify(.{ .MCO = 0b0100 }); - // Enable peripheral clocks for USART2 and GPIOA + + // Enable peripheral clocks for USART1 and GPIOA RCC.APB2PCENR.modify(.{ .IOPAEN = 1, // Enable GPIOA clock .AFIOEN = 1, // Enable AFIO clock .USART1EN = 1, // Enable USART1 clock }); - // Ensure USART2 is NOT remapped (default PA2/PA3, not PD5/PD6) - AFIO.PCFR1.modify(.{ .USART2_RM = 0 }); - - // Configure PA2 as alternate function push-pull for USART2 TX + // Configure TX pin as alternate function push-pull usart_tx_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); - // mco_pin.set_output_mode(.alternate_function_push_pull, .max_50MHz); - - // Initialize USART2 at 115200 baud - usart.apply(.{ .baud_rate = 921600 }); - comptime { - const sysclk: comptime_float = 144_000_000; - const hb: comptime_float = 1.0; - const pb2: comptime_float = 1.0; - const pclk = (sysclk / hb) / pb2; - if (!usart.validate_baudrate(921600, pclk, 1.0)) { - @compileError("Bad baud rate"); - } - } + // Initialize USART1 at 115200 baud + usart.apply(.{ .baud_rate = 115200 }); hal.usart.init_logger(usart); std.log.info("UART logging initialized.", .{}); @@ -102,8 +73,7 @@ pub fn main() !void { std.log.info("Initializing USB device.", .{}); usb_dev = .init(); - // microzig.cpu.interrupt.enable(.USBHS); - PFIC.IPRIOR70 = 255; + var i: u32 = 0; var old: u64 = time.get_time_since_boot().to_us(); var new: u64 = 0; @@ -125,7 +95,7 @@ pub fn main() !void { usb_cdc_write(&drivers.serial, "Your message to me was: {s}\r\n", .{message}); } } - usb_poll(); + usb_dev.poll(false, &usb_controller); } } @@ -139,7 +109,7 @@ pub fn usb_cdc_write(serial: *USB_Serial, comptime fmt: []const u8, args: anytyp while (write_buff.len > 0) { write_buff = write_buff[serial.write(write_buff)..]; while (!serial.flush()) - usb_poll(); + usb_dev.poll(false, &usb_controller); } } diff --git a/port/wch/ch32v/src/hals/usart.zig b/port/wch/ch32v/src/hals/usart.zig index 3a37b7aae..f71655363 100644 --- a/port/wch/ch32v/src/hals/usart.zig +++ b/port/wch/ch32v/src/hals/usart.zig @@ -245,40 +245,6 @@ pub const USART = enum(u2) { regs.BRR.write_raw(@intCast(brr_value)); } - pub fn calc_usartdiv(baud_rate: u32, pclk: u32) u32 { - // Calculate baud rate divisor for 16x oversampling - const oversample = 16; - // Formula: BRR = (25 * PCLK) / (4 * baud_rate) - const integerdivider = (25 * pclk) / (4 * baud_rate); - - const mantissa = integerdivider / 100; - const fraction_part = integerdivider - (100 * mantissa); - - const fraction = ((fraction_part * oversample + 50) / 100) & 0x0F; - const brr_value = (mantissa << 4) | fraction; - return brr_value; - } - pub fn validate_baudrate(self: @This(), comptime baud_rate: u32, comptime pclk: comptime_float, comptime tol: comptime_float) bool { - _ = self; - // const oversample: comptime_float = 16; - const baud_rate_f: comptime_float = @floatFromInt(baud_rate); - const brr = calc_usartdiv(baud_rate, pclk); - const div: comptime_float = @as(comptime_float, @floatFromInt(brr)); - if (div == 0) { - @compileError("how it zero?"); - } - const baud_rate_actual = pclk / div; - - const err = 100.0 * ((baud_rate_actual - baud_rate_f) / baud_rate_f); - if (@abs(err) >= tol) { - @compileLog(std.fmt.comptimePrint("pclk = {}, baud_rate = {}, brr = {}, div = {}, err = {}", .{ pclk, baud_rate, brr, div, err })); - @compileLog(std.fmt.comptimePrint("Baud rate error too high for the given system clock", .{})); - @compileLog(std.fmt.comptimePrint("Desired: {}", .{baud_rate})); - @compileLog(std.fmt.comptimePrint("Actual: {}", .{baud_rate_actual})); - @compileLog(std.fmt.comptimePrint("Error: {}", .{err})); - } - return @abs(err) < tol; - } /// Check if transmit data register is empty (can write) pub inline fn is_writeable(usart: USART) bool { return usart.get_regs().STATR.read().TXE == 1; From 0717b515db24e0cc18baebfae913b82b3449d448 Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 15:19:32 -0500 Subject: [PATCH 79/80] adjust comment --- port/wch/ch32v/src/hals/clocks.zig | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/port/wch/ch32v/src/hals/clocks.zig b/port/wch/ch32v/src/hals/clocks.zig index a59fed27d..9e3ad7d7a 100644 --- a/port/wch/ch32v/src/hals/clocks.zig +++ b/port/wch/ch32v/src/hals/clocks.zig @@ -413,7 +413,10 @@ pub fn get_freqs() ClockSpeeds { }; } -/// USBHS/USBHD clock helper. +// ============================================================================ +// Enable + configure USBHS clocks. +// ============================================================================ + /// This configures RCC_CFGR2 fields for the USBHS PHY PLL reference (if present), /// selects whether the USBHS 48MHz clock comes from the system PLL clock or the USB PHY, /// and enables the AHB clock gate for USBHS. @@ -465,10 +468,6 @@ const HSPLLSRC = enum(u2) { hsi = 1, }; -// ============================================================================ -// Enable + configure USBHS clocks. -// ============================================================================ - /// Selects USBHS Clock source, options are: /// - "PLL CLK" 48MHz source (cfg.use_phy_48mhz = false), or /// - "USB PHY" 48MHz source (cfg.use_phy_48mhz = true), From b9892ab84ae2c7999e641c02b7f5ab57b9c18f5c Mon Sep 17 00:00:00 2001 From: Bob Date: Sat, 31 Jan 2026 15:27:24 -0500 Subject: [PATCH 80/80] get rid of regs function, change a log call --- port/wch/ch32v/src/hals/usbhs.zig | 59 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/port/wch/ch32v/src/hals/usbhs.zig b/port/wch/ch32v/src/hals/usbhs.zig index e445e50dc..272a1d2de 100644 --- a/port/wch/ch32v/src/hals/usbhs.zig +++ b/port/wch/ch32v/src/hals/usbhs.zig @@ -27,10 +27,7 @@ pub const Config = struct { use_interrupts: bool = false, }; -const Regs = @TypeOf(peripherals.USBHS); -pub fn regs() Regs { - return peripherals.USBHS; -} +const Regs = peripherals.USBHS; const EpState = struct { buf: []align(4) u8 = &[_]u8{}, @@ -246,9 +243,9 @@ pub fn Polled(comptime cfg: Config) type { self.arm_ep0_out_always(); // Connect pull-up to signal device ready - regs().USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); + Regs.USB_CTRL.modify(.{ .RB_UC_DEV_PU_EN = 1 }); - regs().USB_CTRL.modify(.{ + Regs.USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 0, }); @@ -326,27 +323,27 @@ pub fn Polled(comptime cfg: Config) type { pub fn poll(self: *Self, in_isr: bool, controller: anytype) void { _ = in_isr; while (true) { - const fg: u8 = regs().USB_INT_FG.raw; + const fg: u8 = Regs.USB_INT_FG.raw; if (fg == 0) break; if ((fg & UIF_HST_SOF) != 0) { // acknowledge SOF but ignore - regs().USB_INT_FG.raw = UIF_HST_SOF; + Regs.USB_INT_FG.raw = UIF_HST_SOF; } if ((fg & UIF_SUSPEND) != 0) { // acknowledge SUSPEND but ignore - regs().USB_INT_FG.raw = UIF_SUSPEND; + Regs.USB_INT_FG.raw = UIF_SUSPEND; } if (fg & UIF_FIFO_OV != 0) { log.warn("FIFO overflow!", .{}); - regs().USB_INT_FG.raw = UIF_FIFO_OV; + Regs.USB_INT_FG.raw = UIF_FIFO_OV; } if ((fg & UIF_BUS_RST) != 0) { log.info("bus reset\n\n\n", .{}); // clear - regs().USB_INT_FG.raw = UIF_BUS_RST; + Regs.USB_INT_FG.raw = UIF_BUS_RST; // address back to 0 set_address(&self.interface, 0); @@ -357,7 +354,7 @@ pub fn Polled(comptime cfg: Config) type { if ((fg & UIF_SETUP_ACT) != 0) { log.info("SETUP received", .{}); - regs().USB_INT_FG.raw = UIF_SETUP_ACT; + Regs.USB_INT_FG.raw = UIF_SETUP_ACT; const setup: types.SetupPacket = self.read_setup_from_ep0(); @@ -370,8 +367,8 @@ pub fn Polled(comptime cfg: Config) type { if ((fg & UIF_TRANSFER) != 0) { // clear transfer - regs().USB_INT_FG.raw = UIF_TRANSFER; - const stv = regs().USB_INT_ST.read(); + Regs.USB_INT_FG.raw = UIF_TRANSFER; + const stv = Regs.USB_INT_ST.read(); const ep: u4 = @as(u4, stv.MASK_UIS_H_RES__MASK_UIS_ENDP); const token: u2 = @as(u2, stv.MASK_UIS_TOKEN); self.handle_transfer(ep, token, controller); @@ -400,8 +397,8 @@ pub fn Polled(comptime cfg: Config) type { } fn handle_out(self: *Self, ep: u4, controller: anytype) void { - const len: u16 = regs().USB_RX_LEN.read().R16_USB_RX_LEN; - const stv = regs().USB_INT_ST.read(); + const len: u16 = Regs.USB_RX_LEN.read().R16_USB_RX_LEN; + const stv = Regs.USB_INT_ST.read(); const rx_ctrl = uep_rx_ctrl(ep).read(); log.debug( "OUT ep{} len={} tog_ok={} rx_res={} rx_tog={}", @@ -461,7 +458,7 @@ pub fn Polled(comptime cfg: Config) type { fn set_address(_: *usb.DeviceInterface, addr: u7) void { log.info("set_address {}", .{addr}); - regs().USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); + Regs.USB_DEV_AD.modify(.{ .MASK_USB_ADDR = addr }); } fn ep_open(itf: *usb.DeviceInterface, desc_ptr: *const descriptor.Endpoint) void { @@ -490,7 +487,7 @@ pub fn Polled(comptime cfg: Config) type { in_st.buf = buf; const ptr_val = @as(u32, @intCast(@intFromPtr(buf.ptr))); - log.info("Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); + log.debug("Setting EP0 DMA buffer at {x}, len={}", .{ ptr_val, buf.len }); ep0_dma().raw = ptr_val; uep_max_len(0).raw = @intCast(64); } else { @@ -524,17 +521,17 @@ pub fn Polled(comptime cfg: Config) type { // Enable endpoint direction in UEP_CONFIG bitmaps. // TODO: make this a function, too ugly here - var cfg_raw: u32 = regs().UEP_CONFIG__UHOST_CTRL.raw; + var cfg_raw: u32 = Regs.UEP_CONFIG__UHOST_CTRL.raw; if (e.num != .ep0) { if (e.dir == .In) cfg_raw |= (@as(u32, 1) << ep_i) else cfg_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); } - regs().UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; + Regs.UEP_CONFIG__UHOST_CTRL.raw = cfg_raw; // Endpoint type ISO marking (only for ISO endpoints). if (e.num != .ep0 and desc.attributes.transfer_type == .Isochronous) { - var type_raw: u32 = regs().UEP_TYPE.raw; + var type_raw: u32 = Regs.UEP_TYPE.raw; if (e.dir == .In) type_raw |= (@as(u32, 1) << ep_i) else type_raw |= (@as(u32, 1) << (16 + @as(u5, ep_i))); - regs().UEP_TYPE.raw = type_raw; + Regs.UEP_TYPE.raw = type_raw; } // EP0 OUT always ACK @@ -647,10 +644,10 @@ pub fn Polled(comptime cfg: Config) type { fn usbhd_hw_init() void { // Reset SIE and clear FIFO - regs().UHOST_CTRL.raw = 0; - regs().UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); - regs().USB_CTRL.raw = 0; // not sure if writing zero then val is ok? - regs().USB_CTRL.modify(.{ + Regs.UHOST_CTRL.raw = 0; + Regs.UHOST_CTRL.modify(.{ .RB_UH_PHY_SUSPENDM = 1 }); + Regs.USB_CTRL.raw = 0; // not sure if writing zero then val is ok? + Regs.USB_CTRL.modify(.{ .RB_UC_CLR_ALL = 1, .RB_UC_RST_SIE = 1, .RB_UC_INT_BUSY = 1, @@ -661,18 +658,18 @@ pub fn Polled(comptime cfg: Config) type { asm volatile ("nop"); } - regs().USB_CTRL.modify(.{ + Regs.USB_CTRL.modify(.{ .RB_UC_RST_SIE = 0, }); - regs().USB_CTRL.modify(.{ + Regs.USB_CTRL.modify(.{ .RB_UC_DMA_EN = 1, .RB_UC_INT_BUSY = 1, .RB_UC_SPEED_TYPE = speed_type(cfg), }); // Enable source interrupts (we poll these flags, interrupt disabled) - regs().USB_INT_EN.modify(.{ + Regs.USB_INT_EN.modify(.{ .RB_U_1WIRE_MODE = 1, // actually SETUP_ACT? .RB_UIE_BUS_RST__RB_UIE_DETECT = 1, .RB_UIE_TRANSFER = 1, @@ -687,7 +684,7 @@ pub fn Polled(comptime cfg: Config) type { ///! Skeleton ISR pub fn usbhs_interrupt_handler() callconv(microzig.cpu.riscv_calling_convention) void { - const fg = regs().USB_INT_FG.raw; - regs().USB_INT_FG.raw = fg; + const fg = Regs.USB_INT_FG.raw; + Regs.USB_INT_FG.raw = fg; @panic("Don't Enable USBHS Interrupt, Not yet supported!"); }