USB CDC ACM support + code refactor (#211)

* part1: code refactor

* part2: config serialization

* usb cdc acm support

---------

Co-authored-by: Arkadiusz Wójcik <wojcik.arkadiusz@outlook.com>

@ikskuh
wch-ch32v003
Arkadiusz Wójcik 2 months ago committed by GitHub
parent 063ade2a3c
commit 3af91ea8f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,7 +9,12 @@ const peripherals = microzig.chip.peripherals;
/// Human Interface Device (HID)
pub const usb = microzig.core.usb;
pub const types = usb.types;
pub const hid = usb.hid;
pub const cdc = usb.cdc;
pub const vendor = usb.vendor;
pub const templates = usb.templates.DescriptorsConfigTemplates;
pub const utils = usb.UsbUtils;
const rom = @import("rom.zig");
const resets = @import("resets.zig");
@ -30,42 +35,26 @@ pub const Usb = usb.Usb(F);
pub const DeviceConfiguration = usb.DeviceConfiguration;
pub const DeviceDescriptor = usb.DeviceDescriptor;
pub const DescType = usb.DescType;
pub const InterfaceDescriptor = usb.InterfaceDescriptor;
pub const ConfigurationDescriptor = usb.ConfigurationDescriptor;
pub const EndpointDescriptor = usb.EndpointDescriptor;
pub const DescType = usb.types.DescType;
pub const InterfaceDescriptor = usb.types.InterfaceDescriptor;
pub const ConfigurationDescriptor = usb.types.ConfigurationDescriptor;
pub const EndpointDescriptor = usb.types.EndpointDescriptor;
pub const EndpointConfiguration = usb.EndpointConfiguration;
pub const Dir = usb.Dir;
pub const TransferType = usb.TransferType;
pub const Dir = usb.types.Dir;
pub const TransferType = usb.types.TransferType;
pub const Endpoint = usb.types.Endpoint;
pub const utf8ToUtf16Le = usb.utf8Toutf16Le;
pub var EP0_OUT_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.EP0_OUT_ADDR,
.attributes = @intFromEnum(usb.TransferType.Control),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = null,
.buffer_control_index = 1,
.data_buffer_index = 0,
.next_pid_1 = false,
};
pub var EP0_IN_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.EP0_IN_ADDR,
.attributes = @intFromEnum(usb.TransferType.Control),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = null,
.buffer_control_index = 0,
.data_buffer_index = 0,
.next_pid_1 = false,
const HardwareEndpoint = struct {
configured: bool,
ep_addr: u8,
next_pid_1: bool,
max_packet_size: u16,
transfer_type: types.TransferType,
endpoint_control_index: usize,
buffer_control_index: usize,
data_buffer_index: usize,
};
// +++++++++++++++++++++++++++++++++++++++++++++++++
@ -118,6 +107,9 @@ pub const buffers = struct {
/// A set of functions required by the abstract USB impl to
/// create a concrete one.
pub const F = struct {
// Fixed number 4, probably should be comptime number up to 16
var endpoints: [4][2]HardwareEndpoint = undefined;
/// Initialize the USB clock to 48 MHz
///
/// This requres that the system clock has been set up before hand
@ -150,7 +142,7 @@ pub const F = struct {
// We now have the stable 48MHz reference clock required for USB:
}
pub fn usb_init_device(device_config: *usb.DeviceConfiguration) void {
pub fn usb_init_device(_: *usb.DeviceConfiguration) void {
// Bring USB out of reset
resets.reset(.{ .usbctrl = true });
@ -264,33 +256,9 @@ pub const F = struct {
.SETUP_REQ = 1,
});
// setup endpoints
for (device_config.endpoints) |ep| {
// EP0 doesn't have an endpoint control index; only process the other
// endpoints here.
if (ep.endpoint_control_index) |epci| {
// We need to compute the offset from the base of USB SRAM to the
// buffer we're choosing, because that's how the peripheral do.
const buf_base = @intFromPtr(buffers.B.get(ep.data_buffer_index));
const dpram_base = @intFromPtr(peripherals.USBCTRL_DPRAM);
// The offset _should_ fit in a u16, but if we've gotten something
// wrong in the past few lines, a common symptom will be integer
// overflow producing a Very Large Number,
const dpram_offset = @as(u16, @intCast(buf_base - dpram_base));
// Configure the endpoint!
modify_endpoint_control(epci, .{
.ENABLE = 1,
// Please set the corresponding bit in buff_status when a
// buffer is done, thx.
.INTERRUPT_PER_BUFF = 1,
// Select bulk vs control (or interrupt as soon as implemented).
.ENDPOINT_TYPE = .{ .raw = @as(u2, @intCast(ep.descriptor.attributes)) },
// And, designate our buffer by its offset.
.BUFFER_ADDRESS = dpram_offset,
});
}
}
@memset(std.mem.asBytes(&endpoints), 0);
endpoint_init(Endpoint.EP0_IN_ADDR, 64, types.TransferType.Control);
endpoint_init(Endpoint.EP0_OUT_ADDR, 64, types.TransferType.Control);
// Present full-speed device by enabling pullup on DP. This is the point
// where the host will notice our presence.
@ -304,7 +272,7 @@ pub const F = struct {
/// reuse `buffer` immediately after this returns. No need to wait for the
/// packet to be sent.
pub fn usb_start_tx(
ep: *usb.EndpointConfiguration,
ep_addr: u8,
buffer: []const u8,
) void {
// It is technically possible to support longer buffers but this demo
@ -313,6 +281,8 @@ pub const F = struct {
// You should only be calling this on IN endpoints.
// TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::In);
const ep = hardware_endpoint_get_by_address(ep_addr);
// Copy the given data into the corresponding ep buffer
const epbuffer = buffers.B.get(ep.data_buffer_index);
_ = rom.memcpy(epbuffer[0..buffer.len], buffer);
@ -349,7 +319,7 @@ pub const F = struct {
}
pub fn usb_start_rx(
ep: *usb.EndpointConfiguration,
ep_addr: u8,
len: usize,
) void {
// It is technically possible to support longer buffers but this demo
@ -358,6 +328,8 @@ pub const F = struct {
// You should only be calling this on OUT endpoints.
// TODO: assert!(UsbDir::of_endpoint_addr(ep.descriptor.endpoint_address) == UsbDir::Out);
const ep = hardware_endpoint_get_by_address(ep_addr);
// Check which DATA0/1 PID this endpoint is expecting next.
const np: u1 = if (ep.next_pid_1) 1 else 0;
// Configure the OUT:
@ -392,7 +364,7 @@ pub const F = struct {
///
/// One can assume that this function is only called if the
/// setup request falg is set.
pub fn get_setup_packet() usb.SetupPacket {
pub fn get_setup_packet() usb.types.SetupPacket {
// Clear the status flag (write-one-to-clear)
peripherals.USBCTRL_REGS.SIE_STATUS.modify(.{ .SETUP_REC = 1 });
@ -411,7 +383,7 @@ pub const F = struct {
_ = rom.memcpy(setup_packet[0..4], std.mem.asBytes(&spl));
_ = rom.memcpy(setup_packet[4..8], std.mem.asBytes(&sph));
// Reinterpret as setup packet
return std.mem.bytesToValue(usb.SetupPacket, &setup_packet);
return std.mem.bytesToValue(usb.types.SetupPacket, &setup_packet);
}
/// Called on a bus reset interrupt
@ -425,6 +397,11 @@ pub const F = struct {
peripherals.USBCTRL_REGS.ADDR_ENDP.modify(.{ .ADDRESS = addr });
}
pub fn reset_ep0() void {
var ep = hardware_endpoint_get_by_address(Endpoint.EP0_IN_IDX);
ep.next_pid_1 = true;
}
pub fn get_EPBIter(dc: *const usb.DeviceConfiguration) usb.EPBIter {
return .{
.bufbits = peripherals.USBCTRL_REGS.BUFF_STATUS.raw,
@ -432,21 +409,61 @@ pub const F = struct {
.next = next,
};
}
fn hardware_endpoint_get_by_address(ep_addr: u8) *HardwareEndpoint {
const num = Endpoint.num_from_address(ep_addr);
const dir = Endpoint.dir_from_address(ep_addr);
return &endpoints[num][dir.as_number()];
}
pub fn endpoint_init(ep_addr: u8, max_packet_size: u16, transfer_type: types.TransferType) void {
const ep_num = Endpoint.num_from_address(ep_addr);
const ep_dir = Endpoint.dir_from_address(ep_addr);
var ep = hardware_endpoint_get_by_address(ep_addr);
ep.ep_addr = ep_addr;
ep.max_packet_size = max_packet_size;
ep.transfer_type = transfer_type;
ep.next_pid_1 = false;
ep.buffer_control_index = 2 * ep_num + ep_dir.as_number_reversed();
// TODO - some other way to deal with it
ep.data_buffer_index = 2 * ep_num + ep_dir.as_number();
if (ep_num == 0) {
ep.endpoint_control_index = 0;
} else {
ep.endpoint_control_index = 2*ep_num - ep_dir.as_number();
endpoint_alloc(ep, transfer_type);
}
}
fn endpoint_alloc(ep: *HardwareEndpoint, transfer_type: TransferType) void {
const buf_base = @intFromPtr(buffers.B.get(ep.data_buffer_index));
const dpram_base = @intFromPtr(peripherals.USBCTRL_DPRAM);
// The offset _should_ fit in a u16, but if we've gotten something
// wrong in the past few lines, a common symptom will be integer
// overflow producing a Very Large Number,
const dpram_offset = @as(u16, @intCast(buf_base - dpram_base));
// Configure the endpoint!
modify_endpoint_control(ep.endpoint_control_index, .{
.ENABLE = 1,
// Please set the corresponding bit in buff_status when a
// buffer is done, thx.
.INTERRUPT_PER_BUFF = 1,
// Select bulk vs control (or interrupt as soon as implemented).
.ENDPOINT_TYPE = .{ .raw = transfer_type.as_number() },
// And, designate our buffer by its offset.
.BUFFER_ADDRESS = dpram_offset,
});
}
};
// +++++++++++++++++++++++++++++++++++++++++++++++++
// Utility functions
// +++++++++++++++++++++++++++++++++++++++++++++++++
/// Check if the corresponding buffer is available
pub fn buffer_available(
ep: *usb.EndpointConfiguration,
) bool {
const rbc = read_raw_buffer_control(ep.buffer_control_index);
// Bit 11 of the EPn_X_BUFFER_CONTROL register represents the AVAILABLE_0 flag
return ((rbc & 0x400) == 0);
}
pub fn modify_buffer_control(
i: usize,
fields: anytype,
@ -596,9 +613,9 @@ pub fn next(self: *usb.EPBIter) ?usb.EPB {
const epnum = @as(u8, @intCast(lowbit_index >> 1));
// Of the pair, the IN endpoint comes first, followed by OUT, so
// we can get the direction by:
const dir = if (lowbit_index & 1 == 0) usb.Dir.In else usb.Dir.Out;
const dir = if (lowbit_index & 1 == 0) usb.types.Dir.In else usb.types.Dir.Out;
const ep_addr = dir.endpoint(epnum);
const ep_addr = Endpoint.to_address(epnum, dir);
// Process the buffer-done event.
// Process the buffer-done event.
@ -607,17 +624,13 @@ pub fn next(self: *usb.EPBIter) ?usb.EPB {
// 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.
var endpoint: ?*usb.EndpointConfiguration = null;
for (self.device_config.endpoints) |ep| {
if (ep.descriptor.endpoint_address == ep_addr) {
endpoint = ep;
break;
}
}
const endpoint = F.hardware_endpoint_get_by_address(ep_addr);
// Buffer event for unknown EP?!
// TODO: if (endpoint == null) return EPBError.UnknownEndpoint;
// Read the buffer control register to check status.
const bc = read_raw_buffer_control(endpoint.?.buffer_control_index);
const bc = read_raw_buffer_control(endpoint.buffer_control_index);
// We should only get here if we've been notified that
// the buffer is ours again. This is indicated by the hw
@ -631,7 +644,7 @@ pub fn next(self: *usb.EPBIter) ?usb.EPB {
// Get a pointer to the buffer in USB SRAM. This is the
// buffer _contents_. See the safety comments below.
const epbuffer = buffers.B.get(endpoint.?.data_buffer_index);
const epbuffer = buffers.B.get(endpoint.data_buffer_index);
// Get the actual length of the data, which may be less
// than the buffer size.
@ -639,7 +652,7 @@ pub fn next(self: *usb.EPBIter) ?usb.EPB {
// Copy the data from SRAM
return usb.EPB{
.endpoint = endpoint.?,
.endpoint_address = ep_addr,
.buffer = epbuffer[0..len],
};
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,254 @@
const std = @import("std");
const types = @import("types.zig");
const utils = @import("utils.zig");
const DescType = types.DescType;
const bos = utils.BosConfig;
pub const DescSubType = enum(u8) {
Header = 0x00,
CallManagement = 0x01,
ACM = 0x02,
Union = 0x06,
pub fn from_u16(v: u16) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
};
pub const CdcManagementRequestType = enum(u8) {
SetLineCoding = 0x20,
GetLineCoding = 0x21,
SetControlLineState = 0x22,
SendBreak = 0x23,
pub fn from_u8(v: u8) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
};
pub const CdcCommSubClassType = enum(u8) {
AbstractControlModel = 2
};
pub const CdcHeaderDescriptor = extern struct {
length: u8 = 5,
// Type of this descriptor, must be `ClassSpecific`.
descriptor_type: DescType = DescType.CsInterface,
// Subtype of this descriptor, must be `Header`.
descriptor_subtype: DescSubType = DescSubType.Header,
// USB Class Definitions for Communication Devices Specification release
// number in binary-coded decimal. Typically 0x01_10.
bcd_cdc: u16 align(1),
pub fn serialize(self: *const @This()) [5]u8 {
var out: [5]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intFromEnum(self.descriptor_subtype);
out[3] = @intCast(self.bcd_cdc & 0xff);
out[4] = @intCast((self.bcd_cdc >> 8) & 0xff);
return out;
}
};
pub const CdcCallManagementDescriptor = extern struct {
length: u8 = 5,
// Type of this descriptor, must be `ClassSpecific`.
descriptor_type: DescType = DescType.CsInterface,
// Subtype of this descriptor, must be `CallManagement`.
descriptor_subtype: DescSubType = DescSubType.CallManagement,
// Capabilities. Should be 0x00 for use as a serial device.
capabilities: u8,
// Data interface number.
data_interface: u8,
pub fn serialize(self: *const @This()) [5]u8 {
var out: [5]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intFromEnum(self.descriptor_subtype);
out[3] = self.capabilities;
out[4] = self.data_interface;
return out;
}
};
pub const CdcAcmDescriptor = extern struct {
length: u8 = 4,
// Type of this descriptor, must be `ClassSpecific`.
descriptor_type: DescType = DescType.CsInterface,
// Subtype of this descriptor, must be `ACM`.
descriptor_subtype: DescSubType = DescSubType.ACM,
// Capabilities. Should be 0x02 for use as a serial device.
capabilities: u8,
pub fn serialize(self: *const @This()) [4]u8 {
var out: [4]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intFromEnum(self.descriptor_subtype);
out[3] = self.capabilities;
return out;
}
};
pub const CdcUnionDescriptor = extern struct {
length: u8 = 5,
// Type of this descriptor, must be `ClassSpecific`.
descriptor_type: DescType = DescType.CsInterface,
// Subtype of this descriptor, must be `Union`.
descriptor_subtype: DescSubType = DescSubType.Union,
// The interface number of the communication or data class interface
// designated as the master or controlling interface for the union.
master_interface: u8,
// The interface number of the first slave or associated interface in the
// union.
slave_interface_0: u8,
pub fn serialize(self: *const @This()) [5]u8 {
var out: [5]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intFromEnum(self.descriptor_subtype);
out[3] = self.master_interface;
out[4] = self.slave_interface_0;
return out;
}
};
pub const CdcLineCoding = extern struct {
bit_rate: u32 align(1),
stop_bits: u8,
parity: u8,
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;
}
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
};
}
fn open(ptr: *anyopaque, cfg: []const u8) !usize {
var self: *CdcClassDriver = @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);
}
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);
}
}
}
}
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 => {}
}
}
}
}
return true;
}
pub fn driver(self: *@This()) types.UsbClassDriver {
return .{
.ptr = self,
.fn_init = init,
.fn_open = open,
.fn_class_control = class_control
};
}
};

@ -1,112 +0,0 @@
/// 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 {
/// Version of the device descriptor / USB protocol, in binary-coded
/// decimal. This is typically `0x01_10` for USB 1.1.
bcdUSB: u16,
/// Class of device, giving a broad functional area.
bDeviceClass: u8,
/// SubClass of device, refining the class.
bDeviceSubClass: u8,
/// Protocol within the subclass.
bDeviceProtocol: u8,
/// Maximum unit of data this device can move.
bMaxPacketSize0: u8,
/// ID of product vendor.
idVendor: u16,
/// ID of product.
idProduct: u16,
/// Device version number, as BCD again.
bcdDevice: u16,
/// Index of manufacturer name in string descriptor table.
iManufacturer: u8,
/// Index of product name in string descriptor table.
iPRoduct: u8,
/// Index of serial number in string descriptor table.
iSerialNumber: u8,
/// Number of configurations supported by this device.
bNumConfigurations: u8,
};
pub const Interface = extern struct {
bInterfaceNumber: u8,
bAlternateSetting: u8,
bNumEndpoints: u8,
bInterfaceClass: u8,
bInterfaceSubClass: u8,
bInterfaceProtocol: u8,
iInterface: u8,
};
/// Description of a single available device configuration.
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.
wTotalLength: u16,
/// Number of interface descriptors in this configuration.
bNumInterfaces: u8,
/// Number to use when requesting this configuration via a
/// `SetConfiguration` request.
bConfigurationValue: u8,
/// Index of this configuration's name in the string descriptor table.
iConfiguration: u8,
/// Bit set of device attributes:
///
/// - Bit 7 should be set (indicates that device can be bus powered in USB
/// 1.0).
/// - Bit 6 indicates that the device can be self-powered.
/// - Bit 5 indicates that the device can signal remote wakeup of the host
/// (like a keyboard).
/// - The rest are reserved and should be zero.
bmAttributes: u8,
/// Maximum device power consumption in units of 2mA.
bMaxPower: u8,
};
pub const InterfaceAssociation = extern struct {
bFirstInterface: u8,
bInterfaceCount: u8,
bFunctionClass: u8,
bFunctionSubClass: u8,
bFunctionProtocol: u8,
iFunction: u8,
};
/// Describes an endpoint within an interface
pub const Endpoint = extern struct {
/// Address of this endpoint, where the bottom 4 bits give the endpoint
/// number (0..15) and the top bit distinguishes IN (1) from OUT (0).
bEndpointAddress: u8,
/// Endpoint attributes; the most relevant part is the bottom 2 bits, which
/// control the transfer type using the values from `TransferType`.
bmAttributes: u8,
/// Maximum packet size this endpoint can accept/produce.
wMaxPacketSize: u16,
/// Interval for polling interrupt/isochronous endpoints (which we don't
/// currently support) in milliseconds.
bInterval: u8,
};
pub const BOS = extern struct {
/// The number of bytes in this descriptor and all of its subordinate
/// descriptors
wTotalLength: u16,
/// The numbre of device capability descriptors subordinate to this BOS
/// descriptor
bNumDeviceCaps: u8,
};
pub const Utf16Le = struct {
bytes: []const u8,
};
/// For string descriptor zero: an array of one or more language identifier
/// codes, for other string descriptors, a unicode UTF-16LE string
pub const String = extern struct {
string_or_lang: union {
bSTRING: Utf16Le,
wLANGID: []const u8,
};
};

