yuca/
sysfs.rs

1//! Accessing the sysfs.
2
3use std::{
4    fmt::{self, Write as FmtWrite},
5    fs::File,
6    io::{Read, Write},
7    marker::PhantomData,
8    str::FromStr,
9};
10
11use camino::{Utf8Path, Utf8PathBuf};
12use rustix::{
13    fd::{AsFd, BorrowedFd, OwnedFd},
14    fs::{openat, Dir, Mode, OFlags, CWD},
15    path::Arg,
16};
17use strum::EnumString;
18
19use crate::{
20    types::*,
21    watcher::{DevicePathStream, WatchResult, Watcher},
22    Error, Result,
23};
24
25mod sealed {
26    pub trait Sealed {}
27}
28
29use sealed::Sealed;
30
31macro_rules! impl_sealed {
32    ($ty:ty $(, forall($($args:tt)*))?) => {
33        impl $($($args)*)? Sealed for $ty {}
34    };
35}
36
37trait PropertyReader {
38    type Read;
39
40    fn read(s: &str) -> Result<Self::Read>;
41}
42
43trait PropertyWriter: PropertyReader {
44    type Write;
45
46    fn write(dest: impl Write, value: &Self::Write) -> Result<()>;
47}
48
49/// A readable sysfs property.
50pub trait PropertyReadable: Sealed + fmt::Debug {
51    type Read;
52
53    // Reads the given property from the sysfs.
54    fn get(&self) -> Result<Self::Read>;
55}
56
57// A readable and writable sysfs property.
58pub trait PropertyWritable: PropertyReadable {
59    type Write;
60
61    // Sets the given property on the sysfs to the value.
62    fn set(&self, value: &Self::Write) -> Result<()>;
63}
64
65struct PropertyImpl<'fd, P: PropertyReader> {
66    dfd: BorrowedFd<'fd>,
67    path: &'static str,
68    _impl: PhantomData<P>,
69}
70
71impl_sealed!(PropertyImpl<'_, P>, forall(<P: PropertyReader>));
72
73impl<P: PropertyReader> fmt::Debug for PropertyImpl<'_, P> {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "(property:{})", self.path)
76    }
77}
78
79impl<'fd, P: PropertyReader> PropertyImpl<'fd, P> {
80    fn new(dfd: BorrowedFd<'fd>, path: &'static str) -> Self {
81        Self {
82            dfd,
83            path,
84            _impl: PhantomData,
85        }
86    }
87}
88
89impl<P: PropertyReader> PropertyReadable for PropertyImpl<'_, P> {
90    type Read = P::Read;
91
92    fn get(&self) -> Result<P::Read> {
93        let fd = openat(
94            self.dfd,
95            self.path,
96            OFlags::RDONLY | OFlags::CLOEXEC,
97            Mode::empty(),
98        )?;
99        let mut file = File::from(fd);
100        let mut s = "".to_owned();
101        file.read_to_string(&mut s)?;
102        s.truncate(s.trim_end().len());
103        P::read(&s)
104    }
105}
106
107impl<P: PropertyReader + PropertyWriter> PropertyWritable for PropertyImpl<'_, P> {
108    type Write = P::Write;
109
110    fn set(&self, value: &P::Write) -> Result<()> {
111        let fd = openat(
112            self.dfd,
113            self.path,
114            OFlags::RDONLY | OFlags::CLOEXEC,
115            Mode::empty(),
116        )?;
117        P::write(File::from(fd), value)?;
118        Ok(())
119    }
120}
121
122struct PropertyParse<R: FromStr> {
123    _phantom: PhantomData<R>,
124}
125
126impl<R: FromStr> PropertyReader for PropertyParse<R>
127where
128    Error: From<R::Err>,
129{
130    type Read = R;
131
132    fn read(s: &str) -> Result<R> {
133        s.parse().map_err(Error::from)
134    }
135}
136
137struct PropertyParseDisplay<R: FromStr, W: fmt::Display> {
138    _phantom: PhantomData<(R, W)>,
139}
140
141impl<R: FromStr, W: fmt::Display> PropertyReader for PropertyParseDisplay<R, W>
142where
143    Error: From<R::Err>,
144{
145    type Read = R;
146
147    fn read(s: &str) -> Result<R> {
148        PropertyParse::<R>::read(s)
149    }
150}
151
152impl<R: FromStr, W: fmt::Display> PropertyWriter for PropertyParseDisplay<R, W>
153where
154    Error: From<R::Err>,
155{
156    type Write = W;
157
158    fn write(mut dest: impl Write, value: &Self::Write) -> Result<()> {
159        write!(dest, "{value}").map_err(Into::into)
160    }
161}
162
163type PropertyRoleSelection<T> = PropertyParseDisplay<RoleSelection<T>, T>;
164
165struct PropertyHexU16;
166
167impl PropertyReader for PropertyHexU16 {
168    type Read = u16;
169
170    fn read(s: &str) -> Result<Self::Read> {
171        u16::from_str_radix(s, 16).or(Err(Error::Parse))
172    }
173}
174
175struct PropertyHexPrefixedU32;
176
177impl PropertyReader for PropertyHexPrefixedU32 {
178    type Read = u32;
179
180    fn read(s: &str) -> Result<Self::Read> {
181        let s = s.strip_prefix("0x").ok_or(Error::Parse)?;
182        u32::from_str_radix(s, 16).or(Err(Error::Parse))
183    }
184}
185
186struct PropertyBoolIntegral;
187
188impl PropertyReader for PropertyBoolIntegral {
189    type Read = bool;
190
191    fn read(s: &str) -> Result<Self::Read> {
192        let n = u32::from_str(s)?;
193        Ok(n != 0)
194    }
195}
196
197struct PropertyBoolYesNo;
198
199impl PropertyReader for PropertyBoolYesNo {
200    type Read = bool;
201
202    fn read(s: &str) -> Result<Self::Read> {
203        match s {
204            "yes" => Ok(true),
205            "no" => Ok(false),
206            _ => Err(Error::Parse),
207        }
208    }
209}
210
211impl PropertyWriter for PropertyBoolYesNo {
212    type Write = bool;
213
214    fn write(mut dest: impl Write, value: &Self::Write) -> Result<()> {
215        if *value {
216            write!(dest, "yes")?;
217        } else {
218            write!(dest, "no")?;
219        }
220        Ok(())
221    }
222}
223
224struct PropertyPreferredRole;
225
226impl PropertyReader for PropertyPreferredRole {
227    type Read = Option<PowerRole>;
228
229    fn read(s: &str) -> Result<Self::Read> {
230        if s.is_empty() {
231            return Ok(None);
232        }
233
234        Ok(Some(s.parse()?))
235    }
236}
237
238impl PropertyWriter for PropertyPreferredRole {
239    type Write = Option<PowerRole>;
240
241    fn write(mut dest: impl Write, value: &Self::Write) -> Result<()> {
242        match value {
243            Some(value) => write!(dest, "{value}")?,
244            None => write!(dest, "none")?,
245        }
246
247        Ok(())
248    }
249}
250
251macro_rules! property {
252    // This is filling in the macro's parameters (function return type, source
253    // filename, etc) one-by-one, most of it is just boilerplate.
254    (
255        _stage_fill_return,
256        $name:ident,
257        ro($read:ty),
258        $($rest:tt)*
259    ) => {
260        property!(_stage_fill_with, $name,
261            read($read),
262            returns(impl PropertyReadable<Read = $read> + '_),
263            $($rest)*);
264    };
265
266    (
267        _stage_fill_return,
268        $name:ident,
269        rw($read:ty),
270        $($rest:tt)*
271    ) => {
272        property!(_stage_fill_with, $name,
273            read($read),
274            returns(impl PropertyWritable<Read = $read, Write = $read> + '_),
275            $($rest)*);
276    };
277
278    (
279        _stage_fill_return,
280        $name:ident,
281        rw($read:ty, $write:ty),
282        $($rest:tt)*
283    ) => {
284        property!(_stage_fill_with, $name,
285            read($read),
286            returns(impl PropertyWritable<Read = $read, Write = $write> + '_),
287            $($rest)*);
288    };
289
290    (
291        _stage_fill_with,
292        $name:ident,
293        read($read:ty),
294        returns($ret:ty),
295        with($impl:ty),
296        $($rest:tt)*
297    ) => {
298        property!(_stage_fill_from, $name,
299            returns($ret),
300            with($impl),
301            $($rest)*);
302    };
303
304    (
305        _stage_fill_with,
306        $name:ident,
307        read($read:ty),
308        returns($ret:ty),
309        with(),
310        $($rest:tt)*
311    ) => {
312        property!(_stage_fill_from, $name,
313            returns($ret),
314            with(PropertyParse::<$read>),
315            $($rest)*);
316    };
317
318    (
319        _stage_fill_from,
320        $name:ident,
321        returns($ret:ty),
322        with($impl:ty),
323        from($from:literal),
324        $($rest:tt)*
325    ) => {
326        property!(_stage_final, $name,
327            returns($ret),
328            with($impl),
329            from($from),
330            $($rest)*);
331    };
332
333    (
334        _stage_fill_from,
335        $name:ident,
336        returns($ret:ty),
337        with($impl:ty),
338        from(subdir($subdir:literal)),
339        $($rest:tt)*
340    ) => {
341        property!(_stage_final, $name,
342            returns($ret),
343            with($impl),
344            from(concat!($subdir, "/", stringify!($name))),
345            $($rest)*);
346    };
347
348    (
349        _stage_fill_from,
350        $name:ident,
351        returns($ret:ty),
352        with($impl:ty),
353        from(),
354        $($rest:tt)*
355    ) => {
356        property!(_stage_final, $name,
357            returns($ret),
358            with($impl),
359            from(stringify!($name)),
360            $($rest)*);
361    };
362
363    (
364        _stage_final,
365        $name:ident,
366        returns($ret:ty),
367        with($impl:ty),
368        from($from:expr),
369        doc($($doc:tt)?)
370    ) => {
371        $(#[doc = $doc])?
372        pub fn $name(&self) -> $ret {
373            PropertyImpl::<'_, $impl>::new(self.dfd.as_fd(), $from)
374        }
375    };
376
377    (
378        $name:ident,
379        $access:ident($read:ty $(, $write:ty)?)
380        $(, with($impl:ty))?
381        $(, from($($from:tt)*))?
382        $(, doc($doc:tt))?
383        $(,)?
384    ) => {
385        property!(_stage_fill_return,
386            $name,
387            $access($read $(, $write)?),
388            with($($impl)?),
389            from($($($from)*)?),
390            doc($($doc)?));
391    };
392}
393
394enum MaybeOwnedFd<'a> {
395    Owned(OwnedFd),
396    Borrowed(BorrowedFd<'a>),
397}
398
399impl AsFd for MaybeOwnedFd<'_> {
400    fn as_fd(&self) -> BorrowedFd<'_> {
401        match &self {
402            MaybeOwnedFd::Owned(fd) => fd.as_fd(),
403            MaybeOwnedFd::Borrowed(fd) => fd.as_fd(),
404        }
405    }
406}
407
408/// Path to the sysfs directory containing Type-C devices.
409pub const SYS_CLASS_TYPEC: &str = "/sys/class/typec";
410
411/// A reference to the location of a [`Device`] on the sysfs.
412///
413/// Rather than being an actual filesystem path, this simply contains
414/// information like "what is the index of the device", which, when combined
415/// with the type itself, gives it the ability to parse or construct actual
416/// paths on-the-fly as needed.
417///
418/// Note that these can be constructed at will and thus may not actually
419/// correspond to an existing device! It's simply a reference to a filesystem
420/// location that can *potentially contain* a device.
421pub trait DevicePath:
422    Sealed + fmt::Debug + Copy + Clone + PartialEq + Eq + std::hash::Hash + Sized + Unpin
423{
424    /// The parent of this path, i.e. the [`DevicePath`] representing the
425    /// filesystem location that *contains* this device. If this device is not
426    /// nested within another, then this will be [`NoParent`].
427    type Parent: DevicePathParent;
428
429    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self>;
430    fn build_basename(&self, s: &mut String);
431
432    fn parent(&self) -> Self::Parent;
433}
434
435macro_rules! device_path_child_collection_getter {
436    ($name:ident, $ret:ty $(, doc($doc:tt))? $(,)?) => {
437        $(#[doc = $doc])?
438        pub fn $name(&self) -> DevicePathCollection<$ret> {
439            DevicePathCollection { parent: *self }
440        }
441    };
442}
443
444/// A parent of a [`DevicePath`]. This can be either a [`DevicePath`] itself or
445/// [`NoParent`].
446pub trait DevicePathParent:
447    Sealed + fmt::Debug + Copy + Clone + PartialEq + Eq + std::hash::Hash + Unpin
448{
449    fn parse_syspath(p: &Utf8Path) -> Option<Self>;
450    fn build_syspath(&self, p: &mut Utf8PathBuf);
451}
452
453impl<P: DevicePath> DevicePathParent for P {
454    fn parse_syspath(p: &Utf8Path) -> Option<Self> {
455        let parent = P::Parent::parse_syspath(p.parent()?)?;
456        P::parse_basename(p.file_name()?, parent)
457    }
458
459    fn build_syspath(&self, p: &mut Utf8PathBuf) {
460        self.parent().build_syspath(p);
461
462        let mut s = "".to_owned();
463        self.build_basename(&mut s);
464        p.push(s);
465    }
466}
467
468/// A stub type used as a [`DevicePathParent`] for when a [`Device`] doesn't
469/// actually have a parent.
470#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
471pub struct NoParent;
472
473impl_sealed!(NoParent);
474
475impl DevicePathParent for NoParent {
476    fn parse_syspath(_p: &Utf8Path) -> Option<Self> {
477        Some(NoParent)
478    }
479
480    fn build_syspath(&self, _p: &mut Utf8PathBuf) {}
481}
482
483/// A child [`DevicePath`] that can be present in its parent multiple times.
484pub trait DevicePathIndexed: DevicePath {
485    type Index;
486
487    /// Prefer using [`DevicePathCollection.get`].
488    fn child_of(parent: Self::Parent, index: Self::Index) -> Self;
489}
490
491/// A [`DevicePath`] that can be watched for add/change/remove events.
492pub trait DevicePathWatchable: DevicePath {
493    /// Returns a stream of all [`DevicePath`]s of this type that are added.
494    fn any_added(ctx: &Watcher) -> WatchResult<DevicePathStream<NoParent, Self>>;
495    /// Returns a stream of all [`DevicePath`]s of this type that are changed.
496    fn any_changed(ctx: &Watcher) -> WatchResult<DevicePathStream<NoParent, Self>>;
497    /// Returns a stream of all [`DevicePath`]s of this type that are removed.
498    fn any_removed(ctx: &Watcher) -> WatchResult<DevicePathStream<NoParent, Self>>;
499
500    /// Returns a stream containing only this [`DevicePath`] whenever it's
501    /// added.
502    fn added(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Self, Self>>;
503    /// Returns a stream containing only this [`DevicePath`] whenever it's
504    /// changed.
505    fn changed(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Self, Self>>;
506    /// Returns a stream containing only this [`DevicePath`] whenever it's
507    /// removed.
508    fn removed(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Self, Self>>;
509}
510
511macro_rules! impl_device_path_watchable {
512    ($path:ty $(, forall($($args:tt)*))?, $channels:ident) => {
513        impl $($($args)*)? DevicePathWatchable for $path {
514            fn any_added(ctx: &Watcher) -> WatchResult<DevicePathStream<NoParent, Self>> {
515                ctx.with_channels(|channels, inner| channels.$channels.on_any_added.insert(NoParent, inner))
516            }
517            fn any_changed(ctx: &Watcher) -> WatchResult<DevicePathStream<NoParent, Self>> {
518                ctx.with_channels(|channels, inner| channels.$channels.on_any_changed.insert(NoParent, inner))
519            }
520            fn any_removed(ctx: &Watcher) -> WatchResult<DevicePathStream<NoParent, Self>> {
521                ctx.with_channels(|channels, inner| channels.$channels.on_any_removed.insert(NoParent, inner))
522            }
523
524            fn added(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Self, Self>> {
525                ctx.with_channels(|channels, inner| channels.$channels.on_added.insert(*self, inner))
526            }
527            fn changed(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Self, Self>> {
528                ctx.with_channels(|channels, inner| channels.$channels.on_changed.insert(*self, inner))
529            }
530            fn removed(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Self, Self>> {
531                ctx.with_channels(|channels, inner| channels.$channels.on_removed.insert(*self, inner))
532            }
533        }
534    }
535}
536
537/// A [`DevicePathIndexed`] that can be watched by invoking one of
538/// [`DevicePathCollection::added`], [`DevicePathCollection::changed`], or
539/// [`DevicePathCollection::removed`] on the parent's corresponding
540/// [`DevicePathCollection`].
541///
542/// This requires [`DevicePathIndexed`] because a singleton child is already at a fixed path
543/// location and thus can have added/removed invoked on itself, whereas, for an indexed
544/// child, we can't exactly predict what the path will be when a device gets added.
545pub trait DevicePathWatchableFromParent: DevicePathIndexed {
546    /// Prefer using [`DevicePathCollection.added`].
547    fn added_in(
548        parent: Self::Parent,
549        ctx: &Watcher,
550    ) -> WatchResult<DevicePathStream<Self::Parent, Self>>;
551    /// Prefer using [`DevicePathCollection.changed`].
552    fn changed_in(
553        parent: Self::Parent,
554        ctx: &Watcher,
555    ) -> WatchResult<DevicePathStream<Self::Parent, Self>>;
556    /// Prefer using [`DevicePathCollection.removed`].
557    fn removed_in(
558        parent: Self::Parent,
559        ctx: &Watcher,
560    ) -> WatchResult<DevicePathStream<Self::Parent, Self>>;
561}
562
563macro_rules! impl_device_path_watchable_from_parent {
564    ($path:ty $(, forall($($args:tt)*))?, $channels:ident) => {
565        impl_device_path_watchable!($path $(, forall($($args)*))?, $channels);
566
567        impl $($($args)*)? DevicePathWatchableFromParent for $path {
568            fn added_in(parent: Self::Parent, ctx: &Watcher) -> WatchResult<DevicePathStream<Self::Parent, Self>> {
569                ctx.with_channels(|channels, inner| channels.$channels.on_inventory_added.insert(parent, inner))
570            }
571            fn changed_in(parent: Self::Parent, ctx: &Watcher) -> WatchResult<DevicePathStream<Self::Parent, Self>> {
572                ctx.with_channels(|channels, inner| channels.$channels.on_inventory_changed.insert(parent, inner))
573            }
574            fn removed_in(parent: Self::Parent, ctx: &Watcher) -> WatchResult<DevicePathStream<Self::Parent, Self>> {
575                ctx.with_channels(|channels, inner| channels.$channels.on_inventory_removed.insert(parent, inner))
576            }
577        }
578    }
579}
580
581/// A wrapper over a parent that can be used to get child paths of a given type.
582pub struct DevicePathCollection<Child: DevicePath> {
583    parent: Child::Parent,
584}
585
586impl<Child: DevicePath> DevicePathCollection<Child> {
587    /// Returns the parent containing the child paths.
588    pub fn parent(&self) -> &Child::Parent {
589        &self.parent
590    }
591}
592
593impl<Child: DevicePathIndexed> DevicePathCollection<Child> {
594    /// Returns a path for the child at the given index.
595    pub fn get(&self, index: Child::Index) -> Child {
596        Child::child_of(self.parent, index)
597    }
598}
599
600impl<Child: DevicePathWatchableFromParent> DevicePathCollection<Child> {
601    /// Returns a stream of [`DevicePath`]s that are added and children of
602    /// this collection's parent path.
603    pub fn added(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Child::Parent, Child>> {
604        Child::added_in(self.parent, ctx)
605    }
606
607    /// Returns a stream of [`DevicePath`]s that are changed and children of
608    /// this collection's parent path.
609    pub fn changed(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Child::Parent, Child>> {
610        Child::changed_in(self.parent, ctx)
611    }
612
613    /// Returns a stream of [`DevicePath`]s that are removed and children of
614    /// this collection's parent path.
615    pub fn removed(&self, ctx: &Watcher) -> WatchResult<DevicePathStream<Child::Parent, Child>> {
616        Child::removed_in(self.parent, ctx)
617    }
618}
619
620/// An open device from the sysfs.
621///
622/// This contains a live file handle for the device. If the device disappears
623/// while this is open, then any property methods and child accessors will
624/// generally return an [`Error::Io`] containing
625/// [`std::io::ErrorKind::NotFound`].
626pub trait Device: Sealed + Sized {
627    type Path: DevicePath;
628
629    fn from_fd(dfd: OwnedFd, path: Self::Path) -> Self;
630
631    /// Returns the [`DevicePath`] that points to this device.
632    fn path(&self) -> &Self::Path;
633
634    /// Opens the device of this type at the given path.
635    fn open(path: Self::Path) -> Result<Self> {
636        let mut sys = Utf8PathBuf::from(SYS_CLASS_TYPEC);
637        path.build_syspath(&mut sys);
638        let dfd = openat(
639            CWD,
640            sys.as_str(),
641            OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
642            Mode::empty(),
643        )?;
644        Ok(Self::from_fd(dfd, path))
645    }
646}
647
648macro_rules! impl_device {
649    ($dev:ty $(, forall($($args:tt)*))?, path($path:ty)) => {
650        impl $($($args)*)? Device for $dev {
651            type Path = $path;
652
653            fn from_fd(dfd: OwnedFd, path: Self::Path) -> Self {
654                Self { dfd, path }
655            }
656
657            fn path(&self) -> &Self::Path {
658                &self.path
659            }
660        }
661    };
662}
663
664/// An unopened [`Device`] within a parent.
665///
666/// Use [`Self::open`] to actually open it and obtain the [`Device`].
667#[derive(Debug)]
668pub struct DeviceEntry<'fd, T: Device> {
669    parent_dfd: BorrowedFd<'fd>,
670    path: T::Path,
671}
672
673impl<T: Device> DeviceEntry<'_, T> {
674    pub fn path(&self) -> &T::Path {
675        &self.path
676    }
677
678    /// Opens the [`Device`] that this entry points to.
679    pub fn open(&self) -> Result<T> {
680        let mut s = String::new();
681        self.path.build_basename(&mut s);
682        let dfd = openat(
683            self.parent_dfd,
684            s,
685            OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
686            Mode::empty(),
687        )?;
688        Ok(T::from_fd(dfd, self.path))
689    }
690}
691
692/// A collection of child [`Device`]s of a single type beneath a parent.
693pub struct DeviceCollection<'fd, Child: Device> {
694    dfd: MaybeOwnedFd<'fd>,
695    parent: <Child::Path as DevicePath>::Parent,
696    phantom: PhantomData<Child>,
697}
698
699impl<Child: Device> DeviceCollection<'_, Child> {
700    /// Returns an iterator of [`DeviceEntry`]s in this collection.
701    pub fn iter(&self) -> Result<DeviceIter<'_, Child>> {
702        Ok(DeviceIter {
703            dfd: self.dfd.as_fd(),
704            dir: Dir::read_from(&self.dfd)?,
705            parent: self.parent,
706            phantom: PhantomData,
707        })
708    }
709
710    /// Returns an iterator of open [`Device`]s in this collection.
711    pub fn iter_opened(&self) -> Result<impl Iterator<Item = Result<Child>> + '_> {
712        let iter = self.iter()?;
713        Ok(iter.map(|x| x.and_then(|x| x.open())))
714    }
715
716    /// Returns an list of [`DeviceEntry`]s in this collection.
717    pub fn list(&self) -> Result<Vec<DeviceEntry<'_, Child>>> {
718        self.iter().and_then(|x| x.collect())
719    }
720
721    /// Returns an list of open [`Device`]s in this collection.
722    pub fn list_opened(&self) -> Result<Vec<Child>> {
723        self.iter_opened().and_then(|x| x.collect())
724    }
725}
726
727impl<Child: Device> DeviceCollection<'_, Child>
728where
729    Child::Path: DevicePathIndexed,
730{
731    pub fn get(&self, index: <Child::Path as DevicePathIndexed>::Index) -> Result<Child> {
732        let path = Child::Path::child_of(self.parent, index);
733        let mut s = String::new();
734        path.build_basename(&mut s);
735
736        let dfd = openat(
737            self.dfd.as_fd(),
738            s,
739            OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
740            Mode::empty(),
741        )?;
742        Ok(Child::from_fd(dfd, path))
743    }
744}
745
746/// An iterator over child [`Device`]s.
747///
748/// Obtained via [`DeviceCollection.iter`].
749pub struct DeviceIter<'fd, Child: Device> {
750    dfd: BorrowedFd<'fd>,
751    dir: Dir,
752    parent: <Child::Path as DevicePath>::Parent,
753    phantom: PhantomData<Child>,
754}
755
756impl<'fd, Child: Device> Iterator for DeviceIter<'fd, Child> {
757    type Item = Result<DeviceEntry<'fd, Child>>;
758
759    fn next(&mut self) -> Option<Self::Item> {
760        for entry in &mut self.dir {
761            let entry = match entry {
762                Ok(entry) => entry,
763                Err(err) => return Some(Err(err.into())),
764            };
765
766            let name = entry.file_name();
767            let name = match name.as_str() {
768                Ok(name) => name,
769                Err(err) => return Some(Err(err.into())),
770            };
771
772            let Some(path) = Child::Path::parse_basename(name, self.parent) else {
773                continue;
774            };
775
776            if path.parent() != self.parent {
777                continue;
778            }
779
780            return Some(Ok(DeviceEntry {
781                parent_dfd: self.dfd,
782                path,
783            }));
784        }
785
786        None
787    }
788}
789
790/// A path to a [`Port`].
791#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
792pub struct PortPath {
793    /// The port number, as used in `/sys/class/typec/port[port]`.
794    pub port: u32,
795}
796
797impl PortPath {
798    pub fn collection() -> DevicePathCollection<PortPath> {
799        DevicePathCollection { parent: NoParent }
800    }
801
802    /// Returns the path for an attached cable.
803    pub fn cable(&self) -> CablePath {
804        CablePath { port: self.port }
805    }
806
807    /// Returns the path for an attached partner device.
808    pub fn partner(&self) -> PartnerPath {
809        PartnerPath { port: self.port }
810    }
811
812    device_path_child_collection_getter!(
813        alt_modes,
814        AltModePath<PortPath>,
815        doc("Returns a path collection for this port's alternate modes."),
816    );
817}
818
819impl_sealed!(PortPath);
820
821impl DevicePath for PortPath {
822    type Parent = NoParent;
823
824    fn parse_basename(s: &str, _parent: Self::Parent) -> Option<Self> {
825        let s = s.strip_prefix("port")?;
826        let port = u32::from_str(s).ok()?;
827        Some(Self { port })
828    }
829
830    fn build_basename(&self, s: &mut String) {
831        write!(s, "port{}", self.port).unwrap();
832    }
833
834    fn parent(&self) -> Self::Parent {
835        NoParent
836    }
837}
838
839impl DevicePathIndexed for PortPath {
840    type Index = u32;
841
842    fn child_of(_parent: Self::Parent, index: Self::Index) -> Self {
843        PortPath { port: index }
844    }
845}
846
847impl_device_path_watchable_from_parent!(PortPath, port);
848
849/// A USB type-C port on the system.
850#[derive(Debug)]
851pub struct Port {
852    dfd: OwnedFd,
853    path: PortPath,
854}
855
856impl_sealed!(Port);
857impl_device!(Port, path(PortPath));
858
859impl Port {
860    pub fn collection() -> Result<DeviceCollection<'static, Port>> {
861        let dfd = openat(
862            CWD,
863            SYS_CLASS_TYPEC,
864            OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
865            Mode::empty(),
866        )?;
867        Ok(DeviceCollection {
868            dfd: MaybeOwnedFd::Owned(dfd),
869            parent: NoParent,
870            phantom: PhantomData,
871        })
872    }
873
874    property!(
875        data_role,
876        rw(RoleSelection<DataRole>, DataRole),
877        with(PropertyRoleSelection::<DataRole>),
878        doc("The port's currently selected role in data transmission."),
879    );
880    property!(
881        port_type,
882        rw(RoleSelection<PortType>, PortType),
883        with(PropertyRoleSelection::<PortType>),
884        doc("The port's currently selected type."),
885    );
886    property!(
887        power_role,
888        rw(RoleSelection<PowerRole>, PowerRole),
889        with(PropertyRoleSelection::<PowerRole>),
890        doc("The port's currently selected role in power transmission."),
891    );
892    property!(
893        preferred_role,
894        ro(Option<PowerRole>),
895        with(PropertyPreferredRole),
896        doc("If this port is dual-role, then its preferred role of the two."),
897    );
898    property!(power_operation_mode, ro(PowerOperationMode));
899    property!(usb_power_delivery_revision, ro(Revision));
900    property!(usb_typec_revision, ro(Revision));
901
902    /// Returns a collection of this port's alternate modes.
903    pub fn alt_modes(&self) -> DeviceCollection<'_, AltMode<PortPath>> {
904        DeviceCollection {
905            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
906            parent: self.path,
907            phantom: PhantomData,
908        }
909    }
910
911    /// Returns the entry for this port's connected cable.
912    pub fn cable(&self) -> DeviceEntry<'_, Cable> {
913        DeviceEntry {
914            parent_dfd: self.dfd.as_fd(),
915            path: self.path.cable(),
916        }
917    }
918
919    /// Returns the entry for this port's connected partner.
920    pub fn partner(&self) -> DeviceEntry<'_, Partner> {
921        DeviceEntry {
922            parent_dfd: self.dfd.as_fd(),
923            path: self.path.partner(),
924        }
925    }
926}
927
928/// A path to a [`Partner`].
929#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
930pub struct PartnerPath {
931    /// The number of the port this partner is connected to.
932    pub port: u32,
933}
934
935impl PartnerPath {
936    device_path_child_collection_getter!(
937        alt_modes,
938        AltModePath<PartnerPath>,
939        doc("Returns a path collection for this partner's alternate modes."),
940    );
941    device_path_child_collection_getter!(
942        pds,
943        PowerDeliveryPath,
944        doc("Returns a path collection for this partner's power delivery devices."),
945    );
946}
947
948impl_sealed!(PartnerPath);
949
950impl DevicePath for PartnerPath {
951    type Parent = PortPath;
952
953    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
954        let s = s.strip_suffix("-partner")?;
955        let parent = Self::Parent::parse_basename(s, parent.parent())?;
956        Some(Self { port: parent.port })
957    }
958
959    fn build_basename(&self, s: &mut String) {
960        self.parent().build_basename(s);
961        write!(s, "-partner").unwrap();
962    }
963
964    fn parent(&self) -> Self::Parent {
965        Self::Parent { port: self.port }
966    }
967}
968
969impl_device_path_watchable!(PartnerPath, partner);
970
971/// A connected partner device.
972#[derive(Debug)]
973pub struct Partner {
974    dfd: OwnedFd,
975    path: PartnerPath,
976}
977
978impl_sealed!(Partner);
979impl_device!(Partner, path(PartnerPath));
980
981impl Partner {
982    // TODO: type
983
984    property!(usb_power_delivery_revision, ro(Revision));
985
986    /// Returns a handle to the identity information for this partner.
987    pub fn identity(&self) -> IdentityPartner<'_> {
988        IdentityPartner {
989            dfd: self.dfd.as_fd(),
990        }
991    }
992
993    /// Returns a collection of this partner's alternate modes.
994    pub fn alt_modes(&self) -> DeviceCollection<'_, AltMode<PartnerPath>> {
995        DeviceCollection {
996            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
997            parent: self.path,
998            phantom: PhantomData,
999        }
1000    }
1001
1002    /// Returns a collection of this partner's power delivery devices.
1003    pub fn pds(&self) -> DeviceCollection<'_, PowerDelivery> {
1004        DeviceCollection {
1005            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
1006            parent: self.path,
1007            phantom: PhantomData,
1008        }
1009    }
1010}
1011
1012/// A path to a [`Cable`].
1013#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1014pub struct CablePath {
1015    /// The number of the port this cable is connected to.
1016    pub port: u32,
1017}
1018
1019impl CablePath {
1020    device_path_child_collection_getter!(
1021        plugs,
1022        PlugPath,
1023        doc("Returns a path collection for this cable's plugs."),
1024    );
1025}
1026
1027impl_sealed!(CablePath);
1028
1029impl DevicePath for CablePath {
1030    type Parent = PortPath;
1031
1032    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
1033        let s = s.strip_suffix("-cable")?;
1034        let parent = Self::Parent::parse_basename(s, parent.parent())?;
1035        Some(Self { port: parent.port })
1036    }
1037
1038    fn build_basename(&self, s: &mut String) {
1039        self.parent().build_basename(s);
1040        write!(s, "-cable").unwrap();
1041    }
1042
1043    fn parent(&self) -> Self::Parent {
1044        Self::Parent { port: self.port }
1045    }
1046}
1047
1048impl_device_path_watchable!(CablePath, cable);
1049
1050/// A connected cable.
1051#[derive(Debug)]
1052pub struct Cable {
1053    dfd: OwnedFd,
1054    path: CablePath,
1055}
1056
1057impl_sealed!(Cable);
1058impl_device!(Cable, path(CablePath));
1059
1060impl Cable {
1061    /// Returns a handle to the identity information for this cable.
1062    pub fn identity(&self) -> IdentityCable<'_> {
1063        IdentityCable {
1064            dfd: self.dfd.as_fd(),
1065        }
1066    }
1067
1068    /// Returns a collection of this cable's plugs.
1069    pub fn plugs(&self) -> DeviceCollection<'_, Plug> {
1070        DeviceCollection {
1071            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
1072            parent: self.path,
1073            phantom: PhantomData,
1074        }
1075    }
1076
1077    property!(cable_type, ro(CableType), from("type"));
1078    property!(plug_type, ro(PlugType));
1079    property!(usb_power_delivery_revision, ro(Revision));
1080}
1081
1082/// A path to a [`Plug`].
1083#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1084pub struct PlugPath {
1085    /// The number of the port this plug's cable is connected to.
1086    pub port: u32,
1087    /// The number of this plug in the parent cable's collection.
1088    pub plug: u32,
1089}
1090
1091impl PlugPath {
1092    device_path_child_collection_getter!(
1093        alt_modes,
1094        AltModePath<PlugPath>,
1095        doc("Returns a path collection for this plug's alternate modes."),
1096    );
1097}
1098
1099impl_sealed!(PlugPath);
1100
1101impl DevicePath for PlugPath {
1102    type Parent = CablePath;
1103
1104    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
1105        let (a, b) = s.split_once('-')?;
1106        let parent =
1107            <Self::Parent as DevicePath>::Parent::parse_basename(a, parent.parent().parent())?;
1108
1109        let b = b.strip_prefix("plug")?;
1110        let plug = u32::from_str(b).ok()?;
1111
1112        Some(Self {
1113            port: parent.port,
1114            plug,
1115        })
1116    }
1117
1118    fn build_basename(&self, s: &mut String) {
1119        self.parent().parent().build_basename(s);
1120        write!(s, "-plug{}", self.plug).unwrap();
1121    }
1122
1123    fn parent(&self) -> Self::Parent {
1124        Self::Parent { port: self.port }
1125    }
1126}
1127
1128impl DevicePathIndexed for PlugPath {
1129    type Index = u32;
1130
1131    fn child_of(parent: Self::Parent, index: Self::Index) -> Self {
1132        PlugPath {
1133            port: parent.port,
1134            plug: index,
1135        }
1136    }
1137}
1138
1139impl_device_path_watchable_from_parent!(PlugPath, plug);
1140
1141/// A [`Cable`]'s plug.
1142#[derive(Debug)]
1143pub struct Plug {
1144    dfd: OwnedFd,
1145    path: PlugPath,
1146}
1147
1148impl_sealed!(Plug);
1149impl_device!(Plug, path(PlugPath));
1150
1151impl Plug {
1152    /// Returns a collection of this plug's alternate modes.
1153    pub fn alt_modes(&self) -> DeviceCollection<'_, AltMode<PlugPath>> {
1154        DeviceCollection {
1155            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
1156            parent: self.path,
1157            phantom: PhantomData,
1158        }
1159    }
1160}
1161
1162/// A path to an [`AltMode`].
1163#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1164pub struct AltModePath<Parent: DevicePath> {
1165    /// The path of this alternate mode's parent device.
1166    pub parent: Parent,
1167    /// The index of this alternate mode in the parent's collection.
1168    pub index: u32,
1169}
1170
1171impl_sealed!(AltModePath<Parent>, forall(<Parent: DevicePath>));
1172
1173impl<Parent: DevicePath> DevicePath for AltModePath<Parent> {
1174    type Parent = Parent;
1175
1176    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
1177        let (a, b) = s.split_once('.')?;
1178
1179        let parent = Parent::parse_basename(a, parent.parent())?;
1180        let index = u32::from_str(b).ok()?;
1181
1182        Some(AltModePath { parent, index })
1183    }
1184
1185    fn build_basename(&self, s: &mut String) {
1186        self.parent().build_basename(s);
1187        write!(s, ".{}", self.index).unwrap();
1188    }
1189
1190    fn parent(&self) -> Self::Parent {
1191        self.parent
1192    }
1193}
1194
1195impl<Parent: DevicePath> DevicePathIndexed for AltModePath<Parent> {
1196    type Index = u32;
1197
1198    fn child_of(parent: Self::Parent, index: Self::Index) -> Self {
1199        Self { parent, index }
1200    }
1201}
1202
1203impl_device_path_watchable_from_parent!(AltModePath<PartnerPath>, partner_alt_mode);
1204impl_device_path_watchable_from_parent!(AltModePath<PlugPath>, plug_alt_mode);
1205
1206/// An alternate mode of some parent device.
1207#[derive(Debug)]
1208pub struct AltMode<Parent: DevicePath> {
1209    dfd: OwnedFd,
1210    path: AltModePath<Parent>,
1211}
1212
1213impl_sealed!(AltMode<Parent>, forall(<Parent: DevicePath>));
1214impl_device!(AltMode<Parent>, forall(<Parent: DevicePath>), path(AltModePath<Parent>));
1215
1216impl<Parent: DevicePath> AltMode<Parent> {
1217    property!(
1218        active,
1219        rw(bool),
1220        with(PropertyBoolYesNo),
1221        doc("Is this alternate mode currently active?"),
1222    );
1223    property!(mode, ro(u32));
1224    property!(
1225        svid,
1226        ro(u16),
1227        with(PropertyHexU16),
1228        doc("A Standard or Vendor ID used to identity this mode"),
1229    );
1230    property!(vdo, ro(u32), with(PropertyHexPrefixedU32));
1231}
1232
1233impl AltMode<PortPath> {
1234    property!(
1235        supported_roles,
1236        ro(SupportedRoles),
1237        doc("The roles supported by this alternate mode"),
1238    );
1239}
1240
1241/// A handle to a [`Partner`]'s identity information.
1242#[derive(Debug)]
1243pub struct IdentityPartner<'fd> {
1244    dfd: BorrowedFd<'fd>,
1245}
1246
1247impl IdentityPartner<'_> {
1248    property!(
1249        id_header,
1250        ro(VdoIdHeaderPartner),
1251        from(subdir("identity")),
1252        doc("The identity header.
1253
1254If this property is not available yet, then calling [`PropertyReadable::get`]
1255will return [`Error::IdentityUnavailable`]."),
1256    );
1257    property!(
1258        cert_stat,
1259        ro(VdoCertStat),
1260        from(subdir("identity")),
1261        doc("The XID from a USB-IF certified device.
1262
1263If this property is not available, either because it has not been determined yet
1264or the device is not USB-IF certified, then calling [`PropertyReadable::get`]
1265will return [`Error::IdentityUnavailable`]."),
1266    );
1267    property!(
1268        product,
1269        ro(VdoProduct),
1270        from(subdir("identity")),
1271        doc("The product IDs.
1272
1273If this property is not available yet, then calling [`PropertyReadable::get`]
1274will return [`Error::IdentityUnavailable`]."),
1275    );
1276
1277    // TODO: should these be different types?
1278    property!(
1279        product_type_vdo1,
1280        ro(u32),
1281        with(PropertyHexPrefixedU32),
1282        from(subdir("identity"))
1283    );
1284    property!(
1285        product_type_vdo2,
1286        ro(u32),
1287        with(PropertyHexPrefixedU32),
1288        from(subdir("identity"))
1289    );
1290    property!(
1291        product_type_vdo3,
1292        ro(u32),
1293        with(PropertyHexPrefixedU32),
1294        from(subdir("identity"))
1295    );
1296}
1297
1298/// A handle to a [`Cable`]'s identity information.
1299#[derive(Debug)]
1300pub struct IdentityCable<'fd> {
1301    dfd: BorrowedFd<'fd>,
1302}
1303
1304impl IdentityCable<'_> {
1305    property!(
1306        id_header,
1307        ro(VdoIdHeaderCable),
1308        from(subdir("identity")),
1309        doc("The identity header.
1310
1311If this property is not available yet, then calling [`PropertyReadable::get`]
1312will return [`Error::IdentityUnavailable`]."),
1313    );
1314    property!(
1315        cert_stat,
1316        ro(VdoCertStat),
1317        from(subdir("identity")),
1318        doc("The XID from a USB-IF certified device.
1319
1320If this property is not available, either because it has not been determined yet
1321or the device is not USB-IF certified, then calling [`PropertyReadable::get`]
1322will return [`Error::IdentityUnavailable`]."),
1323    );
1324    property!(
1325        product,
1326        ro(VdoProduct),
1327        from(subdir("identity")),
1328        doc("The product IDs.
1329
1330If this property is not available yet, then calling [`PropertyReadable::get`]
1331will return [`Error::IdentityUnavailable`]."),
1332    );
1333    // TODO: should these be different types?
1334    property!(
1335        product_type_vdo1,
1336        ro(u32),
1337        with(PropertyHexPrefixedU32),
1338        from(subdir("identity"))
1339    );
1340    property!(
1341        product_type_vdo2,
1342        ro(u32),
1343        with(PropertyHexPrefixedU32),
1344        from(subdir("identity"))
1345    );
1346    property!(
1347        product_type_vdo3,
1348        ro(u32),
1349        with(PropertyHexPrefixedU32),
1350        from(subdir("identity"))
1351    );
1352}
1353
1354/// A path to a [`PowerDelivery`].
1355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1356pub struct PowerDeliveryPath {
1357    /// The number of the port this device is connected to.
1358    pub port: u32,
1359    /// The index of this device in the partner's collection.
1360    pub pd: u32,
1361}
1362
1363impl PowerDeliveryPath {
1364    /// Returns a path for the capabilities as a source of power.
1365    pub fn source_capabilities(&self) -> CapabilitiesPath {
1366        CapabilitiesPath {
1367            port: self.port,
1368            pd: self.pd,
1369            role: PowerRole::Source,
1370        }
1371    }
1372
1373    /// Returns a path for the capabilities as a sink of power.
1374    pub fn sink_capabilities(&self) -> CapabilitiesPath {
1375        CapabilitiesPath {
1376            port: self.port,
1377            pd: self.pd,
1378            role: PowerRole::Sink,
1379        }
1380    }
1381}
1382
1383impl_sealed!(PowerDeliveryPath);
1384
1385impl DevicePath for PowerDeliveryPath {
1386    type Parent = PartnerPath;
1387
1388    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
1389        let s = s.strip_prefix("pd")?;
1390        let pd = u32::from_str(s).ok()?;
1391
1392        Some(Self {
1393            port: parent.port,
1394            pd,
1395        })
1396    }
1397
1398    fn build_basename(&self, s: &mut String) {
1399        write!(s, "pd{}", self.pd).unwrap();
1400    }
1401
1402    fn parent(&self) -> Self::Parent {
1403        Self::Parent { port: self.port }
1404    }
1405}
1406
1407impl DevicePathIndexed for PowerDeliveryPath {
1408    type Index = u32;
1409
1410    fn child_of(parent: Self::Parent, index: Self::Index) -> Self {
1411        Self {
1412            port: parent.port,
1413            pd: index,
1414        }
1415    }
1416}
1417
1418impl_device_path_watchable_from_parent!(PowerDeliveryPath, pd);
1419
1420/// A device containing source and sink capability information.
1421#[derive(Debug)]
1422pub struct PowerDelivery {
1423    dfd: OwnedFd,
1424    path: PowerDeliveryPath,
1425}
1426
1427impl_sealed!(PowerDelivery);
1428impl_device!(PowerDelivery, path(PowerDeliveryPath));
1429
1430impl PowerDelivery {
1431    /// Returns the entry for the capabilities as a source of power.
1432    pub fn source_capabilities(&self) -> DeviceEntry<'_, SourceCapabilities> {
1433        DeviceEntry {
1434            parent_dfd: self.dfd.as_fd(),
1435            path: self.path.source_capabilities(),
1436        }
1437    }
1438
1439    /// Returns the entry for the capabilities as a sink of power.
1440    pub fn sink_capabilities(&self) -> DeviceEntry<'_, SinkCapabilities> {
1441        DeviceEntry {
1442            parent_dfd: self.dfd.as_fd(),
1443            path: self.path.sink_capabilities(),
1444        }
1445    }
1446}
1447
1448/// A path to a [`SourceCapabilities`] or [`SinkCapabilities`].
1449#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1450pub struct CapabilitiesPath {
1451    /// The number of the port this device is connected to.
1452    pub port: u32,
1453    /// The index of this device's parent in the partner's collection.
1454    pub pd: u32,
1455    /// Is this a source or a sink?
1456    pub role: PowerRole,
1457}
1458
1459impl CapabilitiesPath {
1460    // Returns the path for the fixed-supply vSafe5V PDO.
1461    pub fn vsafe5v(&self) -> PdoPath {
1462        self.pdos().get((1, SupplyKind::FixedSupply))
1463    }
1464
1465    device_path_child_collection_getter!(
1466        pdos,
1467        PdoPath,
1468        doc("Returns a path collection for the capabilities' PDOs."),
1469    );
1470}
1471
1472impl_sealed!(CapabilitiesPath);
1473
1474impl DevicePath for CapabilitiesPath {
1475    type Parent = PowerDeliveryPath;
1476
1477    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
1478        let s = s.strip_suffix("-capabilities")?;
1479        let role = PowerRole::from_str(s).ok()?;
1480        Some(Self {
1481            port: parent.port,
1482            pd: parent.pd,
1483            role,
1484        })
1485    }
1486
1487    fn build_basename(&self, s: &mut String) {
1488        write!(s, "{}-capabilities", self.role).unwrap();
1489    }
1490
1491    fn parent(&self) -> Self::Parent {
1492        Self::Parent {
1493            port: self.port,
1494            pd: self.pd,
1495        }
1496    }
1497}
1498
1499/// A device containing the capabilities of a source of power.
1500#[derive(Debug)]
1501pub struct SourceCapabilities {
1502    dfd: OwnedFd,
1503    path: CapabilitiesPath,
1504}
1505
1506impl_sealed!(SourceCapabilities);
1507impl_device!(SourceCapabilities, path(CapabilitiesPath));
1508
1509impl SourceCapabilities {
1510    // Returns the fixed-supply vSafe5V PDO.
1511    pub fn vsafe5v(&self) -> DeviceEntry<'_, SourcePdoFixedSupplyVSafe5V> {
1512        DeviceEntry {
1513            parent_dfd: self.dfd.as_fd(),
1514            path: self.path().vsafe5v(),
1515        }
1516    }
1517
1518    /// Returns a collection of source PDOs.
1519    pub fn pdos(&self) -> DeviceCollection<'_, SourcePdo> {
1520        DeviceCollection {
1521            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
1522            parent: self.path,
1523            phantom: PhantomData,
1524        }
1525    }
1526}
1527
1528// XXX: copy-pasted struct, but making this generic like AltMode is hard
1529/// A device containing the capabilities of a sink of power.
1530#[derive(Debug)]
1531pub struct SinkCapabilities {
1532    dfd: OwnedFd,
1533    path: CapabilitiesPath,
1534}
1535
1536impl_sealed!(SinkCapabilities);
1537impl_device!(SinkCapabilities, path(CapabilitiesPath));
1538
1539impl SinkCapabilities {
1540    // Returns the fixed-supply vSafe5V PDO.
1541    pub fn vsafe5v(&self) -> DeviceEntry<'_, SinkPdoFixedSupplyVSafe5V> {
1542        DeviceEntry {
1543            parent_dfd: self.dfd.as_fd(),
1544            path: self.path().vsafe5v(),
1545        }
1546    }
1547
1548    /// Returns a collection of sink PDOs.
1549    pub fn pdos(&self) -> DeviceCollection<'_, SinkPdo> {
1550        DeviceCollection {
1551            dfd: MaybeOwnedFd::Borrowed(self.dfd.as_fd()),
1552            parent: self.path,
1553            phantom: PhantomData,
1554        }
1555    }
1556}
1557
1558/// The type of a power supply, as exposed in a PDO.
1559#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, strum::Display)]
1560#[strum(serialize_all = "snake_case")]
1561pub enum SupplyKind {
1562    /// A fixed-voltage supply.
1563    FixedSupply,
1564    /// A connected battery.
1565    Battery,
1566    /// A supply with a variable voltage within a given range.
1567    VariableSupply,
1568    /// A supply whose voltage can be changed dynamically.
1569    ProgrammableSupply,
1570}
1571
1572/// A path to a [`SourcePdo`] or [`SinkPdo`].
1573#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1574pub struct PdoPath {
1575    /// The number of the port this device is connected to.
1576    pub port: u32,
1577    /// The index of this pdo's [`PowerDeliveryPath`] the partner's collection.
1578    pub pd: u32,
1579
1580    /// Is this a source or a sink?
1581    pub role: PowerRole,
1582    /// The index of this device in the parent's collection.
1583    pub index: u32,
1584    /// The type of power supply.
1585    pub supply: SupplyKind,
1586}
1587
1588impl_sealed!(PdoPath);
1589
1590impl DevicePath for PdoPath {
1591    type Parent = CapabilitiesPath;
1592
1593    fn parse_basename(s: &str, parent: Self::Parent) -> Option<Self> {
1594        let (a, b) = s.split_once(':')?;
1595        let index = u32::from_str(a).ok()?;
1596        let supply = SupplyKind::from_str(b).ok()?;
1597        Some(Self {
1598            port: parent.port,
1599            pd: parent.pd,
1600            role: parent.role,
1601            index,
1602            supply,
1603        })
1604    }
1605
1606    fn build_basename(&self, s: &mut String) {
1607        write!(s, "{}:{}", self.index, self.supply).unwrap();
1608    }
1609
1610    fn parent(&self) -> Self::Parent {
1611        Self::Parent {
1612            port: self.port,
1613            pd: self.pd,
1614            role: self.role,
1615        }
1616    }
1617}
1618
1619impl DevicePathIndexed for PdoPath {
1620    type Index = (u32, SupplyKind);
1621
1622    fn child_of(parent: Self::Parent, index: Self::Index) -> Self {
1623        PdoPath {
1624            port: parent.port,
1625            pd: parent.pd,
1626            role: parent.role,
1627            index: index.0,
1628            supply: index.1,
1629        }
1630    }
1631}
1632
1633/// A Power Data Object describing a single source power supply.
1634#[derive(Debug)]
1635pub enum SourcePdo {
1636    FixedSupply(SourcePdoFixedSupply),
1637    Battery(SourcePdoBattery),
1638    VariableSupply(SourcePdoVariableSupply),
1639    ProgrammableSupply(SourcePdoProgrammableSupply),
1640}
1641
1642impl_sealed!(SourcePdo);
1643
1644impl Device for SourcePdo {
1645    type Path = PdoPath;
1646
1647    fn from_fd(dfd: OwnedFd, path: Self::Path) -> Self {
1648        match path.supply {
1649            SupplyKind::FixedSupply => SourcePdo::FixedSupply(SourcePdoFixedSupply { dfd, path }),
1650            SupplyKind::Battery => SourcePdo::Battery(SourcePdoBattery { dfd, path }),
1651            SupplyKind::VariableSupply => {
1652                SourcePdo::VariableSupply(SourcePdoVariableSupply { dfd, path })
1653            }
1654            SupplyKind::ProgrammableSupply => {
1655                SourcePdo::ProgrammableSupply(SourcePdoProgrammableSupply { dfd, path })
1656            }
1657        }
1658    }
1659
1660    fn path(&self) -> &Self::Path {
1661        match self {
1662            SourcePdo::FixedSupply(pdo) => pdo.path(),
1663            SourcePdo::Battery(pdo) => pdo.path(),
1664            SourcePdo::VariableSupply(pdo) => pdo.path(),
1665            SourcePdo::ProgrammableSupply(pdo) => pdo.path(),
1666        }
1667    }
1668}
1669
1670/// A Power Data Object describing a single sink power supply.
1671#[derive(Debug)]
1672pub enum SinkPdo {
1673    FixedSupply(SinkPdoFixedSupply),
1674    Battery(SinkPdoBattery),
1675    VariableSupply(SinkPdoVariableSupply),
1676    ProgrammableSupply(SinkPdoProgrammableSupply),
1677}
1678
1679impl_sealed!(SinkPdo);
1680
1681impl Device for SinkPdo {
1682    type Path = PdoPath;
1683
1684    fn from_fd(dfd: OwnedFd, path: Self::Path) -> Self {
1685        match path.supply {
1686            SupplyKind::FixedSupply => SinkPdo::FixedSupply(SinkPdoFixedSupply { dfd, path }),
1687            SupplyKind::Battery => SinkPdo::Battery(SinkPdoBattery { dfd, path }),
1688            SupplyKind::VariableSupply => {
1689                SinkPdo::VariableSupply(SinkPdoVariableSupply { dfd, path })
1690            }
1691            SupplyKind::ProgrammableSupply => {
1692                SinkPdo::ProgrammableSupply(SinkPdoProgrammableSupply { dfd, path })
1693            }
1694        }
1695    }
1696
1697    fn path(&self) -> &Self::Path {
1698        match self {
1699            SinkPdo::FixedSupply(pdo) => pdo.path(),
1700            SinkPdo::Battery(pdo) => pdo.path(),
1701            SinkPdo::VariableSupply(pdo) => pdo.path(),
1702            SinkPdo::ProgrammableSupply(pdo) => pdo.path(),
1703        }
1704    }
1705}
1706
1707/// The vSafe5V fixed-voltage source power supply.
1708///
1709/// Always guaranteed to be the very first PDO.
1710#[derive(Debug)]
1711pub struct SourcePdoFixedSupplyVSafe5V {
1712    dfd: OwnedFd,
1713    path: PdoPath,
1714}
1715
1716impl_sealed!(SourcePdoFixedSupplyVSafe5V);
1717impl_device!(SourcePdoFixedSupplyVSafe5V, path(PdoPath));
1718
1719impl SourcePdoFixedSupplyVSafe5V {
1720    property!(dual_role_power, ro(bool), with(PropertyBoolIntegral));
1721    property!(usb_suspend_supported, ro(bool), with(PropertyBoolIntegral));
1722    property!(unconstrained_power, ro(bool), with(PropertyBoolIntegral));
1723    property!(
1724        usb_communication_capable,
1725        ro(bool),
1726        with(PropertyBoolIntegral)
1727    );
1728    property!(dual_role_data, ro(bool), with(PropertyBoolIntegral));
1729    property!(
1730        unchunked_extended_messages_supported,
1731        ro(bool),
1732        with(PropertyBoolIntegral)
1733    );
1734
1735    // Shared with SourcePdoFixedSupply.
1736    property!(peak_current, ro(PeakCurrent));
1737    property!(voltage, ro(Millivolts));
1738    property!(maximum_current, ro(Milliamps));
1739
1740    /// Converts this into a generic [`SourcePdoFixedSupply`].
1741    pub fn into_fixed_supply(self) -> SourcePdoFixedSupply {
1742        SourcePdoFixedSupply {
1743            dfd: self.dfd,
1744            path: self.path,
1745        }
1746    }
1747}
1748
1749/// A fixed-voltage source power supply.
1750#[derive(Debug)]
1751pub struct SourcePdoFixedSupply {
1752    dfd: OwnedFd,
1753    path: PdoPath,
1754}
1755
1756impl_sealed!(SourcePdoFixedSupply);
1757impl_device!(SourcePdoFixedSupply, path(PdoPath));
1758
1759impl SourcePdoFixedSupply {
1760    property!(peak_current, ro(PeakCurrent));
1761    property!(voltage, ro(Millivolts));
1762    property!(maximum_current, ro(Milliamps));
1763
1764    /// Converts this fixed supply into an instance of
1765    /// [`SinkPdoFixedSupplyVSafe5V`].
1766    ///
1767    /// If this supply is not the first one, returns an [`Err`] containing
1768    /// itself.
1769    pub fn try_into_vsafe5v(self) -> std::result::Result<SourcePdoFixedSupplyVSafe5V, Self> {
1770        if self.path().index == 1 {
1771            Ok(SourcePdoFixedSupplyVSafe5V {
1772                dfd: self.dfd,
1773                path: self.path,
1774            })
1775        } else {
1776            Err(self)
1777        }
1778    }
1779}
1780
1781/// The vSafe5V fixed-voltage power sink.
1782///
1783/// Always guaranteed to be the very first PDO.
1784#[derive(Debug)]
1785pub struct SinkPdoFixedSupplyVSafe5V {
1786    dfd: OwnedFd,
1787    path: PdoPath,
1788}
1789
1790impl_sealed!(SinkPdoFixedSupplyVSafe5V);
1791impl_device!(SinkPdoFixedSupplyVSafe5V, path(PdoPath));
1792
1793impl SinkPdoFixedSupplyVSafe5V {
1794    property!(dual_role_power, ro(bool), with(PropertyBoolIntegral));
1795    property!(higher_capability, ro(bool), with(PropertyBoolIntegral));
1796    property!(unconstrained_power, ro(bool), with(PropertyBoolIntegral));
1797    property!(
1798        usb_communication_capable,
1799        ro(bool),
1800        with(PropertyBoolIntegral)
1801    );
1802    property!(dual_role_data, ro(bool), with(PropertyBoolIntegral));
1803    property!(
1804        unchunked_extended_messages_supported,
1805        ro(bool),
1806        with(PropertyBoolIntegral)
1807    );
1808    property!(fast_role_swap_current, ro(FastRoleSwapCurrent));
1809
1810    // Shared with SinkPdoFixedSupply.
1811    property!(voltage, ro(Millivolts));
1812    property!(operational_current, ro(Milliamps));
1813
1814    /// Converts this into a generic [`SourcePdoFixedSupply`].
1815    pub fn into_fixed_supply(self) -> SinkPdoFixedSupply {
1816        SinkPdoFixedSupply {
1817            dfd: self.dfd,
1818            path: self.path,
1819        }
1820    }
1821}
1822
1823/// A fixed-voltage power sink.
1824#[derive(Debug)]
1825pub struct SinkPdoFixedSupply {
1826    dfd: OwnedFd,
1827    path: PdoPath,
1828}
1829
1830impl_sealed!(SinkPdoFixedSupply);
1831impl_device!(SinkPdoFixedSupply, path(PdoPath));
1832
1833impl SinkPdoFixedSupply {
1834    property!(voltage, ro(Millivolts));
1835    property!(operational_current, ro(Milliamps));
1836
1837    /// Converts this fixed supply into an instance of
1838    /// [`SinkPdoFixedSupplyVSafe5V`].
1839    ///
1840    /// If this supply is not the first one, returns an [`Err`] containing
1841    /// itself.
1842    pub fn try_into_vsafe5v(self) -> std::result::Result<SinkPdoFixedSupplyVSafe5V, Self> {
1843        if self.path().index == 1 {
1844            Ok(SinkPdoFixedSupplyVSafe5V {
1845                dfd: self.dfd,
1846                path: self.path,
1847            })
1848        } else {
1849            Err(self)
1850        }
1851    }
1852}
1853
1854/// A battery source power supply.
1855#[derive(Debug)]
1856pub struct SourcePdoBattery {
1857    dfd: OwnedFd,
1858    path: PdoPath,
1859}
1860
1861impl_sealed!(SourcePdoBattery);
1862impl_device!(SourcePdoBattery, path(PdoPath));
1863
1864impl SourcePdoBattery {
1865    property!(maximum_voltage, ro(Millivolts));
1866    property!(minimum_voltage, ro(Millivolts));
1867    property!(maximum_power, ro(Milliwatts));
1868}
1869
1870/// A battery power sink.
1871#[derive(Debug)]
1872pub struct SinkPdoBattery {
1873    dfd: OwnedFd,
1874    path: PdoPath,
1875}
1876
1877impl_sealed!(SinkPdoBattery);
1878impl_device!(SinkPdoBattery, path(PdoPath));
1879
1880impl SinkPdoBattery {
1881    property!(maximum_voltage, ro(Millivolts));
1882    property!(minimum_voltage, ro(Millivolts));
1883    property!(operational_power, ro(Milliwatts));
1884}
1885
1886/// A source power supply with a variable voltage within a given range.
1887#[derive(Debug)]
1888pub struct SourcePdoVariableSupply {
1889    dfd: OwnedFd,
1890    path: PdoPath,
1891}
1892
1893impl_sealed!(SourcePdoVariableSupply);
1894impl_device!(SourcePdoVariableSupply, path(PdoPath));
1895
1896impl SourcePdoVariableSupply {
1897    property!(maximum_voltage, ro(Millivolts));
1898    property!(minimum_voltage, ro(Millivolts));
1899    property!(maximum_current, ro(Milliamps));
1900}
1901
1902/// A power sink with a variable voltage within a given range.
1903#[derive(Debug)]
1904pub struct SinkPdoVariableSupply {
1905    dfd: OwnedFd,
1906    path: PdoPath,
1907}
1908
1909impl_sealed!(SinkPdoVariableSupply);
1910impl_device!(SinkPdoVariableSupply, path(PdoPath));
1911
1912impl SinkPdoVariableSupply {
1913    property!(maximum_voltage, ro(Millivolts));
1914    property!(minimum_voltage, ro(Millivolts));
1915    property!(operational_current, ro(Milliamps));
1916}
1917
1918/// A source power supply whose voltage can be changed dynamically.
1919#[derive(Debug)]
1920pub struct SourcePdoProgrammableSupply {
1921    dfd: OwnedFd,
1922    path: PdoPath,
1923}
1924
1925impl_sealed!(SourcePdoProgrammableSupply);
1926impl_device!(SourcePdoProgrammableSupply, path(PdoPath));
1927
1928impl SourcePdoProgrammableSupply {
1929    property!(power_limited, ro(bool), with(PropertyBoolIntegral));
1930    property!(maximum_voltage, ro(Millivolts));
1931    property!(minimum_voltage, ro(Millivolts));
1932    property!(maximum_current, ro(Milliamps));
1933}
1934
1935/// A power sink whose voltage can be changed dynamically.
1936#[derive(Debug)]
1937pub struct SinkPdoProgrammableSupply {
1938    dfd: OwnedFd,
1939    path: PdoPath,
1940}
1941
1942impl_sealed!(SinkPdoProgrammableSupply);
1943impl_device!(SinkPdoProgrammableSupply, path(PdoPath));
1944
1945impl SinkPdoProgrammableSupply {
1946    property!(maximum_voltage, ro(Millivolts));
1947    property!(minimum_voltage, ro(Millivolts));
1948    property!(maximum_current, ro(Milliamps));
1949}