|
|
|
@@ -25,6 +25,7 @@ const device_manager = @import("driver");
|
|
|
|
|
const memory = @import("memory");
|
|
|
|
|
const logging = @import("logging");
|
|
|
|
|
const device_manager_protocol = @import("device-manager-protocol");
|
|
|
|
|
const envelope = @import("envelope");
|
|
|
|
|
const usb_ids = @import("usb-ids");
|
|
|
|
|
const usb_abi = @import("usb-abi");
|
|
|
|
|
const usb_transfer_protocol = @import("usb-transfer-protocol");
|
|
|
|
@@ -386,6 +387,23 @@ fn bringUpBehindHub(manager: ipc.Handle, engine: *library.Controller, hub: *libr
|
|
|
|
|
if (deviceIsHub(usb_device)) _ = engine.setupHub(usb_device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Report one interface gone. A removal is addressed by the composite (parent,
|
|
|
|
|
/// bus address) — a pair no single `Header.target` can carry — so that stays the
|
|
|
|
|
/// packet's body and the target addresses the manager itself. `where` names the
|
|
|
|
|
/// port and interface for the log line, or null where the caller stays quiet
|
|
|
|
|
/// (a hub subtree collapsing reports a great many at once).
|
|
|
|
|
fn reportRemoved(manager: ipc.Handle, bus_address: u64, where: ?struct { port: u32, interface: u8 }) void {
|
|
|
|
|
var packet: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
|
|
|
const framed = device_manager_protocol.Protocol.encodeRequest(.child_removed, 0, .{
|
|
|
|
|
.parent = controller_id,
|
|
|
|
|
.bus_address = bus_address,
|
|
|
|
|
}, &.{}, &packet) orelse return;
|
|
|
|
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
|
|
|
_ = ipc.call(manager, framed, &reply) catch {
|
|
|
|
|
if (where) |place| std.log.info("child-removed report for port {d} interface {d} failed", .{ place.port, place.interface });
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Tear down a device that disconnected from a hub: recursively tear down its
|
|
|
|
|
/// own downstream devices first if it is a hub, report each interface removed,
|
|
|
|
|
/// then Disable Slot. Mirrors tearDownPort for a hub-attached device.
|
|
|
|
@@ -398,12 +416,7 @@ fn tearDownHubDevice(manager: ipc.Handle, engine: *library.Controller, dev: *lib
|
|
|
|
|
const key = hubPortKey(dev.parent_slot, dev.parent_port);
|
|
|
|
|
for (dev.interfaces[0..dev.interface_count]) |*interface| {
|
|
|
|
|
if (interface.registered_device_id == 0) continue;
|
|
|
|
|
const event = device_manager_protocol.ChildRemoved{
|
|
|
|
|
.parent = controller_id,
|
|
|
|
|
.bus_address = (@as(u64, key) << 8) | interface.number,
|
|
|
|
|
};
|
|
|
|
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
|
|
|
_ = ipc.call(manager, std.mem.asBytes(&event), &reply) catch {};
|
|
|
|
|
reportRemoved(manager, (@as(u64, key) << 8) | interface.number, null);
|
|
|
|
|
interface.registered_device_id = 0;
|
|
|
|
|
}
|
|
|
|
|
engine.tearDownDevice(dev);
|
|
|
|
@@ -428,14 +441,7 @@ fn tearDownPort(manager: ipc.Handle, engine: *library.Controller, port: u32) voi
|
|
|
|
|
std.log.info("port {d} disconnected", .{port});
|
|
|
|
|
for (usb_device.interfaces[0..usb_device.interface_count]) |*interface| {
|
|
|
|
|
if (interface.registered_device_id == 0) continue;
|
|
|
|
|
const event = device_manager_protocol.ChildRemoved{
|
|
|
|
|
.parent = controller_id,
|
|
|
|
|
.bus_address = (@as(u64, port) << 8) | interface.number,
|
|
|
|
|
};
|
|
|
|
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
|
|
|
_ = ipc.call(manager, std.mem.asBytes(&event), &reply) catch {
|
|
|
|
|
std.log.info("child-removed report for port {d} interface {d} failed", .{ port, interface.number });
|
|
|
|
|
};
|
|
|
|
|
reportRemoved(manager, (@as(u64, port) << 8) | interface.number, .{ .port = port, .interface = interface.number });
|
|
|
|
|
interface.registered_device_id = 0;
|
|
|
|
|
}
|
|
|
|
|
engine.tearDownDevice(usb_device);
|
|
|
|
@@ -469,15 +475,17 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const report = device_manager_protocol.ChildAdded{
|
|
|
|
|
// The registered device id is the packet's target — the manager's object
|
|
|
|
|
// addressing — so the report body carries only where on the bus it sits.
|
|
|
|
|
var packet: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
|
|
|
const framed = device_manager_protocol.Protocol.encodeRequest(.child_added, registered, .{
|
|
|
|
|
.bus = @intFromEnum(device_manager_protocol.BusKind.usb),
|
|
|
|
|
.parent = controller_id,
|
|
|
|
|
.bus_address = (@as(u64, port) << 8) | interface.number,
|
|
|
|
|
.identity = identity,
|
|
|
|
|
.device_id = registered,
|
|
|
|
|
};
|
|
|
|
|
}, &.{}, &packet) orelse return null;
|
|
|
|
|
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
|
|
|
|
|
_ = ipc.call(manager, std.mem.asBytes(&report), &reply) catch {
|
|
|
|
|
_ = ipc.call(manager, framed, &reply) catch {
|
|
|
|
|
std.log.info("child report for port {d} interface {d} failed", .{ port, interface.number });
|
|
|
|
|
return null;
|
|
|
|
|
};
|
|
|
|
@@ -496,59 +504,55 @@ fn reportInterface(manager: ipc.Handle, port: u32, interface: library.InterfaceI
|
|
|
|
|
return registered;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Serve the USB transfer protocol: a class driver opens its device, then issues
|
|
|
|
|
/// control / interrupt-subscribe / bulk requests against it.
|
|
|
|
|
/// The generated transfer dispatch. One controller per process, so the handler
|
|
|
|
|
/// context is empty and the open table stays in this file's globals.
|
|
|
|
|
const Serve = usb_transfer_protocol.Protocol.Provider(void);
|
|
|
|
|
|
|
|
|
|
const Invocation = envelope.Invocation;
|
|
|
|
|
const Answer = envelope.Answer;
|
|
|
|
|
|
|
|
|
|
/// Every refusal here is the same one — this controller does not (or no longer)
|
|
|
|
|
/// serve the device the packet addressed — so there is one errno for all of them.
|
|
|
|
|
const refused: isize = -envelope.ENOENT;
|
|
|
|
|
|
|
|
|
|
/// Set by `onOpen` when the open table has taken ownership of the endpoint the
|
|
|
|
|
/// call carried, and read by `onMessage`, where the turn's `Arrival` lives. The
|
|
|
|
|
/// generated dispatch hands a handler the raw handle rather than the `Arrival`,
|
|
|
|
|
/// so the *claim* travels back out this way. One turn, one handler, one thread.
|
|
|
|
|
var capability_claimed = false;
|
|
|
|
|
|
|
|
|
|
fn onMessage(message: []const u8, reply: []u8, sender: u32, arrived: *ipc.Arrival) usize {
|
|
|
|
|
_ = sender;
|
|
|
|
|
if (message.len < 4) return 0;
|
|
|
|
|
const operation = std.mem.readInt(u32, message[0..4], .little);
|
|
|
|
|
return switch (operation) {
|
|
|
|
|
@intFromEnum(usb_transfer_protocol.Operation.open) => handleOpen(message, reply, arrived),
|
|
|
|
|
@intFromEnum(usb_transfer_protocol.Operation.control) => handleControl(message, reply),
|
|
|
|
|
@intFromEnum(usb_transfer_protocol.Operation.interrupt_subscribe) => handleSubscribe(message, reply),
|
|
|
|
|
@intFromEnum(usb_transfer_protocol.Operation.bulk) => handleBulk(message, reply),
|
|
|
|
|
@intFromEnum(usb_transfer_protocol.Operation.dma_attach) => handleDmaAttach(message, reply, arrived),
|
|
|
|
|
else => 0,
|
|
|
|
|
};
|
|
|
|
|
capability_claimed = false;
|
|
|
|
|
const written = Serve.dispatch({}, handlers, message, sender, arrived.peek(), reply);
|
|
|
|
|
if (capability_claimed) _ = arrived.take();
|
|
|
|
|
return written;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// dma_attach: bind the class driver's DMA-region capability into the controller's IOMMU
|
|
|
|
|
/// domain, so the controller may DMA to the physical addresses inside that buffer. The
|
|
|
|
|
/// binding holds its own kernel reference, so this never claims the arriving handle —
|
|
|
|
|
/// the turn's `defer` in the harness is the close, on the failure paths as well as this
|
|
|
|
|
/// one.
|
|
|
|
|
fn handleDmaAttach(message: []const u8, reply: []u8, arrived: *ipc.Arrival) usize {
|
|
|
|
|
if (message.len < @sizeOf(usb_transfer_protocol.DmaAttachRequest)) return writeReply(reply, usb_transfer_protocol.DmaAttachReply{ .status = -1 });
|
|
|
|
|
const handle = arrived.peek() orelse return writeReply(reply, usb_transfer_protocol.DmaAttachReply{ .status = -1 });
|
|
|
|
|
const ok = device.dmaBind(controller_id, handle);
|
|
|
|
|
return writeReply(reply, usb_transfer_protocol.DmaAttachReply{ .status = if (ok) 0 else -1 });
|
|
|
|
|
}
|
|
|
|
|
const handlers = Serve.Handlers{
|
|
|
|
|
.open = onOpen,
|
|
|
|
|
.control = onControl,
|
|
|
|
|
.interrupt_subscribe = onInterruptSubscribe,
|
|
|
|
|
.bulk = onBulk,
|
|
|
|
|
.dma_attach = onDmaAttach,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
fn writeReply(reply: []u8, value: anytype) usize {
|
|
|
|
|
const bytes = std.mem.asBytes(&value);
|
|
|
|
|
@memcpy(reply[0..bytes.len], bytes);
|
|
|
|
|
return bytes.len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// open: resolve the assigned device id to an interface, remember the caller's
|
|
|
|
|
/// endpoint (for interrupt reports), and answer with a device token + the
|
|
|
|
|
/// interface's endpoints so the class driver need not re-read the config.
|
|
|
|
|
fn handleOpen(message: []const u8, reply: []u8, arrived: *ipc.Arrival) usize {
|
|
|
|
|
if (message.len < @sizeOf(usb_transfer_protocol.OpenRequest)) return writeReply(reply, usb_transfer_protocol.OpenReply{ .status = -1, .endpoint_count = 0, .device_token = 0, .interface_class = 0, .interface_subclass = 0, .interface_protocol = 0, .interface_number = 0 });
|
|
|
|
|
const request = std.mem.bytesToValue(usb_transfer_protocol.OpenRequest, message[0..@sizeOf(usb_transfer_protocol.OpenRequest)]);
|
|
|
|
|
const engine = if (controller) |*c| c else return writeReply(reply, usb_transfer_protocol.OpenReply{ .status = -1, .endpoint_count = 0, .device_token = 0, .interface_class = 0, .interface_subclass = 0, .interface_protocol = 0, .interface_number = 0 });
|
|
|
|
|
const found = engine.findInterface(request.device_id) orelse return writeReply(reply, usb_transfer_protocol.OpenReply{ .status = -1, .endpoint_count = 0, .device_token = 0, .interface_class = 0, .interface_subclass = 0, .interface_protocol = 0, .interface_number = 0 });
|
|
|
|
|
/// open: the target is the class driver's assigned device id. Resolve it to an
|
|
|
|
|
/// interface, remember the caller's endpoint (for interrupt reports), and answer
|
|
|
|
|
/// with a device token — the target of every later packet — plus the interface's
|
|
|
|
|
/// endpoints, so the class driver need not re-read the configuration descriptor.
|
|
|
|
|
fn onOpen(_: void, invocation: Invocation(void), answer: Answer(usb_transfer_protocol.Opened)) isize {
|
|
|
|
|
const engine = if (controller) |*c| c else return refused;
|
|
|
|
|
const found = engine.findInterface(invocation.target) orelse return refused;
|
|
|
|
|
|
|
|
|
|
// The report endpoint is claimed only if the open table actually keeps it;
|
|
|
|
|
// a full table leaves it to the turn to close.
|
|
|
|
|
if (arrived.peek()) |endpoint| {
|
|
|
|
|
if (recordOpen(request.device_id, endpoint)) _ = arrived.take();
|
|
|
|
|
if (invocation.capability) |endpoint| {
|
|
|
|
|
if (recordOpen(invocation.target, endpoint)) capability_claimed = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var open_reply = usb_transfer_protocol.OpenReply{
|
|
|
|
|
.status = 0,
|
|
|
|
|
var opened = usb_transfer_protocol.Opened{
|
|
|
|
|
.device_token = invocation.target,
|
|
|
|
|
.endpoint_count = found.interface.endpoint_count,
|
|
|
|
|
.device_token = request.device_id,
|
|
|
|
|
.interface_class = found.interface.class,
|
|
|
|
|
.interface_subclass = found.interface.subclass,
|
|
|
|
|
.interface_protocol = found.interface.protocol,
|
|
|
|
@@ -556,57 +560,71 @@ fn handleOpen(message: []const u8, reply: []u8, arrived: *ipc.Arrival) usize {
|
|
|
|
|
};
|
|
|
|
|
const count = @min(found.interface.endpoint_count, usb_transfer_protocol.max_reported_endpoints);
|
|
|
|
|
for (found.interface.endpoints[0..count], 0..) |endpoint, index| {
|
|
|
|
|
open_reply.endpoints[index] = .{
|
|
|
|
|
opened.endpoints[index] = .{
|
|
|
|
|
.address = endpoint.address,
|
|
|
|
|
.transfer_type = endpoint.transfer_type,
|
|
|
|
|
.max_packet_size = endpoint.max_packet_size,
|
|
|
|
|
.interval = endpoint.interval,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return writeReply(reply, open_reply);
|
|
|
|
|
answer.set(opened);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// control: one EP0 control transfer, small data inline both ways.
|
|
|
|
|
fn handleControl(message: []const u8, reply: []u8) usize {
|
|
|
|
|
if (message.len < @sizeOf(usb_transfer_protocol.ControlRequest)) return writeReply(reply, usb_transfer_protocol.ControlReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
const request = std.mem.bytesToValue(usb_transfer_protocol.ControlRequest, message[0..@sizeOf(usb_transfer_protocol.ControlRequest)]);
|
|
|
|
|
const engine = if (controller) |*c| c else return writeReply(reply, usb_transfer_protocol.ControlReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
const found = engine.findInterface(request.device_token) orelse return writeReply(reply, usb_transfer_protocol.ControlReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
/// control: one EP0 control transfer. The data stage rides the tail in both
|
|
|
|
|
/// directions, so an IN transfer's answer is simply however many bytes were
|
|
|
|
|
/// written into `answer.tail()` — which is what `Status.len` then reports.
|
|
|
|
|
fn onControl(_: void, invocation: Invocation(usb_transfer_protocol.Control), answer: Answer(void)) isize {
|
|
|
|
|
const engine = if (controller) |*c| c else return refused;
|
|
|
|
|
const found = engine.findInterface(invocation.target) orelse return refused;
|
|
|
|
|
|
|
|
|
|
const setup = std.mem.bytesToValue(usb_abi.Request, &invocation.request.setup);
|
|
|
|
|
const direction_in = invocation.request.direction_in != 0;
|
|
|
|
|
const room = @min(usb_transfer_protocol.max_inline_data, answer.tail().len);
|
|
|
|
|
const data_length = @min(@as(usize, invocation.request.data_length), room);
|
|
|
|
|
|
|
|
|
|
const setup = std.mem.bytesToValue(usb_abi.Request, &request.setup);
|
|
|
|
|
const direction_in = request.direction_in != 0;
|
|
|
|
|
const data_length = @min(request.data_length, usb_transfer_protocol.max_inline_data);
|
|
|
|
|
var data: [usb_transfer_protocol.max_inline_data]u8 = undefined;
|
|
|
|
|
if (!direction_in) @memcpy(data[0..data_length], request.data[0..data_length]);
|
|
|
|
|
if (!direction_in) {
|
|
|
|
|
const supplied = @min(data_length, invocation.tail.len);
|
|
|
|
|
@memcpy(data[0..supplied], invocation.tail[0..supplied]);
|
|
|
|
|
if (supplied < data_length) @memset(data[supplied..data_length], 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const ok = engine.controlTransfer(found.device, setup, data[0..data_length], direction_in);
|
|
|
|
|
var control_reply = usb_transfer_protocol.ControlReply{ .status = if (ok) 0 else -1, .actual_length = if (ok) data_length else 0 };
|
|
|
|
|
if (ok and direction_in) @memcpy(control_reply.data[0..data_length], data[0..data_length]);
|
|
|
|
|
return writeReply(reply, control_reply);
|
|
|
|
|
if (!engine.controlTransfer(found.device, setup, data[0..data_length], direction_in)) return refused;
|
|
|
|
|
if (!direction_in) return 0; // nothing follows an OUT: the status is the whole answer
|
|
|
|
|
@memcpy(answer.tail()[0..data_length], data[0..data_length]);
|
|
|
|
|
return @intCast(data_length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// interrupt_subscribe: arm periodic IN polling; reports flow back asynchronously.
|
|
|
|
|
fn handleSubscribe(message: []const u8, reply: []u8) usize {
|
|
|
|
|
if (message.len < @sizeOf(usb_transfer_protocol.InterruptSubscribeRequest)) return writeReply(reply, usb_transfer_protocol.InterruptSubscribeReply{ .status = -1 });
|
|
|
|
|
const request = std.mem.bytesToValue(usb_transfer_protocol.InterruptSubscribeRequest, message[0..@sizeOf(usb_transfer_protocol.InterruptSubscribeRequest)]);
|
|
|
|
|
const engine = if (controller) |*c| c else return writeReply(reply, usb_transfer_protocol.InterruptSubscribeReply{ .status = -1 });
|
|
|
|
|
const found = engine.findInterface(request.device_token) orelse return writeReply(reply, usb_transfer_protocol.InterruptSubscribeReply{ .status = -1 });
|
|
|
|
|
const endpoint = library.Controller.endpointForAddress(found.interface, request.endpoint_address) orelse return writeReply(reply, usb_transfer_protocol.InterruptSubscribeReply{ .status = -1 });
|
|
|
|
|
const report_endpoint = reportEndpointFor(request.device_token) orelse return writeReply(reply, usb_transfer_protocol.InterruptSubscribeReply{ .status = -1 });
|
|
|
|
|
const ok = engine.subscribeInterrupt(found.device, endpoint, request.device_token, report_endpoint);
|
|
|
|
|
return writeReply(reply, usb_transfer_protocol.InterruptSubscribeReply{ .status = if (ok) 0 else -1 });
|
|
|
|
|
/// interrupt_subscribe: arm periodic IN polling; reports flow back asynchronously
|
|
|
|
|
/// to the endpoint this device's `open` handed over.
|
|
|
|
|
fn onInterruptSubscribe(_: void, invocation: Invocation(usb_transfer_protocol.InterruptSubscribe), _: Answer(void)) isize {
|
|
|
|
|
const engine = if (controller) |*c| c else return refused;
|
|
|
|
|
const found = engine.findInterface(invocation.target) orelse return refused;
|
|
|
|
|
const endpoint = library.Controller.endpointForAddress(found.interface, invocation.request.endpoint_address) orelse return refused;
|
|
|
|
|
const report_endpoint = reportEndpointFor(invocation.target) orelse return refused;
|
|
|
|
|
return if (engine.subscribeInterrupt(found.device, endpoint, invocation.target, report_endpoint)) 0 else refused;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// bulk: one bulk transfer to/from the class driver's own DMA buffer (by physical
|
|
|
|
|
/// address), so sector-sized data never crosses IPC.
|
|
|
|
|
fn handleBulk(message: []const u8, reply: []u8) usize {
|
|
|
|
|
if (message.len < @sizeOf(usb_transfer_protocol.BulkRequest)) return writeReply(reply, usb_transfer_protocol.BulkReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
const request = std.mem.bytesToValue(usb_transfer_protocol.BulkRequest, message[0..@sizeOf(usb_transfer_protocol.BulkRequest)]);
|
|
|
|
|
const engine = if (controller) |*c| c else return writeReply(reply, usb_transfer_protocol.BulkReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
const found = engine.findInterface(request.device_token) orelse return writeReply(reply, usb_transfer_protocol.BulkReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
const endpoint = library.Controller.endpointForAddress(found.interface, request.endpoint_address) orelse return writeReply(reply, usb_transfer_protocol.BulkReply{ .status = -1, .actual_length = 0 });
|
|
|
|
|
const transferred = engine.bulkTransfer(found.device, endpoint, request.physical_address, request.length);
|
|
|
|
|
return writeReply(reply, usb_transfer_protocol.BulkReply{ .status = if (transferred != null) 0 else -1, .actual_length = transferred orelse 0 });
|
|
|
|
|
fn onBulk(_: void, invocation: Invocation(usb_transfer_protocol.Bulk), answer: Answer(usb_transfer_protocol.Transferred)) isize {
|
|
|
|
|
const engine = if (controller) |*c| c else return refused;
|
|
|
|
|
const found = engine.findInterface(invocation.target) orelse return refused;
|
|
|
|
|
const endpoint = library.Controller.endpointForAddress(found.interface, invocation.request.endpoint_address) orelse return refused;
|
|
|
|
|
const transferred = engine.bulkTransfer(found.device, endpoint, invocation.request.physical_address, invocation.request.length) orelse return refused;
|
|
|
|
|
answer.set(.{ .actual_length = transferred });
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// dma_attach: bind the class driver's DMA-region capability into the controller's IOMMU
|
|
|
|
|
/// domain, so the controller may DMA to the physical addresses inside that buffer. The
|
|
|
|
|
/// binding holds its own kernel reference, so this never claims the arriving handle —
|
|
|
|
|
/// the turn's `defer` in the harness is the close, on the failure paths as well as this
|
|
|
|
|
/// one.
|
|
|
|
|
fn onDmaAttach(_: void, invocation: Invocation(void), _: Answer(void)) isize {
|
|
|
|
|
const handle = invocation.capability orelse return -envelope.EPROTO;
|
|
|
|
|
return if (device.dmaBind(controller_id, handle)) 0 else refused;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A timer tick or an MSI landed: drain the event ring, reconcile ports, and fan out.
|
|
|
|
@@ -679,14 +697,18 @@ fn serviceController() void {
|
|
|
|
|
if (serviced >= 32) break;
|
|
|
|
|
}
|
|
|
|
|
while (engine.takeReport()) |report| {
|
|
|
|
|
var message = usb_transfer_protocol.InterruptReport{
|
|
|
|
|
.device_token = report.device_token,
|
|
|
|
|
.endpoint_address = report.endpoint_address,
|
|
|
|
|
.length = @intCast(@min(report.length, usb_transfer_protocol.max_report_data)),
|
|
|
|
|
};
|
|
|
|
|
// One `interrupt_report` event packet: the device token in the folded
|
|
|
|
|
// header, the report after it. A device that produced more than the
|
|
|
|
|
// push floor admits has its report truncated here, never split.
|
|
|
|
|
const n = @min(report.length, usb_transfer_protocol.max_report_data);
|
|
|
|
|
@memcpy(message.data[0..n], report.data[0..n]);
|
|
|
|
|
_ = ipc.send(report.report_endpoint, std.mem.asBytes(&message));
|
|
|
|
|
var payload = usb_transfer_protocol.InterruptReport{
|
|
|
|
|
.endpoint_address = report.endpoint_address,
|
|
|
|
|
.length = @intCast(n),
|
|
|
|
|
};
|
|
|
|
|
@memcpy(payload.data[0..n], report.data[0..n]);
|
|
|
|
|
var packet: [envelope.post_maximum]u8 = undefined;
|
|
|
|
|
const framed = usb_transfer_protocol.Protocol.encodeEvent(.interrupt_report, report.device_token, payload, &packet) orelse continue;
|
|
|
|
|
_ = ipc.send(report.report_endpoint, framed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|