diff --git a/build.zig b/build.zig index 706f60f..86c3010 100644 --- a/build.zig +++ b/build.zig @@ -25,4 +25,8 @@ pub fn build(b: *std.Build) void { const run_tests = b.addRunArtifact(test_exe); const test_step = b.step("test", "Run tests."); test_step.dependOn(&run_tests.step); + + const check_obj = b.addObject(.{ .name = "check", .root_module = drm }); + const check_step = b.step("check", "ZLS check"); + check_step.dependOn(&check_obj.step); } diff --git a/example.zig b/example.zig index c8829eb..b9551ee 100644 --- a/example.zig +++ b/example.zig @@ -7,7 +7,7 @@ pub fn main(init: std.process.Init) !void { const card = try drm.Card.openAuto(io); defer card.close(io); - const res = try card.getModesettingResourcesAlloc(gpa); + const res = try card.getModesettingResources(gpa); defer res.deinit(gpa); const connector = try chooseConnector(card, gpa, res.connectors); @@ -30,7 +30,7 @@ fn chooseConnector( connectors: []const u32, ) !drm.Connector { for (connectors) |id| { - const connector = try card.getConnectorAlloc(gpa, id); + const connector = try card.getConnector(gpa, id); if (connector.connection == .connected) return connector; connector.deinit(gpa); } diff --git a/src/Card.zig b/src/Card.zig new file mode 100644 index 0000000..97929b0 --- /dev/null +++ b/src/Card.zig @@ -0,0 +1,422 @@ +const std = @import("std"); + +const drm = @import("drm.zig"); +const fmt = @import("format.zig"); +const sys = @import("sys.zig"); + +const log = std.log.scoped(.drm); + +const Card = @This(); + +handle: std.Io.File, + +pub const OpenError = std.Io.File.OpenError || std.Io.Dir.OpenError; + +pub fn open(io: std.Io, path: []const u8) OpenError!Card { + if (std.fs.path.isAbsolute(path)) + return Card{ .handle = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }) }; + + const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); + defer dir.close(io); + + return Card{ .handle = try dir.openFile(io, path, .{ .mode = .read_write }) }; +} + +pub const OpenAutoError = OpenError || std.Io.Dir.Iterator.Error || error{NoDevicesFound}; + +pub fn openAuto(io: std.Io) OpenAutoError!Card { + const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{ .iterate = true }); + defer dir.close(io); + + var it = dir.iterateAssumeFirstIteration(); + while (try it.next(io)) |entry| if (entry.kind == .character_device) + return Card{ .handle = try dir.openFile(io, entry.name, .{ .mode = .read_write }) }; + + return error.NoDevicesFound; +} + +pub fn close(self: Card, io: std.Io) void { + self.handle.close(io); +} + +pub fn handleEvents( + self: Card, + io: std.Io, + callback: fn (Card, drm.Event, anytype) anyerror!void, + args: anytype, +) anyerror!void { + var buf: [4096]u8 = undefined; + const read = try self.handle.readStreaming(io, &.{&buf}); + if (read == 0) return error.EndOfStream; + + var offset: usize = 0; + while (offset < read) { + if (read - offset < @sizeOf(sys.Event)) return error.IncompleteEvent; + + const ev: *align(1) const sys.Event = std.mem.bytesAsValue( + sys.Event, + buf[offset..][0..@sizeOf(sys.Event)], + ); + + offset += ev.length; + + const event = drm.Event.parse(ev); + try callback(self, event, args); + } +} + +pub fn setClientCapability(self: Card, capability: sys.ClientCapability, value: u64) sys.IoctlError!void { + var set_cap = sys.SetClientCap{ + .capability = capability, + .value = value, + }; + try sys.ioctl(self.handle.handle, .set_client_cap, &set_cap); +} + +pub fn getCapability(self: Card, capability: sys.Capability) sys.IoctlError!u64 { + var get_cap = sys.GetCap{ + .capability = capability, + .value = 0, + }; + try sys.ioctl(self.handle.handle, .get_cap, &get_cap); + return get_cap.value; +} + +pub fn createModesettingPropertyBlob(self: Card, data: []const u8) sys.IoctlError!u32 { + var create = std.mem.zeroInit(sys.mode.CreateBlob, .{ + .length = @as(u32, @intCast(data.len)), + .data = @intFromPtr(data.ptr), + }); + try sys.ioctl(self.handle.handle, .mode_createpropblob, &create); + return create.blob_id; +} + +// BEGIN MODESETTING API + +pub const GetResourcesError = sys.IoctlError || error{OutOfMemory}; + +pub fn getModesettingResources( + self: Card, + gpa: std.mem.Allocator, +) GetResourcesError!drm.ModesettingResources { + while (true) { + var res = std.mem.zeroes(sys.mode.CardRes); + try sys.ioctl(self.handle.handle, .mode_getresources, &res); + + var ret = drm.ModesettingResources{ + .min_width = res.min_width, + .max_width = res.max_width, + .min_height = res.min_height, + .max_height = res.max_height, + .fbs = &.{}, + .crtcs = &.{}, + .connectors = &.{}, + .encoders = &.{}, + }; + + ret.fbs = try gpa.alloc(u32, res.count_fbs); + errdefer gpa.free(ret.fbs); + res.fb_id_ptr = @intFromPtr(ret.fbs.ptr); + + ret.crtcs = try gpa.alloc(u32, res.count_crtcs); + errdefer gpa.free(ret.crtcs); + res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr); + + ret.connectors = try gpa.alloc(u32, res.count_connectors); + errdefer gpa.free(ret.connectors); + res.connector_id_ptr = @intFromPtr(ret.connectors.ptr); + + ret.encoders = try gpa.alloc(u32, res.count_encoders); + errdefer gpa.free(ret.encoders); + res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr); + + const cached = res; + try sys.ioctl(self.handle.handle, .mode_getresources, &res); + + if (cached.count_fbs < res.count_fbs or + cached.count_crtcs < res.count_crtcs or + cached.count_connectors < res.count_connectors or + cached.count_encoders < res.count_encoders) + { + gpa.free(ret.encoders); + gpa.free(ret.connectors); + gpa.free(ret.crtcs); + gpa.free(ret.fbs); + continue; + } + + return ret; + } +} + +pub const GetConnectorError = sys.IoctlError || error{OutOfMemory}; + +pub fn getConnector(self: Card, gpa: std.mem.Allocator, id: u32) GetConnectorError!drm.Connector { + while (true) { + var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ .connector_id = id }); + try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); + const cached = get_conn; + + const encoders = try gpa.alloc(u32, get_conn.count_encoders); + errdefer gpa.free(encoders); + + const modes = try gpa.alloc(sys.mode.ModeInfo, get_conn.count_modes); + errdefer gpa.free(modes); + + const props = try gpa.alloc(u32, get_conn.count_props); + errdefer gpa.free(props); + const prop_values = try gpa.alloc(u64, get_conn.count_props); + errdefer gpa.free(prop_values); + + get_conn.encoders_ptr = @intFromPtr(encoders.ptr); + get_conn.modes_ptr = @intFromPtr(modes.ptr); + get_conn.props_ptr = @intFromPtr(props.ptr); + get_conn.prop_values_ptr = @intFromPtr(prop_values.ptr); + + try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); + + if (cached.count_encoders < get_conn.count_encoders or + cached.count_modes < get_conn.count_modes or + cached.count_props < get_conn.count_props) + { + gpa.free(prop_values); + gpa.free(props); + gpa.free(modes); + gpa.free(encoders); + continue; + } + + return drm.Connector{ + .id = id, + .encoder_id = get_conn.encoder_id, + .connection = @enumFromInt(get_conn.connection), + // Unsure why libdrm does this conversion, + // but we'll follow suite for compatability. + .subpixel = @enumFromInt(get_conn.subpixel + 1), + .encoders = encoders[0..get_conn.count_encoders], + .modes = modes[0..get_conn.count_modes], + .props = props[0..get_conn.count_props], + .prop_values = prop_values[0..get_conn.count_props], + .mm_width = get_conn.mm_width, + .mm_height = get_conn.mm_height, + .type = get_conn.connector_type, + .type_id = get_conn.connector_type_id, + }; + } +} + +pub fn getEncoder(self: Card, id: u32) sys.IoctlError!drm.Encoder { + var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id }); + try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder); + + return drm.Encoder{ + .id = id, + .type = get_encoder.encoder_type, + .crtc_id = get_encoder.crtc_id, + .possible_crtcs = get_encoder.possible_crtcs, + .possible_clones = get_encoder.possible_clones, + }; +} + +pub fn getCrtc(self: Card, id: u32) sys.IoctlError!drm.Crtc { + var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ .crtc_id = id }); + try sys.ioctl(self.handle.handle, .mode_getcrtc, &crtc); + + return drm.Crtc{ + .id = id, + .fb_id = crtc.fb_id, + .x = crtc.x, + .y = crtc.y, + .gamma_size = crtc.gamma_size, + .mode = if (crtc.mode_valid != 0) crtc.mode else null, + }; +} + +pub fn setCrtc( + self: Card, + id: u32, + fb_id: u32, + x: u32, + y: u32, + connectors: []u32, + mode: ?sys.mode.ModeInfo, +) sys.IoctlError!void { + var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ + .crtc_id = id, + .fb_id = fb_id, + .x = x, + .y = y, + .count_connectors = @as(u32, @intCast(connectors.len)), + .set_connectors_ptr = @intFromPtr(connectors.ptr), + }); + if (mode) |m| { + crtc.mode_valid = 1; + crtc.mode = m; + } + try sys.ioctl(self.handle.handle, .mode_setcrtc, &crtc); +} + +pub fn createDumbBuffer(self: Card, width: u32, height: u32, bpp: u32) sys.IoctlError!drm.DumbBuffer { + var create = std.mem.zeroInit(sys.mode.CreateDumb, .{ + .width = width, + .height = height, + .bpp = bpp, + }); + try sys.ioctl(self.handle.handle, .mode_create_dumb, &create); + + return drm.DumbBuffer{ + .handle = create.handle, + .width = create.width, + .height = create.height, + .stride = create.pitch, + .size = @intCast(create.size), + }; +} + +pub fn destroyDumbBuffer(self: Card, handle: u32) sys.IoctlError!void { + var destroy = sys.mode.DestroyDumb{ .handle = handle }; + try sys.ioctl(self.handle.handle, .mode_destroy_dumb, &destroy); +} + +pub fn mapDumbBuffer(self: Card, dumb: drm.DumbBuffer, offset: usize) sys.IoctlError!usize { + var map = sys.mode.MapDumb{ .handle = dumb.handle, .pad = 0, .offset = offset }; + try sys.ioctl(self.handle.handle, .mode_map_dumb, &map); + return @intCast(map.offset); +} + +pub fn addFb2( + self: Card, + width: u32, + height: u32, + format: fmt.Format, + handles: [4]u32, + pitches: [4]u32, + offsets: [4]u32, + flags: u32, // FIXME: add flags type +) sys.IoctlError!u32 { + var cmd = std.mem.zeroInit(sys.mode.FbCmd2, .{ + .width = width, + .height = height, + .pixel_format = @intFromEnum(format), + .handles = handles, + .pitches = pitches, + .offsets = offsets, + .flags = flags, + }); + try sys.ioctl(self.handle.handle, .mode_addfb2, &cmd); + return cmd.fb_id; +} + +pub fn removeFb(self: Card, fb: u32) sys.IoctlError!void { + try sys.ioctl(self.handle.handle, .mode_rmfb, &fb); +} + +pub fn pageFlip( + self: Card, + crtc_id: u32, + fb_id: u32, + flags: sys.mode.PageFlipFlags, + data: ?*anyopaque, +) sys.IoctlError!void { + var flip = sys.mode.CrtcPageFlip{ + .crtc_id = crtc_id, + .fb_id = fb_id, + .flags = flags, + .reserved = 0, + .user_data = @intFromPtr(data), + }; + try sys.ioctl(self.handle.handle, .mode_page_flip, &flip); +} + +pub const GetPlaneResourcesError = sys.IoctlError || error{OutOfMemory}; + +pub fn getPlaneResources(self: Card, gpa: std.mem.Allocator) GetPlaneResourcesError!drm.PlaneResources { + while (true) { + var res = std.mem.zeroes(sys.mode.GetPlaneRes); + try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res); + const cached = res; + + const planes = try gpa.alloc(u32, res.count_planes); + errdefer gpa.free(planes); + res.plane_id_ptr = @intFromPtr(planes.ptr); + + try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res); + if (res.count_planes > cached.count_planes) { + gpa.free(planes); + continue; + } + + return drm.PlaneResources{ .planes = planes }; + } +} + +pub const GetPlaneError = sys.IoctlError || error{OutOfMemory}; + +pub fn getPlane(self: Card, gpa: std.mem.Allocator, id: u32) GetPlaneError!drm.Plane { + while (true) { + var get = std.mem.zeroInit(sys.mode.GetPlane, .{ .plane_id = id }); + try sys.ioctl(self.handle.handle, .mode_getplane, &get); + const cached = get; + + const formats = try gpa.alloc(u32, get.count_format_types); + errdefer gpa.free(formats); + get.format_type_ptr = @intFromPtr(formats.ptr); + + try sys.ioctl(self.handle.handle, .mode_getplane, &get); + if (get.count_format_types > cached.count_format_types) { + gpa.free(formats); + continue; + } + + return drm.Plane{ + .id = id, + .formats = formats, + .crtc_id = get.crtc_id, + .fb_id = get.fb_id, + .crtc_x = 0, + .crtc_y = 0, + .x = 0, + .y = 0, + .possible_crtcs = get.possible_crtcs, + .gamma_size = get.gamma_size, + }; + } +} + +pub const GetObjectPropertiesError = sys.IoctlError || error{OutOfMemory}; + +pub fn getObjectProperties( + self: Card, + gpa: std.mem.Allocator, + object_id: u32, + object_type: sys.mode.ObjType, +) GetObjectPropertiesError!drm.ObjectProperties { + while (true) { + var get = std.mem.zeroInit(sys.mode.ObjGetProperties, .{ + .obj_id = object_id, + .obj_type = object_type, + }); + try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get); + const cached = get; + + const keys = try gpa.alloc(u32, get.count_props); + errdefer gpa.free(keys); + get.props_ptr = keys.ptr; + + const values = try gpa.alloc(u64, get.count_props); + errdefer gpa.free(values); + get.prop_values_ptr = values.ptr; + + try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get); + if (get.count_props > cached.count_props) { + gpa.free(keys); + gpa.free(values); + continue; + } + + return drm.ObjectProperties{ + .keys = keys, + .values = values, + }; + } +} diff --git a/src/Device.zig b/src/Device.zig new file mode 100644 index 0000000..83d76b5 --- /dev/null +++ b/src/Device.zig @@ -0,0 +1,200 @@ +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 Device = @This(); + +nodes: [node_max][]const u8, +available_nodes: packed struct(u3) { + primary: bool = false, + /// Deprecated + control: bool = false, + render: bool = false, +}, +bus_info: union(BusType) { + pci: PciBusInfo, + usb: UsbBusInfo, + platform: PlatformBusInfo, + host1x: Host1xBusInfo, + faux: FauxBusInfo, + virtio: void, +}, +device_info: union(BusType) { + pci: PciDeviceInfo, + usb: UsbDeviceInfo, + platform: PlatformDeviceInfo, + host1x: Host1xDeviceInfo, + faux: void, + virtio: void, +}, + +pub const Flags = packed struct { + get_pci_revision: bool = false, +}; + +pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: Flags) !Device { + var local_devices: [drm.max_nodes]Device = undefined; + + const major = drm.devMajor(devid); + const minor = drm.devMinor(devid); + + if (!drm.nodeIsDrm(io, major, minor)) + return error.NotDrmDevice; + + const subsystem_type = try drm.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; + + if (i >= drm.max_nodes) { + log.err( + "More than {d} drm nodes detected. This is a bug. Extra nodes will be skipped.", + .{drm.max_nodes}, + ); + break; + } + + local_devices[i] = dev; + i += 1; + } + + return for (local_devices[0..i]) |dev| { + if (hasRdev(dev, devid)) break dev; + } else error.NoDeviceFound; +} + +pub const BusType = enum(c_int) { + pci = 0, + usb = 1, + platform = 2, + host1x = 3, + faux = 4, + /// According to libdrm, "Little white lie to avoid major rework of the existing code" + virtio = 0x10, +}; + +pub const PciBusInfo = struct { + domain: u16, + bus: u8, + dev: u8, + func: u8, +}; + +pub const PciDeviceInfo = struct { + vendor_id: u16, + device_id: u16, + subvendor_id: u16, + subdevice_id: u16, + revision_id: u8, +}; + +pub const UsbBusInfo = struct { + bus: u8, + dev: u8, +}; + +pub const UsbDeviceInfo = struct { + vendor: u16, + product: u16, +}; + +pub const platform_device_name_len = 512; + +pub const PlatformBusInfo = struct { + fullname: [platform_device_name_len]u8, +}; + +pub const PlatformDeviceInfo = struct { + compatible: [][]const u8, +}; + +pub const host1x_device_name_len = 512; + +pub const Host1xBusInfo = struct { + fullname: [host1x_device_name_len]u8, +}; + +pub const Host1xDeviceInfo = struct { + compatible: [][]const u8, +}; + +pub const faux_device_name_len = 512; + +pub const FauxBusInfo = struct { + name: [faux_device_name_len]u8, +}; + +fn processDevice( + io: std.Io, + name: []const u8, + required_subsystem_type: sys.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; + 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); + + if (!drm.nodeIsDrm(io, stat.rdev_major, stat.rdev_minor) || !std.os.linux.S.ISCHR(stat.mode)) + return error.InvalidDevice; + + const subsystem_type = try drm.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, + .usb => error.UsbInimplemented, + .platform => error.PlatformUnimplemented, + .host1x => error.Host1xUnimplemented, + .faux => error.FauxUnimplemented, + }; +} + +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; +} + +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; + + return error.InvalidName; +} + +fn statx(path: []const u8) !std.os.linux.Statx { + const posix_path = 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)) { + .SUCCESS => stat_buf, + else => |err| std.posix.unexpectedErrno(err), + }; +} diff --git a/src/drm.zig b/src/drm.zig index efd1daa..769654d 100644 --- a/src/drm.zig +++ b/src/drm.zig @@ -1,30 +1,69 @@ const std = @import("std"); -const fmt = @import("format.zig"); -const log = std.log.scoped(.drm); +const dev_t = std.posix.dev_t; -pub const sys = @import("sys.zig"); +pub const Card = @import("Card.zig"); +pub const Device = @import("Device.zig"); +const fmt = @import("format.zig"); pub const Format = fmt.Format; pub const FormatModifiers = fmt.FormatModifiers; +pub const sys = @import("sys.zig"); pub const ModeInfo = sys.mode.ModeInfo; +const log = std.log.scoped(.drm); + +pub const dir_name = "/dev/dri"; +pub const primary_minor_name = "card"; +pub const control_minor_name = "controlD"; +pub const render_minor_name = "renderD"; +pub const max_nodes = 256; +pub const max_node_name = dir_name.len + @max( + primary_minor_name.len, + control_minor_name.len, + render_minor_name.len, +) + 3; + +pub 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 { + return @intCast(((dev & @as(dev_t, 0x00000000000000ff)) >> 0) | + ((dev & @as(dev_t, 0x00000ffffff00000)) >> 12)); +} + +pub 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) | + (@as(dev_t, minor & 0xffffff00) << 12); +} + pub fn nodeIsDrm(io: std.Io, major: u32, minor: u32) bool { const drm_node_path_format = "/sys/dev/char/{d}:{d}/device/drm"; var path_buf: [64]u8 = undefined; const path = std.fmt.bufPrint(&path_buf, drm_node_path_format, .{ major, minor }) catch unreachable; - _ = std.Io.Dir.cwd().statFile(io, path, .{}) catch return false; - - return true; + return if (std.Io.Dir.cwd().statFile(io, path, .{})) |_| true else |_| false; } -pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !sys.BusType { +pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !Device.BusType { var path_buf: [std.os.linux.PATH_MAX]u8 = undefined; var path = std.ArrayList(u8).initBuffer(&path_buf); - path.printAssumeCapacity("/sys/dev/char/{d}:{d}/device", .{major, minor}); - path.appendSliceAssumeCapacity("/subsystem"); + path.printAssumeCapacity("/sys/dev/char/{d}:{d}/device", .{ major, minor }); - const bus_types = [_]struct{name: []const u8, bus_type: sys.BusType}{ + var subsystem_type = try getSubsystemType(io, path.items); + if (subsystem_type == .virtio) { + path.appendSliceAssumeCapacity("/.."); + subsystem_type = getSubsystemType(io, path.items) catch .virtio; + } + + return subsystem_type; +} + +fn getSubsystemType(io: std.Io, device_path: []const u8) !Device.BusType { + const bus_types = [_]struct { name: []const u8, bus_type: Device.BusType }{ .{ .name = "/pci", .bus_type = .pci }, .{ .name = "/usb", .bus_type = .usb }, .{ .name = "/platform", .bus_type = .platform }, @@ -34,686 +73,28 @@ pub fn parseSubsystemType(io: std.Io, major: u32, minor: u32) !sys.BusType { .{ .name = "/faux", .bus_type = .faux }, }; + var path_buf: [std.os.linux.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; - const link_len = try std.Io.Dir.cwd().readLink(io, path.items, &link_buf); + const link_len = try std.Io.Dir.cwd().readLink(io, path, &link_buf); const link = link_buf[0..link_len]; const last = std.mem.findScalarLast(u8, link, '/') orelse unreachable; const name = link[last..]; if (name.len == 0) return error.InvalidDevice; - const bus_type = for (bus_types) |bus_type| { - if (std.mem.eql(u8, bus_type.name, name)) break bus_type; - } else return error.InvalidDevice; - - /* Try to get the parent (underlying) device type */ - if (subsystem_type == DRM_BUS_VIRTIO) { - /* Assume virtio-pci on error */ - if (!realpath(path, real_path)) - return DRM_BUS_VIRTIO; - strncat(path, "/..", PATH_MAX); - subsystem_type = get_subsystem_type(path); - if (subsystem_type < 0) - return DRM_BUS_VIRTIO; - } - return subsystem_type; + return for (bus_types) |bus_type| { + if (std.mem.eql(u8, bus_type.name, name)) break bus_type.bus_type; + } else error.InvalidDevice; } -pub const Device = struct { - nodes: [3][]const u8, - available_nodes: packed struct(u3) { - primary: bool = false, - /// Deprecated - control: bool = false, - render: bool = false, - }, - bus_info: union(sys.BusType) { - pci: sys.PciBusInfo, - usb: sys.UsbBusInfo, - platform: sys.PlatformBusInfo, - host1x: sys.Host1xBusInfo, - faux: sys.FauxBusInfo, - }, - device_info: union(sys.BusType) { - pci: sys.PciDeviceInfo, - usb: sys.UsbDeviceInfo, - platform: sys.PlatformDeviceInfo, - host1x: sys.Host1xDeviceInfo, - faux: sys.FauxDeviceInfo, - }, - - pub const GetFromDevIdFlags = packed struct { - get_pci_revision: bool = false, - }; - - const dev_t = std.posix.dev_t; - pub fn devMajor(dev: dev_t) u32 { - return ((dev & @as(dev_t, 0x00000000000fff00)) >> 8) | - ((dev & @as(dev_t, 0xfffff00000000000)) >> 32); - } - - pub fn devMinor(dev: dev_t) u32 { - return ((dev & @as(dev_t, 0x00000000000000ff)) >> 0) | - ((dev & @as(dev_t, 0x00000ffffff00000)) >> 12); - } - - pub 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) | - (@as(dev_t, minor & 0xffffff00) << 12); - } - - pub fn getFromDevId(io: std.Io, devid: std.posix.dev_t, flags: GetFromDevIdFlags) !Device { - var local_devices: [256]Device = undefined; - - const major = devMajor(devid); - const minor = devMinor(devid); - - if (!nodeIsDrm(io, major, minor)) - return error.NotDrmDevice; - - // subsystem_type = drmParseSubsystemType(maj, min); - // if (subsystem_type < 0) - // return subsystem_type; - - // sysdir = opendir(DRM_DIR_NAME); - // if (!sysdir) - // return -errno; - - // i = 0; - // while ((dent = readdir(sysdir))) { - // ret = process_device(&d, dent->d_name, subsystem_type, true, flags); - // if (ret) - // continue; - - // if (i >= MAX_DRM_NODES) { - // fprintf(stderr, "More than %d drm nodes detected. " - // "Please report a bug - that should not happen.\n" - // "Skipping extra nodes\n", MAX_DRM_NODES); - // break; - // } - // local_devices[i] = d; - // i++; - // } - // node_count = i; - - // drmFoldDuplicatedDevices(local_devices, node_count); - - // *device = NULL; - - // for (i = 0; i < node_count; i++) { - // if (!local_devices[i]) - // continue; - - // if (drm_device_has_rdev(local_devices[i], find_rdev)) - // *device = local_devices[i]; - // else - // drmFreeDevice(&local_devices[i]); - // } - - // closedir(sysdir); - // if (*device == NULL) - // return -ENODEV; - // return 0; - } -}; - -pub const Card = struct { - handle: std.Io.File, - - pub const OpenError = std.Io.File.OpenError || std.Io.Dir.OpenError; - - pub fn open(io: std.Io, path: []const u8) OpenError!Card { - if (std.fs.path.isAbsolute(path)) - return Card{ .handle = try std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_write }) }; - - const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{}); - defer dir.close(io); - - return Card{ .handle = try dir.openFile(io, path, .{ .mode = .read_write }) }; - } - - pub const OpenAutoError = OpenError || std.Io.Dir.Iterator.Error || error{NoDevicesFound}; - - pub fn openAuto(io: std.Io) OpenAutoError!Card { - const dir = try std.Io.Dir.openDirAbsolute(io, "/dev/dri", .{ .iterate = true }); - defer dir.close(io); - - var it = dir.iterateAssumeFirstIteration(); - while (try it.next(io)) |entry| if (entry.kind == .character_device) - return Card{ .handle = try dir.openFile(io, entry.name, .{ .mode = .read_write }) }; - - return error.NoDevicesFound; - } - - pub fn close(self: Card, io: std.Io) void { - self.handle.close(io); - } - - pub fn handleEvents( - self: Card, - io: std.Io, - callback: fn (Card, Event, anytype) anyerror!void, - args: anytype, - ) anyerror!void { - var buf: [4096]u8 = undefined; - const read = try self.handle.readStreaming(io, &.{&buf}); - if (read == 0) return error.EndOfStream; - - var offset: usize = 0; - while (offset < read) { - if (read - offset < @sizeOf(sys.Event)) return error.IncompleteEvent; - - const ev: *align(1) const sys.Event = std.mem.bytesAsValue( - sys.Event, - buf[offset..][0..@sizeOf(sys.Event)], - ); - - offset += ev.length; - - const event = Event.parse(ev); - try callback(self, event, args); - } - } - - pub fn setClientCapability(self: Card, capability: sys.ClientCapability, value: u64) sys.IoctlError!void { - var set_cap = sys.SetClientCap{ - .capability = capability, - .value = value, - }; - try sys.ioctl(self.handle.handle, .set_client_cap, &set_cap); - } - - pub fn getCapability(self: Card, capability: sys.Capability) sys.IoctlError!u64 { - var get_cap = sys.GetCap{ - .capability = capability, - .value = 0, - }; - try sys.ioctl(self.handle.handle, .get_cap, &get_cap); - return get_cap.value; - } - - pub fn createModesettingPropertyBlob(self: Card, data: []const u8) sys.IoctlError!u32 { - var create = std.mem.zeroInit(sys.mode.CreateBlob, .{ - .length = @as(u32, @intCast(data.len)), - .data = @intFromPtr(data.ptr), - }); - try sys.ioctl(self.handle.handle, .mode_createpropblob, &create); - return create.blob_id; - } - - // BEGIN MODESETTING API - - pub const GetResourcesError = sys.IoctlError || error{OutOfMemory}; - - pub fn getModesettingResourcesAlloc( - self: Card, - gpa: std.mem.Allocator, - ) GetResourcesError!ModesettingResources { - while (true) { - var res = std.mem.zeroes(sys.mode.CardRes); - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - var ret = ModesettingResources{ - .min_width = res.min_width, - .max_width = res.max_width, - .min_height = res.min_height, - .max_height = res.max_height, - .fbs = &.{}, - .crtcs = &.{}, - .connectors = &.{}, - .encoders = &.{}, - }; - - ret.fbs = try gpa.alloc(u32, res.count_fbs); - errdefer gpa.free(ret.fbs); - res.fb_id_ptr = @intFromPtr(ret.fbs.ptr); - - ret.crtcs = try gpa.alloc(u32, res.count_crtcs); - errdefer gpa.free(ret.crtcs); - res.crtc_id_ptr = @intFromPtr(ret.crtcs.ptr); - - ret.connectors = try gpa.alloc(u32, res.count_connectors); - errdefer gpa.free(ret.connectors); - res.connector_id_ptr = @intFromPtr(ret.connectors.ptr); - - ret.encoders = try gpa.alloc(u32, res.count_encoders); - errdefer gpa.free(ret.encoders); - res.encoder_id_ptr = @intFromPtr(ret.encoders.ptr); - - const cached = res; - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - if (cached.count_fbs < res.count_fbs or - cached.count_crtcs < res.count_crtcs or - cached.count_connectors < res.count_connectors or - cached.count_encoders < res.count_encoders) - { - gpa.free(ret.encoders); - gpa.free(ret.connectors); - gpa.free(ret.crtcs); - gpa.free(ret.fbs); - continue; - } - - return ret; - } - } - - pub fn getModesettingResourcesBuffered( - self: Card, - fbs: []u32, - crtcs: []u32, - connectors: []u32, - encoders: []u32, - ) GetResourcesError!ModesettingResources { - var res = sys.mode.CardRes{ - .min_width = 0, - .max_width = 0, - .min_height = 0, - .max_height = 0, - .count_fbs = @intCast(fbs.len), - .fb_id_ptr = @intFromPtr(fbs.ptr), - .count_crtcs = @intCast(crtcs.len), - .crtc_id_ptr = @intFromPtr(crtcs.ptr), - .count_connectors = @intCast(connectors.len), - .connector_id_ptr = @intFromPtr(connectors.ptr), - .count_encoders = @intCast(encoders.len), - .encoder_id_ptr = @intFromPtr(encoders.ptr), - }; - try sys.ioctl(self.handle.handle, .mode_getresources, &res); - - if (res.count_fbs > fbs.len or - res.count_crtcs > crtcs.len or - res.count_connectors > connectors.len or - res.count_encoders > encoders.len) - return error.OutOfMemory; - - return ModesettingResources{ - .min_width = res.min_width, - .max_width = res.max_width, - .min_height = res.min_height, - .max_height = res.max_height, - .fbs = fbs[0..res.count_fbs], - .crtcs = crtcs[0..res.count_crtcs], - .connectors = connectors[0..res.count_connectors], - .encoders = encoders[0..res.count_encoders], - }; - } - - pub const GetConnectorError = sys.IoctlError || error{OutOfMemory}; - - pub fn getConnectorAlloc(self: Card, gpa: std.mem.Allocator, id: u32) GetConnectorError!Connector { - while (true) { - var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ .connector_id = id }); - try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); - const cached = get_conn; - - const encoders = try gpa.alloc(u32, get_conn.count_encoders); - errdefer gpa.free(encoders); - - const modes = try gpa.alloc(sys.mode.ModeInfo, get_conn.count_modes); - errdefer gpa.free(modes); - - const props = try gpa.alloc(u32, get_conn.count_props); - errdefer gpa.free(props); - const prop_values = try gpa.alloc(u64, get_conn.count_props); - errdefer gpa.free(prop_values); - - get_conn.encoders_ptr = @intFromPtr(encoders.ptr); - get_conn.modes_ptr = @intFromPtr(modes.ptr); - get_conn.props_ptr = @intFromPtr(props.ptr); - get_conn.prop_values_ptr = @intFromPtr(prop_values.ptr); - - try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); - - if (cached.count_encoders < get_conn.count_encoders or - cached.count_modes < get_conn.count_modes or - cached.count_props < get_conn.count_props) - { - gpa.free(prop_values); - gpa.free(props); - gpa.free(modes); - gpa.free(encoders); - continue; - } - - return Connector{ - .id = id, - .encoder_id = get_conn.encoder_id, - .connection = @enumFromInt(get_conn.connection), - // Unsure why libdrm does this conversion, - // but we'll follow suite for compatability. - .subpixel = @enumFromInt(get_conn.subpixel + 1), - .encoders = encoders[0..get_conn.count_encoders], - .modes = modes[0..get_conn.count_modes], - .props = props[0..get_conn.count_props], - .prop_values = prop_values[0..get_conn.count_props], - .mm_width = get_conn.mm_width, - .mm_height = get_conn.mm_height, - .type = get_conn.connector_type, - .type_id = get_conn.connector_type_id, - }; - } - } - - pub fn getConnectorBuffered( - self: Card, - encoders: []u32, - modes: []sys.mode.ModeInfo, - props: []u32, - prop_values: []u64, - id: u32, - ) GetConnectorError!Connector { - std.debug.assert(props.len == prop_values.len); - var get_conn = std.mem.zeroInit(sys.mode.GetConnector, .{ - .connector_id = id, - .count_encoders = @as(u32, @intCast(encoders.len)), - .encoders_ptr = @intFromPtr(encoders.ptr), - .count_modes = @as(u32, @intCast(modes.len)), - .modes_ptr = @intFromPtr(modes.ptr), - .count_props = @as(u32, @intCast(props.len)), - .props_ptr = @intFromPtr(props.ptr), - .prop_values_ptr = @intFromPtr(prop_values.ptr), - }); - try sys.ioctl(self.handle.handle, .mode_getconnector, &get_conn); - - if (get_conn.count_encoders > encoders.len or - get_conn.count_modes > modes.len or - get_conn.count_props > props.len) - return error.OutOfMemory; - - return Connector{ - .id = id, - .encoder_id = get_conn.encoder_id, - .connection = @enumFromInt(get_conn.connection), - .subpixel = @enumFromInt(get_conn.subpixel + 1), - .encoders = encoders[0..get_conn.count_encoders], - .modes = modes[0..get_conn.count_modes], - .props = props[0..get_conn.count_props], - .prop_values = prop_values[0..get_conn.count_props], - .mm_width = get_conn.mm_width, - .mm_height = get_conn.mm_height, - .type = get_conn.connector_type, - .type_id = get_conn.connector_type_id, - }; - } - - pub fn getEncoder(self: Card, id: u32) sys.IoctlError!Encoder { - var get_encoder = std.mem.zeroInit(sys.mode.GetEncoder, .{ .encoder_id = id }); - try sys.ioctl(self.handle.handle, .mode_getencoder, &get_encoder); - - return Encoder{ - .id = id, - .type = get_encoder.encoder_type, - .crtc_id = get_encoder.crtc_id, - .possible_crtcs = get_encoder.possible_crtcs, - .possible_clones = get_encoder.possible_clones, - }; - } - - pub const GetCrtcError = sys.IoctlError; - - pub fn getCrtc(self: Card, id: u32) GetCrtcError!Crtc { - var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ .crtc_id = id }); - try sys.ioctl(self.handle.handle, .mode_getcrtc, &crtc); - - return Crtc{ - .id = id, - .fb_id = crtc.fb_id, - .x = crtc.x, - .y = crtc.y, - .gamma_size = crtc.gamma_size, - .mode = if (crtc.mode_valid != 0) crtc.mode else null, - }; - } - - pub fn setCrtc( - self: Card, - id: u32, - fb_id: u32, - x: u32, - y: u32, - connectors: []u32, - mode: ?sys.mode.ModeInfo, - ) sys.IoctlError!void { - var crtc = std.mem.zeroInit(sys.mode.Crtc, .{ - .crtc_id = id, - .fb_id = fb_id, - .x = x, - .y = y, - .count_connectors = @as(u32, @intCast(connectors.len)), - .set_connectors_ptr = @intFromPtr(connectors.ptr), - }); - if (mode) |m| { - crtc.mode_valid = 1; - crtc.mode = m; - } - try sys.ioctl(self.handle.handle, .mode_setcrtc, &crtc); - } - - pub fn createDumbBuffer(self: Card, width: u32, height: u32, bpp: u32) sys.IoctlError!DumbBuffer { - var create = std.mem.zeroInit(sys.mode.CreateDumb, .{ - .width = width, - .height = height, - .bpp = bpp, - }); - try sys.ioctl(self.handle.handle, .mode_create_dumb, &create); - - return DumbBuffer{ - .handle = create.handle, - .width = create.width, - .height = create.height, - .stride = create.pitch, - .size = @intCast(create.size), - }; - } - - pub fn destroyDumbBuffer(self: Card, handle: u32) sys.IoctlError!void { - var destroy = sys.mode.DestroyDumb{ .handle = handle }; - try sys.ioctl(self.handle.handle, .mode_destroy_dumb, &destroy); - } - - pub fn mapDumbBuffer(self: Card, dumb: DumbBuffer, offset: usize) sys.IoctlError!usize { - var map = sys.mode.MapDumb{ .handle = dumb.handle, .pad = 0, .offset = offset }; - try sys.ioctl(self.handle.handle, .mode_map_dumb, &map); - return @intCast(map.offset); - } - - pub fn addFb2( - self: Card, - width: u32, - height: u32, - format: fmt.Format, - handles: [4]u32, - pitches: [4]u32, - offsets: [4]u32, - flags: u32, // FIXME: add flags type - ) sys.IoctlError!u32 { - var cmd = std.mem.zeroInit(sys.mode.FbCmd2, .{ - .width = width, - .height = height, - .pixel_format = @intFromEnum(format), - .handles = handles, - .pitches = pitches, - .offsets = offsets, - .flags = flags, - }); - try sys.ioctl(self.handle.handle, .mode_addfb2, &cmd); - return cmd.fb_id; - } - - pub fn removeFb(self: Card, fb: u32) sys.IoctlError!void { - try sys.ioctl(self.handle.handle, .mode_rmfb, &fb); - } - - pub fn pageFlip( - self: Card, - crtc_id: u32, - fb_id: u32, - flags: sys.mode.PageFlipFlags, - data: ?*anyopaque, - ) sys.IoctlError!void { - var flip = sys.mode.CrtcPageFlip{ - .crtc_id = crtc_id, - .fb_id = fb_id, - .flags = flags, - .reserved = 0, - .user_data = @intFromPtr(data), - }; - try sys.ioctl(self.handle.handle, .mode_page_flip, &flip); - } - - pub const GetPlaneResourcesError = sys.IoctlError || error{OutOfMemory}; - - pub fn getPlaneResourcesAlloc(self: Card, gpa: std.mem.Allocator) GetPlaneResourcesError!PlaneResources { - while (true) { - var res = std.mem.zeroes(sys.mode.GetPlaneRes); - try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res); - const cached = res; - - const planes = try gpa.alloc(u32, res.count_planes); - errdefer gpa.free(planes); - res.plane_id_ptr = @intFromPtr(planes.ptr); - - try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res); - if (res.count_planes > cached.count_planes) { - gpa.free(planes); - continue; - } - - return PlaneResources{ .planes = planes }; - } - } - - pub fn getPlaneResourcesBuffered(self: Card, plane_buf: []u32) GetPlaneResourcesError!PlaneResources { - var res = sys.mode.GetPlaneRes{ - .count_planes = plane_buf.len, - .plane_id_ptr = @intFromPtr(plane_buf.ptr), - }; - try sys.ioctl(self.handle.handle, .mode_getplaneresources, &res); - if (res.count_planes > plane_buf.len) return error.OutOfMemory; - return PlaneResources{ .planes = plane_buf[0..res.count_planes] }; - } - - pub const GetPlaneError = sys.IoctlError || error{OutOfMemory}; - - pub fn getPlaneAlloc(self: Card, gpa: std.mem.Allocator, id: u32) GetPlaneError!Plane { - while (true) { - var get = std.mem.zeroInit(sys.mode.GetPlane, .{ .plane_id = id }); - try sys.ioctl(self.handle.handle, .mode_getplane, &get); - const cached = get; - - const formats = try gpa.alloc(u32, get.count_format_types); - errdefer gpa.free(formats); - get.format_type_ptr = @intFromPtr(formats.ptr); - - try sys.ioctl(self.handle.handle, .mode_getplane, &get); - if (get.count_format_types > cached.count_format_types) { - gpa.free(formats); - continue; - } - - return Plane{ - .id = id, - .formats = formats, - .crtc_id = get.crtc_id, - .fb_id = get.fb_id, - .crtc_x = 0, - .crtc_y = 0, - .x = 0, - .y = 0, - .possible_crtcs = get.possible_crtcs, - .gamma_size = get.gamma_size, - }; - } - } - - pub fn getPlaneBuffered(self: Card, format_buf: []u32, id: u32) GetPlaneError!Plane { - var get = std.mem.zeroInit(sys.mode.GetPlane, .{ - .plane_id = id, - .count_format_types = format_buf.len, - .format_types_ptr = @intFromPtr(format_buf.ptr), - }); - - try sys.ioctl(self.handle.handle, .mode_getplane, &get); - if (get.count_format_types > format_buf.len) return error.OutOfMemory; - - return Plane{ - .id = id, - .formats = format_buf[0..get.count_format_types], - .crtc_id = get.crtc_id, - .fb_id = get.fb_id, - .crtc_x = 0, - .crtc_y = 0, - .x = 0, - .y = 0, - .possible_crtcs = get.possible_crtcs, - .gamma_size = get.gamma_size, - }; - } - - pub const GetObjectPropertiesError = sys.IoctlError || error{OutOfMemory}; - - pub fn getObjectPropertiesAlloc( - self: Card, - gpa: std.mem.Allocator, - object_id: u32, - object_type: sys.mode.ObjType, - ) GetObjectPropertiesError!ObjectProperties { - while (true) { - var get = std.mem.zeroInit(sys.mode.ObjGetProperties, .{ - .obj_id = object_id, - .obj_type = object_type, - }); - try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get); - const cached = get; - - const keys = try gpa.alloc(u32, get.count_props); - errdefer gpa.free(keys); - get.props_ptr = keys.ptr; - - const values = try gpa.alloc(u64, get.count_props); - errdefer gpa.free(values); - get.prop_values_ptr = values.ptr; - - try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get); - if (get.count_props > cached.count_props) { - gpa.free(keys); - gpa.free(values); - continue; - } - - return ObjectProperties{ - .keys = keys, - .values = values, - }; - } - } - - pub fn getObjectPropertiesBuffered( - self: Card, - key_buf: []u32, - value_buf: []u64, - object_id: u32, - object_type: sys.mode.ObjType, - ) GetObjectPropertiesError!ObjectProperties { - var get = sys.mode.ObjGetProperties{ - .obj_id = object_id, - .obj_type = object_type, - .count_props = @intCast(@min(key_buf.len, value_buf.len)), - .props_ptr = @intFromEnum(key_buf.ptr), - .prop_values_ptr = @intFromEnum(value_buf.ptr), - }; - try sys.ioctl(self.handle.handle, .mode_obj_getproperties, &get); - if (get.count_props > @min(key_buf.len, value_buf.len)) return error.OutOfMemory; - - return ObjectProperties{ - .keys = key_buf[0..get.count_props], - .values = value_buf[0..get.count_props], - }; - } +pub const NodeType = enum { + primary, + /// Deprecated + control, + render, + _max, }; pub const Event = union(enum) { diff --git a/src/sys.zig b/src/sys.zig index c706380..40d48e1 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -334,17 +334,6 @@ pub const EventCrtcSequence = extern struct { sequence: u64, }; -pub const BusType = enum(u32) { - pci = 0, - usb = 1, - platform = 2, - host1x = 3, - faux = 4, - virtio = 0x10, -}; - -pub const PciBusInfo = extern struct {}; - pub const mode = struct { pub const display_mode_len = 32; pub const prop_name_len = 32;