Skip to content

Commit 301ac94

Browse files
arkadiuszwojcikArkadiusz Wójcik
and
Arkadiusz Wójcik
authored
USB driver: support chunked data (#201)
* USB driver: support chunked data * USB driver: increase tmp buffer size * Improved buffer API (aligned with FixedBufferReader from std.dwarf) * Added API documentation * Bug fix: Send empty packet at very end --------- Co-authored-by: Arkadiusz Wójcik <[email protected]>
1 parent 1b1e3e6 commit 301ac94

File tree

1 file changed

+164
-79
lines changed

1 file changed

+164
-79
lines changed

core/src/core/usb.zig

Lines changed: 164 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//! 5. Call `usb.task()` within the main loop
1212

1313
const std = @import("std");
14+
const builtin = @import("builtin");
1415

1516
/// USB Human Interface Device (HID)
1617
pub const hid = @import("usb/hid.zig");
@@ -84,7 +85,28 @@ pub fn Usb(comptime f: anytype) type {
8485
var started = false;
8586
// Some scratch space that we'll use for things like preparing string
8687
// descriptors for transmission.
87-
var tmp: [64]u8 = .{0} ** 64;
88+
var tmp: [128]u8 = .{0} ** 128;
89+
// Keeps track of sent data from tmp buffer
90+
var buffer_reader = BufferReader { .buffer = &.{} };
91+
};
92+
93+
// Command endpoint utilities
94+
const CmdEndpoint = struct {
95+
96+
/// Command response utility function that can split long data in multiple packets
97+
fn send_cmd_response(data: []const u8, expected_max_length: u16) void {
98+
const cmd_in_endpoint = usb_config.?.endpoints[EP0_IN_IDX];
99+
100+
S.buffer_reader = BufferReader { .buffer = data[0..@min(data.len, expected_max_length)] };
101+
const data_chunk = S.buffer_reader.try_peek(cmd_in_endpoint.descriptor.max_packet_size);
102+
103+
if (data_chunk.len > 0) {
104+
f.usb_start_tx(
105+
cmd_in_endpoint,
106+
data_chunk
107+
);
108+
}
109+
}
88110
};
89111

90112
// Check which interrupt flags are set.
@@ -156,69 +178,36 @@ pub fn Usb(comptime f: anytype) type {
156178
// implementation.
157179
usb_config.?.endpoints[EP0_IN_IDX].next_pid_1 = true;
158180

159-
const dc = usb_config.?.device_descriptor.serialize();
160-
@memcpy(S.tmp[0..dc.len], &dc);
181+
var bw = BufferWriter { .buffer = &S.tmp };
182+
try bw.write(&usb_config.?.device_descriptor.serialize());
161183

162-
// Configure EP0 IN to send the device descriptor
163-
// when it's next asked.
164-
f.usb_start_tx(
165-
usb_config.?.endpoints[EP0_IN_IDX],
166-
S.tmp[0..dc.len],
167-
);
184+
CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length);
168185
},
169186
.Config => {
170187
if (debug) std.log.info(" Config", .{});
188+
171189
// Config descriptor requests are slightly unusual.
172190
// We can respond with just our config descriptor,
173191
// but we can _also_ append our interface and
174192
// endpoint descriptors to the end, saving some
175-
// round trips. We'll choose to do this if the
176-
// number of bytes the host will accept (in the
177-
// `length` field) is large enough.
178-
var used: usize = 0;
179-
180-
const cd = usb_config.?.config_descriptor.serialize();
181-
@memcpy(S.tmp[used .. used + cd.len], &cd);
182-
used += cd.len;
183-
184-
if (setup.length > used) {
185-
// Do the rest!
186-
//
187-
// This is slightly incorrect because the host
188-
// might have asked for a number of bytes in
189-
// between the size of a config descriptor, and
190-
// the amount we're going to send back. However,
191-
// in practice, the host always asks for either
192-
// (1) the exact size of a config descriptor, or
193-
// (2) 64 bytes, and this all fits in 64 bytes.
194-
const id = usb_config.?.interface_descriptor.serialize();
195-
@memcpy(S.tmp[used .. used + id.len], &id);
196-
used += id.len;
197-
198-
// Seems like the host does not bother asking for the
199-
// hid descriptor so we'll just send it with the
200-
// other descriptors.
201-
if (usb_config.?.hid) |hid_conf| {
202-
const hd = hid_conf.hid_descriptor.serialize();
203-
@memcpy(S.tmp[used .. used + hd.len], &hd);
204-
used += hd.len;
205-
}
193+
// round trips.
194+
var bw = BufferWriter { .buffer = &S.tmp };
195+
try bw.write(&usb_config.?.config_descriptor.serialize());
196+
try bw.write(&usb_config.?.interface_descriptor.serialize());
206197

207-
// TODO: depending on the number of endpoints
208-
// this might not fit in 64 bytes -> split message
209-
// into multiple packets
210-
for (usb_config.?.endpoints[2..]) |ep| {
211-
const ed = ep.descriptor.serialize();
212-
@memcpy(S.tmp[used .. used + ed.len], &ed);
213-
used += ed.len;
214-
}
198+
199+
// Seems like the host does not bother asking for the
200+
// hid descriptor so we'll just send it with the
201+
// other descriptors.
202+
if (usb_config.?.hid) |hid_conf| {
203+
try bw.write(&hid_conf.hid_descriptor.serialize());
215204
}
216205

217-
// Set up EP0 IN to send the stuff we just composed.
218-
f.usb_start_tx(
219-
usb_config.?.endpoints[EP0_IN_IDX],
220-
S.tmp[0..used],
221-
);
206+
for (usb_config.?.endpoints[2..]) |ep| {
207+
try bw.write(&ep.descriptor.serialize());
208+
}
209+
210+
CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length);
222211
},
223212
.String => {
224213
if (debug) std.log.info(" String", .{});
@@ -235,19 +224,16 @@ pub fn Usb(comptime f: anytype) type {
235224
const s = usb_config.?.descriptor_strings[i - 1];
236225
const len = 2 + s.len;
237226

238-
S.tmp[0] = @intCast(len);
239-
S.tmp[1] = 0x03;
240-
@memcpy(S.tmp[2..len], s);
227+
var wb = BufferWriter { .buffer = &S.tmp };
228+
try wb.write_int(u8, @intCast(len));
229+
try wb.write_int(u8, 0x03);
230+
try wb.write(s);
241231

242-
break :StringBlk S.tmp[0..len];
232+
break :StringBlk wb.get_written_slice();
243233
}
244234
};
245-
// Set up EP0 IN to send whichever thing we just
246-
// decided on.
247-
f.usb_start_tx(
248-
usb_config.?.endpoints[EP0_IN_IDX],
249-
bytes,
250-
);
235+
236+
CmdEndpoint.send_cmd_response(bytes, setup.length);
251237
},
252238
.Interface => {
253239
if (debug) std.log.info(" Interface", .{});
@@ -278,13 +264,10 @@ pub fn Usb(comptime f: anytype) type {
278264
.num_configurations = usb_config.?.device_descriptor.num_configurations,
279265
};
280266

281-
const data = dqd.serialize();
282-
@memcpy(S.tmp[0..data.len], &data);
267+
var bw = BufferWriter { .buffer = &S.tmp };
268+
try bw.write(&dqd.serialize());
283269

284-
f.usb_start_tx(
285-
usb_config.?.endpoints[EP0_IN_IDX],
286-
S.tmp[0..data.len],
287-
);
270+
CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length);
288271
},
289272
}
290273
} else {
@@ -298,23 +281,17 @@ pub fn Usb(comptime f: anytype) type {
298281
.Hid => {
299282
if (debug) std.log.info(" HID", .{});
300283

301-
const hd = hid_conf.hid_descriptor.serialize();
302-
@memcpy(S.tmp[0..hd.len], &hd);
284+
var bw = BufferWriter { .buffer = &S.tmp };
285+
try bw.write(&hid_conf.hid_descriptor.serialize());
303286

304-
f.usb_start_tx(
305-
usb_config.?.endpoints[EP0_IN_IDX],
306-
S.tmp[0..hd.len],
307-
);
287+
CmdEndpoint.send_cmd_response(bw.get_written_slice(), setup.length);
308288
},
309289
.Report => {
310290
if (debug) std.log.info(" Report", .{});
311291

312292
// The report descriptor is already a (static)
313293
// u8 array, i.e., we can pass it directly
314-
f.usb_start_tx(
315-
usb_config.?.endpoints[EP0_IN_IDX],
316-
hid_conf.report_descriptor,
317-
);
294+
CmdEndpoint.send_cmd_response(hid_conf.report_descriptor, setup.length);
318295
},
319296
.Physical => {
320297
if (debug) std.log.info(" Physical", .{});
@@ -352,11 +329,31 @@ pub fn Usb(comptime f: anytype) type {
352329
switch (epb.endpoint.descriptor.endpoint_address) {
353330
EP0_IN_ADDR => {
354331
if (debug) std.log.info(" EP0_IN_ADDR", .{});
332+
333+
const cmd_in_endpoint = usb_config.?.endpoints[EP0_IN_IDX];
334+
const buffer_reader = &S.buffer_reader;
335+
355336
// We use this opportunity to finish the delayed
356337
// SetAddress request, if there is one:
357338
if (S.new_address) |addr| {
358339
// Change our address:
359340
f.set_address(@intCast(addr));
341+
}
342+
343+
if (epb.buffer.len > 0 and buffer_reader.get_remaining_bytes_count() > 0) {
344+
_ = buffer_reader.try_advance(epb.buffer.len);
345+
const next_data_chunk = buffer_reader.try_read(cmd_in_endpoint.descriptor.max_packet_size);
346+
if (next_data_chunk.len > 0) {
347+
f.usb_start_tx(
348+
cmd_in_endpoint,
349+
next_data_chunk,
350+
);
351+
} else {
352+
f.usb_start_rx(
353+
usb_config.?.endpoints[EP0_OUT_IDX], // EP0_OUT_CFG,
354+
0,
355+
);
356+
}
360357
} else {
361358
// Otherwise, we've just finished sending
362359
// something to the host. We expect an ensuing
@@ -393,6 +390,7 @@ pub fn Usb(comptime f: anytype) type {
393390
S.new_address = null;
394391
S.configured = false;
395392
S.started = false;
393+
S.buffer_reader = BufferReader { .buffer = &.{} };
396394
}
397395

398396
// If we have been configured but haven't reached this point yet, set up
@@ -842,6 +840,93 @@ pub const EPBIter = struct {
842840
next: *const fn (self: *@This()) ?EPB,
843841
};
844842

843+
const BufferWriter = struct {
844+
buffer: []u8,
845+
pos: usize = 0,
846+
endian: std.builtin.Endian = builtin.cpu.arch.endian(),
847+
848+
pub const Error = error{ EndOfBuffer };
849+
850+
/// Moves forward write cursor by the provided number of bytes.
851+
pub fn advance(self: *@This(), bytes: usize) Error!void {
852+
try self.bound_check(bytes);
853+
self.advance_unsafe(bytes);
854+
}
855+
856+
/// Writes data provided as a slice to the buffer and moves write cursor forward by data size.
857+
pub fn write(self: *@This(), data: []const u8) Error!void {
858+
try self.bound_check(data.len);
859+
defer self.advance_unsafe(data.len);
860+
@memcpy(self.buffer[self.pos..self.pos + data.len], data);
861+
}
862+
863+
/// Writes an int with respect to the buffer's endianness and moves write cursor forward by int size.
864+
pub fn write_int(self: *@This(), comptime T: type, value: T) Error!void {
865+
const size = @divExact(@typeInfo(T).Int.bits, 8);
866+
try self.bound_check(size);
867+
defer self.advance_unsafe(size);
868+
std.mem.writeInt(T, self.buffer[self.pos..][0..size], value, self.endian);
869+
}
870+
871+
/// Writes an int with respect to the buffer's endianness but skip bound check.
872+
/// Useful in cases where the bound can be checked once for batch of ints.
873+
pub fn write_int_unsafe(self: *@This(), comptime T: type, value: T) void {
874+
const size = @divExact(@typeInfo(T).Int.bits, 8);
875+
defer self.advance_unsafe(size);
876+
std.mem.writeInt(T, self.buffer[self.pos..][0..size], value, self.endian);
877+
}
878+
879+
/// Returns a slice of the internal buffer containing the written data.
880+
pub fn get_written_slice(self: *const @This()) []const u8 {
881+
return self.buffer[0..self.pos];
882+
}
883+
884+
/// Performs a buffer bound check against the current cursor position and the provided number of bytes to check forward.
885+
pub fn bound_check(self: *const @This(), bytes: usize) Error!void {
886+
if (self.pos + bytes > self.buffer.len) return error.EndOfBuffer;
887+
}
888+
889+
fn advance_unsafe(self: *@This(), bytes: usize) void {
890+
self.pos += bytes;
891+
}
892+
};
893+
894+
const BufferReader = struct {
895+
buffer: []const u8,
896+
pos: usize = 0,
897+
endian: std.builtin.Endian = builtin.cpu.arch.endian(),
898+
899+
/// Attempts to move read cursor forward by the specified number of bytes.
900+
/// Returns the actual number of bytes advanced, up to the specified number.
901+
pub fn try_advance(self: *@This(), bytes: usize) usize {
902+
const size = @min(bytes, self.buffer.len - self.pos);
903+
self.advance_unsafe(size);
904+
return size;
905+
}
906+
907+
/// Attempts to read the given amount of bytes (or less if close to buffer end) and advances the read cursor.
908+
pub fn try_read(self: *@This(), bytes: usize) []const u8 {
909+
const size = @min(bytes, self.buffer.len - self.pos);
910+
defer self.advance_unsafe(size);
911+
return self.buffer[self.pos..self.pos + size];
912+
}
913+
914+
/// Attempts to read the given amount of bytes (or less if close to buffer end) without advancing the read cursor.
915+
pub fn try_peek(self: *@This(), bytes: usize) []const u8 {
916+
const size = @min(bytes, self.buffer.len - self.pos);
917+
return self.buffer[self.pos..self.pos + size];
918+
}
919+
920+
/// Returns the number of bytes remaining from the current read cursor position to the end of the underlying buffer.
921+
pub fn get_remaining_bytes_count(self: *const @This()) usize {
922+
return self.buffer.len - self.pos;
923+
}
924+
925+
fn advance_unsafe(self: *@This(), bytes: usize) void {
926+
self.pos += bytes;
927+
}
928+
};
929+
845930
/// Convert an utf8 into an utf16 (little endian) string
846931
pub fn utf8Toutf16Le(comptime s: []const u8) [s.len << 1]u8 {
847932
const l = s.len << 1;

0 commit comments

Comments
 (0)