• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Module for abstractions on drm device nodes.
2 
3 pub mod constants;
4 
5 use std::error::Error;
6 use std::fmt::{self, Debug, Display, Formatter};
7 use std::io;
8 use std::os::unix::io::AsFd;
9 use std::path::{Path, PathBuf};
10 
11 use rustix::fs::{fstat, major, minor, stat, Dev as dev_t, Stat};
12 
13 use crate::node::constants::*;
14 
15 /// A node which refers to a DRM device.
16 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17 pub struct DrmNode {
18     dev: dev_t,
19     ty: NodeType,
20 }
21 
22 impl DrmNode {
23     /// Creates a DRM node from an open drm device.
from_file<A: AsFd>(file: A) -> Result<DrmNode, CreateDrmNodeError>24     pub fn from_file<A: AsFd>(file: A) -> Result<DrmNode, CreateDrmNodeError> {
25         let stat = fstat(file).map_err(Into::<io::Error>::into)?;
26         DrmNode::from_stat(stat)
27     }
28 
29     /// Creates a DRM node from path.
from_path<A: AsRef<Path>>(path: A) -> Result<DrmNode, CreateDrmNodeError>30     pub fn from_path<A: AsRef<Path>>(path: A) -> Result<DrmNode, CreateDrmNodeError> {
31         let stat = stat(path.as_ref()).map_err(Into::<io::Error>::into)?;
32         DrmNode::from_stat(stat)
33     }
34 
35     /// Creates a DRM node from a file stat.
from_stat(stat: Stat) -> Result<DrmNode, CreateDrmNodeError>36     pub fn from_stat(stat: Stat) -> Result<DrmNode, CreateDrmNodeError> {
37         let dev = stat.st_rdev;
38         DrmNode::from_dev_id(dev)
39     }
40 
41     /// Creates a DRM node from a [`dev_t`].
from_dev_id(dev: dev_t) -> Result<Self, CreateDrmNodeError>42     pub fn from_dev_id(dev: dev_t) -> Result<Self, CreateDrmNodeError> {
43         if !is_device_drm(dev) {
44             return Err(CreateDrmNodeError::NotDrmNode);
45         }
46 
47         // The type of the DRM node is determined by the minor number ranges:
48         //   0 -  63 -> Primary
49         //  64 - 127 -> Control
50         // 128 - 255 -> Render
51         let ty = match minor(dev) >> 6 {
52             0 => NodeType::Primary,
53             1 => NodeType::Control,
54             2 => NodeType::Render,
55             _ => return Err(CreateDrmNodeError::NotDrmNode),
56         };
57 
58         Ok(DrmNode { dev, ty })
59     }
60 
61     /// Returns the type of the DRM node.
ty(&self) -> NodeType62     pub fn ty(&self) -> NodeType {
63         self.ty
64     }
65 
66     /// Returns the device_id of the underlying DRM node.
dev_id(&self) -> dev_t67     pub fn dev_id(&self) -> dev_t {
68         self.dev
69     }
70 
71     /// Returns the path of the open device if possible.
dev_path(&self) -> Option<PathBuf>72     pub fn dev_path(&self) -> Option<PathBuf> {
73         node_path(self, self.ty).ok()
74     }
75 
76     /// Returns the path of the specified node type matching the device, if available.
dev_path_with_type(&self, ty: NodeType) -> Option<PathBuf>77     pub fn dev_path_with_type(&self, ty: NodeType) -> Option<PathBuf> {
78         node_path(self, ty).ok()
79     }
80 
81     /// Returns a new node of the specified node type matching the device, if available.
node_with_type(&self, ty: NodeType) -> Option<Result<DrmNode, CreateDrmNodeError>>82     pub fn node_with_type(&self, ty: NodeType) -> Option<Result<DrmNode, CreateDrmNodeError>> {
83         self.dev_path_with_type(ty).map(DrmNode::from_path)
84     }
85 
86     /// Returns the major device number of the DRM device.
major(&self) -> u3287     pub fn major(&self) -> u32 {
88         major(self.dev_id())
89     }
90 
91     /// Returns the minor device number of the DRM device.
minor(&self) -> u3292     pub fn minor(&self) -> u32 {
93         minor(self.dev_id())
94     }
95 
96     /// Returns whether the DRM device has render nodes.
has_render(&self) -> bool97     pub fn has_render(&self) -> bool {
98         #[cfg(target_os = "linux")]
99         {
100             node_path(self, NodeType::Render).is_ok()
101         }
102 
103         // TODO: More robust checks on non-linux.
104 
105         #[cfg(target_os = "freebsd")]
106         {
107             false
108         }
109 
110         #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
111         {
112             false
113         }
114     }
115 }
116 
117 impl Display for DrmNode {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result118     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
119         write!(f, "{}{}", self.ty.minor_name_prefix(), minor(self.dev_id()))
120     }
121 }
122 
123 /// A type of node
124 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
125 pub enum NodeType {
126     /// A primary node may be used to allocate buffers.
127     ///
128     /// If no other node is present, this may be used to post a buffer to an output with mode-setting.
129     Primary,
130 
131     /// A control node may be used for mode-setting.
132     ///
133     /// This is almost never used since no DRM API for control nodes is available yet.
134     Control,
135 
136     /// A render node may be used by a client to allocate buffers.
137     ///
138     /// Mode-setting is not possible with a render node.
139     Render,
140 }
141 
142 impl NodeType {
143     /// Returns a string representing the prefix of a minor device's name.
144     ///
145     /// For example, on Linux with a primary node, the returned string would be `card`.
minor_name_prefix(&self) -> &'static str146     pub fn minor_name_prefix(&self) -> &'static str {
147         match self {
148             NodeType::Primary => PRIMARY_NAME,
149             NodeType::Control => CONTROL_NAME,
150             NodeType::Render => RENDER_NAME,
151         }
152     }
153 
154     #[cfg(not(target_os = "linux"))]
minor_base(&self) -> u32155     fn minor_base(&self) -> u32 {
156         match self {
157             NodeType::Primary => 0,
158             NodeType::Control => 64,
159             NodeType::Render => 128,
160         }
161     }
162 }
163 
164 impl Display for NodeType {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result165     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
166         Debug::fmt(self, f)
167     }
168 }
169 
170 /// An error that may occur when creating a [`DrmNode`] from a file descriptor.
171 #[derive(Debug)]
172 pub enum CreateDrmNodeError {
173     /// Some underlying IO error occured while trying to create a DRM node.
174     Io(io::Error),
175 
176     /// The provided file descriptor does not refer to a DRM node.
177     NotDrmNode,
178 }
179 
180 impl Display for CreateDrmNodeError {
fmt(&self, f: &mut Formatter<'_>) -> fmt::Result181     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
182         match self {
183             Self::Io(err) => Display::fmt(err, f),
184             Self::NotDrmNode => {
185                 f.write_str("the provided file descriptor does not refer to a DRM node")
186             }
187         }
188     }
189 }
190 
191 impl Error for CreateDrmNodeError {
source(&self) -> Option<&(dyn Error + 'static)>192     fn source(&self) -> Option<&(dyn Error + 'static)> {
193         match self {
194             Self::Io(err) => Some(err),
195             Self::NotDrmNode => None,
196         }
197     }
198 }
199 
200 impl From<io::Error> for CreateDrmNodeError {
201     #[inline]
from(err: io::Error) -> Self202     fn from(err: io::Error) -> Self {
203         CreateDrmNodeError::Io(err)
204     }
205 }
206 
207 #[cfg(target_os = "freebsd")]
devname(dev: dev_t) -> Option<String>208 fn devname(dev: dev_t) -> Option<String> {
209     use std::os::raw::{c_char, c_int};
210 
211     // Matching value of SPECNAMELEN in FreeBSD 13+
212     let mut dev_name = vec![0u8; 255];
213 
214     let buf: *mut c_char = unsafe {
215         libc::devname_r(
216             dev,
217             libc::S_IFCHR, // Must be S_IFCHR or S_IFBLK
218             dev_name.as_mut_ptr() as *mut c_char,
219             dev_name.len() as c_int,
220         )
221     };
222 
223     // Buffer was too small (weird issue with the size of buffer) or the device could not be named.
224     if buf.is_null() {
225         return None;
226     }
227 
228     // SAFETY: The buffer written to by devname_r is guaranteed to be NUL terminated.
229     unsafe { dev_name.set_len(libc::strlen(buf)) };
230 
231     Some(String::from_utf8(dev_name).expect("Returned device name is not valid utf8"))
232 }
233 
234 /// Returns if the given device by major:minor pair is a DRM device.
235 #[cfg(target_os = "linux")]
is_device_drm(dev: dev_t) -> bool236 pub fn is_device_drm(dev: dev_t) -> bool {
237     // We `stat` the path rather than comparing the major to support dynamic device numbers:
238     //   https://gitlab.freedesktop.org/mesa/drm/-/commit/f8392583418aef5e27bfed9989aeb601e20cc96d
239     let path = format!("/sys/dev/char/{}:{}/device/drm", major(dev), minor(dev));
240     stat(path.as_str()).is_ok()
241 }
242 
243 /// Returns if the given device by major:minor pair is a DRM device.
244 #[cfg(target_os = "freebsd")]
is_device_drm(dev: dev_t) -> bool245 pub fn is_device_drm(dev: dev_t) -> bool {
246     devname(dev).map_or(false, |dev_name| {
247         dev_name.starts_with("drm/")
248             || dev_name.starts_with("dri/card")
249             || dev_name.starts_with("dri/control")
250             || dev_name.starts_with("dri/renderD")
251     })
252 }
253 
254 /// Returns if the given device by major:minor pair is a DRM device.
255 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
is_device_drm(dev: dev_t) -> bool256 pub fn is_device_drm(dev: dev_t) -> bool {
257     major(dev) == DRM_MAJOR
258 }
259 
260 /// Returns the path of a specific type of node from the same DRM device as another path of the same node.
path_to_type<P: AsRef<Path>>(path: P, ty: NodeType) -> io::Result<PathBuf>261 pub fn path_to_type<P: AsRef<Path>>(path: P, ty: NodeType) -> io::Result<PathBuf> {
262     let stat = stat(path.as_ref()).map_err(Into::<io::Error>::into)?;
263     dev_path(stat.st_rdev, ty)
264 }
265 
266 /// Returns the path of a specific type of node from the same DRM device as an existing [`DrmNode`].
node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf>267 pub fn node_path(node: &DrmNode, ty: NodeType) -> io::Result<PathBuf> {
268     dev_path(node.dev, ty)
269 }
270 
271 /// Returns the path of a specific type of node from the DRM device described by major and minor device numbers.
272 #[cfg(target_os = "linux")]
dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf>273 pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf> {
274     use std::fs;
275     use std::io::ErrorKind;
276 
277     if !is_device_drm(dev) {
278         return Err(io::Error::new(
279             ErrorKind::NotFound,
280             format!("{}:{} is no DRM device", major(dev), minor(dev)),
281         ));
282     }
283 
284     let read = fs::read_dir(format!(
285         "/sys/dev/char/{}:{}/device/drm",
286         major(dev),
287         minor(dev)
288     ))?;
289 
290     for entry in read.flatten() {
291         let name = entry.file_name();
292         let name = name.to_string_lossy();
293 
294         // Only 1 primary, control and render node may exist simultaneously, so the
295         // first occurrence is good enough.
296         if name.starts_with(ty.minor_name_prefix()) {
297             let path = Path::new("/dev/dri").join(&*name);
298             if path.exists() {
299                 return Ok(path);
300             }
301         }
302     }
303 
304     Err(io::Error::new(
305         ErrorKind::NotFound,
306         format!(
307             "Could not find node of type {} from DRM device {}:{}",
308             ty,
309             major(dev),
310             minor(dev)
311         ),
312     ))
313 }
314 
315 /// Returns the path of a specific type of node from the DRM device described by major and minor device numbers.
316 #[cfg(target_os = "freebsd")]
dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf>317 pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf> {
318     // Based on libdrm `drmGetMinorNameForFD`. Should be updated if the code
319     // there is replaced with anything more sensible...
320 
321     use std::io::ErrorKind;
322 
323     if !is_device_drm(dev) {
324         return Err(io::Error::new(
325             ErrorKind::NotFound,
326             format!("{}:{} is no DRM device", major(dev), minor(dev)),
327         ));
328     }
329 
330     if let Some(dev_name) = devname(dev) {
331         let suffix = dev_name.trim_start_matches(|c: char| !c.is_numeric());
332         if let Ok(old_id) = suffix.parse::<u32>() {
333             let id_mask = 0b11_1111;
334             let id = old_id & id_mask + ty.minor_base();
335             let path = PathBuf::from(format!("/dev/dri/{}{}", ty.minor_name_prefix(), id));
336             if path.exists() {
337                 return Ok(path);
338             }
339         }
340     }
341 
342     Err(io::Error::new(
343         ErrorKind::NotFound,
344         format!(
345             "Could not find node of type {} from DRM device {}:{}",
346             ty,
347             major(dev),
348             minor(dev)
349         ),
350     ))
351 }
352 
353 /// Returns the path of a specific type of node from the DRM device described by major and minor device numbers.
354 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf>355 pub fn dev_path(dev: dev_t, ty: NodeType) -> io::Result<PathBuf> {
356     use std::io::ErrorKind;
357 
358     if !is_device_drm(dev) {
359         return Err(io::Error::new(
360             ErrorKind::NotFound,
361             format!("{}:{} is no DRM device", major(dev), minor(dev)),
362         ));
363     }
364 
365     let old_id = minor(dev);
366     let id_mask = 0b11_1111;
367     let id = old_id & id_mask + ty.minor_base();
368     let path = PathBuf::from(format!("/dev/dri/{}{}", ty.minor_name_prefix(), id));
369     if path.exists() {
370         return Ok(path);
371     }
372 
373     Err(io::Error::new(
374         ErrorKind::NotFound,
375         format!(
376             "Could not find node of type {} for DRM device {}:{}",
377             ty,
378             major(dev),
379             minor(dev)
380         ),
381     ))
382 }
383