//! 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 }); }, } } }