1#![allow(non_camel_case_types)]
16
17use crate::native_unsafe_methods::{ioc, IOC_READ, IOC_WRITE};
18
19#[repr(C)]
21#[derive(Clone, Copy, Debug)]
22pub struct drm_mode_crtc {
23 pub set_connectors_ptr: u64,
24 pub count_connectors: u32,
25 pub crtc_id: u32,
26 pub fb_id: u32,
27 pub x: u32,
28 pub y: u32,
29 pub gamma_size: u32,
30 pub mode_valid: u32,
31 pub mode: [u8; 68],
33}
34
35#[repr(C)]
37#[derive(Clone, Copy, Debug, Default)]
38pub struct drm_mode_fb_cmd {
39 pub fb_id: u32,
40 pub width: u32,
41 pub height: u32,
42 pub pitch: u32,
43 pub bpp: u32,
44 pub depth: u32,
45 pub handle: u32,
46}
47
48#[repr(C)]
50#[derive(Clone, Copy, Debug, Default)]
51pub struct drm_mode_map_dumb {
52 pub handle: u32,
53 pub pad: u32,
54 pub offset: u64,
55}
56
57#[repr(C)]
59#[derive(Clone, Copy, Debug, Default)]
60pub struct drm_gem_close {
61 pub handle: u32,
62 pub pad: u32,
63}
64
65#[repr(C)]
67#[derive(Clone, Copy, Debug)]
68pub struct drm_version {
69 pub version_major: libc::c_int,
70 pub version_minor: libc::c_int,
71 pub version_patchlevel: libc::c_int,
72 pub name_len: libc::size_t,
73 pub name: *mut libc::c_char,
74 pub date_len: libc::size_t,
75 pub date: *mut libc::c_char,
76 pub desc_len: libc::size_t,
77 pub desc: *mut libc::c_char,
78}
79
80const DRM_IOCTL_BASE: u8 = b'd';
81pub const DRM_IOCTL_VERSION: u32 = ioc(IOC_READ | IOC_WRITE, DRM_IOCTL_BASE, 0x00, std::mem::size_of::<drm_version>());
82pub const DRM_IOCTL_GEM_CLOSE: u32 = ioc(IOC_WRITE, DRM_IOCTL_BASE, 0x09, std::mem::size_of::<drm_gem_close>());
83pub const DRM_IOCTL_MODE_GETCRTC: u32 =
84 ioc(IOC_READ | IOC_WRITE, DRM_IOCTL_BASE, 0xA1, std::mem::size_of::<drm_mode_crtc>());
85pub const DRM_IOCTL_MODE_GETFB: u32 =
86 ioc(IOC_READ | IOC_WRITE, DRM_IOCTL_BASE, 0xAD, std::mem::size_of::<drm_mode_fb_cmd>());
87pub const DRM_IOCTL_MODE_MAP_DUMB: u32 =
88 ioc(IOC_READ | IOC_WRITE, DRM_IOCTL_BASE, 0xB3, std::mem::size_of::<drm_mode_map_dumb>());
89
90#[derive(Clone, Debug)]
94pub struct DrmScanout {
95 pub fb_id: u32,
96 pub width: u32,
97 pub height: u32,
98 pub pitch: u32,
99 pub bits_per_pixel: u32,
100 pub depth: u32,
101 pub data: Vec<u8>,
102}
103
104impl DrmScanout {
105 pub fn pixel(&self, x: u32, y: u32) -> Option<(u8, u8, u8)> {
107 if self.bits_per_pixel != 32 || x >= self.width || y >= self.height {
108 return None;
109 }
110 let offset = y as usize * self.pitch as usize + x as usize * 4;
111 let px = self.data.get(offset..offset + 4)?;
112 Some((px[2], px[1], px[0]))
113 }
114}
115
116#[cfg(target_os = "linux")]
117fn ioctl<T>(fd: i32, request: u32, argument: &mut T) -> std::io::Result<()> {
118 let rv = unsafe { libc::ioctl(fd, request as _, argument as *mut T) };
121 if rv < 0 {
122 return Err(std::io::Error::last_os_error());
123 }
124 Ok(())
125}
126
127#[cfg(target_os = "linux")]
131pub fn driver_name(fd: i32) -> std::io::Result<String> {
132 let mut name = [0u8; 64];
133 let mut version = drm_version {
134 version_major: 0,
135 version_minor: 0,
136 version_patchlevel: 0,
137 name_len: name.len(),
138 name: name.as_mut_ptr().cast(),
139 date_len: 0,
140 date: std::ptr::null_mut(),
141 desc_len: 0,
142 desc: std::ptr::null_mut(),
143 };
144 ioctl(fd, DRM_IOCTL_VERSION, &mut version)?;
146 let length = version.name_len.min(name.len());
147 Ok(String::from_utf8_lossy(&name[..length]).into_owned())
148}
149
150#[cfg(target_os = "linux")]
152pub fn read_scanout(fd: i32, crtc_id: u32) -> std::io::Result<DrmScanout> {
153 use std::io;
154
155 let mut crtc = drm_mode_crtc {
156 set_connectors_ptr: 0,
157 count_connectors: 0,
158 crtc_id,
159 fb_id: 0,
160 x: 0,
161 y: 0,
162 gamma_size: 0,
163 mode_valid: 0,
164 mode: [0; 68],
165 };
166 ioctl(fd, DRM_IOCTL_MODE_GETCRTC, &mut crtc)?;
167 if crtc.fb_id == 0 {
168 return Err(io::Error::other(format!("the CRTC {crtc_id} shows no framebuffer")));
169 }
170
171 let mut fb = drm_mode_fb_cmd { fb_id: crtc.fb_id, ..Default::default() };
172 ioctl(fd, DRM_IOCTL_MODE_GETFB, &mut fb)?;
173 if fb.handle == 0 {
174 return Err(io::Error::new(
175 io::ErrorKind::PermissionDenied,
176 "the framebuffer has no handle for this process (not the master of the card and no CAP_SYS_ADMIN)",
177 ));
178 }
179
180 let read = || -> io::Result<Vec<u8>> {
181 let mut map = drm_mode_map_dumb { handle: fb.handle, ..Default::default() };
182 ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mut map)?;
183 let length = fb.pitch as usize * fb.height as usize;
184 let address =
186 unsafe { libc::mmap(std::ptr::null_mut(), length, libc::PROT_READ, libc::MAP_SHARED, fd, map.offset as libc::off_t) };
187 if address == libc::MAP_FAILED {
188 return Err(io::Error::last_os_error());
189 }
190 let data = unsafe {
192 let data = std::slice::from_raw_parts(address.cast::<u8>(), length).to_vec();
193 libc::munmap(address, length);
194 data
195 };
196 Ok(data)
197 };
198 let data = read();
199 let mut close = drm_gem_close { handle: fb.handle, pad: 0 };
200 let _ = ioctl(fd, DRM_IOCTL_GEM_CLOSE, &mut close);
201
202 Ok(DrmScanout {
203 fb_id: fb.fb_id,
204 width: fb.width,
205 height: fb.height,
206 pitch: fb.pitch,
207 bits_per_pixel: fb.bpp,
208 depth: fb.depth,
209 data: data?,
210 })
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
218 use std::mem::{offset_of, size_of};
219
220 #[test]
221 fn the_structures_and_requests_are_those_of_the_kernel() {
222 assert_eq!(size_of::<drm_mode_crtc>(), 104);
223 assert_eq!(offset_of!(drm_mode_crtc, crtc_id), 12);
224 assert_eq!(offset_of!(drm_mode_crtc, fb_id), 16);
225 assert_eq!(offset_of!(drm_mode_crtc, mode), 36);
226 assert_eq!(size_of::<drm_mode_fb_cmd>(), 28);
227 assert_eq!(size_of::<drm_mode_map_dumb>(), 16);
228 assert_eq!(offset_of!(drm_mode_map_dumb, offset), 8);
229 assert_eq!(size_of::<drm_gem_close>(), 8);
230
231 assert_eq!(DRM_IOCTL_MODE_GETCRTC, 0xC068_64A1);
232 assert_eq!(DRM_IOCTL_MODE_GETFB, 0xC01C_64AD);
233 assert_eq!(DRM_IOCTL_MODE_MAP_DUMB, 0xC010_64B3);
234 assert_eq!(DRM_IOCTL_GEM_CLOSE, 0x4008_6409);
235 }
236
237 #[cfg(target_pointer_width = "64")]
238 #[test]
239 fn the_version_has_the_layout_of_the_kernel_on_64_bits() {
240 assert_eq!(size_of::<drm_version>(), 64);
241 assert_eq!(offset_of!(drm_version, name_len), 16);
242 assert_eq!(offset_of!(drm_version, desc), 56);
243 assert_eq!(DRM_IOCTL_VERSION, 0xC040_6400);
244 }
245
246 #[test]
247 fn a_pixel_of_xrgb8888_is_blue_green_red_in_memory() {
248 let scanout = DrmScanout {
249 fb_id: 1,
250 width: 2,
251 height: 1,
252 pitch: 8,
253 bits_per_pixel: 32,
254 depth: 24,
255 data: vec![0x99, 0x66, 0x33, 0, 1, 2, 3, 0],
256 };
257 assert_eq!(scanout.pixel(0, 0), Some((0x33, 0x66, 0x99)));
258 assert_eq!(scanout.pixel(1, 0), Some((3, 2, 1)));
259 assert_eq!(scanout.pixel(2, 0), None);
260 }
261}