danos/test/system/services/device-list/device-list.zig

92 lines
4.2 KiB
Zig

//! device-list — the `ps` analog for the device tree (docs/device-manager.md
//! M18.3): asks the device manager for the tree over IPC, prints it, then
//! subscribes and prints every published add/remove event. The manager is the
//! one answer to "what devices exist" for user space; nothing here touches a
//! device_* system call.
const std = @import("std");
const channel = @import("channel");
const envelope = @import("envelope");
const ipc = @import("ipc");
const time = @import("time");
const logging = @import("logging");
const device_manager_protocol = @import("device-manager-protocol");
fn writeLine(comptime fmt: []const u8, arguments: anytype) void {
var line: [96]u8 = undefined;
_ = logging.write(std.fmt.bufPrint(&line, fmt, arguments) catch return);
}
pub fn main() void {
var manager: ?ipc.Handle = null;
var tries: u32 = 0;
while (manager == null and tries < 200) : (tries += 1) {
manager = channel.openEndpoint("device-manager");
if (manager == null) time.sleepMillis(20);
}
const h = manager orelse {
_ = logging.write("device-list: no device manager\n");
return;
};
// The snapshot — polled briefly, because at boot the bus drivers may still
// be scanning: an empty first answer usually just means "too early". This is
// the envelope's reserved `enumerate` verb, so the request is nothing but a
// header and the answer is one `ChildEntry` per record in the reply's tail —
// how many arrived is the reply's own length, which is why no count header
// says it a second time.
const Entry = device_manager_protocol.ChildEntry;
const enumerate = envelope.Header{ .operation = envelope.operation_enumerate };
var reply: [device_manager_protocol.message_maximum]u8 = undefined;
var count: usize = 0;
tries = 0;
while (tries < 20) : (tries += 1) {
const length = ipc.call(h, std.mem.asBytes(&enumerate), &reply) catch 0;
if (envelope.statusOf(reply[0..length])) |status| {
if (status.status == 0) {
const carried = @min(@as(usize, status.len), length - envelope.prefix_size);
count = carried / @sizeOf(Entry);
if (count != 0) break;
}
}
time.sleepMillis(100);
}
writeLine("device-list: {d} devices\n", .{count});
for (0..count) |index| {
const entry = std.mem.bytesToValue(Entry, reply[envelope.prefix_size + index * @sizeOf(Entry) ..][0..@sizeOf(Entry)]);
writeLine("device-list: device {d} port {d} identity {d}\n", .{ entry.parent, entry.bus_address, entry.identity });
}
// The subscription — the reserved `subscribe` verb: our endpoint rides as
// the call's capability, and events arrive as buffered packets carrying the
// same structs the bus drivers send, under the events' own numbering.
const endpoint = ipc.createIpcEndpoint() orelse {
_ = logging.write("device-list: no endpoint\n");
return;
};
const subscribe = envelope.Header{ .operation = envelope.operation_subscribe };
_ = ipc.callCap(h, std.mem.asBytes(&subscribe), &reply, endpoint) catch {
_ = logging.write("device-list: subscribe failed\n");
return;
};
_ = logging.write("device-list: subscribed\n");
var receive: [device_manager_protocol.message_maximum]u8 = undefined;
while (true) {
const got = ipc.replyWait(endpoint, &.{}, &receive, null);
if (!got.isMessage()) continue;
const packet = receive[0..got.len];
const event = device_manager_protocol.Protocol.eventOf(packet) orelse continue;
switch (event) {
.child_added => {
const added = device_manager_protocol.Protocol.decodeEvent(.child_added, packet) orelse continue;
writeLine("device-list: added (device {d} port {d})\n", .{ added.parent, added.bus_address });
},
.child_removed => {
const removed = device_manager_protocol.Protocol.decodeEvent(.child_removed, packet) orelse continue;
writeLine("device-list: removed (device {d} port {d})\n", .{ removed.parent, removed.bus_address });
},
}
}
}