Skip to content

Commit 2847c4f

Browse files
authored
Merge branch 'main' into mmio-usage
2 parents 7d6c93c + f578cd8 commit 2847c4f

File tree

29 files changed

+1410
-424
lines changed

29 files changed

+1410
-424
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: CI / Unused Imports Check
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
jobs:
9+
check-unused-imports:
10+
runs-on: ubuntu-latest
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
15+
steps:
16+
- name: Checkout PR code
17+
uses: actions/checkout@v3
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Setup Zig
22+
uses: mlugg/setup-zig@v2
23+
with:
24+
version: 0.15.1
25+
26+
- name: Build zigimports
27+
run: |
28+
git clone --depth 1 https://github.com/tusharsadhwani/zigimports.git /tmp/zigimports
29+
cd /tmp/zigimports
30+
zig build --release=safe
31+
# Binary is named zigimports-<arch>-<os>, find and symlink it
32+
ln -s /tmp/zigimports/zig-out/bin/zigimports-* /tmp/zigimports/zig-out/bin/zigimports
33+
34+
- name: Get changed Zig files
35+
id: changed-files
36+
run: |
37+
FILES=$(git diff --name-only --diff-filter=d ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | grep '\.zig$' || true)
38+
echo "files<<EOF" >> $GITHUB_OUTPUT
39+
echo "$FILES" >> $GITHUB_OUTPUT
40+
echo "EOF" >> $GITHUB_OUTPUT
41+
42+
- name: Check for unused imports
43+
id: check
44+
run: |
45+
RESULTS=""
46+
echo "${{ steps.changed-files.outputs.files }}" | while read file; do
47+
if [ -n "$file" ] && [ -f "$file" ]; then
48+
/tmp/zigimports/zig-out/bin/zigimports "$file" 2>&1 || true
49+
fi
50+
done | tee unused_imports.txt
51+
52+
# Set output for later steps
53+
if grep -q "is unused" unused_imports.txt 2>/dev/null; then
54+
echo "has_issues=true" >> $GITHUB_OUTPUT
55+
else
56+
echo "has_issues=false" >> $GITHUB_OUTPUT
57+
fi
58+
59+
- name: Post PR comment
60+
if: steps.check.outputs.has_issues == 'true'
61+
uses: actions/github-script@v7
62+
with:
63+
script: |
64+
const fs = require('fs');
65+
66+
const content = fs.readFileSync('unused_imports.txt', 'utf8').trim();
67+
const lines = content.split('\n').filter(l => l.includes('is unused'));
68+
69+
if (lines.length === 0) return;
70+
71+
// Check for existing comment and update it
72+
const marker = '<!-- unused-imports-check -->';
73+
const comments = await github.paginate(
74+
github.rest.issues.listComments,
75+
{
76+
owner: context.repo.owner,
77+
repo: context.repo.repo,
78+
issue_number: context.issue.number
79+
}
80+
);
81+
82+
const existing = comments.find(c =>
83+
c.user.login === 'github-actions[bot]' &&
84+
c.body.includes(marker)
85+
);
86+
87+
let body = '## ⚠️ Unused Imports Found\n\n';
88+
body += '```\n' + lines.join('\n') + '\n```\n\n';
89+
body += 'Run `zigimports --fix <file>` locally to automatically remove unused imports.\n\n';
90+
body += marker;
91+
92+
if (existing) {
93+
await github.rest.issues.updateComment({
94+
owner: context.repo.owner,
95+
repo: context.repo.repo,
96+
comment_id: existing.id,
97+
body: body
98+
});
99+
} else {
100+
await github.rest.issues.createComment({
101+
owner: context.repo.owner,
102+
repo: context.repo.repo,
103+
issue_number: context.issue.number,
104+
body: body
105+
});
106+
}
107+
108+
- name: Remove comment if no issues
109+
if: steps.check.outputs.has_issues == 'false'
110+
uses: actions/github-script@v7
111+
with:
112+
script: |
113+
const marker = '<!-- unused-imports-check -->';
114+
const comments = await github.paginate(
115+
github.rest.issues.listComments,
116+
{
117+
owner: context.repo.owner,
118+
repo: context.repo.repo,
119+
issue_number: context.issue.number
120+
}
121+
);
122+
123+
const existing = comments.find(c =>
124+
c.user.login === 'github-actions[bot]' &&
125+
c.body.includes(marker)
126+
);
127+
128+
if (existing) {
129+
await github.rest.issues.deleteComment({
130+
owner: context.repo.owner,
131+
repo: context.repo.repo,
132+
comment_id: existing.id
133+
});
134+
}

