Skip to content

Commit

Permalink
USB CDC - improve writing and implement reading (almost) (#293)
Browse files Browse the repository at this point in the history
  • Loading branch information
arkadiuszwojcik authored Nov 24, 2024
1 parent 80d5b66 commit 0dfb791
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 117 deletions.
33 changes: 26 additions & 7 deletions core/src/core/usb.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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];
}

Expand All @@ -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.
///
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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);
}
},
}
}
Expand All @@ -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();

Expand Down
263 changes: 160 additions & 103 deletions core/src/core/usb/cdc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}
};
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
};
}
};
}
6 changes: 5 additions & 1 deletion core/src/core/usb/hid.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}
};
Expand Down
Loading

0 comments on commit 0dfb791

Please sign in to comment.