1 | //! Modesetting operations that the DRM subsystem exposes. |
2 | //! |
3 | //! # Summary |
4 | //! |
5 | //! The DRM subsystem provides Kernel Modesetting (KMS) functionality by |
6 | //! exposing the following resource types: |
7 | //! |
8 | //! * FrameBuffer - Specific to an individual process, these wrap around generic |
9 | //! GPU buffers so that they can be attached to a Plane. |
10 | //! |
11 | //! * Planes - Dedicated memory objects which contain a buffer that can then be |
12 | //! scanned out by a CRTC. There exist a few different types of planes depending |
13 | //! on the use case. |
14 | //! |
15 | //! * CRTC - Scanout engines that read pixel data from a Plane and sends it to |
16 | //! a Connector. Each CRTC has at least one Primary Plane. |
17 | //! |
18 | //! * Connector - Represents the physical output, such as a DisplayPort or |
19 | //! VGA connector. |
20 | //! |
21 | //! * Encoder - Encodes pixel data from a CRTC into something a Connector can |
22 | //! understand. |
23 | //! |
24 | //! Further details on each resource can be found in their respective modules. |
25 | //! |
26 | //! # Usage |
27 | //! |
28 | //! To begin using modesetting functionality, the [`Device`] trait |
29 | //! must be implemented on top of the basic [`super::Device`] trait. |
30 | |
31 | use drm_ffi as ffi; |
32 | use drm_fourcc::{DrmFourcc, DrmModifier, UnrecognizedFourcc}; |
33 | |
34 | use bytemuck::allocation::TransparentWrapperAlloc; |
35 | use rustix::io::Errno; |
36 | |
37 | pub mod atomic; |
38 | pub mod connector; |
39 | pub mod crtc; |
40 | pub mod dumbbuffer; |
41 | pub mod encoder; |
42 | pub mod framebuffer; |
43 | pub mod plane; |
44 | pub mod syncobj; |
45 | |
46 | pub mod property; |
47 | |
48 | use self::dumbbuffer::*; |
49 | use crate::buffer; |
50 | |
51 | use super::util::*; |
52 | |
53 | use std::collections::HashMap; |
54 | use std::convert::TryFrom; |
55 | use std::error; |
56 | use std::fmt; |
57 | use std::io; |
58 | use std::iter::Zip; |
59 | use std::mem; |
60 | use std::ops::RangeBounds; |
61 | use std::os::unix::io::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}; |
62 | use std::time::Duration; |
63 | |
64 | use core::num::NonZeroU32; |
65 | |
66 | /// Raw handle for a drm resource |
67 | pub type RawResourceHandle = NonZeroU32; |
68 | |
69 | /// Id of a Lease |
70 | pub type LeaseId = NonZeroU32; |
71 | |
72 | /// Handle for a drm resource |
73 | pub trait ResourceHandle: |
74 | From<RawResourceHandle> + Into<RawResourceHandle> + Into<u32> + Copy + Sized |
75 | { |
76 | /// Associated encoded object type |
77 | const FFI_TYPE: u32; |
78 | } |
79 | |
80 | /// Convert from a raw drm object value to a typed Handle |
81 | /// |
82 | /// Note: This does no verification on the validity of the original value |
83 | pub fn from_u32<T: From<RawResourceHandle>>(raw: u32) -> Option<T> { |
84 | RawResourceHandle::new(raw).map(T::from) |
85 | } |
86 | |
87 | /// Error from [`Device::get_planar_framebuffer`] |
88 | #[derive (Debug)] |
89 | pub enum GetPlanarFramebufferError { |
90 | /// IO error |
91 | Io(io::Error), |
92 | /// Unrecognized fourcc format |
93 | UnrecognizedFourcc(drm_fourcc::UnrecognizedFourcc), |
94 | } |
95 | |
96 | impl fmt::Display for GetPlanarFramebufferError { |
97 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
98 | match self { |
99 | Self::Io(err: &Error) => write!(f, " {}" , err), |
100 | Self::UnrecognizedFourcc(err: &UnrecognizedFourcc) => write!(f, " {}" , err), |
101 | } |
102 | } |
103 | } |
104 | |
105 | impl error::Error for GetPlanarFramebufferError { |
106 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { |
107 | match self { |
108 | Self::Io(err: &Error) => Some(err), |
109 | Self::UnrecognizedFourcc(err: &UnrecognizedFourcc) => Some(err), |
110 | } |
111 | } |
112 | } |
113 | |
114 | impl From<io::Error> for GetPlanarFramebufferError { |
115 | fn from(err: io::Error) -> Self { |
116 | Self::Io(err) |
117 | } |
118 | } |
119 | |
120 | impl From<UnrecognizedFourcc> for GetPlanarFramebufferError { |
121 | fn from(err: UnrecognizedFourcc) -> Self { |
122 | Self::UnrecognizedFourcc(err) |
123 | } |
124 | } |
125 | |
126 | /// This trait should be implemented by any object that acts as a DRM device and |
127 | /// provides modesetting functionality. |
128 | /// |
129 | /// Like the parent [`super::Device`] trait, this crate does not |
130 | /// provide a concrete object for this trait. |
131 | /// |
132 | /// # Example |
133 | /// ```ignore |
134 | /// use drm::control::Device as ControlDevice; |
135 | /// |
136 | /// /// Assuming the [`Card`] wrapper already implements [`drm::Device`] |
137 | /// impl ControlDevice for Card {} |
138 | /// ``` |
139 | pub trait Device: super::Device { |
140 | /// Gets the set of resource handles that this device currently controls |
141 | fn resource_handles(&self) -> io::Result<ResourceHandles> { |
142 | let mut fbs = Vec::new(); |
143 | let mut crtcs = Vec::new(); |
144 | let mut connectors = Vec::new(); |
145 | let mut encoders = Vec::new(); |
146 | |
147 | let ffi_res = ffi::mode::get_resources( |
148 | self.as_fd(), |
149 | Some(&mut fbs), |
150 | Some(&mut crtcs), |
151 | Some(&mut connectors), |
152 | Some(&mut encoders), |
153 | )?; |
154 | |
155 | let res = unsafe { |
156 | ResourceHandles { |
157 | fbs: transmute_vec_from_u32(fbs), |
158 | crtcs: transmute_vec_from_u32(crtcs), |
159 | connectors: transmute_vec_from_u32(connectors), |
160 | encoders: transmute_vec_from_u32(encoders), |
161 | width: (ffi_res.min_width, ffi_res.max_width), |
162 | height: (ffi_res.min_height, ffi_res.max_height), |
163 | } |
164 | }; |
165 | |
166 | Ok(res) |
167 | } |
168 | |
169 | /// Gets the set of plane handles that this device currently has |
170 | fn plane_handles(&self) -> io::Result<Vec<plane::Handle>> { |
171 | let mut planes = Vec::new(); |
172 | let _ = ffi::mode::get_plane_resources(self.as_fd(), Some(&mut planes))?; |
173 | Ok(unsafe { transmute_vec_from_u32(planes) }) |
174 | } |
175 | |
176 | /// Returns information about a specific connector |
177 | /// |
178 | /// ## Force-probing |
179 | /// |
180 | /// If `force_probe` is set to `true` and the DRM client is the current DRM master, |
181 | /// the kernel will perform a forced probe on the connector to refresh the connector status, modes and EDID. |
182 | /// A forced-probe can be slow, might cause flickering and the ioctl will block. |
183 | /// |
184 | /// - User needs to force-probe connectors to ensure their metadata is up-to-date at startup and after receiving a hot-plug event. |
185 | /// - User may perform a forced-probe when the user explicitly requests it. |
186 | /// - User shouldn’t perform a forced-probe in other situations. |
187 | fn get_connector( |
188 | &self, |
189 | handle: connector::Handle, |
190 | force_probe: bool, |
191 | ) -> io::Result<connector::Info> { |
192 | // Maximum number of encoders is 3 due to kernel restrictions |
193 | let mut encoders = Vec::new(); |
194 | let mut modes = Vec::new(); |
195 | |
196 | let ffi_info = ffi::mode::get_connector( |
197 | self.as_fd(), |
198 | handle.into(), |
199 | None, |
200 | None, |
201 | Some(&mut modes), |
202 | Some(&mut encoders), |
203 | force_probe, |
204 | )?; |
205 | |
206 | let connector = connector::Info { |
207 | handle, |
208 | interface: connector::Interface::from(ffi_info.connector_type), |
209 | interface_id: ffi_info.connector_type_id, |
210 | connection: connector::State::from(ffi_info.connection), |
211 | size: match (ffi_info.mm_width, ffi_info.mm_height) { |
212 | (0, 0) => None, |
213 | (x, y) => Some((x, y)), |
214 | }, |
215 | modes: Mode::wrap_vec(modes), |
216 | encoders: unsafe { transmute_vec_from_u32(encoders) }, |
217 | curr_enc: unsafe { mem::transmute(ffi_info.encoder_id) }, |
218 | subpixel: connector::SubPixel::from_raw(ffi_info.subpixel), |
219 | }; |
220 | |
221 | Ok(connector) |
222 | } |
223 | |
224 | /// Returns information about a specific encoder |
225 | fn get_encoder(&self, handle: encoder::Handle) -> io::Result<encoder::Info> { |
226 | let info = ffi::mode::get_encoder(self.as_fd(), handle.into())?; |
227 | |
228 | let enc = encoder::Info { |
229 | handle, |
230 | enc_type: encoder::Kind::from(info.encoder_type), |
231 | crtc: from_u32(info.crtc_id), |
232 | pos_crtcs: info.possible_crtcs, |
233 | pos_clones: info.possible_clones, |
234 | }; |
235 | |
236 | Ok(enc) |
237 | } |
238 | |
239 | /// Returns information about a specific CRTC |
240 | fn get_crtc(&self, handle: crtc::Handle) -> io::Result<crtc::Info> { |
241 | let info = ffi::mode::get_crtc(self.as_fd(), handle.into())?; |
242 | |
243 | let crtc = crtc::Info { |
244 | handle, |
245 | position: (info.x, info.y), |
246 | mode: match info.mode_valid { |
247 | 0 => None, |
248 | _ => Some(Mode::from(info.mode)), |
249 | }, |
250 | fb: from_u32(info.fb_id), |
251 | gamma_length: info.gamma_size, |
252 | }; |
253 | |
254 | Ok(crtc) |
255 | } |
256 | |
257 | /// Set CRTC state |
258 | fn set_crtc( |
259 | &self, |
260 | handle: crtc::Handle, |
261 | framebuffer: Option<framebuffer::Handle>, |
262 | pos: (u32, u32), |
263 | conns: &[connector::Handle], |
264 | mode: Option<Mode>, |
265 | ) -> io::Result<()> { |
266 | let _info = ffi::mode::set_crtc( |
267 | self.as_fd(), |
268 | handle.into(), |
269 | framebuffer.map(Into::into).unwrap_or(0), |
270 | pos.0, |
271 | pos.1, |
272 | unsafe { &*(conns as *const _ as *const [u32]) }, |
273 | mode.map(|m| m.into()), |
274 | )?; |
275 | |
276 | Ok(()) |
277 | } |
278 | |
279 | /// Returns information about a specific framebuffer |
280 | fn get_framebuffer(&self, handle: framebuffer::Handle) -> io::Result<framebuffer::Info> { |
281 | let info = ffi::mode::get_framebuffer(self.as_fd(), handle.into())?; |
282 | |
283 | let fb = framebuffer::Info { |
284 | handle, |
285 | size: (info.width, info.height), |
286 | pitch: info.pitch, |
287 | bpp: info.bpp, |
288 | depth: info.depth, |
289 | buffer: from_u32(info.handle), |
290 | }; |
291 | |
292 | Ok(fb) |
293 | } |
294 | |
295 | /// Returns information about a specific framebuffer (with modifiers) |
296 | fn get_planar_framebuffer( |
297 | &self, |
298 | handle: framebuffer::Handle, |
299 | ) -> Result<framebuffer::PlanarInfo, GetPlanarFramebufferError> { |
300 | let info = ffi::mode::get_framebuffer2(self.as_fd(), handle.into())?; |
301 | |
302 | let pixel_format = DrmFourcc::try_from(info.pixel_format)?; |
303 | |
304 | let flags = FbCmd2Flags::from_bits_truncate(info.flags); |
305 | let modifier = flags |
306 | .contains(FbCmd2Flags::MODIFIERS) |
307 | .then(|| DrmModifier::from(info.modifier[0])); |
308 | |
309 | let fb = framebuffer::PlanarInfo { |
310 | handle, |
311 | size: (info.width, info.height), |
312 | pixel_format, |
313 | flags, |
314 | buffers: bytemuck::cast(info.handles), |
315 | pitches: info.pitches, |
316 | offsets: info.offsets, |
317 | modifier, |
318 | }; |
319 | |
320 | Ok(fb) |
321 | } |
322 | |
323 | /// Add a new framebuffer |
324 | fn add_framebuffer<B>( |
325 | &self, |
326 | buffer: &B, |
327 | depth: u32, |
328 | bpp: u32, |
329 | ) -> io::Result<framebuffer::Handle> |
330 | where |
331 | B: buffer::Buffer + ?Sized, |
332 | { |
333 | let (w, h) = buffer.size(); |
334 | let info = ffi::mode::add_fb( |
335 | self.as_fd(), |
336 | w, |
337 | h, |
338 | buffer.pitch(), |
339 | bpp, |
340 | depth, |
341 | buffer.handle().into(), |
342 | )?; |
343 | |
344 | Ok(from_u32(info.fb_id).unwrap()) |
345 | } |
346 | |
347 | /// Add framebuffer (with modifiers) |
348 | fn add_planar_framebuffer<B>( |
349 | &self, |
350 | planar_buffer: &B, |
351 | flags: FbCmd2Flags, |
352 | ) -> io::Result<framebuffer::Handle> |
353 | where |
354 | B: buffer::PlanarBuffer + ?Sized, |
355 | { |
356 | let modifier = planar_buffer |
357 | .modifier() |
358 | .filter(|modifier| !matches!(modifier, DrmModifier::Invalid)); |
359 | let has_modifier = flags.contains(FbCmd2Flags::MODIFIERS); |
360 | assert!((has_modifier && modifier.is_some()) || (!has_modifier && modifier.is_none())); |
361 | let modifier = if let Some(modifier) = modifier { |
362 | u64::from(modifier) |
363 | } else { |
364 | 0 |
365 | }; |
366 | |
367 | let (w, h) = planar_buffer.size(); |
368 | let opt_handles = planar_buffer.handles(); |
369 | |
370 | let handles = bytemuck::cast(opt_handles); |
371 | let mods = [ |
372 | opt_handles[0].map_or(0, |_| modifier), |
373 | opt_handles[1].map_or(0, |_| modifier), |
374 | opt_handles[2].map_or(0, |_| modifier), |
375 | opt_handles[3].map_or(0, |_| modifier), |
376 | ]; |
377 | |
378 | let info = ffi::mode::add_fb2( |
379 | self.as_fd(), |
380 | w, |
381 | h, |
382 | planar_buffer.format() as u32, |
383 | &handles, |
384 | &planar_buffer.pitches(), |
385 | &planar_buffer.offsets(), |
386 | &mods, |
387 | flags.bits(), |
388 | )?; |
389 | |
390 | Ok(from_u32(info.fb_id).unwrap()) |
391 | } |
392 | |
393 | /// Mark parts of a framebuffer dirty |
394 | fn dirty_framebuffer(&self, handle: framebuffer::Handle, clips: &[ClipRect]) -> io::Result<()> { |
395 | ffi::mode::dirty_fb(self.as_fd(), handle.into(), unsafe { |
396 | // SAFETY: ClipRect is repr(transparent) for drm_clip_rect |
397 | core::slice::from_raw_parts(clips.as_ptr() as *const ffi::drm_clip_rect, clips.len()) |
398 | })?; |
399 | Ok(()) |
400 | } |
401 | |
402 | /// Destroy a framebuffer |
403 | fn destroy_framebuffer(&self, handle: framebuffer::Handle) -> io::Result<()> { |
404 | ffi::mode::rm_fb(self.as_fd(), handle.into()) |
405 | } |
406 | |
407 | /// Returns information about a specific plane |
408 | fn get_plane(&self, handle: plane::Handle) -> io::Result<plane::Info> { |
409 | let mut formats = Vec::new(); |
410 | |
411 | let info = ffi::mode::get_plane(self.as_fd(), handle.into(), Some(&mut formats))?; |
412 | |
413 | let plane = plane::Info { |
414 | handle, |
415 | crtc: from_u32(info.crtc_id), |
416 | fb: from_u32(info.fb_id), |
417 | pos_crtcs: info.possible_crtcs, |
418 | formats: unsafe { transmute_vec_from_u32(formats) }, |
419 | }; |
420 | |
421 | Ok(plane) |
422 | } |
423 | |
424 | /// Set plane state. |
425 | /// |
426 | /// Providing no framebuffer clears the plane. |
427 | fn set_plane( |
428 | &self, |
429 | handle: plane::Handle, |
430 | crtc: crtc::Handle, |
431 | framebuffer: Option<framebuffer::Handle>, |
432 | flags: u32, |
433 | crtc_rect: (i32, i32, u32, u32), |
434 | src_rect: (u32, u32, u32, u32), |
435 | ) -> io::Result<()> { |
436 | let _info = ffi::mode::set_plane( |
437 | self.as_fd(), |
438 | handle.into(), |
439 | crtc.into(), |
440 | framebuffer.map(Into::into).unwrap_or(0), |
441 | flags, |
442 | crtc_rect.0, |
443 | crtc_rect.1, |
444 | crtc_rect.2, |
445 | crtc_rect.3, |
446 | src_rect.0, |
447 | src_rect.1, |
448 | src_rect.2, |
449 | src_rect.3, |
450 | )?; |
451 | |
452 | Ok(()) |
453 | } |
454 | |
455 | /// Returns information about a specific property. |
456 | fn get_property(&self, handle: property::Handle) -> io::Result<property::Info> { |
457 | let mut values = Vec::new(); |
458 | let mut enums = Vec::new(); |
459 | |
460 | let info = ffi::mode::get_property( |
461 | self.as_fd(), |
462 | handle.into(), |
463 | Some(&mut values), |
464 | Some(&mut enums), |
465 | )?; |
466 | |
467 | let flags = ModePropFlags::from_bits_truncate(info.flags); |
468 | |
469 | let val_type = { |
470 | use self::property::ValueType; |
471 | |
472 | if flags.contains(ModePropFlags::RANGE) { |
473 | let min = values[0]; |
474 | let max = values[1]; |
475 | |
476 | match (min, max) { |
477 | (0, 1) => ValueType::Boolean, |
478 | (min, max) => ValueType::UnsignedRange(min, max), |
479 | } |
480 | } else if flags.contains(ModePropFlags::SIGNED_RANGE) { |
481 | let min = values[0]; |
482 | let max = values[1]; |
483 | |
484 | ValueType::SignedRange(min as i64, max as i64) |
485 | } else if flags.contains(ModePropFlags::ENUM) { |
486 | let enum_values = self::property::EnumValues { |
487 | values, |
488 | enums: property::EnumValue::wrap_vec(enums), |
489 | }; |
490 | |
491 | ValueType::Enum(enum_values) |
492 | } else if flags.contains(ModePropFlags::BLOB) { |
493 | ValueType::Blob |
494 | } else if flags.contains(ModePropFlags::BITMASK) { |
495 | ValueType::Bitmask |
496 | } else if flags.contains(ModePropFlags::OBJECT) { |
497 | match values[0] as u32 { |
498 | ffi::DRM_MODE_OBJECT_CRTC => ValueType::CRTC, |
499 | ffi::DRM_MODE_OBJECT_CONNECTOR => ValueType::Connector, |
500 | ffi::DRM_MODE_OBJECT_ENCODER => ValueType::Encoder, |
501 | ffi::DRM_MODE_OBJECT_FB => ValueType::Framebuffer, |
502 | ffi::DRM_MODE_OBJECT_PLANE => ValueType::Plane, |
503 | ffi::DRM_MODE_OBJECT_PROPERTY => ValueType::Property, |
504 | ffi::DRM_MODE_OBJECT_BLOB => ValueType::Blob, |
505 | ffi::DRM_MODE_OBJECT_ANY => ValueType::Object, |
506 | _ => ValueType::Unknown, |
507 | } |
508 | } else { |
509 | ValueType::Unknown |
510 | } |
511 | }; |
512 | |
513 | let property = property::Info { |
514 | handle, |
515 | val_type, |
516 | mutable: !flags.contains(ModePropFlags::IMMUTABLE), |
517 | atomic: flags.contains(ModePropFlags::ATOMIC), |
518 | info, |
519 | }; |
520 | |
521 | Ok(property) |
522 | } |
523 | |
524 | /// Sets a property for a specific resource. |
525 | fn set_property<T: ResourceHandle>( |
526 | &self, |
527 | handle: T, |
528 | prop: property::Handle, |
529 | value: property::RawValue, |
530 | ) -> io::Result<()> { |
531 | ffi::mode::set_property(self.as_fd(), prop.into(), handle.into(), T::FFI_TYPE, value)?; |
532 | |
533 | Ok(()) |
534 | } |
535 | |
536 | /// Create a property blob value from a given data blob |
537 | fn create_property_blob<T>(&self, data: &T) -> io::Result<property::Value<'static>> { |
538 | let data = unsafe { |
539 | std::slice::from_raw_parts_mut(data as *const _ as *mut u8, mem::size_of::<T>()) |
540 | }; |
541 | let blob = ffi::mode::create_property_blob(self.as_fd(), data)?; |
542 | |
543 | Ok(property::Value::Blob(blob.blob_id.into())) |
544 | } |
545 | |
546 | /// Get a property blob's data |
547 | fn get_property_blob(&self, blob: u64) -> io::Result<Vec<u8>> { |
548 | let mut data = Vec::new(); |
549 | let _ = ffi::mode::get_property_blob(self.as_fd(), blob as u32, Some(&mut data))?; |
550 | Ok(data) |
551 | } |
552 | |
553 | /// Destroy a given property blob value |
554 | fn destroy_property_blob(&self, blob: u64) -> io::Result<()> { |
555 | ffi::mode::destroy_property_blob(self.as_fd(), blob as u32)?; |
556 | |
557 | Ok(()) |
558 | } |
559 | |
560 | /// Returns the set of [`Mode`]s that a particular connector supports. |
561 | fn get_modes(&self, handle: connector::Handle) -> io::Result<Vec<Mode>> { |
562 | let mut modes = Vec::new(); |
563 | |
564 | let _ffi_info = ffi::mode::get_connector( |
565 | self.as_fd(), |
566 | handle.into(), |
567 | None, |
568 | None, |
569 | Some(&mut modes), |
570 | None, |
571 | false, |
572 | )?; |
573 | |
574 | Ok(Mode::wrap_vec(modes)) |
575 | } |
576 | |
577 | /// Gets a list of property handles and values for this resource. |
578 | fn get_properties<T: ResourceHandle>(&self, handle: T) -> io::Result<PropertyValueSet> { |
579 | let mut prop_ids = Vec::new(); |
580 | let mut prop_vals = Vec::new(); |
581 | |
582 | ffi::mode::get_properties( |
583 | self.as_fd(), |
584 | handle.into(), |
585 | T::FFI_TYPE, |
586 | Some(&mut prop_ids), |
587 | Some(&mut prop_vals), |
588 | )?; |
589 | |
590 | let prop_val_set = PropertyValueSet { |
591 | prop_ids: unsafe { transmute_vec_from_u32(prop_ids) }, |
592 | prop_vals, |
593 | }; |
594 | |
595 | Ok(prop_val_set) |
596 | } |
597 | |
598 | /// Receive the currently set gamma ramp of a crtc |
599 | fn get_gamma( |
600 | &self, |
601 | crtc: crtc::Handle, |
602 | red: &mut [u16], |
603 | green: &mut [u16], |
604 | blue: &mut [u16], |
605 | ) -> io::Result<()> { |
606 | let crtc_info = self.get_crtc(crtc)?; |
607 | if crtc_info.gamma_length as usize > red.len() |
608 | || crtc_info.gamma_length as usize > green.len() |
609 | || crtc_info.gamma_length as usize > blue.len() |
610 | { |
611 | return Err(Errno::INVAL.into()); |
612 | } |
613 | |
614 | ffi::mode::get_gamma( |
615 | self.as_fd(), |
616 | crtc.into(), |
617 | crtc_info.gamma_length as usize, |
618 | red, |
619 | green, |
620 | blue, |
621 | )?; |
622 | |
623 | Ok(()) |
624 | } |
625 | |
626 | /// Set a gamma ramp for the given crtc |
627 | fn set_gamma( |
628 | &self, |
629 | crtc: crtc::Handle, |
630 | red: &[u16], |
631 | green: &[u16], |
632 | blue: &[u16], |
633 | ) -> io::Result<()> { |
634 | let crtc_info = self.get_crtc(crtc)?; |
635 | if crtc_info.gamma_length as usize > red.len() |
636 | || crtc_info.gamma_length as usize > green.len() |
637 | || crtc_info.gamma_length as usize > blue.len() |
638 | { |
639 | return Err(Errno::INVAL.into()); |
640 | } |
641 | |
642 | ffi::mode::set_gamma( |
643 | self.as_fd(), |
644 | crtc.into(), |
645 | crtc_info.gamma_length as usize, |
646 | red, |
647 | green, |
648 | blue, |
649 | )?; |
650 | |
651 | Ok(()) |
652 | } |
653 | |
654 | /// Open a GEM buffer handle by name |
655 | fn open_buffer(&self, name: buffer::Name) -> io::Result<buffer::Handle> { |
656 | let info = drm_ffi::gem::open(self.as_fd(), name.into())?; |
657 | Ok(from_u32(info.handle).unwrap()) |
658 | } |
659 | |
660 | /// Close a GEM buffer handle |
661 | fn close_buffer(&self, handle: buffer::Handle) -> io::Result<()> { |
662 | let _info = drm_ffi::gem::close(self.as_fd(), handle.into())?; |
663 | Ok(()) |
664 | } |
665 | |
666 | /// Create a new dumb buffer with a given size and pixel format |
667 | fn create_dumb_buffer( |
668 | &self, |
669 | size: (u32, u32), |
670 | format: buffer::DrmFourcc, |
671 | bpp: u32, |
672 | ) -> io::Result<DumbBuffer> { |
673 | let info = drm_ffi::mode::dumbbuffer::create(self.as_fd(), size.0, size.1, bpp, 0)?; |
674 | |
675 | let dumb = DumbBuffer { |
676 | size: (info.width, info.height), |
677 | length: info.size as usize, |
678 | format, |
679 | pitch: info.pitch, |
680 | handle: from_u32(info.handle).unwrap(), |
681 | }; |
682 | |
683 | Ok(dumb) |
684 | } |
685 | /// Map the buffer for access |
686 | fn map_dumb_buffer<'a>(&self, buffer: &'a mut DumbBuffer) -> io::Result<DumbMapping<'a>> { |
687 | let info = drm_ffi::mode::dumbbuffer::map(self.as_fd(), buffer.handle.into(), 0, 0)?; |
688 | |
689 | let map = { |
690 | use rustix::mm; |
691 | let prot = mm::ProtFlags::READ | mm::ProtFlags::WRITE; |
692 | let flags = mm::MapFlags::SHARED; |
693 | let fd = self.as_fd(); |
694 | let offset = info.offset as _; |
695 | unsafe { mm::mmap(std::ptr::null_mut(), buffer.length, prot, flags, fd, offset)? } |
696 | }; |
697 | |
698 | let mapping = DumbMapping { |
699 | _phantom: std::marker::PhantomData, |
700 | map: unsafe { std::slice::from_raw_parts_mut(map as *mut _, buffer.length) }, |
701 | }; |
702 | |
703 | Ok(mapping) |
704 | } |
705 | |
706 | /// Free the memory resources of a dumb buffer |
707 | fn destroy_dumb_buffer(&self, buffer: DumbBuffer) -> io::Result<()> { |
708 | let _info = drm_ffi::mode::dumbbuffer::destroy(self.as_fd(), buffer.handle.into())?; |
709 | |
710 | Ok(()) |
711 | } |
712 | |
713 | /// Sets a hardware-cursor on the given crtc with the image of a given buffer |
714 | /// |
715 | /// A buffer argument of [`None`] will clear the cursor. |
716 | #[deprecated (note = "Usage of deprecated ioctl set_cursor: use a cursor plane instead" )] |
717 | #[allow (deprecated)] |
718 | fn set_cursor<B>(&self, crtc: crtc::Handle, buffer: Option<&B>) -> io::Result<()> |
719 | where |
720 | B: buffer::Buffer + ?Sized, |
721 | { |
722 | let (id, w, h) = buffer |
723 | .map(|buf| { |
724 | let (w, h) = buf.size(); |
725 | (buf.handle().into(), w, h) |
726 | }) |
727 | .unwrap_or((0, 0, 0)); |
728 | drm_ffi::mode::set_cursor(self.as_fd(), crtc.into(), id, w, h)?; |
729 | |
730 | Ok(()) |
731 | } |
732 | |
733 | /// Sets a hardware-cursor on the given crtc with the image of a given buffer |
734 | /// and a hotspot marking the click point of the cursor. |
735 | /// |
736 | /// A buffer argument of [`None`] will clear the cursor. |
737 | #[deprecated (note = "Usage of deprecated ioctl set_cursor2: use a cursor plane instead" )] |
738 | #[allow (deprecated)] |
739 | fn set_cursor2<B>( |
740 | &self, |
741 | crtc: crtc::Handle, |
742 | buffer: Option<&B>, |
743 | hotspot: (i32, i32), |
744 | ) -> io::Result<()> |
745 | where |
746 | B: buffer::Buffer + ?Sized, |
747 | { |
748 | let (id, w, h) = buffer |
749 | .map(|buf| { |
750 | let (w, h) = buf.size(); |
751 | (buf.handle().into(), w, h) |
752 | }) |
753 | .unwrap_or((0, 0, 0)); |
754 | drm_ffi::mode::set_cursor2(self.as_fd(), crtc.into(), id, w, h, hotspot.0, hotspot.1)?; |
755 | |
756 | Ok(()) |
757 | } |
758 | |
759 | /// Moves a set cursor on a given crtc |
760 | #[deprecated (note = "Usage of deprecated ioctl move_cursor: use a cursor plane instead" )] |
761 | #[allow (deprecated)] |
762 | fn move_cursor(&self, crtc: crtc::Handle, pos: (i32, i32)) -> io::Result<()> { |
763 | drm_ffi::mode::move_cursor(self.as_fd(), crtc.into(), pos.0, pos.1)?; |
764 | |
765 | Ok(()) |
766 | } |
767 | |
768 | /// Request an atomic commit with given flags and property-value pair for a list of objects. |
769 | fn atomic_commit( |
770 | &self, |
771 | flags: AtomicCommitFlags, |
772 | mut req: atomic::AtomicModeReq, |
773 | ) -> io::Result<()> { |
774 | drm_ffi::mode::atomic_commit( |
775 | self.as_fd(), |
776 | flags.bits(), |
777 | unsafe { &mut *(&mut *req.objects as *mut _ as *mut [u32]) }, |
778 | &mut req.count_props_per_object, |
779 | unsafe { &mut *(&mut *req.props as *mut _ as *mut [u32]) }, |
780 | &mut req.values, |
781 | ) |
782 | } |
783 | |
784 | /// Convert a prime file descriptor to a GEM buffer handle |
785 | fn prime_fd_to_buffer(&self, fd: BorrowedFd<'_>) -> io::Result<buffer::Handle> { |
786 | let info = ffi::gem::fd_to_handle(self.as_fd(), fd)?; |
787 | Ok(from_u32(info.handle).unwrap()) |
788 | } |
789 | |
790 | /// Convert a GEM buffer handle to a prime file descriptor |
791 | fn buffer_to_prime_fd(&self, handle: buffer::Handle, flags: u32) -> io::Result<OwnedFd> { |
792 | let info = ffi::gem::handle_to_fd(self.as_fd(), handle.into(), flags)?; |
793 | Ok(unsafe { OwnedFd::from_raw_fd(info.fd) }) |
794 | } |
795 | |
796 | /// Queue a page flip on the given crtc |
797 | fn page_flip( |
798 | &self, |
799 | handle: crtc::Handle, |
800 | framebuffer: framebuffer::Handle, |
801 | flags: PageFlipFlags, |
802 | target_sequence: Option<PageFlipTarget>, |
803 | ) -> io::Result<()> { |
804 | let mut flags = flags.bits(); |
805 | |
806 | let sequence = match target_sequence { |
807 | Some(PageFlipTarget::Absolute(n)) => { |
808 | flags |= ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET_ABSOLUTE; |
809 | n |
810 | } |
811 | Some(PageFlipTarget::Relative(n)) => { |
812 | flags |= ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET_RELATIVE; |
813 | n |
814 | } |
815 | None => 0, |
816 | }; |
817 | |
818 | ffi::mode::page_flip( |
819 | self.as_fd(), |
820 | handle.into(), |
821 | framebuffer.into(), |
822 | flags, |
823 | sequence, |
824 | )?; |
825 | |
826 | Ok(()) |
827 | } |
828 | |
829 | /// Creates a syncobj. |
830 | fn create_syncobj(&self, signalled: bool) -> io::Result<syncobj::Handle> { |
831 | let info = ffi::syncobj::create(self.as_fd(), signalled)?; |
832 | Ok(from_u32(info.handle).unwrap()) |
833 | } |
834 | |
835 | /// Destroys a syncobj. |
836 | fn destroy_syncobj(&self, handle: syncobj::Handle) -> io::Result<()> { |
837 | ffi::syncobj::destroy(self.as_fd(), handle.into())?; |
838 | Ok(()) |
839 | } |
840 | |
841 | /// Exports a syncobj as an inter-process file descriptor or as a poll()-able sync file. |
842 | fn syncobj_to_fd( |
843 | &self, |
844 | handle: syncobj::Handle, |
845 | export_sync_file: bool, |
846 | ) -> io::Result<OwnedFd> { |
847 | let info = ffi::syncobj::handle_to_fd(self.as_fd(), handle.into(), export_sync_file)?; |
848 | Ok(unsafe { OwnedFd::from_raw_fd(info.fd) }) |
849 | } |
850 | |
851 | /// Imports a file descriptor exported by [`Self::syncobj_to_fd`] back into a process-local handle. |
852 | fn fd_to_syncobj( |
853 | &self, |
854 | fd: BorrowedFd<'_>, |
855 | import_sync_file: bool, |
856 | ) -> io::Result<syncobj::Handle> { |
857 | let info = ffi::syncobj::fd_to_handle(self.as_fd(), fd, import_sync_file)?; |
858 | Ok(from_u32(info.handle).unwrap()) |
859 | } |
860 | |
861 | /// Waits for one or more syncobjs to become signalled. |
862 | fn syncobj_wait( |
863 | &self, |
864 | handles: &[syncobj::Handle], |
865 | timeout_nsec: i64, |
866 | wait_all: bool, |
867 | wait_for_submit: bool, |
868 | ) -> io::Result<u32> { |
869 | let info = ffi::syncobj::wait( |
870 | self.as_fd(), |
871 | bytemuck::cast_slice(handles), |
872 | timeout_nsec, |
873 | wait_all, |
874 | wait_for_submit, |
875 | )?; |
876 | Ok(info.first_signaled) |
877 | } |
878 | |
879 | /// Resets (un-signals) one or more syncobjs. |
880 | fn syncobj_reset(&self, handles: &[syncobj::Handle]) -> io::Result<()> { |
881 | ffi::syncobj::reset(self.as_fd(), bytemuck::cast_slice(handles))?; |
882 | Ok(()) |
883 | } |
884 | |
885 | /// Signals one or more syncobjs. |
886 | fn syncobj_signal(&self, handles: &[syncobj::Handle]) -> io::Result<()> { |
887 | ffi::syncobj::signal(self.as_fd(), bytemuck::cast_slice(handles))?; |
888 | Ok(()) |
889 | } |
890 | |
891 | /// Waits for one or more specific timeline syncobj points. |
892 | fn syncobj_timeline_wait( |
893 | &self, |
894 | handles: &[syncobj::Handle], |
895 | points: &[u64], |
896 | timeout_nsec: i64, |
897 | wait_all: bool, |
898 | wait_for_submit: bool, |
899 | wait_available: bool, |
900 | ) -> io::Result<u32> { |
901 | let info = ffi::syncobj::timeline_wait( |
902 | self.as_fd(), |
903 | bytemuck::cast_slice(handles), |
904 | points, |
905 | timeout_nsec, |
906 | wait_all, |
907 | wait_for_submit, |
908 | wait_available, |
909 | )?; |
910 | Ok(info.first_signaled) |
911 | } |
912 | |
913 | /// Queries for state of one or more timeline syncobjs. |
914 | fn syncobj_timeline_query( |
915 | &self, |
916 | handles: &[syncobj::Handle], |
917 | points: &mut [u64], |
918 | last_submitted: bool, |
919 | ) -> io::Result<()> { |
920 | ffi::syncobj::query( |
921 | self.as_fd(), |
922 | bytemuck::cast_slice(handles), |
923 | points, |
924 | last_submitted, |
925 | )?; |
926 | Ok(()) |
927 | } |
928 | |
929 | /// Transfers one timeline syncobj point to another. |
930 | fn syncobj_timeline_transfer( |
931 | &self, |
932 | src_handle: syncobj::Handle, |
933 | dst_handle: syncobj::Handle, |
934 | src_point: u64, |
935 | dst_point: u64, |
936 | ) -> io::Result<()> { |
937 | ffi::syncobj::transfer( |
938 | self.as_fd(), |
939 | src_handle.into(), |
940 | dst_handle.into(), |
941 | src_point, |
942 | dst_point, |
943 | )?; |
944 | Ok(()) |
945 | } |
946 | |
947 | /// Signals one or more specific timeline syncobj points. |
948 | fn syncobj_timeline_signal( |
949 | &self, |
950 | handles: &[syncobj::Handle], |
951 | points: &[u64], |
952 | ) -> io::Result<()> { |
953 | ffi::syncobj::timeline_signal(self.as_fd(), bytemuck::cast_slice(handles), points)?; |
954 | Ok(()) |
955 | } |
956 | |
957 | /// Register an eventfd to be signalled by a syncobj. |
958 | fn syncobj_eventfd( |
959 | &self, |
960 | handle: syncobj::Handle, |
961 | point: u64, |
962 | eventfd: BorrowedFd<'_>, |
963 | wait_available: bool, |
964 | ) -> io::Result<()> { |
965 | ffi::syncobj::eventfd(self.as_fd(), handle.into(), point, eventfd, wait_available)?; |
966 | Ok(()) |
967 | } |
968 | |
969 | /// Create a drm lease |
970 | fn create_lease( |
971 | &self, |
972 | objects: &[RawResourceHandle], |
973 | flags: u32, |
974 | ) -> io::Result<(LeaseId, OwnedFd)> { |
975 | let lease = ffi::mode::create_lease(self.as_fd(), bytemuck::cast_slice(objects), flags)?; |
976 | Ok(( |
977 | unsafe { NonZeroU32::new_unchecked(lease.lessee_id) }, |
978 | unsafe { OwnedFd::from_raw_fd(lease.fd as RawFd) }, |
979 | )) |
980 | } |
981 | |
982 | /// List active lessees |
983 | fn list_lessees(&self) -> io::Result<Vec<LeaseId>> { |
984 | let mut lessees = Vec::new(); |
985 | ffi::mode::list_lessees(self.as_fd(), Some(&mut lessees))?; |
986 | Ok(unsafe { transmute_vec_from_u32(lessees) }) |
987 | } |
988 | |
989 | /// Revoke a previously issued drm lease |
990 | fn revoke_lease(&self, lessee_id: LeaseId) -> io::Result<()> { |
991 | ffi::mode::revoke_lease(self.as_fd(), lessee_id.get()) |
992 | } |
993 | |
994 | /// Receive pending events |
995 | fn receive_events(&self) -> io::Result<Events> |
996 | where |
997 | Self: Sized, |
998 | { |
999 | let mut event_buf: [u8; 1024] = [0; 1024]; |
1000 | let amount = rustix::io::read(self.as_fd(), &mut event_buf)?; |
1001 | |
1002 | Ok(Events::with_event_buf(event_buf, amount)) |
1003 | } |
1004 | } |
1005 | |
1006 | /// List of leased resources |
1007 | pub struct LeaseResources { |
1008 | /// leased crtcs |
1009 | pub crtcs: Vec<crtc::Handle>, |
1010 | /// leased connectors |
1011 | pub connectors: Vec<connector::Handle>, |
1012 | /// leased planes |
1013 | pub planes: Vec<plane::Handle>, |
1014 | } |
1015 | |
1016 | /// Query lease resources |
1017 | pub fn get_lease<D: AsFd>(lease: D) -> io::Result<LeaseResources> { |
1018 | let mut crtcs = Vec::new(); |
1019 | let mut connectors = Vec::new(); |
1020 | let mut planes = Vec::new(); |
1021 | let mut objects = Vec::new(); |
1022 | |
1023 | ffi::mode::get_lease(lease.as_fd(), Some(&mut objects))?; |
1024 | |
1025 | let _ = ffi::mode::get_resources( |
1026 | lease.as_fd(), |
1027 | None, |
1028 | Some(&mut crtcs), |
1029 | Some(&mut connectors), |
1030 | None, |
1031 | )?; |
1032 | let _ = ffi::mode::get_plane_resources(lease.as_fd(), Some(&mut planes))?; |
1033 | |
1034 | unsafe { |
1035 | Ok(LeaseResources { |
1036 | crtcs: transmute_vec_from_u32::<crtc::Handle>( |
1037 | crtcs |
1038 | .into_iter() |
1039 | .filter(|handle| objects.contains(handle)) |
1040 | .collect(), |
1041 | ), |
1042 | connectors: transmute_vec_from_u32::<connector::Handle>( |
1043 | connectors |
1044 | .into_iter() |
1045 | .filter(|handle| objects.contains(handle)) |
1046 | .collect(), |
1047 | ), |
1048 | planes: transmute_vec_from_u32::<plane::Handle>( |
1049 | planes |
1050 | .into_iter() |
1051 | .filter(|handle| objects.contains(handle)) |
1052 | .collect(), |
1053 | ), |
1054 | }) |
1055 | } |
1056 | } |
1057 | |
1058 | bitflags::bitflags! { |
1059 | /// Flags to alter the behaviour of a page flip |
1060 | /// |
1061 | /// Limited to the values in [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_FLAGS`], |
1062 | /// minus [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET`] bits which are |
1063 | /// passed through [`PageFlipTarget`]. |
1064 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
1065 | pub struct PageFlipFlags : u32 { |
1066 | /// Request a vblank event on page flip |
1067 | const EVENT = ffi::drm_sys::DRM_MODE_PAGE_FLIP_EVENT; |
1068 | /// Request page flip as soon as possible, not waiting for vblank |
1069 | const ASYNC = ffi::drm_sys::DRM_MODE_PAGE_FLIP_ASYNC; |
1070 | } |
1071 | } |
1072 | |
1073 | /// Target to alter the sequence of page flips |
1074 | /// |
1075 | /// These represent the [`ffi::drm_sys::DRM_MODE_PAGE_FLIP_TARGET`] bits |
1076 | /// of [`PageFlipFlags`] wrapped in a regular `enum` due to their |
1077 | /// mutual-exclusiveness. |
1078 | #[derive (Debug, Clone, Copy, PartialEq, Eq, Hash)] |
1079 | pub enum PageFlipTarget { |
1080 | /// Absolute Vblank Sequence |
1081 | Absolute(u32), |
1082 | /// Relative Vblank Sequence (to the current, when calling) |
1083 | Relative(u32), |
1084 | } |
1085 | |
1086 | /// Iterator over [`Event`]s of a device. Create via [`Device::receive_events()`]. |
1087 | pub struct Events { |
1088 | event_buf: [u8; 1024], |
1089 | amount: usize, |
1090 | i: usize, |
1091 | } |
1092 | |
1093 | impl Events { |
1094 | /// Create [`Event`]s iterator from buffer read using something other than |
1095 | /// [`Device::receive_events()`]. |
1096 | pub fn with_event_buf(event_buf: [u8; 1024], amount: usize) -> Self { |
1097 | Events { |
1098 | event_buf, |
1099 | amount, |
1100 | i: 0, |
1101 | } |
1102 | } |
1103 | } |
1104 | |
1105 | /// An event from a device. |
1106 | pub enum Event { |
1107 | /// A vblank happened |
1108 | Vblank(VblankEvent), |
1109 | /// A page flip happened |
1110 | PageFlip(PageFlipEvent), |
1111 | /// Unknown event, raw data provided |
1112 | Unknown(Vec<u8>), |
1113 | } |
1114 | |
1115 | /// Vblank event |
1116 | pub struct VblankEvent { |
1117 | /// sequence of the frame |
1118 | pub frame: u32, |
1119 | /// time at which the vblank occurred |
1120 | pub time: Duration, |
1121 | /// crtc that did throw the event |
1122 | pub crtc: crtc::Handle, |
1123 | /// user data that was passed to wait_vblank |
1124 | pub user_data: usize, |
1125 | } |
1126 | |
1127 | /// Page Flip event |
1128 | pub struct PageFlipEvent { |
1129 | /// sequence of the frame |
1130 | pub frame: u32, |
1131 | /// duration between events |
1132 | pub duration: Duration, |
1133 | /// crtc that did throw the event |
1134 | pub crtc: crtc::Handle, |
1135 | } |
1136 | |
1137 | impl Iterator for Events { |
1138 | type Item = Event; |
1139 | |
1140 | fn next(&mut self) -> Option<Event> { |
1141 | if self.amount > 0 && self.i < self.amount { |
1142 | let event_ptr = unsafe { self.event_buf.as_ptr().add(self.i) as *const ffi::drm_event }; |
1143 | let event = unsafe { std::ptr::read_unaligned(event_ptr) }; |
1144 | self.i += event.length as usize; |
1145 | match event.type_ { |
1146 | ffi::DRM_EVENT_VBLANK => { |
1147 | let vblank_event = unsafe { |
1148 | std::ptr::read_unaligned(event_ptr as *const ffi::drm_event_vblank) |
1149 | }; |
1150 | Some(Event::Vblank(VblankEvent { |
1151 | frame: vblank_event.sequence, |
1152 | time: Duration::new( |
1153 | vblank_event.tv_sec as u64, |
1154 | vblank_event.tv_usec * 1000, |
1155 | ), |
1156 | #[allow (clippy::unnecessary_cast)] |
1157 | crtc: from_u32(vblank_event.crtc_id as u32).unwrap(), |
1158 | user_data: vblank_event.user_data as usize, |
1159 | })) |
1160 | } |
1161 | ffi::DRM_EVENT_FLIP_COMPLETE => { |
1162 | let vblank_event = unsafe { |
1163 | std::ptr::read_unaligned(event_ptr as *const ffi::drm_event_vblank) |
1164 | }; |
1165 | Some(Event::PageFlip(PageFlipEvent { |
1166 | frame: vblank_event.sequence, |
1167 | duration: Duration::new( |
1168 | vblank_event.tv_sec as u64, |
1169 | vblank_event.tv_usec * 1000, |
1170 | ), |
1171 | crtc: from_u32(if vblank_event.crtc_id != 0 { |
1172 | vblank_event.crtc_id |
1173 | } else { |
1174 | vblank_event.user_data as u32 |
1175 | }) |
1176 | .unwrap(), |
1177 | })) |
1178 | } |
1179 | _ => Some(Event::Unknown( |
1180 | self.event_buf[self.i - (event.length as usize)..self.i].to_vec(), |
1181 | )), |
1182 | } |
1183 | } else { |
1184 | None |
1185 | } |
1186 | } |
1187 | } |
1188 | |
1189 | /// The set of [`ResourceHandles`] that a |
1190 | /// [`Device`] exposes. Excluding Plane resources. |
1191 | #[derive (Debug, Clone, Hash, PartialEq, Eq)] |
1192 | pub struct ResourceHandles { |
1193 | /// Set of [`framebuffer::Handle`] |
1194 | pub fbs: Vec<framebuffer::Handle>, |
1195 | /// Set of [`crtc::Handle`] |
1196 | pub crtcs: Vec<crtc::Handle>, |
1197 | /// Set of [`connector::Handle`] |
1198 | pub connectors: Vec<connector::Handle>, |
1199 | /// Set of [`encoder::Handle`] |
1200 | pub encoders: Vec<encoder::Handle>, |
1201 | width: (u32, u32), |
1202 | height: (u32, u32), |
1203 | } |
1204 | |
1205 | impl ResourceHandles { |
1206 | /// Returns the set of [`connector::Handle`] |
1207 | pub fn connectors(&self) -> &[connector::Handle] { |
1208 | &self.connectors |
1209 | } |
1210 | |
1211 | /// Returns the set of [`encoder::Handle`] |
1212 | pub fn encoders(&self) -> &[encoder::Handle] { |
1213 | &self.encoders |
1214 | } |
1215 | |
1216 | /// Returns the set of [`crtc::Handle`] |
1217 | pub fn crtcs(&self) -> &[crtc::Handle] { |
1218 | &self.crtcs |
1219 | } |
1220 | |
1221 | /// Returns the set of [`framebuffer::Handle`] |
1222 | pub fn framebuffers(&self) -> &[framebuffer::Handle] { |
1223 | &self.fbs |
1224 | } |
1225 | |
1226 | /// Returns the supported minimum and maximum width for framebuffers |
1227 | pub fn supported_fb_width(&self) -> impl RangeBounds<u32> { |
1228 | self.width.0..=self.width.1 |
1229 | } |
1230 | |
1231 | /// Returns the supported minimum and maximum height for framebuffers |
1232 | pub fn supported_fb_height(&self) -> impl RangeBounds<u32> { |
1233 | self.height.0..=self.height.1 |
1234 | } |
1235 | |
1236 | /// Apply a filter the all crtcs of these resources, resulting in a list of crtcs allowed. |
1237 | pub fn filter_crtcs(&self, filter: CrtcListFilter) -> Vec<crtc::Handle> { |
1238 | self.crtcs |
1239 | .iter() |
1240 | .enumerate() |
1241 | .filter(|&(n, _)| (1 << n) & filter.0 != 0) |
1242 | .map(|(_, &e)| e) |
1243 | .collect() |
1244 | } |
1245 | } |
1246 | |
1247 | #[derive (Debug, Clone, Copy, PartialEq, Eq)] |
1248 | /// A filter that can be used with a [`ResourceHandles`] to determine the set of |
1249 | /// Crtcs that can attach to a specific encoder. |
1250 | pub struct CrtcListFilter(u32); |
1251 | |
1252 | /// Resolution and timing information for a display mode. |
1253 | #[repr (transparent)] |
1254 | #[derive (Copy, Clone, Hash, PartialEq, Eq, bytemuck::TransparentWrapper)] |
1255 | pub struct Mode { |
1256 | // We're using the FFI struct because the DRM API expects it when giving it |
1257 | // to a CRTC or creating a blob from it. Rather than rearranging the fields |
1258 | // to convert to/from an abstracted type, just use the raw object. |
1259 | mode: ffi::drm_mode_modeinfo, |
1260 | } |
1261 | |
1262 | impl Mode { |
1263 | /// Returns the name of this mode. |
1264 | pub fn name(&self) -> &std::ffi::CStr { |
1265 | unsafe { std::ffi::CStr::from_ptr(&self.mode.name[0] as _) } |
1266 | } |
1267 | |
1268 | /// Returns the clock speed of this mode. |
1269 | pub fn clock(&self) -> u32 { |
1270 | self.mode.clock |
1271 | } |
1272 | |
1273 | /// Returns the size (resolution) of the mode. |
1274 | pub fn size(&self) -> (u16, u16) { |
1275 | (self.mode.hdisplay, self.mode.vdisplay) |
1276 | } |
1277 | |
1278 | /// Returns the horizontal sync start, end, and total. |
1279 | pub fn hsync(&self) -> (u16, u16, u16) { |
1280 | (self.mode.hsync_start, self.mode.hsync_end, self.mode.htotal) |
1281 | } |
1282 | |
1283 | /// Returns the vertical sync start, end, and total. |
1284 | pub fn vsync(&self) -> (u16, u16, u16) { |
1285 | (self.mode.vsync_start, self.mode.vsync_end, self.mode.vtotal) |
1286 | } |
1287 | |
1288 | /// Returns the horizontal skew of this mode. |
1289 | pub fn hskew(&self) -> u16 { |
1290 | self.mode.hskew |
1291 | } |
1292 | |
1293 | /// Returns the vertical scan of this mode. |
1294 | pub fn vscan(&self) -> u16 { |
1295 | self.mode.vscan |
1296 | } |
1297 | |
1298 | /// Returns the vertical refresh rate of this mode |
1299 | pub fn vrefresh(&self) -> u32 { |
1300 | self.mode.vrefresh |
1301 | } |
1302 | |
1303 | /// Returns the bitmask of this mode |
1304 | pub fn mode_type(&self) -> ModeTypeFlags { |
1305 | ModeTypeFlags::from_bits_truncate(self.mode.type_) |
1306 | } |
1307 | |
1308 | /// Returns the flags of this mode |
1309 | pub fn flags(&self) -> ModeFlags { |
1310 | ModeFlags::from_bits_truncate(self.mode.flags) |
1311 | } |
1312 | } |
1313 | |
1314 | impl From<ffi::drm_mode_modeinfo> for Mode { |
1315 | fn from(raw: ffi::drm_mode_modeinfo) -> Mode { |
1316 | Mode { mode: raw } |
1317 | } |
1318 | } |
1319 | |
1320 | impl From<Mode> for ffi::drm_mode_modeinfo { |
1321 | fn from(mode: Mode) -> Self { |
1322 | mode.mode |
1323 | } |
1324 | } |
1325 | |
1326 | impl fmt::Debug for Mode { |
1327 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
1328 | f&mut DebugStruct<'_, '_>.debug_struct("Mode" ) |
1329 | .field("name" , &self.name()) |
1330 | .field("clock" , &self.clock()) |
1331 | .field("size" , &self.size()) |
1332 | .field("hsync" , &self.hsync()) |
1333 | .field("vsync" , &self.vsync()) |
1334 | .field("hskew" , &self.hskew()) |
1335 | .field("vscan" , &self.vscan()) |
1336 | .field("vrefresh" , &self.vrefresh()) |
1337 | .field(name:"mode_type" , &self.mode_type()) |
1338 | .finish() |
1339 | } |
1340 | } |
1341 | |
1342 | bitflags::bitflags! { |
1343 | /// Display mode type flags |
1344 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
1345 | pub struct ModeTypeFlags : u32 { |
1346 | /// Builtin mode type |
1347 | #[deprecated ] |
1348 | const BUILTIN = ffi::DRM_MODE_TYPE_BUILTIN; |
1349 | /// CLOCK_C mode type |
1350 | #[deprecated ] |
1351 | const CLOCK_C = ffi::DRM_MODE_TYPE_CLOCK_C; |
1352 | /// CRTC_C mode type |
1353 | #[deprecated ] |
1354 | const CRTC_C = ffi::DRM_MODE_TYPE_CRTC_C; |
1355 | /// Preferred mode |
1356 | const PREFERRED = ffi::DRM_MODE_TYPE_PREFERRED; |
1357 | /// Default mode |
1358 | #[deprecated ] |
1359 | const DEFAULT = ffi::DRM_MODE_TYPE_DEFAULT; |
1360 | /// User defined mode type |
1361 | const USERDEF = ffi::DRM_MODE_TYPE_USERDEF; |
1362 | /// Mode created by driver |
1363 | const DRIVER = ffi::DRM_MODE_TYPE_DRIVER; |
1364 | /// Bitmask of all valid (non-deprecated) mode type flags |
1365 | const ALL = ffi::DRM_MODE_TYPE_ALL; |
1366 | } |
1367 | } |
1368 | |
1369 | bitflags::bitflags! { |
1370 | /// Display mode flags |
1371 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
1372 | pub struct ModeFlags: u32 { |
1373 | /// PHSYNC flag |
1374 | const PHSYNC = ffi::DRM_MODE_FLAG_PHSYNC; |
1375 | /// NHSYNC flag |
1376 | const NHSYNC = ffi::DRM_MODE_FLAG_NHSYNC; |
1377 | /// PVSYNC flag |
1378 | const PVSYNC = ffi::DRM_MODE_FLAG_PVSYNC; |
1379 | /// NVSYNC flag |
1380 | const NVSYNC = ffi::DRM_MODE_FLAG_NVSYNC; |
1381 | /// Interlace flag |
1382 | const INTERLACE = ffi::DRM_MODE_FLAG_INTERLACE; |
1383 | /// DBLSCAN flag |
1384 | const DBLSCAN = ffi::DRM_MODE_FLAG_DBLSCAN; |
1385 | /// CSYNC flag |
1386 | const CSYNC = ffi::DRM_MODE_FLAG_CSYNC; |
1387 | /// PCSYNC flag |
1388 | const PCSYNC = ffi::DRM_MODE_FLAG_PCSYNC; |
1389 | /// NCSYNC flag |
1390 | const NCSYNC = ffi::DRM_MODE_FLAG_NCSYNC; |
1391 | /// HSKEW flag |
1392 | const HSKEW = ffi::DRM_MODE_FLAG_HSKEW; |
1393 | #[deprecated ] |
1394 | /// BCAST flag |
1395 | const BCAST = ffi::DRM_MODE_FLAG_BCAST; |
1396 | #[deprecated ] |
1397 | /// PIXMUX flag |
1398 | const PIXMUX = ffi::DRM_MODE_FLAG_PIXMUX; |
1399 | /// DBLCLK flag |
1400 | const DBLCLK = ffi::DRM_MODE_FLAG_DBLCLK; |
1401 | /// CLKDIV2 flag |
1402 | const CLKDIV2 = ffi::DRM_MODE_FLAG_CLKDIV2; |
1403 | /// Stereo 3D mode utilizing frame packing |
1404 | const _3D_FRAME_PACKING = ffi::DRM_MODE_FLAG_3D_FRAME_PACKING; |
1405 | /// Stereo 3D mode utilizing alternating fields |
1406 | const _3D_FIELD_ALTERNATIVE = ffi::DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE; |
1407 | /// Stereo 3D mode utilizing alternating lines |
1408 | const _3D_LINE_ALTERNATIVE = ffi::DRM_MODE_FLAG_3D_LINE_ALTERNATIVE; |
1409 | /// Stereo 3D mode utilizing side by side full size image |
1410 | const _3D_SIDE_BY_SIDE_FULL = ffi::DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL; |
1411 | /// Stereo 3D mode utilizing depth images |
1412 | const _3D_L_DEPTH = ffi::DRM_MODE_FLAG_3D_L_DEPTH; |
1413 | /// Stereo 3D mode utilizing depth images |
1414 | const _3D_L_DEPTH_GFX_GFX_DEPTH = ffi::DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH; |
1415 | /// Stereo 3D mode utilizing top and bottom images |
1416 | const _3D_TOP_AND_BOTTOM = ffi::DRM_MODE_FLAG_3D_TOP_AND_BOTTOM; |
1417 | /// Stereo 3D mode utilizing side by side half size image |
1418 | const _3D_SIDE_BY_SIDE_HALF = ffi::DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF; |
1419 | } |
1420 | } |
1421 | |
1422 | /// Type of a plane |
1423 | #[repr (u32)] |
1424 | #[derive (Debug, Clone, Copy, PartialEq, Eq, Hash)] |
1425 | pub enum PlaneType { |
1426 | /// Overlay plane |
1427 | Overlay = ffi::DRM_PLANE_TYPE_OVERLAY, |
1428 | /// Primary plane |
1429 | Primary = ffi::DRM_PLANE_TYPE_PRIMARY, |
1430 | /// Cursor plane |
1431 | Cursor = ffi::DRM_PLANE_TYPE_CURSOR, |
1432 | } |
1433 | |
1434 | /// Wrapper around a set of property IDs and their raw values. |
1435 | #[derive (Debug, Clone)] |
1436 | pub struct PropertyValueSet { |
1437 | prop_ids: Vec<property::Handle>, |
1438 | prop_vals: Vec<property::RawValue>, |
1439 | } |
1440 | |
1441 | impl PropertyValueSet { |
1442 | /// Returns a HashMap mapping property names to info |
1443 | pub fn as_hashmap(&self, device: &impl Device) -> io::Result<HashMap<String, property::Info>> { |
1444 | let mut map: HashMap = HashMap::new(); |
1445 | for id: &Handle in self.prop_ids.iter() { |
1446 | let info: Info = device.get_property(*id)?; |
1447 | let name: String = info.name().to_str().unwrap().to_owned(); |
1448 | map.insert(k:name, v:info); |
1449 | } |
1450 | Ok(map) |
1451 | } |
1452 | |
1453 | /// Returns a pair representing a set of [`property::Handle`] and their raw values |
1454 | pub fn as_props_and_values(&self) -> (&[property::Handle], &[property::RawValue]) { |
1455 | (&self.prop_ids, &self.prop_vals) |
1456 | } |
1457 | |
1458 | /// Returns iterator over pairs representing a set of [`property::Handle`] and their raw values |
1459 | pub fn iter(&self) -> impl Iterator<Item = (&property::Handle, &property::RawValue)> { |
1460 | self.into_iter() |
1461 | } |
1462 | } |
1463 | |
1464 | impl<'a> IntoIterator for &'a PropertyValueSet { |
1465 | type Item = (&'a property::Handle, &'a property::RawValue); |
1466 | type IntoIter = |
1467 | Zip<std::slice::Iter<'a, property::Handle>, std::slice::Iter<'a, property::RawValue>>; |
1468 | |
1469 | fn into_iter(self) -> Self::IntoIter { |
1470 | self.prop_ids.iter().zip(self.prop_vals.iter()) |
1471 | } |
1472 | } |
1473 | |
1474 | impl IntoIterator for PropertyValueSet { |
1475 | type Item = (property::Handle, property::RawValue); |
1476 | type IntoIter = |
1477 | Zip<std::vec::IntoIter<property::Handle>, std::vec::IntoIter<property::RawValue>>; |
1478 | |
1479 | fn into_iter(self) -> Self::IntoIter { |
1480 | self.prop_ids.into_iter().zip(self.prop_vals) |
1481 | } |
1482 | } |
1483 | |
1484 | /// Describes a rectangular region of a buffer |
1485 | #[repr (transparent)] |
1486 | #[derive (Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] |
1487 | pub struct ClipRect(ffi::drm_sys::drm_clip_rect); |
1488 | |
1489 | impl ClipRect { |
1490 | /// Create a new clipping rectangle. |
1491 | pub fn new(x1: u16, y1: u16, x2: u16, y2: u16) -> Self { |
1492 | Self(ffi::drm_sys::drm_clip_rect { x1, y1, x2, y2 }) |
1493 | } |
1494 | |
1495 | /// Get the X coordinate of the top left corner of the rectangle. |
1496 | pub fn x1(self) -> u16 { |
1497 | self.0.x1 |
1498 | } |
1499 | |
1500 | /// Get the Y coordinate of the top left corner of the rectangle. |
1501 | pub fn y1(self) -> u16 { |
1502 | self.0.y1 |
1503 | } |
1504 | |
1505 | /// Get the X coordinate of the bottom right corner of the rectangle |
1506 | pub fn x2(self) -> u16 { |
1507 | self.0.x2 |
1508 | } |
1509 | |
1510 | /// Get the Y coordinate of the bottom right corner of the rectangle. |
1511 | pub fn y2(self) -> u16 { |
1512 | self.0.y2 |
1513 | } |
1514 | } |
1515 | |
1516 | bitflags::bitflags! { |
1517 | /// Commit flags for atomic mode setting |
1518 | /// |
1519 | /// Limited to the values in [`ffi::drm_sys::DRM_MODE_ATOMIC_FLAGS`]. |
1520 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
1521 | pub struct AtomicCommitFlags : u32 { |
1522 | /// Generate a page flip event, when the changes are applied |
1523 | const PAGE_FLIP_EVENT = ffi::drm_sys::DRM_MODE_PAGE_FLIP_EVENT; |
1524 | /// Request page flip when the changes are applied, not waiting for vblank |
1525 | const PAGE_FLIP_ASYNC = ffi::drm_sys::DRM_MODE_PAGE_FLIP_ASYNC; |
1526 | /// Test only validity of the request, do not actually apply the requested changes |
1527 | const TEST_ONLY = ffi::drm_sys::DRM_MODE_ATOMIC_TEST_ONLY; |
1528 | /// Do not block on the request and return early |
1529 | const NONBLOCK = ffi::drm_sys::DRM_MODE_ATOMIC_NONBLOCK; |
1530 | /// Allow the changes to trigger a modeset, if necessary |
1531 | /// |
1532 | /// Changes requiring a modeset are rejected otherwise. |
1533 | const ALLOW_MODESET = ffi::drm_sys::DRM_MODE_ATOMIC_ALLOW_MODESET; |
1534 | } |
1535 | } |
1536 | |
1537 | bitflags::bitflags! { |
1538 | /// Mode property flags |
1539 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
1540 | pub struct ModePropFlags : u32 { |
1541 | /// Do not use |
1542 | #[deprecated ] |
1543 | const PENDING = ffi::DRM_MODE_PROP_PENDING; |
1544 | |
1545 | /// Non-extended types: legacy bitmask, one bit per type: |
1546 | const LEGACY_TYPE = ffi::DRM_MODE_PROP_LEGACY_TYPE; |
1547 | /// An unsigned integer that has a min and max value |
1548 | const RANGE = ffi::DRM_MODE_PROP_RANGE; |
1549 | /// Set when this property is informational only and cannot be modified |
1550 | const IMMUTABLE = ffi::DRM_MODE_PROP_IMMUTABLE; |
1551 | /// Enumerated type with text strings |
1552 | const ENUM = ffi::DRM_MODE_PROP_ENUM; |
1553 | /// A chunk of binary data that must be acquired |
1554 | const BLOB = ffi::DRM_MODE_PROP_BLOB; |
1555 | /// Bitmask of enumerated types |
1556 | const BITMASK = ffi::DRM_MODE_PROP_BITMASK; |
1557 | |
1558 | /// Extended-types: rather than continue to consume a bit per type, |
1559 | /// grab a chunk of the bits to use as integer type id. |
1560 | const EXTENDED_TYPE = ffi::DRM_MODE_PROP_EXTENDED_TYPE; |
1561 | /// A DRM object that can have a specific type |
1562 | /// |
1563 | /// See `ffi::DRM_MODE_OBJECT_*` for specific types. |
1564 | const OBJECT = ffi::DRM_MODE_PROP_OBJECT; |
1565 | /// A signed integer that has a min and max value |
1566 | const SIGNED_RANGE = ffi::DRM_MODE_PROP_SIGNED_RANGE; |
1567 | /// the [`Self::ATOMIC`] flag is used to hide properties from userspace that |
1568 | /// is not aware of atomic properties. This is mostly to work around |
1569 | /// older userspace (DDX drivers) that read/write each prop they find, |
1570 | /// witout being aware that this could be triggering a lengthy modeset. |
1571 | const ATOMIC = ffi::DRM_MODE_PROP_ATOMIC; |
1572 | } |
1573 | } |
1574 | |
1575 | bitflags::bitflags! { |
1576 | /// Planar framebuffer flags |
1577 | #[derive (Clone, Copy, Debug, PartialEq, Eq, Hash)] |
1578 | pub struct FbCmd2Flags : u32 { |
1579 | /// For interlaced framebuffers |
1580 | const INTERLACED = ffi::DRM_MODE_FB_INTERLACED; |
1581 | /// Enables .modifier |
1582 | const MODIFIERS = ffi::DRM_MODE_FB_MODIFIERS; |
1583 | } |
1584 | } |
1585 | |