@ -43,6 +43,12 @@
const std = @import("std");
const types = @import("types.zig");
const utils = @import("utils.zig");
const DescType = types.DescType;
const bos = utils.BosConfig;
// +++++++++++++++++++++++++++++++++++++++++++++++++
// Common Data Types
// +++++++++++++++++++++++++++++++++++++++++++++++++
@ -68,37 +74,46 @@ const std = @import("std");
// | ReportDescriptor | | PhysicalDesc |
// ----------------------- ---------------------
pub const DescType = enum(u8) {
/// HID descriptor
Hid = 0x21,
/// Report descriptor
Report = 0x22,
/// Physical descriptor
pub const HidDescType = enum(u8) {
Hid = 0x21,
Report = 0x22,
Physical = 0x23,
pub fn from_u16(v: u16) ?@This() {
return switch (v) {
0x21 => @This().Hid,
0x22 => @This().Report,
0x23 => @This().Physical,
else => null,
};
pub fn from_u8(v: u8) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
};
pub const HidRequestType = enum(u8) {
GetReport = 0x01,
GetIdle = 0x02,
GetProtocol = 0x03,
SetReport = 0x09,
SetIdle = 0x0a,
SetProtocol = 0x0b,
pub fn from_u8(v: u8) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
};
/// USB HID descriptor
pub const HidDescriptor = struct {
descriptor_type: DescType = DescType.Hid,
pub const const_descriptor_type = HidDescType.Hid;
length: u8 = 9,
/// Type of this descriptor
descriptor_type: HidDescType = const_descriptor_type,
/// Numeric expression identifying the HID Class Specification release
bcd_hid: u16,
bcd_hid: u16 align(1),
/// Numeric expression identifying country code of the localized hardware
country_code: u8,
/// Numeric expression specifying the number of class descriptors
num_descriptors: u8,
/// Type of HID class report
report_type: DescType = DescType.Report,
report_type: HidDescType = HidDescType.Report,
/// The total size of the Report descriptor
report_length: u16,
report_length: u16 align(1),
pub fn serialize(self: *const @This()) [9]u8 {
var out: [9]u8 = undefined;
@ -249,6 +264,7 @@ pub const LocalItem = enum(u4) {
pub const UsageTable = struct {
const fido: [2]u8 = "\xD0\xF1".*;
const vendor: [2]u8 = "\x00\xFF".*;
};
pub const FidoAllianceUsage = struct {
@ -429,6 +445,126 @@ pub const ReportDescriptorFidoU2f = hid_usage_page(2, UsageTable.fido) //
// End
++ hid_collection_end();
pub const ReportDescriptorGenericInOut = hid_usage_page(2, UsageTable.vendor) //
++ hid_usage(1, "\x01".*) //
++ hid_collection(CollectionItem.Application) //
// Usage Data In
++ hid_usage(1, "\x02".*) //
++ hid_logical_min(1, "\x00".*) //
++ hid_logical_max(2, "\xff\x00".*) //
++ hid_report_size(1, "\x08".*) //
++ hid_report_count(1, "\x40".*) //
++ hid_input(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) //
// Usage Data Out
++ hid_usage(1, "\x03".*) //
++ hid_logical_min(1, "\x00".*) //
++ hid_logical_max(2, "\xff\x00".*) //
++ hid_report_size(1, "\x08".*) //
++ hid_report_count(1, "\x40".*) //
++ hid_output(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) //
// End
++ hid_collection_end();
pub const HidClassDriver = struct {
device: ?types.UsbDevice = null,
ep_in: u8 = 0,
ep_out: u8 = 0,
hid_descriptor: []const u8 = &.{},
report_descriptor: []const u8,
fn init(ptr: *anyopaque, device: types.UsbDevice) void {
var self: *HidClassDriver = @ptrCast(@alignCast(ptr));
self.device = device;
}
fn open(ptr: *anyopaque, cfg: []const u8) !usize {
var self: *HidClassDriver = @ptrCast(@alignCast(ptr));
var curr_cfg = cfg;
if (bos.try_get_desc_as(types.InterfaceDescriptor, curr_cfg)) |desc_itf| {
if (desc_itf.interface_class != @intFromEnum(types.ClassCode.Hid)) return types.DriverErrors.UnsupportedInterfaceClassType;
} else {
return types.DriverErrors.ExpectedInterfaceDescriptor;
}
curr_cfg = bos.get_desc_next(curr_cfg);
if (bos.try_get_desc_as(HidDescriptor, curr_cfg)) |_| {
self.hid_descriptor = curr_cfg[0..bos.get_desc_len(curr_cfg)];
curr_cfg = bos.get_desc_next(curr_cfg);
} else {
return types.DriverErrors.UnexpectedDescriptor;
}
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);
}
}
return cfg.len - curr_cfg.len;
}
fn class_control(ptr: *anyopaque, stage: types.ControlStage, setup: *const types.SetupPacket) bool {
var self: *HidClassDriver = @ptrCast(@alignCast(ptr));
switch (setup.request_type.type) {
.Standard => {
if (stage == .Setup) {
const hid_desc_type = HidDescType.from_u8(@intCast((setup.value >> 8) & 0xff));
const request_code = types.SetupRequest.from_u8(setup.request);
if (hid_desc_type == null or request_code == null) {
return false;
}
if (request_code.? == .GetDescriptor and hid_desc_type == .Hid) {
self.device.?.control_transfer(setup, self.hid_descriptor);
} else if (request_code.? == .GetDescriptor and hid_desc_type == .Report) {
self.device.?.control_transfer(setup, self.report_descriptor);
} else {
return false;
}
}
},
.Class => {
const hid_request_type = HidRequestType.from_u8(setup.request);
if (hid_request_type == null) return false;
switch (hid_request_type.?) {
.SetIdle => {
if (stage == .Setup) {
self.device.?.control_ack(setup);
}
},
else => {
return false;
}
}
},
else => {
return false;
}
}
return true;
}
pub fn driver(self: *@This()) types.UsbClassDriver {
return .{
.ptr = self,
.fn_init = init,
.fn_open = open,
.fn_class_control = class_control
};
}
};
test "create hid report item" {
const r = hid_report_item(
2,

@ -0,0 +1,56 @@
const types = @import("types.zig");
const hid = @import("hid.zig");
const cdc = @import("cdc.zig");
pub const DescriptorsConfigTemplates = struct {
pub const config_descriptor_len = 9;
pub fn config_descriptor(config_num: u8, interfaces_num: u8, string_index: u8, total_len: u16, attributes: u8, max_power_ma: u9) [9]u8 {
const desc1 = types.ConfigurationDescriptor { .total_length = total_len, .num_interfaces = interfaces_num, .configuration_value = config_num, .configuration_s = string_index, .attributes = 0b01000000 | attributes, .max_power = max_power_ma/2 };
return desc1.serialize();
}
pub const cdc_descriptor_len = 8 + 9 + 5 + 5 + 4 + 5 + 7 + 9 + 7 + 7;
pub fn cdc_descriptor(interface_number: u8, string_index: u8, endpoint_notifi_address: u8, endpoint_notifi_size: u16, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16) [cdc_descriptor_len]u8 {
const desc1 = types.InterfaceAssociationDescriptor { .first_interface = interface_number, .interface_count = 2, .function_class = 2, .function_subclass = 2, .function_protocol = 0, .function = 0 };
const desc2 = types.InterfaceDescriptor { .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 2, .interface_subclass = 2, .interface_protocol = 0, .interface_s = string_index };
const desc3 = cdc.CdcHeaderDescriptor { .descriptor_type = .CsInterface, .descriptor_subtype = .Header, .bcd_cdc = 0x0120 };
const desc4 = cdc.CdcCallManagementDescriptor { .descriptor_type = .CsInterface, .descriptor_subtype = .CallManagement, .capabilities = 0, .data_interface = interface_number + 1 };
const desc5 = cdc.CdcAcmDescriptor { .descriptor_type = .CsInterface, .descriptor_subtype = .ACM, .capabilities = 6 };
const desc6 = cdc.CdcUnionDescriptor { .descriptor_type = .CsInterface, .descriptor_subtype = .Union, .master_interface = interface_number, .slave_interface_0 = interface_number + 1 };
const desc7 = types.EndpointDescriptor { .endpoint_address = endpoint_notifi_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_notifi_size, .interval = 16 };
const desc8 = types.InterfaceDescriptor { .interface_number = interface_number + 1, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 10, .interface_subclass = 0, .interface_protocol = 0, .interface_s = 0 };
const desc9 = types.EndpointDescriptor { .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 };
const desc10 = types.EndpointDescriptor { .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 };
return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize() ++ desc5.serialize() ++ desc6.serialize() ++ desc7.serialize() ++ desc8.serialize() ++ desc9.serialize() ++ desc10.serialize();
}
pub const hid_in_descriptor_len = 9 + 9 + 7;
pub fn hid_in_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_in_address: u8, endpoint_size: u16, endpoint_interval: u16) [hid_in_descriptor_len]u8 {
const desc1 = types.InterfaceDescriptor { .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 1, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index };
const desc2 = hid.HidDescriptor { .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len };
const desc3 = types.EndpointDescriptor { .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval };
return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize();
}
pub const hid_in_out_descriptor_len = 9 + 9 + 7 + 7;
pub fn hid_in_out_descriptor(interface_number: u8, string_index: u8, boot_protocol: u8, report_desc_len: u16, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16, endpoint_interval: u16) [hid_in_out_descriptor_len]u8 {
const desc1 = types.InterfaceDescriptor { .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 3, .interface_subclass = if (boot_protocol > 0) 1 else 0, .interface_protocol = boot_protocol, .interface_s = string_index };
const desc2 = hid.HidDescriptor { .bcd_hid = 0x0111, .country_code = 0, .num_descriptors = 1, .report_length = report_desc_len };
const desc3 = types.EndpointDescriptor { .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval };
const desc4 = types.EndpointDescriptor { .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Interrupt), .max_packet_size = endpoint_size, .interval = endpoint_interval };
return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize() ++ desc4.serialize();
}
pub const vendor_descriptor_len = 9 + 7 + 7;
pub fn vendor_descriptor(interface_number: u8, string_index: u8, endpoint_out_address: u8, endpoint_in_address: u8, endpoint_size: u16) [vendor_descriptor_len]u8 {
const desc1 = types.InterfaceDescriptor { .interface_number = interface_number, .alternate_setting = 0, .num_endpoints = 2, .interface_class = 0xff, .interface_subclass = 0, .interface_protocol = 0, .interface_s = string_index };
const desc2 = types.EndpointDescriptor { .endpoint_address = endpoint_out_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 };
const desc3 = types.EndpointDescriptor { .endpoint_address = endpoint_in_address, .attributes = @intFromEnum(types.TransferType.Bulk), .max_packet_size = endpoint_size, .interval = 0 };
return desc1.serialize() ++ desc2.serialize() ++ desc3.serialize();
}
};

@ -0,0 +1,476 @@
const std = @import("std");
pub const DescType = enum(u8) {
Device = 0x01,
Config = 0x02,
String = 0x03,
Interface = 0x04,
Endpoint = 0x05,
DeviceQualifier = 0x06,
InterfaceAssociation = 0x0b,
CsDevice = 0x21,
CsConfig = 0x22,
CsString = 0x23,
CsInterface = 0x24,
CsEndpoint = 0x25,
pub fn from_u8(v: u8) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
};
pub const ClassCode = enum(u8) {
Unspecified = 0,
Audio = 1,
Cdc = 2,
Hid = 3,
CdcData = 10,
};
/// Types of transfer that can be indicated by the `attributes` field on `EndpointDescriptor`.
pub const TransferType = enum(u2) {
Control = 0,
Isochronous = 1,
Bulk = 2,
Interrupt = 3,
pub fn from_u8(v: u8) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
pub inline fn as_number(self: @This()) u2 {
return @intFromEnum(self);
}
};
pub const ControlStage = enum {
Idle,
Setup,
Data,
Ack
};
/// The types of USB SETUP requests that we understand.
pub const SetupRequest = enum(u8) {
GetDescriptor = 0x06,
SetAddress = 0x05,
SetConfiguration = 0x09,
pub fn from_u8(v: u8) ?@This() {
return std.meta.intToEnum(@This(), v) catch null;
}
};
/// USB deals in two different transfer directions, called OUT (host-to-device)
/// and IN (device-to-host). In the vast majority of cases, OUT is represented
/// by a 0 byte, and IN by an `0x80` byte.
pub const Dir = enum(u1) {
Out = 0,
In = 1,
pub const DIR_IN_MASK = 0x80;
pub inline fn as_number(self: @This()) u1 {
return @intFromEnum(self);
}
pub inline fn as_number_reversed(self: @This()) u1 {
return ~@intFromEnum(self);
}
};
pub const Endpoint = struct {
pub inline fn to_address(num: u8, dir: Dir) u8 {
return switch (dir) {
.Out => num,
.In => num | Dir.DIR_IN_MASK
};
}
pub inline fn num_from_address(addr: u8) u8 {
return addr & ~@as(u8, @intCast(Dir.DIR_IN_MASK));
}
pub inline fn dir_from_address(addr: u8) Dir {
return if (addr & Dir.DIR_IN_MASK != 0) Dir.In else Dir.Out;
}
pub const EP0_OUT_IDX = 0;
pub const EP0_IN_IDX = 1;
pub const EP0_IN_ADDR: u8 = to_address(0, .In);
pub const EP0_OUT_ADDR: u8 = to_address(0, .Out);
};
pub const RequestType = packed struct(u8) {
recipient: Recipient,
type: Type,
direction: Dir,
const Type = enum(u2) {
Standard,
Class,
Vendor,
Other
};
/// RequestType is created from raw bytes using std.mem.bytesToValue, we need to support all potential values if we don't want to crash such conversion
const Recipient = enum(u5) {
Device,
Interface,
Endpoint,
Other,
Reserved1,
Reserved2,
Reserved3,
Reserved4,
Reserved5,
Reserved6,
Reserved7,
Reserved8,
Reserved9,
Reserved10,
Reserved11,
Reserved12,
Reserved13,
Reserved14,
Reserved15,
Reserved16,
Reserved17,
Reserved18,
Reserved19,
Reserved20,
Reserved21,
Reserved22,
Reserved23,
Reserved24,
Reserved25,
Reserved26,
Reserved27,
Reserved28
};
};
/// Layout of an 8-byte USB SETUP packet.
pub const SetupPacket = extern struct {
/// Request metadata: recipient, type, direction.
request_type: RequestType,
/// Request. Standard setup requests are in the `SetupRequest` enum.
/// Devices can extend this with additional types as long as they don't
/// conflict.
request: u8,
/// A simple argument of up to 16 bits, specific to the request.
value: u16,
/// Not used in the requests we support.
index: u16,
/// 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,
};
/// Describes an endpoint within an interface
pub const EndpointDescriptor = extern struct {
pub const const_descriptor_type = DescType.Endpoint;
length: u8 = 7,
/// Type of this descriptor, must be `Endpoint`.
descriptor_type: DescType = const_descriptor_type,
/// Address of this endpoint, where the bottom 4 bits give the endpoint
/// number (0..15) and the top bit distinguishes IN (1) from OUT (0).
endpoint_address: u8,
/// Endpoint attributes; the most relevant part is the bottom 2 bits, which
/// control the transfer type using the values from `TransferType`.
attributes: u8,
/// Maximum packet size this endpoint can accept/produce.
max_packet_size: u16 align(1),
/// Interval for polling interrupt/isochronous endpoints (which we don't
/// currently support) in milliseconds.
interval: u8,
pub fn serialize(self: *const @This()) [7]u8 {
var out: [7]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = self.endpoint_address;
out[3] = self.attributes;
out[4] = @intCast(self.max_packet_size & 0xff);
out[5] = @intCast((self.max_packet_size >> 8) & 0xff);
out[6] = self.interval;
return out;
}
};
/// Description of an interface within a configuration.
pub const InterfaceDescriptor = extern struct {
pub const const_descriptor_type = DescType.Interface;
length: u8 = 9,
/// Type of this descriptor, must be `Interface`.
descriptor_type: DescType = const_descriptor_type,
/// ID of this interface.
interface_number: u8,
/// Allows a single `interface_number` to have several alternate interface
/// settings, where each alternate increments this field. Normally there's
/// only one, and `alternate_setting` is zero.
alternate_setting: u8,
/// Number of endpoint descriptors in this interface.
num_endpoints: u8,
/// Interface class code, distinguishing the type of interface.
interface_class: u8,
/// Interface subclass code, refining the class of interface.
interface_subclass: u8,
/// Protocol within the interface class/subclass.
interface_protocol: u8,
/// Index of interface name within string descriptor table.
interface_s: u8,
pub fn serialize(self: *const @This()) [9]u8 {
var out: [9]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = self.interface_number;
out[3] = self.alternate_setting;
out[4] = self.num_endpoints;
out[5] = self.interface_class;
out[6] = self.interface_subclass;
out[7] = self.interface_protocol;
out[8] = self.interface_s;
return out;
}
};
/// USB interface association descriptor (IAD) allows the device to group interfaces that belong to a function.
pub const InterfaceAssociationDescriptor = extern struct {
pub const const_descriptor_type = DescType.InterfaceAssociation;
length: u8 = 8,
// Type of this descriptor, must be `InterfaceAssociation`.
descriptor_type: DescType = const_descriptor_type,
// First interface number of the set of interfaces that follow this
// descriptor.
first_interface: u8,
// The number of interfaces that follow this descriptor that are considered
// associated.
interface_count: u8,
// The interface class used for associated interfaces.
function_class: u8,
// The interface subclass used for associated interfaces.
function_subclass: u8,
// The interface protocol used for associated interfaces.
function_protocol: u8,
// Index of the string descriptor describing the associated interfaces.
function: u8,
pub fn serialize(self: *const @This()) [8]u8 {
var out: [8]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = self.first_interface;
out[3] = self.interface_count;
out[4] = self.function_class;
out[5] = self.function_subclass;
out[6] = self.function_protocol;
out[7] = self.function;
return out;
}
};
/// Description of a single available device configuration.
pub const ConfigurationDescriptor = extern struct {
pub const const_descriptor_type = DescType.Config;
length: u8 = 9,
/// Type of this descriptor, must be `Config`.
descriptor_type: DescType = const_descriptor_type,
/// Total length of all descriptors in this configuration, concatenated.
/// This will include this descriptor, plus at least one interface
/// descriptor, plus each interface descriptor's endpoint descriptors.
total_length: u16 align(1),
/// Number of interface descriptors in this configuration.
num_interfaces: u8,
/// Number to use when requesting this configuration via a
/// `SetConfiguration` request.
configuration_value: u8,
/// Index of this configuration's name in the string descriptor table.
configuration_s: u8,
/// Bit set of device attributes:
///
/// - Bit 7 should be set (indicates that device can be bus powered in USB
/// 1.0).
/// - Bit 6 indicates that the device can be self-powered.
/// - Bit 5 indicates that the device can signal remote wakeup of the host
/// (like a keyboard).
/// - The rest are reserved and should be zero.
attributes: u8,
/// Maximum device power consumption in units of 2mA.
max_power: u8,
pub fn serialize(self: *const @This()) [9]u8 {
var out: [9]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intCast(self.total_length & 0xff);
out[3] = @intCast((self.total_length >> 8) & 0xff);
out[4] = self.num_interfaces;
out[5] = self.configuration_value;
out[6] = self.configuration_s;
out[7] = self.attributes;
out[8] = self.max_power;
return out;
}
};
/// Describes a device. This is the most broad description in USB and is
/// typically the first thing the host asks for.
pub const DeviceDescriptor = extern struct {
pub const const_descriptor_type = DescType.Device;
length: u8 = 18,
/// Type of this descriptor, must be `Device`.
descriptor_type: DescType = const_descriptor_type,
/// Version of the device descriptor / USB protocol, in binary-coded
/// decimal. This is typically `0x01_10` for USB 1.1.
bcd_usb: u16 align(1),
/// Class of device, giving a broad functional area.
device_class: u8,
/// Subclass of device, refining the class.
device_subclass: u8,
/// Protocol within the subclass.
device_protocol: u8,
/// Maximum unit of data this device can move.
max_packet_size0: u8,
/// ID of product vendor.
vendor: u16 align(1),
/// ID of product.
product: u16 align(1),
/// Device version number, as BCD again.
bcd_device: u16 align(1),
/// Index of manufacturer name in string descriptor table.
manufacturer_s: u8,
/// Index of product name in string descriptor table.
product_s: u8,
/// Index of serial number in string descriptor table.
serial_s: u8,
/// Number of configurations supported by this device.
num_configurations: u8,
pub fn serialize(self: *const @This()) [18]u8 {
var out: [18]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intCast(self.bcd_usb & 0xff);
out[3] = @intCast((self.bcd_usb >> 8) & 0xff);
out[4] = self.device_class;
out[5] = self.device_subclass;
out[6] = self.device_protocol;
out[7] = self.max_packet_size0;
out[8] = @intCast(self.vendor & 0xff);
out[9] = @intCast((self.vendor >> 8) & 0xff);
out[10] = @intCast(self.product & 0xff);
out[11] = @intCast((self.product >> 8) & 0xff);
out[12] = @intCast(self.bcd_device & 0xff);
out[13] = @intCast((self.bcd_device >> 8) & 0xff);
out[14] = self.manufacturer_s;
out[15] = self.product_s;
out[16] = self.serial_s;
out[17] = self.num_configurations;
return out;
}
};
/// USB Device Qualifier Descriptor
/// This descriptor is mostly the same as the DeviceDescriptor
pub const DeviceQualifierDescriptor = extern struct {
pub const const_descriptor_type = DescType.DeviceQualifier;
length: u8 = 10,
/// Type of this descriptor, must be `Device`.
descriptor_type: DescType = const_descriptor_type,
/// Version of the device descriptor / USB protocol, in binary-coded
/// decimal. This is typically `0x01_10` for USB 1.1.
bcd_usb: u16 align(1),
/// Class of device, giving a broad functional area.
device_class: u8,
/// Subclass of device, refining the class.
device_subclass: u8,
/// Protocol within the subclass.
device_protocol: u8,
/// Maximum unit of data this device can move.
max_packet_size0: u8,
/// Number of configurations supported by this device.
num_configurations: u8,
/// Reserved for future use; must be 0
reserved: u8 = 0,
pub fn serialize(self: *const @This()) [10]u8 {
var out: [10]u8 = undefined;
out[0] = out.len;
out[1] = @intFromEnum(self.descriptor_type);
out[2] = @intCast(self.bcd_usb & 0xff);
out[3] = @intCast((self.bcd_usb >> 8) & 0xff);
out[4] = self.device_class;
out[5] = self.device_subclass;
out[6] = self.device_protocol;
out[7] = self.max_packet_size0;
out[8] = self.num_configurations;
out[9] = self.reserved;
return out;
}
};
pub const DriverErrors = error {
ExpectedInterfaceDescriptor,
UnsupportedInterfaceClassType,
UnsupportedInterfaceSubClassType,
UnexpectedDescriptor
};
pub const UsbDevice = struct {
fn_ready: *const fn () bool,
fn_control_transfer: *const fn (setup: *const SetupPacket, data: []const u8) void,
fn_control_ack: *const fn (setup: *const SetupPacket) void,
fn_endpoint_open: *const fn (ep_desc: []const u8) void,
fn_endpoint_transfer: *const fn (ep_addr: u8, data: []const u8) void,
pub fn ready(self: *@This()) bool {
return self.fn_ready();
}
pub fn control_transfer(self: *@This(), setup: *const SetupPacket, data: []const u8) void {
return self.fn_control_transfer(setup, data);
}
pub fn control_ack(self: *@This(), setup: *const SetupPacket) void {
return self.fn_control_ack(setup);
}
pub fn endpoint_open(self: *@This(), ep_desc: []const u8) void {
return self.fn_endpoint_open(ep_desc);
}
pub fn endpoint_transfer(self: *@This(), ep_addr: u8, data: []const u8) void {
return self.fn_endpoint_transfer(ep_addr, data);
}
};
/// USB Class driver interface
pub const UsbClassDriver = struct {
ptr: *anyopaque,
fn_init: *const fn (ptr: *anyopaque, device: UsbDevice) void,
fn_open: *const fn (ptr: *anyopaque, cfg: []const u8) anyerror!usize,
fn_class_control: *const fn (ptr: *anyopaque, stage: ControlStage, setup: *const SetupPacket) bool,
pub fn init(self: *@This(), device: UsbDevice) void {
return self.fn_init(self.ptr, device);
}
/// Driver open (set config) operation. Must return length of consumed driver config data.
pub fn open(self: *@This(), cfg: []const u8) !usize {
return self.fn_open(self.ptr, cfg);
}
pub fn class_control(self: *@This(), stage: ControlStage, setup: *const SetupPacket) bool {
return self.fn_class_control(self.ptr, stage, setup);
}
};

@ -0,0 +1,53 @@
const std = @import("std");
const types = @import("types.zig");
pub const BosConfig = struct {
const DESC_OFFSET_LEN = 0;
const DESC_OFFSET_TYPE = 1;
pub fn get_desc_len(bos_cfg: []const u8) u8 {
return bos_cfg[DESC_OFFSET_LEN];
}
pub fn get_desc_type(bos_cfg: []const u8) ?types.DescType {
return types.DescType.from_u8(bos_cfg[DESC_OFFSET_TYPE]);
}
pub fn get_data_u8(bos_cfg: []const u8, offset: u16) u8 {
return bos_cfg[offset];
}
pub fn get_data_u16(bos_cfg: []const u8, offset: u16) u16 {
const low_byte: u16 = bos_cfg[offset];
const high_byte: u16 = bos_cfg[offset+1];
return (high_byte << 8) | low_byte;
}
/// Only for temporal u8 fields use as u16 fields will have wrong values because BOS endianness
pub fn get_desc_as(comptime T: type, bos_cfg: []const u8) *const T {
return @ptrCast(@constCast(bos_cfg.ptr));
}
/// Only for temporal u8 fields use as u16 fields will have wrong values because BOS endianness
pub fn try_get_desc_as(comptime T: type, bos_cfg: []const u8) ?*const T {
if (bos_cfg.len == 0) return null;
const exp_desc_type = @field(T, "const_descriptor_type");
const cfg_desc_type = bos_cfg[DESC_OFFSET_TYPE];
if (cfg_desc_type != @intFromEnum(exp_desc_type)) {
return null;
}
else {
return @constCast(@ptrCast(bos_cfg.ptr));
}
}
pub fn get_desc_next(bos_cfg: []const u8) []const u8 {
const len = bos_cfg[DESC_OFFSET_LEN];
return bos_cfg[len..];
}
};
test "Test try_get_desc_as" {
const cfg = [_]u8{ 7, 5, 129, 3, 8, 0, 16 };
try std.testing.expect(BosConfig.try_get_desc_as(types.EndpointDescriptor, cfg[0..]) != null);
}

@ -0,0 +1,26 @@
const std = @import("std");
const types = @import("types.zig");
pub const VendorClassDriver = struct {
fn init(_: *anyopaque, _: types.UsbDevice) void {
}
fn open(_: *anyopaque, _: []const u8) !usize {
return 0;
}
pub fn class_control(_: *anyopaque, _: types.ControlStage, _: *const types.SetupPacket) bool {
return true;
}
pub fn driver(self: *@This()) types.UsbClassDriver {
return .{
.ptr = self,
.fn_init = init,
.fn_open = open,
.fn_class_control = class_control
};
}
};

@ -15,8 +15,8 @@ const available_examples = [_]Example{
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_spi-master", .file = "src/spi_master.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_squarewave", .file = "src/squarewave.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_uart", .file = "src/uart.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_usb-device", .file = "src/usb_device.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_usb-hid", .file = "src/usb_hid.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_usb-cdc", .file = "src/usb_cdc.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_ws2812", .file = "src/ws2812.zig" },
.{ .target = rp2040.boards.raspberrypi.pico, .name = "pico_multicore" , .file = "src/blinky_core1.zig" },

@ -0,0 +1,105 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const flash = rp2040.flash;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const usb = rp2040.usb;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
const usb_config_len = usb.templates.config_descriptor_len + usb.templates.cdc_descriptor_len;
const usb_config_descriptor =
usb.templates.config_descriptor(1, 2, 0, usb_config_len, 0xc0, 100) ++
usb.templates.cdc_descriptor(0, 4, usb.Endpoint.to_address(1, .In), 8, usb.Endpoint.to_address(2, .Out), usb.Endpoint.to_address(2, .In), 64);
var driver_cdc = usb.cdc.CdcClassDriver{};
var drivers = [_]usb.types.UsbClassDriver{ driver_cdc.driver() };
// This is our device configuration
pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
.device_descriptor = &.{
.descriptor_type = usb.DescType.Device,
.bcd_usb = 0x0200,
.device_class = 0xEF,
.device_subclass = 2,
.device_protocol = 1,
.max_packet_size0 = 64,
.vendor = 0x2E8A,
.product = 0x000a,
.bcd_device = 0x0100,
.manufacturer_s = 1,
.product_s = 2,
.serial_s = 0,
.num_configurations = 1,
},
.config_descriptor = &usb_config_descriptor,
.lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409)
.descriptor_strings = &.{
&usb.utils.utf8ToUtf16Le("Raspberry Pi"),
&usb.utils.utf8ToUtf16Le("Pico Test Device"),
&usb.utils.utf8ToUtf16Le("someserial"),
&usb.utils.utf8ToUtf16Le("Board CDC"),
},
.drivers = &drivers
};
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const microzig_options = .{
.log_level = .debug,
.logFn = rp2040.uart.log,
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
// First we initialize the USB clock
rp2040.usb.Usb.init_clk();
// Then initialize the USB device using the configuration defined above
rp2040.usb.Usb.init_device(&DEVICE_CONFIGURATION) catch unreachable;
var old: u64 = time.get_time_since_boot().to_us();
var new: u64 = 0;
var i: u32 = 0;
var buf: [1024]u8 = undefined;
while (true) {
// You can now poll for USB events
rp2040.usb.Usb.task(
false, // debug output over UART [Y/n]
) catch unreachable;
new = time.get_time_since_boot().to_us();
if (new - old > 500000) {
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);
}
}
}

@ -1,169 +0,0 @@
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const flash = rp2040.flash;
const time = rp2040.time;
const gpio = rp2040.gpio;
const clocks = rp2040.clocks;
const usb = rp2040.usb;
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
// First we define two callbacks that will be used by the endpoints we define next...
fn ep1_in_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
_ = data;
// The host has collected the data we repeated onto
// EP1! Set up to receive more data on EP1.
usb.Usb.callbacks.usb_start_rx(
dc.endpoints[2], // EP1_OUT_CFG,
64,
);
}
fn ep1_out_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
// We've gotten data from the host on our custom
// EP1! Set up EP1 to repeat it.
usb.Usb.callbacks.usb_start_tx(
dc.endpoints[3], // EP1_IN_CFG,
data,
);
}
// The endpoints EP0_IN and EP0_OUT are already defined but you can
// add your own endpoints to...
pub var EP1_OUT_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.Out.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Bulk),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 2,
.buffer_control_index = 3,
.data_buffer_index = 2,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_OUT
.callback = ep1_out_callback,
};
pub var EP1_IN_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.In.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Bulk),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 1,
.buffer_control_index = 2,
.data_buffer_index = 3,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_IN
.callback = ep1_in_callback,
};
// This is our device configuration
pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
.device_descriptor = &.{
.descriptor_type = usb.DescType.Device,
.bcd_usb = 0x0110,
.device_class = 0,
.device_subclass = 0,
.device_protocol = 0,
.max_packet_size0 = 64,
.vendor = 0,
.product = 1,
.bcd_device = 0,
.manufacturer_s = 1,
.product_s = 2,
.serial_s = 0,
.num_configurations = 1,
},
.interface_descriptor = &.{
.descriptor_type = usb.DescType.Interface,
.interface_number = 0,
.alternate_setting = 0,
// We have two endpoints (EP0 IN/OUT don't count)
.num_endpoints = 2,
.interface_class = 0xff,
.interface_subclass = 0,
.interface_protocol = 0,
.interface_s = 0,
},
.config_descriptor = &.{
.descriptor_type = usb.DescType.Config,
// This is calculated via the sizes of underlying descriptors contained in this configuration.
// ConfigurationDescriptor(9) + InterfaceDescriptor(9) * 1 + EndpointDescriptor(8) * 2
.total_length = 34,
.num_interfaces = 1,
.configuration_value = 1,
.configuration_s = 0,
.attributes = 0xc0,
.max_power = 0x32,
},
.lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409)
.descriptor_strings = &.{
// ugly unicode :|
"R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00",
"P\x00i\x00c\x00o\x00 \x00T\x00e\x00s\x00t\x00 \x00D\x00e\x00v\x00i\x00c\x00e\x00",
},
// Here we pass all endpoints to the config
// Dont forget to pass EP0_[IN|OUT] in the order seen below!
.endpoints = .{
&usb.EP0_OUT_CFG,
&usb.EP0_IN_CFG,
&EP1_OUT_CFG,
&EP1_IN_CFG,
},
};
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const microzig_options = .{
.log_level = .debug,
.logFn = rp2040.uart.log,
};
pub fn main() !void {
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
// First we initialize the USB clock
rp2040.usb.Usb.init_clk();
// Then initialize the USB device using the configuration defined above
rp2040.usb.Usb.init_device(&DEVICE_CONFIGURATION) catch unreachable;
var old: u64 = time.get_time_since_boot().to_us();
var new: u64 = 0;
while (true) {
// You can now poll for USB events
rp2040.usb.Usb.task(
false, // debug output over UART [Y/n]
) catch unreachable;
new = time.get_time_since_boot().to_us();
if (new - old > 500000) {
old = new;
led.toggle();
}
}
}

