From 0dfb791972b113b413fc2fad490e82b842a447e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arkadiusz=20W=C3=B3jcik?= Date: Sun, 24 Nov 2024 08:25:57 +0100 Subject: [PATCH] USB CDC - improve writing and implement reading (almost) (#293) --- core/src/core/usb.zig | 33 ++- core/src/core/usb/cdc.zig | 263 +++++++++++------- core/src/core/usb/hid.zig | 6 +- core/src/core/usb/types.zig | 5 + .../rp2xxx/src/rp2040_only/usb_cdc.zig | 45 ++- port/raspberrypi/rp2xxx/src/hal/usb.zig | 1 + 6 files changed, 236 insertions(+), 117 deletions(-) diff --git a/core/src/core/usb.zig b/core/src/core/usb.zig index 5b222355..223adac3 100644 --- a/core/src/core/usb.zig +++ b/core/src/core/usb.zig @@ -55,6 +55,8 @@ pub fn Usb(comptime f: anytype) type { var clk_init: bool = false; var itf_to_drv: [f.cfg_max_interfaces_count]u8 = .{0} ** f.cfg_max_interfaces_count; var ep_to_drv: [f.cfg_max_endpoints_count][2]u8 = .{.{0} ** 2} ** f.cfg_max_endpoints_count; + pub const max_packet_size = if(f.high_speed) 512 else 64; + const drvid_invalid = 0xff; /// The callbacks passed provided by the caller pub const callbacks = f; @@ -155,10 +157,17 @@ pub fn Usb(comptime f: anytype) type { } fn device_endpoint_transfer(ep_addr: u8, data: []const u8) void { - f.usb_start_tx(ep_addr, data); + if (Endpoint.dir_from_address(ep_addr) == .In) { + f.usb_start_tx(ep_addr, data); + } else { + f.usb_start_rx(ep_addr, max_packet_size); + } } - fn get_driver(drv_idx: u8) *types.UsbClassDriver { + fn get_driver(drv_idx: u8) ?*types.UsbClassDriver { + if (drv_idx == drvid_invalid) { + return null; + } return &usb_config.?.drivers[drv_idx]; } @@ -169,6 +178,11 @@ pub fn Usb(comptime f: anytype) type { return setup; } + fn configuration_reset() void { + @memset(&itf_to_drv, drvid_invalid); + @memset(&ep_to_drv, .{drvid_invalid, drvid_invalid}); + } + /// Usb task function meant to be executed in regular intervals after /// initializing the device. /// @@ -201,7 +215,7 @@ pub fn Usb(comptime f: anytype) type { const cfg_num = setup.value; if (S.cfg_num != cfg_num) { if (S.cfg_num > 0) { - // TODO - cleanup current config drivers + configuration_reset(); } if (cfg_num > 0) { @@ -359,9 +373,10 @@ pub fn Usb(comptime f: anytype) type { fn process_setup_request(setup: *const types.SetupPacket) !void { const itf: u8 = @intCast(setup.index & 0xFF); var driver = get_driver(itf_to_drv[itf]); + if (driver == null) return; S.driver = driver; - if (driver.class_control(.Setup, setup) == false) { + if (driver.?.class_control(.Setup, setup) == false) { // TODO } } @@ -456,9 +471,12 @@ pub fn Usb(comptime f: anytype) type { } }, else => { - if (debug) std.log.info(" ELSE, ep_addr: {}", .{ - epb.endpoint_address & 0x7f, - }); + const ep_num = Endpoint.num_from_address(epb.endpoint_address); + const ep_dir = Endpoint.dir_from_address(epb.endpoint_address).as_number(); + var driver = get_driver(ep_to_drv[ep_num][ep_dir]); + if (driver != null) { + driver.?.transfer(epb.endpoint_address, epb.buffer); + } }, } } @@ -468,6 +486,7 @@ pub fn Usb(comptime f: anytype) type { if (ints.BusReset) { if (debug) std.log.info("bus reset", .{}); + configuration_reset(); // Reset the device f.bus_reset(); diff --git a/core/src/core/usb/cdc.zig b/core/src/core/usb/cdc.zig index 0a4159bb..d8c4c691 100644 --- a/core/src/core/usb/cdc.zig +++ b/core/src/core/usb/cdc.zig @@ -125,130 +125,187 @@ pub const CdcLineCoding = extern struct { data_bits: u8, }; -pub const CdcClassDriver = struct { - - device: ?types.UsbDevice = null, - ep_notif: u8 = 0, - ep_in: u8 = 0, - ep_out: u8 = 0, - - line_coding: CdcLineCoding = undefined, - - pub fn write(self: *@This(), data: []const u8) void { - // TODO - ugly hack, current limitation 63 chars (in future endpoints should implement ring buffers) - const max_size = 63; - var buf: [64]u8 = undefined; - const size = @min(data.len, max_size); - @memcpy(buf[0..size], data[0..size]); - buf[max_size-2] = '\r'; - buf[max_size-1] = '\n'; - const data_packet = buf[0..size]; - - if (self.device.?.ready() == false) { - return; +pub fn CdcClassDriver(comptime usb: anytype) type { + const fifo = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = usb.max_packet_size }); + + return struct { + device: ?types.UsbDevice = null, + ep_notif: u8 = 0, + ep_in: u8 = 0, + ep_out: u8 = 0, + + line_coding: CdcLineCoding = undefined, + + rx: fifo = fifo.init(), + tx: fifo = fifo.init(), + + epin_buf: [usb.max_packet_size]u8 = undefined, + + pub fn available(self: *@This()) usize { + return self.rx.readableLength(); } - self.device.?.endpoint_transfer(self.ep_in, data_packet); - } - fn init(ptr: *anyopaque, device: types.UsbDevice) void { - var self: *CdcClassDriver = @ptrCast(@alignCast(ptr)); - self.device = device; - self.line_coding = .{ - .bit_rate = 115200, - .stop_bits = 0, - .parity = 0, - .data_bits = 8 - }; - } + pub fn read(self: *@This(), dst: [] u8) usize { + const read_count = self.rx.read(dst); + self.prep_out_transaction(); + return read_count; + } + + pub fn write(self: *@This(), data: []const u8) []const u8 { + const write_count = @min(self.tx.writableLength(), data.len); + + if (write_count > 0) { + self.tx.writeAssumeCapacity(data[0..write_count]); + } else { + return data[0..]; + } - fn open(ptr: *anyopaque, cfg: []const u8) !usize { - var self: *CdcClassDriver = @ptrCast(@alignCast(ptr)); - var curr_cfg = cfg; + if (self.tx.writableLength() == 0) { + _ = self.write_flush(); + } - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) return types.DriverErrors.UnsupportedInterfaceClassType; - if (desc_itf.interface_subclass != @intFromEnum(CdcCommSubClassType.AbstractControlModel)) return types.DriverErrors.UnsupportedInterfaceSubClassType; - } else { - return types.DriverErrors.ExpectedInterfaceDescriptor; + return data[write_count..]; } - curr_cfg = bos.get_desc_next(curr_cfg); + pub fn write_flush(self: *@This()) usize { + if (self.device.?.ready() == false) { + return 0; + } + if (self.tx.readableLength() == 0) { + return 0; + } + const len = self.tx.read(&self.epin_buf); + self.device.?.endpoint_transfer(self.ep_in, self.epin_buf[0..len]); + return len; + } - while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == DescType.CsInterface) { - curr_cfg = bos.get_desc_next(curr_cfg); + fn prep_out_transaction(self: *@This()) void { + if (self.rx.writableLength() >= usb.max_packet_size) { + // Let endpoint know that we are ready for next packet + self.device.?.endpoint_transfer(self.ep_out, &.{}); + } } - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - self.ep_notif = desc_ep.endpoint_address; - curr_cfg = bos.get_desc_next(curr_cfg); + fn init(ptr: *anyopaque, device: types.UsbDevice) void { + var self: *@This() = @ptrCast(@alignCast(ptr)); + self.device = device; + self.line_coding = .{ + .bit_rate = 115200, + .stop_bits = 0, + .parity = 0, + .data_bits = 8 + }; } - if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { - if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { + fn open(ptr: *anyopaque, cfg: []const u8) !usize { + var self: *@This() = @ptrCast(@alignCast(ptr)); + var curr_cfg = cfg; + + if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Cdc)) return types.DriverErrors.UnsupportedInterfaceClassType; + if (desc_itf.interface_subclass != @intFromEnum(CdcCommSubClassType.AbstractControlModel)) return types.DriverErrors.UnsupportedInterfaceSubClassType; + } else { + return types.DriverErrors.ExpectedInterfaceDescriptor; + } + + curr_cfg = bos.get_desc_next(curr_cfg); + + while (curr_cfg.len > 0 and bos.get_desc_type(curr_cfg) == DescType.CsInterface) { curr_cfg = bos.get_desc_next(curr_cfg); - for (0..2) |_| { - if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { - switch (types.Endpoint.dir_from_address(desc_ep.endpoint_address)) { - .In => { self.ep_in = desc_ep.endpoint_address; }, - .Out => { self.ep_out = desc_ep.endpoint_address; }, + } + + if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + self.ep_notif = desc_ep.endpoint_address; + curr_cfg = bos.get_desc_next(curr_cfg); + } + + if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| { + if (desc_itf.interface_class == @intFromEnum(types.ClassCode.CdcData)) { + curr_cfg = bos.get_desc_next(curr_cfg); + for (0..2) |_| { + if (bos.try_get_desc_as(types.EndpointDescriptor, curr_cfg)) |desc_ep| { + switch (types.Endpoint.dir_from_address(desc_ep.endpoint_address)) { + .In => { self.ep_in = desc_ep.endpoint_address; }, + .Out => { self.ep_out = desc_ep.endpoint_address; }, + } + self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); + curr_cfg = bos.get_desc_next(curr_cfg); } - self.device.?.endpoint_open(curr_cfg[0..desc_ep.length]); - curr_cfg = bos.get_desc_next(curr_cfg); } } } - } - return cfg.len - curr_cfg.len; - } + return cfg.len - curr_cfg.len; + } - fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool { - var self: *CdcClassDriver = @ptrCast(@alignCast(ptr)); - - if (CdcManagementRequestType.from_u8(setup.request)) |request| { - switch (request) { - .SetLineCoding => { - switch (stage) { - .Setup => { - // HACK, we should handle data phase somehow to read sent line_coding - self.device.?.control_ack(setup); - }, - else => {} - } - }, - .GetLineCoding => { - if (stage == .Setup) { - self.device.?.control_transfer(setup, std.mem.asBytes(&self.line_coding)); - } - }, - .SetControlLineState => { - switch (stage) { - .Setup => { - self.device.?.control_ack(setup); - }, - else => {} - } - }, - .SendBreak => { - switch (stage) { - .Setup => { - self.device.?.control_ack(setup); - }, - else => {} + fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool { + var self: *@This() = @ptrCast(@alignCast(ptr)); + + if (CdcManagementRequestType.from_u8(setup.request)) |request| { + switch (request) { + .SetLineCoding => { + switch (stage) { + .Setup => { + // HACK, we should handle data phase somehow to read sent line_coding + self.device.?.control_ack(setup); + }, + else => {} + } + }, + .GetLineCoding => { + if (stage == .Setup) { + self.device.?.control_transfer(setup, std.mem.asBytes(&self.line_coding)); + } + }, + .SetControlLineState => { + switch (stage) { + .Setup => { + self.device.?.control_ack(setup); + }, + else => {} + } + }, + .SendBreak => { + switch (stage) { + .Setup => { + self.device.?.control_ack(setup); + }, + else => {} + } } } } + + return true; } - return true; - } + fn transfer(ptr: *anyopaque, ep_addr: u8, data: []u8) void { + var self: *@This() = @ptrCast(@alignCast(ptr)); - pub fn driver(self: *@This()) types.UsbClassDriver { - return .{ - .ptr = self, - .fn_init = init, - .fn_open = open, - .fn_class_control = class_control - }; - } -}; \ No newline at end of file + if (ep_addr == self.ep_out) { + self.rx.write(data) catch {}; + self.prep_out_transaction(); + } + + if (ep_addr == self.ep_in) { + if (self.write_flush() == 0) { + // If there is no data left, a empty packet should be sent if + // data len is multiple of EP Packet size and not zero + if (self.tx.readableLength() == 0 and data.len > 0 and data.len == usb.max_packet_size) { + self.device.?.endpoint_transfer(self.ep_in, &.{}); + } + } + } + } + + pub fn driver(self: *@This()) types.UsbClassDriver { + return .{ + .ptr = self, + .fn_init = init, + .fn_open = open, + .fn_class_control = class_control, + .fn_transfer = transfer + }; + } + }; +} \ No newline at end of file diff --git a/core/src/core/usb/hid.zig b/core/src/core/usb/hid.zig index b10a91a3..6ff2e9e1 100644 --- a/core/src/core/usb/hid.zig +++ b/core/src/core/usb/hid.zig @@ -555,12 +555,16 @@ pub const HidClassDriver = struct { return true; } + fn transfer(_: *anyopaque, _: u8, _: []u8) void { + } + pub fn driver(self: *@This()) types.UsbClassDriver { return .{ .ptr = self, .fn_init = init, .fn_open = open, - .fn_class_control = class_control + .fn_class_control = class_control, + .fn_transfer = transfer }; } }; diff --git a/core/src/core/usb/types.zig b/core/src/core/usb/types.zig index a669930c..03861b1b 100644 --- a/core/src/core/usb/types.zig +++ b/core/src/core/usb/types.zig @@ -460,6 +460,7 @@ pub const UsbClassDriver = struct { fn_init: *const fn (ptr: *anyopaque, device: UsbDevice) void, fn_open: *const fn (ptr: *anyopaque, cfg: []const u8) anyerror!usize, fn_class_control: *const fn (ptr: *anyopaque, stage: ControlStage, setup: *const SetupPacket) bool, + fn_transfer: *const fn (ptr: *anyopaque, ep_addr: u8, data: []u8) void, pub fn init(self: *@This(), device: UsbDevice) void { return self.fn_init(self.ptr, device); @@ -473,4 +474,8 @@ pub const UsbClassDriver = struct { pub fn class_control(self: *@This(), stage: ControlStage, setup: *const SetupPacket) bool { return self.fn_class_control(self.ptr, stage, setup); } + + pub fn transfer(self: *@This(), ep_addr: u8, data: []u8) void { + return self.fn_transfer(self.ptr, ep_addr, data); + } }; \ No newline at end of file diff --git a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig index 67aed98f..456e0e87 100644 --- a/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig +++ b/examples/raspberrypi/rp2xxx/src/rp2040_only/usb_cdc.zig @@ -21,7 +21,7 @@ const usb_config_descriptor = usb.templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++ usb.templates.cdc_descriptor(0, 4, usb.Endpoint.to_address(1, .In), 8, usb.Endpoint.to_address(2, .Out), usb.Endpoint.to_address(2, .In), 64); -var driver_cdc = usb.cdc.CdcClassDriver{}; +var driver_cdc: usb.cdc.CdcClassDriver(usb_dev) = .{}; var drivers = [_]usb.types.UsbClassDriver{driver_cdc.driver()}; // This is our device configuration @@ -87,7 +87,6 @@ pub fn main() !void { var new: u64 = 0; var i: u32 = 0; - var buf: [1024]u8 = undefined; while (true) { // You can now poll for USB events usb_dev.task( @@ -99,11 +98,45 @@ pub fn main() !void { old = new; led.toggle(); i += 1; - // uart log std.log.info("cdc test: {}\r\n", .{i}); - // usb log (at this moment 63 bytes is max limit per single call) - const text = std.fmt.bufPrint(&buf, "cdc test: {}\r\n", .{i}) catch &.{}; - driver_cdc.write(text); + + usb_cdc_write("This is very very long text sent from RP Pico by USB CDC to your device: {}\r\n", .{i}); + } + + // read and print host command if present + const message = usb_cdc_read(); + if (message.len > 0) { + usb_cdc_write("Your message to me was: {s}\r\n", .{message}); } } } + +var usb_tx_buff: [1024]u8 = undefined; + +pub fn usb_cdc_write(comptime fmt: []const u8, args: anytype) void { + const text = std.fmt.bufPrint(&usb_tx_buff, fmt, args) catch &.{}; + + var write_buff = text; + while (write_buff.len > 0) { + write_buff = driver_cdc.write(write_buff); + } +} + +var usb_rx_buff: [1024]u8 = undefined; + +// TODO - right now there are 2 issues with reading data from serial: +// 1. It not always work, so sometime data is not received. This is probably related to how we reset usb buffer flags. +// 2. Even when usb host sends small chunk of data cdc read returns full packet length. This require further investigation. +pub fn usb_cdc_read() []const u8 { + var total_read: usize = 0; + var read_buff: []u8 = usb_rx_buff[0..]; + + while (true) { + const len = driver_cdc.read(read_buff); + read_buff = read_buff[len..]; + total_read += len; + if (len == 0) break; + } + + return usb_rx_buff[0..total_read]; +} diff --git a/port/raspberrypi/rp2xxx/src/hal/usb.zig b/port/raspberrypi/rp2xxx/src/hal/usb.zig index 022602d1..c3042393 100644 --- a/port/raspberrypi/rp2xxx/src/hal/usb.zig +++ b/port/raspberrypi/rp2xxx/src/hal/usb.zig @@ -127,6 +127,7 @@ pub fn F(comptime config: UsbConfig) type { return struct { pub const cfg_max_endpoints_count: u8 = config.max_endpoints_count; pub const cfg_max_interfaces_count: u8 = config.max_interfaces_count; + pub const high_speed = false; var endpoints: [config.max_endpoints_count][2]HardwareEndpoint = undefined;