core/src/core/usb.zig

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ pub const StructFieldAttributes = struct {
2020
default_value_ptr: ?*const anyopaque = null,
2121
};
2222

23-
/// Meant to make transition to zig 0.16 easier
23+
/// Helper to create a struct, wrapping around @Type, meant to make transition to zig 0.16 easier
2424
pub fn Struct(
25-
comptime layout: std.builtin.Type.ContainerLayout,
26-
comptime BackingInt: ?type,
27-
comptime field_names: []const [:0]const u8,
28-
comptime field_types: *const [field_names.len]type,
29-
comptime field_attrs: *const [field_names.len]StructFieldAttributes,
25+
layout: std.builtin.Type.ContainerLayout,
26+
BackingInt: ?type,
27+
field_names: []const [:0]const u8,
28+
field_types: *const [field_names.len]type,
29+
field_attrs: *const [field_names.len]StructFieldAttributes,
3030
) type {
31-
comptime var fields: []const std.builtin.Type.StructField = &.{};
31+
var fields: []const std.builtin.Type.StructField = &.{};
32+
// Iterate over the names, field types, and attributes, creating a new struct field entry
3233
for (field_names, field_types, field_attrs) |n, T, a| {
3334
fields = fields ++ &[1]std.builtin.Type.StructField{.{
3435
.name = n,
@@ -47,6 +48,7 @@ pub fn Struct(
4748
} });
4849
}
4950

51+
// What does this do? It lets you iterate through interfaces and endpoints?
5052
pub const DescriptorAllocator = struct {
5153
next_ep_num: [2]u8,
5254
next_itf_num: u8,
@@ -96,6 +98,7 @@ pub const DescriptorAllocator = struct {
9698
}
9799
};
98100

101+
/// Wraps a Descriptor type. Returned by the `create` method of the Descriptor.
99102
pub fn DescriptorCreateResult(Descriptor: type) type {
100103
return struct {
101104
descriptor: Descriptor,
@@ -108,7 +111,7 @@ pub fn DescriptorCreateResult(Descriptor: type) type {
108111
}
109112

110113
/// USB Device interface
111-
/// Any device implementation used with DeviceController must implement those functions
114+
/// Any device implementation used with DeviceController must implement these functions
112115
pub const DeviceInterface = struct {
113116
pub const VTable = struct {
114117
ep_writev: *const fn (*DeviceInterface, types.Endpoint.Num, []const []const u8) types.Len,
@@ -191,16 +194,24 @@ pub const Config = struct {
191194
max_current_ma: u9,
192195
Drivers: type,
193196

197+
/// Generate A struct with a field for each field in Drivers, where the type is the third
198+
/// arg of the Drivers' Descriptor's 'create' method.
194199
pub fn Args(self: @This()) type {
195200
const fields = @typeInfo(self.Drivers).@"struct".fields;
196201
var field_names: [fields.len][:0]const u8 = undefined;
197202
var field_types: [fields.len]type = undefined;
198203
for (fields, 0..) |fld, i| {
204+
// Collect field names
199205
field_names[i] = fld.name;
206+
// Collect the type info for the Descriptor.create function parameter
200207
const params = @typeInfo(@TypeOf(fld.type.Descriptor.create)).@"fn".params;
208+
// Ensure it takes 3 parameters
201209
assert(params.len == 3);
210+
// The first must be a DescriptorAllocator
202211
assert(params[0].type == *DescriptorAllocator);
212+
// The second is usb.types.Len
203213
assert(params[1].type == types.Len);
214+
// And save the type of the third
204215
field_types[i] = params[2].type.?;
205216
}
206217
return Struct(.auto, null, &field_names, &field_types, &@splat(.{}));
@@ -251,11 +262,14 @@ pub fn validate_controller(T: type) void {
251262

252263
/// USB device controller
253264
///
254-
/// This code handles usb enumeration and configuration and routes packets to drivers.
265+
/// Responds to host requests and dispatches to the appropriate drivers.
266+
/// When this type is build (at comptime), it builds descriptor and handler tables based on the
267+
/// provided config.
255268
pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
256269
std.debug.assert(config.configurations.len == 1);
257270

258271
return struct {
272+
// We only support one configuration
259273
const config0 = config.configurations[0];
260274
const driver_fields = @typeInfo(config0.Drivers).@"struct".fields;
261275
const DriverEnum = std.meta.FieldEnum(config0.Drivers);
@@ -298,24 +312,33 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
298312
var driver_alloc_attrs: []const StructFieldAttributes = &.{};
299313

300314
for (driver_fields, 0..) |drv, drv_id| {
315+
// Get descriptor type for the current driver
301316
const Descriptors = drv.type.Descriptor;
317+
// Call the create method on the descriptor, which wraps it with the allow stuff.
302318
const result = Descriptors.create(&alloc, max_psize, @field(driver_args[0], drv.name));
319+
// Get the descriptor instance from the result.
303320
const descriptors = result.descriptor;
304321

322+
// If the driver requests memory, then collect the names, types, and attrs
305323
if (result.alloc_bytes) |len| {
306-
driver_alloc_names = driver_alloc_names ++ &[1][:0]const u8{drv.name};
307-
driver_alloc_types = driver_alloc_types ++ &[1]type{[len]u8};
308-
driver_alloc_attrs = driver_alloc_attrs ++ &[1]StructFieldAttributes{.{ .@"align" = result.alloc_align }};
324+
driver_alloc_names = driver_alloc_names ++ &[_][:0]const u8{drv.name};
325+
driver_alloc_types = driver_alloc_types ++ &[_]type{[len]u8};
326+
driver_alloc_attrs = driver_alloc_attrs ++ &[_]StructFieldAttributes{.{ .@"align" = result.alloc_align }};
309327
} else {
310328
assert(result.alloc_align == null);
311329
}
312330

313331
assert(@alignOf(Descriptors) == 1);
314332
size += @sizeOf(Descriptors);
315333

334+
// Collect handler types, names, and drivers, to later be bound to the appropriate
335+
// endpoints
316336
for (@typeInfo(Descriptors).@"struct".fields, 0..) |fld, desc_num| {
317337
const desc = @field(descriptors, fld.name);
318338

339+
// Determine which interface numbers this driver owns. If it is an
340+
// InterfaceAssociation, then use the interface count. If it is an Interface,
341+
// then the driver owns just that one interface.
319342
if (desc_num == 0) {
320343
const itf_start, const itf_count = switch (fld.type) {
321344
descriptor.InterfaceAssociation => .{ desc.first_interface, desc.interface_count },
@@ -329,10 +352,11 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
329352
};
330353
if (itf_start != itf_handlers.len)
331354
@compileError("interface numbering mismatch");
332-
itf_handlers = itf_handlers ++ &[1]DriverEnum{@field(DriverEnum, drv.name)} ** itf_count;
355+
itf_handlers = itf_handlers ++ &[_]DriverEnum{@field(DriverEnum, drv.name)} ** itf_count;
333356
}
334357

335358
switch (fld.type) {
359+
// Register handler for endpoints
336360
descriptor.Endpoint => {
337361
const ep_dir = @intFromEnum(desc.endpoint.dir);
338362
const ep_num = @intFromEnum(desc.endpoint.num);
@@ -347,6 +371,7 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
347371
ep_handler_drivers[ep_dir][ep_num] = drv_id;
348372
ep_handler_names[ep_dir][ep_num] = fld.name;
349373
},
374+
// Interface association must be first
350375
descriptor.InterfaceAssociation => assert(desc_num == 0),
351376
else => {},
352377
}
@@ -356,9 +381,12 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
356381
field_attrs[drv_id] = .{ .default_value_ptr = &descriptors };
357382
}
358383

384+
// Finally, bind the handler functions based on the data collected above.
359385
const Tuple = std.meta.Tuple;
386+
// Create a tuple with the appropriate types
360387
const ep_handlers_types: [2]type = .{ Tuple(&ep_handler_types[0]), Tuple(&ep_handler_types[1]) };
361388
var ep_handlers: Tuple(&ep_handlers_types) = undefined;
389+
// Iterate over all IN and OUT endpoints and bind the handler for any that are set.
362390
for (&ep_handler_types, &ep_handler_names, &ep_handler_drivers, 0..) |htypes, hnames, hdrivers, dir| {
363391
for (&htypes, &hnames, &hdrivers, 0..) |T, name, drv_id, ep| {
364392
if (T != void)
@@ -558,6 +586,7 @@ pub fn DeviceController(config: Config, driver_args: config.DriverArgs()) type {
558586
}
559587
}
560588

589+
// Return the appropriate descriptor type as determined by the top 8 bits of the value.
561590
fn get_descriptor(value: u16) ?[]const u8 {
562591
const asBytes = std.mem.asBytes;
563592
const desc_type: descriptor.Type = @enumFromInt(value >> 8);

core/src/core/usb/drivers/CDC.zig

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const usb = @import("../../usb.zig");
33
const assert = std.debug.assert;
44
const log = std.log.scoped(.usb_cdc);
55

6+
/// CDC PSTN Subclass Management Element Requests
67
pub const ManagementRequestType = enum(u8) {
78
SetCommFeature = 0x02,
89
GetCommFeature = 0x03,
@@ -27,6 +28,7 @@ pub const ManagementRequestType = enum(u8) {
2728
_,
2829
};
2930

31+
/// Line coding structure for SetLineCoding/GetLineCoding requests
3032
pub const LineCoding = extern struct {
3133
pub const StopBits = enum(u8) { @"1" = 0, @"1.5" = 1, @"2" = 2, _ };
3234
pub const Parity = enum(u8) {
@@ -44,13 +46,14 @@ pub const LineCoding = extern struct {
4446
data_bits: u8,
4547

4648
pub const init: @This() = .{
47-
.bit_rate = 115200,
48-
.stop_bits = 0,
49-
.parity = 0,
49+
.bit_rate = .from(115200),
50+
.stop_bits = .@"1",
51+
.parity = .none,
5052
.data_bits = 8,
5153
};
5254
};
5355

56+
// This struct bundles all the descriptors CDC needs into one Configuration.
5457
pub const Descriptor = extern struct {
5558
const desc = usb.descriptor;
5659

@@ -70,6 +73,11 @@ pub const Descriptor = extern struct {
7073
itf_data: []const u8 = "",
7174
};
7275

76+
/// This function is used during descriptor creation. Endpoint and interface numbers are
77+
/// allocated through the `alloc` parameter. Third argument can be of any type, it's passed
78+
/// by the user when creating the device controller type. If multiple instances of a driver
79+
/// are used, this function is called for each, with different arguments. Passing arguments
80+
/// through this function is preferred to making the whole driver generic.
7381
pub fn create(
7482
alloc: *usb.DescriptorAllocator,
7583
max_supported_packet_size: usb.types.Len,
@@ -130,6 +138,8 @@ pub const Descriptor = extern struct {
130138
}
131139
};
132140

141+
// These field names are matched (at comptime) to the field names in the descriptor returned from
142+
// `create` when binding the endpoints.
133143
pub const handlers: usb.DriverHandlers(@This()) = .{
134144
.ep_notifi = on_notifi_ready,
135145
.ep_out = on_rx,
@@ -202,21 +212,18 @@ pub fn flush(self: *@This()) bool {
202212
return true;
203213
}
204214

215+
// Called when the host selects a configuration.
205216
pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterface, data: []u8) void {
206217
const len_half = @divExact(data.len, 2);
207218
assert(len_half == desc.ep_in.max_packet_size.into());
208219
assert(len_half == desc.ep_out.max_packet_size.into());
209220
self.* = .{
210221
.device = device,
211222
.descriptor = desc,
212-
.line_coding = .{
213-
.bit_rate = .from(115200),
214-
.stop_bits = .@"1",
215-
.parity = .none,
216-
.data_bits = 8,
217-
},
223+
.line_coding = .init,
218224
.notifi_ready = .init(true),
219225

226+
// `init` provides a data buffer, which we split in half for rx and tx data.
220227
.rx_data = data[0..len_half],
221228
.rx_seek = 0,
222229
.rx_end = 0,
@@ -229,6 +236,8 @@ pub fn init(self: *@This(), desc: *const Descriptor, device: *usb.DeviceInterfac
229236
device.ep_listen(desc.ep_out.endpoint.num, @intCast(self.rx_data.len));
230237
}
231238

239+
/// Handle class-specific SETUP requests where recipient=Interface
240+
/// Called by DeviceController when the interface number matches this driver.
232241
pub fn class_request(self: *@This(), setup: *const usb.types.SetupPacket) ?[]const u8 {
233242
const mgmt_request: ManagementRequestType = @enumFromInt(setup.request);
234243
log.debug("cdc setup: {any} {} {}", .{ mgmt_request, setup.length.into(), setup.value.into() });

0 commit comments

Comments
 (0)