@ -14,59 +14,14 @@ const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
// First we define two callbacks that will be used by the endpoints we define next...
fn ep1_in_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
_ = data;
// The host has collected the data we repeated onto
// EP1! Set up to receive more data on EP1.
usb.Usb.callbacks.usb_start_rx(
dc.endpoints[2], // EP1_OUT_CFG,
64,
);
}
fn ep1_out_callback(dc: *usb.DeviceConfiguration, data: []const u8) void {
// We've gotten data from the host on our custom
// EP1! Set up EP1 to repeat it.
usb.Usb.callbacks.usb_start_tx(
dc.endpoints[3], // EP1_IN_CFG,
data,
);
}
// The endpoints EP0_IN and EP0_OUT are already defined but you can
// add your own endpoints to...
pub var EP1_OUT_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.Out.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Interrupt),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 2,
.buffer_control_index = 3,
.data_buffer_index = 2,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_OUT
.callback = ep1_out_callback,
};
const usb_packet_size = 64;
const usb_config_len = usb.templates.config_descriptor_len + usb.templates.hid_in_out_descriptor_len;
const usb_config_descriptor =
usb.templates.config_descriptor(1, 1, 0, usb_config_len, 0xc0, 100) ++
usb.templates.hid_in_out_descriptor(0, 0, 0, usb.hid.ReportDescriptorGenericInOut.len, usb.Endpoint.to_address(1, .Out), usb.Endpoint.to_address(1, .In), usb_packet_size, 0);
pub var EP1_IN_CFG: usb.EndpointConfiguration = .{
.descriptor = &usb.EndpointDescriptor{
.descriptor_type = usb.DescType.Endpoint,
.endpoint_address = usb.Dir.In.endpoint(1),
.attributes = @intFromEnum(usb.TransferType.Interrupt),
.max_packet_size = 64,
.interval = 0,
},
.endpoint_control_index = 1,
.buffer_control_index = 2,
.data_buffer_index = 3,
.next_pid_1 = false,
// The callback will be executed if we got an interrupt on EP1_IN
.callback = ep1_in_callback,
};
var driver_hid = usb.hid.HidClassDriver{ .report_descriptor = &usb.hid.ReportDescriptorGenericInOut };
var drivers = [_]usb.types.UsbClassDriver{ driver_hid.driver() };
// This is our device configuration
pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
@ -78,7 +33,7 @@ pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
.device_protocol = 0,
.max_packet_size0 = 64,
.vendor = 0xCafe,
.product = 1,
.product = 2,
.bcd_device = 0x0100,
// Those are indices to the descriptor strings
// Make sure to provide enough string descriptors!
@ -87,55 +42,14 @@ pub var DEVICE_CONFIGURATION: usb.DeviceConfiguration = .{
.serial_s = 3,
.num_configurations = 1,
},
.interface_descriptor = &.{
.descriptor_type = usb.DescType.Interface,
.interface_number = 0,
.alternate_setting = 0,
// We have two endpoints (EP0 IN/OUT don't count)
.num_endpoints = 2,
.interface_class = 3,
.interface_subclass = 0,
.interface_protocol = 0,
.interface_s = 0,
},
.config_descriptor = &.{
.descriptor_type = usb.DescType.Config,
// This is calculated via the sizes of underlying descriptors contained in this configuration.
// ConfigurationDescriptor(9) + InterfaceDescriptor(9) * 1 + EndpointDescriptor(8) * 2
.total_length = 34,
.num_interfaces = 1,
.configuration_value = 1,
.configuration_s = 0,
.attributes = 0xc0,
.max_power = 0x32,
},
.config_descriptor = &usb_config_descriptor,
.lang_descriptor = "\x04\x03\x09\x04", // length || string descriptor (0x03) || Engl (0x0409)
.descriptor_strings = &.{
// ugly unicode :|
//"R\x00a\x00s\x00p\x00b\x00e\x00r\x00r\x00y\x00 \x00P\x00i\x00",
&usb.utf8ToUtf16Le("Raspberry Pi"),
//"P\x00i\x00c\x00o\x00 \x00T\x00e\x00s\x00t\x00 \x00D\x00e\x00v\x00i\x00c\x00e\x00",
&usb.utf8ToUtf16Le("Pico Test Device"),
//"c\x00a\x00f\x00e\x00b\x00a\x00b\x00e\x00",
&usb.utf8ToUtf16Le("cafebabe"),
},
.hid = .{
.hid_descriptor = &.{
.bcd_hid = 0x0111,
.country_code = 0,
.num_descriptors = 1,
.report_length = 34,
},
.report_descriptor = &usb.hid.ReportDescriptorFidoU2f,
},
// Here we pass all endpoints to the config
// Dont forget to pass EP0_[IN|OUT] in the order seen below!
.endpoints = .{
&usb.EP0_OUT_CFG,
&usb.EP0_IN_CFG,
&EP1_OUT_CFG,
&EP1_IN_CFG,
&usb.utils.utf8ToUtf16Le("Raspberry Pi"),
&usb.utils.utf8ToUtf16Le("Pico Test Device"),
&usb.utils.utf8ToUtf16Le("cafebabe"),
},
.drivers = &drivers
};
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
@ -172,7 +86,7 @@ pub fn main() !void {
while (true) {
// You can now poll for USB events
rp2040.usb.Usb.task(
true, // debug output over UART [Y/n]
false, // debug output over UART [Y/n]
) catch unreachable;
new = time.get_time_since_boot().to_us();

Loading…
Cancel
Save