summaryrefslogtreecommitdiff
path: root/src/screen
diff options
context:
space:
mode:
authorNathan Reiner <nathan@nathanreiner.xyz>2025-02-01 12:47:35 +0100
committerNathan Reiner <nathan@nathanreiner.xyz>2025-02-01 12:47:35 +0100
commit85bcada8cf78bdf2bfb3be583289686026e0f25e (patch)
tree0ce404c4840432db9b6d3addd3947a736d103382 /src/screen
parent2ce14aec655589f00442ab469b9d877a143eeefd (diff)
screen: start drm implementation
Diffstat (limited to 'src/screen')
-rw-r--r--src/screen/cerror.zig272
-rw-r--r--src/screen/drm/card.zig45
-rw-r--r--src/screen/drm/connector.zig156
-rw-r--r--src/screen/drm/request.zig19
-rw-r--r--src/screen/drm/resources.zig76
-rw-r--r--src/screen/main.zig20
6 files changed, 588 insertions, 0 deletions
diff --git a/src/screen/cerror.zig b/src/screen/cerror.zig
new file mode 100644
index 0000000..cd7f6e6
--- /dev/null
+++ b/src/screen/cerror.zig
@@ -0,0 +1,272 @@
+
+pub const CError = error {
+ OperationNotPermitted,
+ NoSuchFileOrDirectory,
+ NoSuchProcess,
+ InterruptedSystemCall,
+ InputOutputError,
+ NoSuchDeviceOrAddress,
+ ArgumentListTooLong,
+ ExecFormatError,
+ BadFileDescriptor,
+ NoChildProcesses,
+ ResourceTemporarilyUnavailable,
+ CannotAllocateMemory,
+ PermissionDenied,
+ BadAddress,
+ BlockDeviceRequired,
+ DeviceOrResourceBusy,
+ FileExists,
+ InvalidCrossDeviceLink,
+ NoSuchDevice,
+ NotADirectory,
+ IsADirectory,
+ InvalidArgument,
+ TooManyOpenFilesInSystem,
+ TooManyOpenFiles,
+ InappropriateIoctlForDevice,
+ TextFileBusy,
+ FileTooLarge,
+ NoSpaceLeftOnDevice,
+ IllegalSeek,
+ ReadOnlyFileSystem,
+ TooManyLinks,
+ BrokenPipe,
+ NumericalArgumentOutOfDomain,
+ NumericalResultOutOfRange,
+ ResourceDeadlockAvoided,
+ FileNameTooLong,
+ NoLocksAvailable,
+ FunctionNotImplemented,
+ DirectoryNotEmpty,
+ TooManyLevelsOfSymbolicLinks,
+ NoMessageOfDesiredType,
+ IdentifierRemoved,
+ ChannelNumberOutOfRange,
+ Level2NotSynchronized,
+ Level3Halted,
+ Level3Reset,
+ LinkNumberOutOfRange,
+ ProtocolDriverNotAttached,
+ NoCSIStructureAvailable,
+ Level2Halted,
+ InvalidExchange,
+ InvalidRequestDescriptor,
+ ExchangeFull,
+ NoAnode,
+ InvalidRequestCode,
+ InvalidSlot,
+ BadFontFileFormat,
+ DeviceNotAStream,
+ NoDataAvailable,
+ TimerExpired,
+ OutOfStreamsResources,
+ MachineIsNotOnTheNetwork,
+ PackageNotInstalled,
+ ObjectIsRemote,
+ LinkHasBeenSevered,
+ AdvertiseError,
+ SrmountError,
+ CommunicationErrorOnSend,
+ ProtocolError,
+ MultihopAttempted,
+ RFSSpecificError,
+ BadMessage,
+ ValueTooLargeForDefinedDataType,
+ NameNotUniqueOnNetwork,
+ FileDescriptorInBadState,
+ RemoteAddressChanged,
+ CanNotAccessANeededSharedLibrary,
+ AccessingACorruptedSharedLibrary,
+ LibSectionInAOutCorrupted,
+ AttemptingToLinkInTooManySharedLibraries,
+ CannotExecASharedLibraryDirectly,
+ InvalidOrIncompleteMultibyteOrWideCharacter,
+ InterruptedSystemCallShouldBeRestarted,
+ StreamsPipeError,
+ TooManyUsers,
+ SocketOperationOnNonSocket,
+ DestinationAddressRequired,
+ MessageTooLong,
+ ProtocolWrongTypeForSocket,
+ ProtocolNotAvailable,
+ ProtocolNotSupported,
+ SocketTypeNotSupported,
+ OperationNotSupported,
+ ProtocolFamilyNotSupported,
+ AddressFamilyNotSupportedByProtocol,
+ AddressAlreadyInUse,
+ CannotAssignRequestedAddress,
+ NetworkIsDown,
+ NetworkIsUnreachable,
+ NetworkDroppedConnectionOnReset,
+ SoftwareCausedConnectionAbort,
+ ConnectionResetByPeer,
+ NoBufferSpaceAvailable,
+ TransportEndpointIsAlreadyConnected,
+ TransportEndpointIsNotConnected,
+ CannotSendAfterTransportEndpointShutdown,
+ TooManyReferences,
+ ConnectionTimedOut,
+ ConnectionRefused,
+ HostIsDown,
+ NoRouteToHost,
+ OperationAlreadyInProgress,
+ OperationNowInProgress,
+ StaleFileHandle,
+ StructureNeedsCleaning,
+ NotAXENIXNamedTypeFile,
+ NoXENIXSemaphoresAvailable,
+ IsANamedTypeFile,
+ RemoteIOError,
+ DiskQuotaExceeded,
+ NoMediumFound,
+ WrongMediumType,
+ OperationCanceled,
+ RequiredKeyNotAvailable,
+ KeyHasExpired,
+ KeyHasBeenRevoked,
+ KeyWasRejectedByService,
+ OwnerDied,
+ StateNotRecoverable,
+ OperationNotPossibleDueToRFKill,
+ MemoryPageHasHardwareError,
+};
+
+pub fn from_usize(errno: usize) CError!void {
+ const n = -@as(isize, @bitCast(errno));
+ return switch (n) {
+ 1 => CError.OperationNotPermitted,
+ 2 => CError.NoSuchFileOrDirectory,
+ 3 => CError.NoSuchProcess,
+ 4 => CError.InterruptedSystemCall,
+ 5 => CError.InputOutputError,
+ 6 => CError.NoSuchDeviceOrAddress,
+ 7 => CError.ArgumentListTooLong,
+ 8 => CError.ExecFormatError,
+ 9 => CError.BadFileDescriptor,
+ 10 => CError.NoChildProcesses,
+ 11 => CError.ResourceTemporarilyUnavailable,
+ 12 => CError.CannotAllocateMemory,
+ 13 => CError.PermissionDenied,
+ 14 => CError.BadAddress,
+ 15 => CError.BlockDeviceRequired,
+ 16 => CError.DeviceOrResourceBusy,
+ 17 => CError.FileExists,
+ 18 => CError.InvalidCrossDeviceLink,
+ 19 => CError.NoSuchDevice,
+ 20 => CError.NotADirectory,
+ 21 => CError.IsADirectory,
+ 22 => CError.InvalidArgument,
+ 23 => CError.TooManyOpenFilesInSystem,
+ 24 => CError.TooManyOpenFiles,
+ 25 => CError.InappropriateIoctlForDevice,
+ 26 => CError.TextFileBusy,
+ 27 => CError.FileTooLarge,
+ 28 => CError.NoSpaceLeftOnDevice,
+ 29 => CError.IllegalSeek,
+ 30 => CError.ReadOnlyFileSystem,
+ 31 => CError.TooManyLinks,
+ 32 => CError.BrokenPipe,
+ 33 => CError.NumericalArgumentOutOfDomain,
+ 34 => CError.NumericalResultOutOfRange,
+ 35 => CError.ResourceDeadlockAvoided,
+ 36 => CError.FileNameTooLong,
+ 37 => CError.NoLocksAvailable,
+ 38 => CError.FunctionNotImplemented,
+ 39 => CError.DirectoryNotEmpty,
+ 40 => CError.TooManyLevelsOfSymbolicLinks,
+ 42 => CError.NoMessageOfDesiredType,
+ 43 => CError.IdentifierRemoved,
+ 44 => CError.ChannelNumberOutOfRange,
+ 45 => CError.Level2NotSynchronized,
+ 46 => CError.Level3Halted,
+ 47 => CError.Level3Reset,
+ 48 => CError.LinkNumberOutOfRange,
+ 49 => CError.ProtocolDriverNotAttached,
+ 50 => CError.NoCSIStructureAvailable,
+ 51 => CError.Level2Halted,
+ 52 => CError.InvalidExchange,
+ 53 => CError.InvalidRequestDescriptor,
+ 54 => CError.ExchangeFull,
+ 55 => CError.NoAnode,
+ 56 => CError.InvalidRequestCode,
+ 57 => CError.InvalidSlot,
+ 59 => CError.BadFontFileFormat,
+ 60 => CError.DeviceNotAStream,
+ 61 => CError.NoDataAvailable,
+ 62 => CError.TimerExpired,
+ 63 => CError.OutOfStreamsResources,
+ 64 => CError.MachineIsNotOnTheNetwork,
+ 65 => CError.PackageNotInstalled,
+ 66 => CError.ObjectIsRemote,
+ 67 => CError.LinkHasBeenSevered,
+ 68 => CError.AdvertiseError,
+ 69 => CError.SrmountError,
+ 70 => CError.CommunicationErrorOnSend,
+ 71 => CError.ProtocolError,
+ 72 => CError.MultihopAttempted,
+ 73 => CError.RFSSpecificError,
+ 74 => CError.BadMessage,
+ 75 => CError.ValueTooLargeForDefinedDataType,
+ 76 => CError.NameNotUniqueOnNetwork,
+ 77 => CError.FileDescriptorInBadState,
+ 78 => CError.RemoteAddressChanged,
+ 79 => CError.CanNotAccessANeededSharedLibrary,
+ 80 => CError.AccessingACorruptedSharedLibrary,
+ 81 => CError.LibSectionInAOutCorrupted,
+ 82 => CError.AttemptingToLinkInTooManySharedLibraries,
+ 83 => CError.CannotExecASharedLibraryDirectly,
+ 84 => CError.InvalidOrIncompleteMultibyteOrWideCharacter,
+ 85 => CError.InterruptedSystemCallShouldBeRestarted,
+ 86 => CError.StreamsPipeError,
+ 87 => CError.TooManyUsers,
+ 88 => CError.SocketOperationOnNonSocket,
+ 89 => CError.DestinationAddressRequired,
+ 90 => CError.MessageTooLong,
+ 91 => CError.ProtocolWrongTypeForSocket,
+ 92 => CError.ProtocolNotAvailable,
+ 93 => CError.ProtocolNotSupported,
+ 94 => CError.SocketTypeNotSupported,
+ 95 => CError.OperationNotSupported,
+ 96 => CError.ProtocolFamilyNotSupported,
+ 97 => CError.AddressFamilyNotSupportedByProtocol,
+ 98 => CError.AddressAlreadyInUse,
+ 99 => CError.CannotAssignRequestedAddress,
+ 100 => CError.NetworkIsDown,
+ 101 => CError.NetworkIsUnreachable,
+ 102 => CError.NetworkDroppedConnectionOnReset,
+ 103 => CError.SoftwareCausedConnectionAbort,
+ 104 => CError.ConnectionResetByPeer,
+ 105 => CError.NoBufferSpaceAvailable,
+ 106 => CError.TransportEndpointIsAlreadyConnected,
+ 107 => CError.TransportEndpointIsNotConnected,
+ 108 => CError.CannotSendAfterTransportEndpointShutdown,
+ 109 => CError.TooManyReferences,
+ 110 => CError.ConnectionTimedOut,
+ 111 => CError.ConnectionRefused,
+ 112 => CError.HostIsDown,
+ 113 => CError.NoRouteToHost,
+ 114 => CError.OperationAlreadyInProgress,
+ 115 => CError.OperationNowInProgress,
+ 116 => CError.StaleFileHandle,
+ 117 => CError.StructureNeedsCleaning,
+ 118 => CError.NotAXENIXNamedTypeFile,
+ 119 => CError.NoXENIXSemaphoresAvailable,
+ 120 => CError.IsANamedTypeFile,
+ 121 => CError.RemoteIOError,
+ 122 => CError.DiskQuotaExceeded,
+ 123 => CError.NoMediumFound,
+ 124 => CError.WrongMediumType,
+ 125 => CError.OperationCanceled,
+ 126 => CError.RequiredKeyNotAvailable,
+ 127 => CError.KeyHasExpired,
+ 128 => CError.KeyHasBeenRevoked,
+ 129 => CError.KeyWasRejectedByService,
+ 130 => CError.OwnerDied,
+ 131 => CError.StateNotRecoverable,
+ 132 => CError.OperationNotPossibleDueToRFKill,
+ 133 => CError.MemoryPageHasHardwareError,
+ else => void{}
+ };
+}
diff --git a/src/screen/drm/card.zig b/src/screen/drm/card.zig
new file mode 100644
index 0000000..67f81f3
--- /dev/null
+++ b/src/screen/drm/card.zig
@@ -0,0 +1,45 @@
+const std = @import("std");
+const os = std.os.linux;
+
+const cerror = @import("../cerror.zig");
+
+const Resources = @import("resources.zig").Resources;
+const Connector = @import("connector.zig").Connector;
+
+
+pub const Card = struct {
+ const Self = @This();
+
+ file: std.fs.File,
+ allocator: std.mem.Allocator,
+
+ pub fn open(name: []const u8, allocator: std.mem.Allocator) !Self {
+ var dri_dir = try std.fs.openDirAbsolute("/dev/dri", .{});
+ defer dri_dir.close();
+
+ return .{
+ .file = try dri_dir.openFile(name, .{
+ .mode = .read_write,
+ .lock_nonblocking = true,
+ }),
+ .allocator = allocator,
+ };
+ }
+
+ pub fn close(self: *Card) void {
+ self.file.close();
+ }
+
+ pub fn is_kms(self: *Card) !bool {
+ const raw_info = Resources.raw_without_ids(self);
+ return raw_info.count_crtcs > 0 and raw_info.count_connectors > 0 and raw_info.count_encoders > 0;
+ }
+
+ pub fn resources(self: *Card) !Resources {
+ return Resources.init(self);
+ }
+
+ pub fn connector(self: *Card, id: u32) !Connector {
+ return Connector.init(self, id);
+ }
+};
diff --git a/src/screen/drm/connector.zig b/src/screen/drm/connector.zig
new file mode 100644
index 0000000..9d7f9ad
--- /dev/null
+++ b/src/screen/drm/connector.zig
@@ -0,0 +1,156 @@
+const std = @import("std");
+const os = std.os.linux;
+const Drm = @import("request.zig").Drm;
+const Card = @import("card.zig").Card;
+
+const RawConnector = extern struct {
+ encoder_ids: ?*u32,
+ modes: ?*ModeInfo,
+ prop_ids: ?*u32,
+ prop_value_ids: ?*u64,
+ count_modes: u32,
+ count_props: u32,
+ count_encoders: u32,
+ encoder_id: u32,
+ connector_id: u32,
+ connector_type: u32,
+ connector_type_id: u32,
+ connection: Connection,
+ mm_width: u32,
+ mm_height: u32,
+ subpixel: u32,
+ pad: u32,
+};
+
+const Connection = enum(u32) {
+ undefined_state = 0,
+ connected = 1,
+ disconnected = 2,
+ unknown = 3,
+};
+
+const ModeInfo = extern struct {
+ const Self = @This();
+ const Flags = packed struct(u32) {
+ phsync: bool,
+ nhsync: bool,
+ pvsync: bool,
+ nvsync: bool,
+ interlace: bool,
+ dblscan: bool,
+ csync: bool,
+ pcsync: bool,
+ ncsync: bool,
+ hskew: bool,
+ bcast: bool,
+ pixmux: bool,
+ dblclk: bool,
+ clkdiv2: bool,
+ _padding: u18,
+ };
+
+ clock: u32,
+ hdisplay: u16,
+ hsync_start: u16,
+ hsync_end: u16,
+ htotal: u16,
+ hskew: u16,
+ vdisplay: u16,
+ vsync_start: u16,
+ vsync_end: u16,
+ vtotal: u16,
+ vscan: u16,
+
+ vrefresh: u32,
+
+ flags: Flags,
+ type: u32,
+ name: [32]u8,
+
+ pub fn frame_rate(self: *const Self) f32 {
+ var rate = (@as(u64, @intCast(self.clock)) * 1000000 / self.htotal + self.vtotal / 2) / self.vtotal;
+
+ if (self.flags.interlace) {
+ rate *= 2;
+ }
+
+ if (self.flags.dblscan) {
+ rate /= 2;
+ }
+
+ if (self.vscan > 1) {
+ rate /= self.vscan;
+ }
+
+ return @as(f32, @floatFromInt(rate)) / 1000.0;
+ }
+};
+
+pub const Connector = struct {
+ const Self = @This();
+
+ card: *Card,
+
+ encoder_ids: []u32,
+ modes: []ModeInfo,
+ prop_ids: []u32,
+ prop_value_ids: []u64,
+ encoder_id: u32,
+ connector_id: u32,
+ connector_type: u32,
+ connector_type_id: u32,
+ connection: Connection,
+ mm_width: u32,
+ mm_height: u32,
+ subpixel: u32,
+ pad: u32,
+
+ pub fn raw_without_ids(card: *Card, id: u32) !RawConnector {
+ var result = std.mem.zeroInit(RawConnector, .{ .connector_id = id });
+ try Drm.get_connector.request(card.file.handle, RawConnector, &result);
+ return result;
+ }
+
+ // NOTE: This function does not take in account
+ // that there might be some hot-plugging going
+ // on. This might have to change in the future.
+ pub fn init(card: *Card, id: u32) !Self {
+ var raw = try Self.raw_without_ids(card, id);
+ const resources = .{
+ .encoder_ids = try card.allocator.alloc(u32, raw.count_encoders),
+ .modes = try card.allocator.alloc(ModeInfo, raw.count_modes),
+ .prop_ids = try card.allocator.alloc(u32, raw.count_props),
+ .prop_value_ids = try card.allocator.alloc(u64, raw.count_props),
+ .encoder_id = raw.encoder_id,
+ .connector_id = raw.connector_id,
+ .connector_type = raw.connector_type,
+ .connector_type_id = raw.connector_type_id,
+ .connection = raw.connection,
+ .mm_width = raw.mm_width,
+ .mm_height = raw.mm_height,
+ .subpixel = raw.subpixel,
+ .pad = raw.pad,
+ .card = card,
+ };
+
+ @memset(resources.encoder_ids, 0);
+ @memset(resources.modes, std.mem.zeroes(ModeInfo));
+ @memset(resources.prop_ids, 0);
+ @memset(resources.prop_value_ids, 0);
+
+ raw.encoder_ids = @ptrCast(resources.encoder_ids);
+ raw.modes = @ptrCast(resources.modes);
+ raw.prop_ids = @ptrCast(resources.prop_ids);
+ raw.prop_value_ids = @ptrCast(resources.prop_value_ids);
+
+ try Drm.get_connector.request(card.file.handle, RawConnector, &raw);
+
+ return resources;
+ }
+
+ pub fn deinit(self: *Self) void {
+ self.card.allocator.free(self.encoder_ids);
+ self.card.allocator.free(self.modes);
+ self.card.allocator.free(self.prop_ids);
+ }
+};
diff --git a/src/screen/drm/request.zig b/src/screen/drm/request.zig
new file mode 100644
index 0000000..bf9701c
--- /dev/null
+++ b/src/screen/drm/request.zig
@@ -0,0 +1,19 @@
+const std = @import("std");
+const os = std.os.linux;
+const cerror = @import("../cerror.zig");
+
+fn ioctl(fd: os.fd_t, request: u32, arg: usize) !void {
+ try cerror.from_usize(os.ioctl(fd, request, arg));
+}
+
+pub const Drm = enum(u8) {
+ const Self = @This();
+
+ get_resources = 0xA0,
+ get_connector = 0xA7,
+
+ pub fn request(self: Self, fd: os.fd_t, T: type, arg: *T) !void {
+ const id = os.IOCTL.IOWR('d', @intFromEnum(self), T);
+ try ioctl(fd, id, @intFromPtr(arg));
+ }
+};
diff --git a/src/screen/drm/resources.zig b/src/screen/drm/resources.zig
new file mode 100644
index 0000000..6128f37
--- /dev/null
+++ b/src/screen/drm/resources.zig
@@ -0,0 +1,76 @@
+const std = @import("std");
+const os = std.os.linux;
+const cerror = @import("../cerror.zig");
+const Card = @import("card.zig").Card;
+const Drm = @import("request.zig").Drm;
+
+const RawResources = extern struct {
+ fb_ids: ?*u32,
+ crtc_ids: ?*u32,
+ connector_ids: ?*u32,
+ encoder_ids: ?*u32,
+ count_fbs: u32,
+ count_crtcs: u32,
+ count_connectors: u32,
+ count_encoders: u32,
+ min_width: u32,
+ max_width: u32,
+ min_height: u32,
+ max_height: u32,
+};
+
+pub const Resources = struct {
+ const Self = @This();
+ const Range = struct { min: u32, max: u32 };
+
+ card: *Card,
+
+ fb_ids: []u32,
+ crtc_ids: []u32,
+ connector_ids: []u32,
+ encoder_ids: []u32,
+ width: Range,
+ height: Range,
+
+ pub fn raw_without_ids(card: *Card) !RawResources {
+ var result = std.mem.zeroes(RawResources);
+ try Drm.get_resources.request(card.file.handle, RawResources, &result);
+ return result;
+ }
+
+ // NOTE: This function does not take in account
+ // that there might be some hot-plugging going
+ // on. This might have to change in the future.
+ pub fn init(card: *Card) !Self {
+ var raw = try Self.raw_without_ids(card);
+ const resources = .{
+ .fb_ids = try card.allocator.alloc(u32, raw.count_fbs),
+ .crtc_ids = try card.allocator.alloc(u32, raw.count_crtcs),
+ .connector_ids = try card.allocator.alloc(u32, raw.count_connectors),
+ .encoder_ids = try card.allocator.alloc(u32, raw.count_encoders),
+ .width = .{ .min = raw.min_width, .max = raw.max_width },
+ .height = .{ .min = raw.min_height, .max = raw.max_height },
+ .card = card,
+ };
+
+ @memset(resources.fb_ids, 0);
+ @memset(resources.crtc_ids, 0);
+ @memset(resources.connector_ids, 0);
+ @memset(resources.encoder_ids, 0);
+
+ raw.fb_ids = @ptrCast(resources.fb_ids);
+ raw.crtc_ids = @ptrCast(resources.crtc_ids);
+ raw.connector_ids = @ptrCast(resources.connector_ids);
+ raw.encoder_ids = @ptrCast(resources.encoder_ids);
+ try Drm.get_resources.request(card.file.handle, RawResources, &raw);
+
+ return resources;
+ }
+
+ pub fn deinit(self: *Self) void {
+ self.card.allocator.free(self.fb_ids);
+ self.card.allocator.free(self.crtc_ids);
+ self.card.allocator.free(self.connector_ids);
+ self.card.allocator.free(self.encoder_ids);
+ }
+};
diff --git a/src/screen/main.zig b/src/screen/main.zig
new file mode 100644
index 0000000..c23e7c5
--- /dev/null
+++ b/src/screen/main.zig
@@ -0,0 +1,20 @@
+const std = @import("std");
+const drm = @import("drm/card.zig");
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ const allocator = gpa.allocator();
+
+ var card = try drm.Card.open("card0", allocator);
+ defer card.close();
+
+ var resources = try card.resources();
+ defer resources.deinit();
+
+ var connector = try card.connector(resources.connector_ids[0]);
+ defer connector.deinit();
+
+ for (connector.modes) |mode| {
+ std.debug.print("{d}x{d}@{d}\n", .{mode.hdisplay, mode.vdisplay, mode.frame_rate()});
+ }
+}