diff --git a/example.zig b/example.zig index b9551ee..bb27d47 100644 --- a/example.zig +++ b/example.zig @@ -5,6 +5,11 @@ pub fn main(init: std.process.Init) !void { const io = init.io; const gpa = init.gpa; + const devid = drm.makeDev(226, 128); + const device = try drm.Device.getFromDevId(io, gpa, devid, .{}); + + std.log.info("Device node path: {s}.", .{device.nodePath()}); + const card = try drm.Card.openAuto(io); defer card.close(io); const res = try card.getModesettingResources(gpa); diff --git a/src/Device.zig b/src/Device.zig index 339f57f..9480698 100644 --- a/src/Device.zig +++ b/src/Device.zig @@ -2,17 +2,12 @@ const std = @import("std"); const drm = @import("drm.zig"); const sys = @import("sys.zig"); const log = std.log.scoped(.drm); - -const node_max = 3; +const path_max = std.os.linux.PATH_MAX; const Device = @This(); -nodes: struct { - primary: ?[drm.max_node_name]u8, - /// Deprecated - control: ?[drm.max_node_name]u8, - render: ?[drm.max_node_name]u8, -}, +node_type: NodeType, +node_path_buf: [drm.max_node_name:0]u8, bus_info: union(BusType) { pci: PciBusInfo, usb: UsbBusInfo, @@ -21,7 +16,7 @@ bus_info: union(BusType) { faux: FauxBusInfo, virtio: void, }, -device_info: union(BusType) { +device_info: ?union(BusType) { pci: PciDeviceInfo, usb: UsbDeviceInfo, platform: PlatformDeviceInfo, @@ -30,11 +25,15 @@ device_info: union(BusType) { virtio: void, }, +pub fn nodePath(self: *const Device) []const u8 { + return std.mem.sliceTo(&self.node_path_buf, 0); +} + pub const Flags = packed struct { get_pci_revision: bool = false, }; -pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: Flags) !Device { +pub fn getFromDevId(io: std.Io, gpa: std.mem.Allocator, devid: std.posix.dev_t, flags: Flags) !Device { var local_devices: [drm.max_nodes]Device = undefined; const major = drm.devMajor(devid); @@ -43,15 +42,15 @@ pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: Flags) !Device { if (!drm.nodeIsDrm(io, major, minor)) return error.NotDrmDevice; - const subsystem_type = try drm.parseSubsystemType(io, major, minor); + const subsystem_type = try parseSubsystemType(io, major, minor); const sysdir = try std.Io.Dir.openDirAbsolute(io, drm.dir_name, .{ .iterate = true }); defer sysdir.close(io); var it = sysdir.iterate(); - const i: usize = 0; - while (try it.next(io)) |entry| { - const dev = processDevice(entry.name, subsystem_type, true, flags) catch continue; + var i: usize = 0; + while (try it.next(io)) |entry| if (entry.kind == .character_device) { + const dev = processDevice(io, gpa, entry.name, subsystem_type, true, flags) catch continue; if (i >= drm.max_nodes) { log.err( @@ -63,7 +62,7 @@ pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: Flags) !Device { local_devices[i] = dev; i += 1; - } + }; return for (local_devices[0..i]) |dev| { if (hasRdev(dev, devid)) break dev; @@ -132,7 +131,7 @@ pub const FauxBusInfo = struct { }; pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !Device.BusType { - var path_buf: [std.os.linux.PATH_MAX]u8 = undefined; + var path_buf: [path_max]u8 = undefined; var path = std.ArrayList(u8).initBuffer(&path_buf); path.printAssumeCapacity("/sys/dev/char/{d}:{d}/device", .{ major, minor }); @@ -156,10 +155,10 @@ fn getSubsystemType(io: std.Io, device_path: []const u8) !Device.BusType { .{ .name = "/faux", .bus_type = .faux }, }; - var path_buf: [std.os.linux.PATH_MAX]u8 = undefined; + var path_buf: [path_max]u8 = undefined; const path = std.fmt.bufPrint(&path_buf, "{s}/subsystem", .{device_path}) catch unreachable; - var link_buf: [std.os.linux.PATH_MAX]u8 = undefined; + var link_buf: [path_max]u8 = undefined; const link_len = try std.Io.Dir.cwd().readLink(io, path, &link_buf); const link = link_buf[0..link_len]; @@ -177,39 +176,39 @@ pub const NodeType = enum { /// Deprecated control, render, - _max, + pub const max = 3; }; fn processDevice( io: std.Io, + gpa: std.mem.Allocator, name: []const u8, - required_subsystem_type: sys.BusType, + required_subsystem_type: BusType, fetch_devinfo: bool, flags: Flags, ) !Device { const max_node_length = std.mem.alignForward(usize, drm.max_node_name, @sizeOf(usize)); const node_type = try getNodeType(name); - var path_buf: [std.os.linux.PATH_MAX]u8 = undefined; + var path_buf: [path_max]u8 = undefined; const node = std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ drm.dir_name, name }) catch unreachable; if (node.len + 1 > max_node_length) return error.NodePathTooLong; const stat = try statx(node); + const major = stat.rdev_major; + const minor = stat.rdev_minor; - if (!drm.nodeIsDrm(io, stat.rdev_major, stat.rdev_minor) || !std.os.linux.S.ISCHR(stat.mode)) + if (!drm.nodeIsDrm(io, stat.rdev_major, stat.rdev_minor) or !std.os.linux.S.ISCHR(stat.mode)) return error.InvalidDevice; - const subsystem_type = try drm.parseSubsystemType(io, stat.rdev_major, stat.rdev_minor); + const subsystem_type = try parseSubsystemType(io, stat.rdev_major, stat.rdev_minor); if (subsystem_type != required_subsystem_type) return error.IncorrectSubsystemType; - _ = fetch_devinfo; - _ = flags; - _ = node_type; return switch (subsystem_type) { - .pci, .virtio => error.PciUnimplemented, + .pci, .virtio => try processPciDevice(io, gpa, node, node_type, major, minor, fetch_devinfo, flags), .usb => error.UsbInimplemented, .platform => error.PlatformUnimplemented, .host1x => error.Host1xUnimplemented, @@ -218,61 +217,172 @@ fn processDevice( } fn processPciDevice( - node: []const u8, - node_type: drm.NodeType, + io: std.Io, + gpa: std.mem.Allocator, + node_path: []const u8, + node_type: NodeType, major: u32, minor: u32, fetch_devinfo: bool, flags: Flags, -) !*Device { +) !Device { var device = Device{ - .nodes = .{ .primary = null, .control = null, .render = null }, - .bus_info = undefined, - .device_info = undefined, + .node_type = node_type, + .node_path_buf = @splat(0), + .bus_info = .{ .pci = try parsePciBusInfo(io, gpa, major, minor) }, + .device_info = if (fetch_devinfo) .{ .pci = try parsePciDeviceInfo(io, major, minor, flags) } else null, }; - - _ = &device; - + @memcpy(device.node_path_buf[0..node_path.len], node_path); return device; } -fn parsePciBusInfo(major: u32, minor: u32) !PciBusInfo { - const pci_path = try getPciPath(major, minor); +fn parsePciBusInfo(io: std.Io, gpa: std.mem.Allocator, major: u32, minor: u32) !PciBusInfo { + var pci_path_buf: [path_max]u8 = undefined; + const pci_path = try getPciPath(io, &pci_path_buf, major, minor); - const value = try sysfsUeventGet(&pci_path, "PCI_SLOT_NAME"); + const value = try sysfsUeventGet(io, gpa, pci_path, "PCI_SLOT_NAME", .{}); + defer gpa.free(value); - const format = "{x:4}:{x:2}:{x:2}.{d:1}"; + const domain = try std.fmt.parseInt(u16, value[0..4], 16); + const bus = try std.fmt.parseInt(u8, value[5..7], 16); + const dev = try std.fmt.parseInt(u8, value[8..10], 16); + const func = try std.fmt.parseInt(u8, value[11..12], 16); return PciBusInfo{ - .domain = 0, - .bus = 0, - .dev = 0, - .func = 0, + .domain = domain, + .bus = bus, + .dev = dev, + .func = func, + }; +} + +fn parsePciDeviceInfo(io: std.Io, major: u32, minor: u32, flags: Flags) !PciDeviceInfo { + return if (!flags.get_pci_revision) + parseSeparateSysfsFiles(io, major, minor, true) + else + parseSeparateSysfsFiles(io, major, minor, false) catch + parseConfigSysfsFile(io, major, minor); +} + +fn getPciPath(io: std.Io, buf: []u8, major: u32, minor: u32) ![]const u8 { + var path_buf: [path_max]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "/sys/dev/char/{}:{}/device", .{ major, minor }); + + const len = try std.Io.Dir.realPathFileAbsolute(io, path, buf); + const real_path = buf[0..len]; + + if (std.mem.endsWith(u8, real_path, "/virtio")) return buf[0 .. len - 7]; + return real_path; +} + +fn sysfsUeventGet( + io: std.Io, + gpa: std.mem.Allocator, + path: []const u8, + comptime format: []const u8, + args: anytype, +) ![]const u8 { + const key = try std.fmt.allocPrint(gpa, format, args); + defer gpa.free(key); + + var filename_buf: [path_max]u8 = undefined; + const filename = try std.fmt.bufPrint(&filename_buf, "{s}/uevent", .{path}); + + const file = try std.Io.Dir.openFileAbsolute(io, filename, .{}); + defer file.close(io); + + var reader = file.reader(io, &.{}); + const content = try reader.interface.allocRemaining(gpa, .unlimited); + defer gpa.free(content); + + var it = std.mem.tokenizeScalar(u8, content, '\n'); + while (it.next()) |line| { + if (std.mem.eql(u8, line[0..key.len], key) and line[key.len] == '=') { + return gpa.dupe(u8, line[key.len + 1 ..]); + } + } + + return error.KeyNotFound; +} + +fn parseSeparateSysfsFiles( + io: std.Io, + major: u32, + minor: u32, + ignore_revision: bool, +) !PciDeviceInfo { + const attrs = [_][]const u8{ + "revision", + "vendor", + "device", + "subsystem_vendor", + "subsystem_device", + }; + + var pci_path_buf: [path_max]u8 = undefined; + const pci_path = try getPciPath(io, &pci_path_buf, major, minor); + const start: usize = if (ignore_revision) 1 else 0; + + var data: [attrs.len]u16 = undefined; + for (attrs[start..], data[start..]) |attr, *value| { + var path_buf: [path_max]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "{s}/{s}", .{ pci_path, attr }); + + const file = try std.Io.Dir.openFileAbsolute(io, path, .{}); + defer file.close(io); + + var value_buf: [16]u8 = undefined; + const len = try file.readStreaming(io, &.{&value_buf}) - 1; + + value.* = try std.fmt.parseInt(u16, value_buf[0..len], 0); + } + + return PciDeviceInfo{ + .revision_id = if (ignore_revision) 0xff else @intCast(data[0] & 0xff), + .vendor_id = data[1] & 0xffff, + .device_id = data[2] & 0xffff, + .subvendor_id = data[3] & 0xffff, + .subdevice_id = data[4] & 0xffff, + }; +} + +fn parseConfigSysfsFile(io: std.Io, major: u32, minor: u32) !PciDeviceInfo { + var pci_path_buf: [path_max]u8 = undefined; + const pci_path = try getPciPath(io, &pci_path_buf, major, minor); + + var path_buf: [path_max]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, "{s}/config", .{pci_path}); + + const file = try std.Io.Dir.openFileAbsolute(io, path, .{}); + defer file.close(io); + + var config: [64]u8 = undefined; + _ = try file.readStreaming(io, &.{&config}); + + return PciDeviceInfo{ + .vendor_id = config[0] | @as(u16, config[1]) << 8, + .device_id = config[2] | @as(u16, config[3]) << 8, + .revision_id = config[8], + .subvendor_id = config[44] | @as(u16, config[45]) << 8, + .subdevice_id = config[46] | @as(u16, config[47]) << 8, }; } fn hasRdev(dev: Device, rdev: std.posix.dev_t) bool { - const node_fields = @typeInfo(@FieldType(Device, "available_nodes")).@"struct".fields; - inline for (node_fields, dev.nodes) |field, node| { - if (@field(dev.available_nodes, field.name)) { - const stat = statx(node) catch continue; - if (drm.makeDev(stat.rdev_major, stat.rdev_minor) == rdev) return true; - } - } - - return false; + const stat = statx(dev.nodePath()) catch return false; + return drm.makeDev(stat.rdev_major, stat.rdev_minor) == rdev; } -fn getNodeType(name: []const u8) !drm.NodeType { - if (std.mem.eql(u8, name, drm.primary_minor_name)) return .primary; - if (std.mem.eql(u8, name, drm.control_minor_name)) return .control; - if (std.mem.eql(u8, name, drm.render_minor_name)) return .render; +fn getNodeType(name: []const u8) !NodeType { + if (std.mem.eql(u8, name[0..drm.primary_minor_name.len], drm.primary_minor_name)) return .primary; + if (std.mem.eql(u8, name[0..drm.control_minor_name.len], drm.control_minor_name)) return .control; + if (std.mem.eql(u8, name[0..drm.render_minor_name.len], drm.render_minor_name)) return .render; return error.InvalidName; } fn statx(path: []const u8) !std.os.linux.Statx { - const posix_path = std.posix.toPosixPath(path); + const posix_path = try std.posix.toPosixPath(path); var stat_buf: std.os.linux.Statx = undefined; const rc = std.os.linux.statx(std.os.linux.AT.FDCWD, &posix_path, 0, .{}, &stat_buf); return switch (std.posix.errno(rc)) { diff --git a/src/drm.zig b/src/drm.zig index d1ccdf2..78f68cf 100644 --- a/src/drm.zig +++ b/src/drm.zig @@ -22,17 +22,17 @@ pub const max_node_name = dir_name.len + @max( render_minor_name.len, ) + 3; -pub fn devMajor(dev: dev_t) u32 { +pub inline fn devMajor(dev: dev_t) u32 { return @intCast(((dev & @as(dev_t, 0x00000000000fff00)) >> 8) | ((dev & @as(dev_t, 0xfffff00000000000)) >> 32)); } -pub fn devMinor(dev: dev_t) u32 { +pub inline fn devMinor(dev: dev_t) u32 { return @intCast(((dev & @as(dev_t, 0x00000000000000ff)) >> 0) | ((dev & @as(dev_t, 0x00000ffffff00000)) >> 12)); } -pub fn makeDev(major: u32, minor: u32) dev_t { +pub inline fn makeDev(major: u32, minor: u32) dev_t { return (@as(dev_t, major & 0x00000fff) << 8) | (@as(dev_t, major & 0xfffff000) << 32) | (@as(dev_t, minor & 0x000000ff) << 0) |