Expand description
§yuca
Yuca is a Rust crate to access USB Type-C device information on Linux.
use std::error::Error;
use futures_lite::StreamExt;
use yuca::{sysfs::*, watcher::*};
fn show() -> Result<(), Box<dyn Error>> {
for port in Port::collection()?.list()? {
let port = port.open()?;
println!("Port: {}", port.path().port);
let Ok(partner) = port.partner().open() else { continue; };
println!(" Partner: {}:{}",
partner.identity().id_header().get()?.0.vendor_id(),
partner.identity().product().get()?.product_id);
}
Ok(())
}
async fn watch() -> Result<(), Box<dyn Error>> {
let (w, _) = Watcher::spawn_tokio(EventSource::Netlink)?;
let mut stream = PartnerPath::any_added(&w)?;
while let Some(path) = stream.next().await {
let Ok(path) = path else { continue; };
println!("Partner added to port: {}", path.port);
}
Ok(())
}
§Terminology
This crate uses a variety of terms from the USB Power Delivery specification, which can be obtained from here. Consult the spec’s “Terms and Abbreviations” for complete definitions.
§Overview
+----------------------+
| Port |
| PortPath |
+----------------------+
|
+------------------------+ +--------------------+
| | |
v v v
+-------------------------+ +-------------+ +---------------+
| AltMode<PortPath> | | Cable | | Partner |
| AltModePath<PortPath> | | CablePath | | PartnerPath |
+-------------------------+ +-------------+ +---------------+
| |
+-----------------------+ +----------+ +--------------+
| | |
v v v
+--------------+ +---------------------+ +----------------------------+
| Plug | | PowerDelivery | | AltMode<PartnerPath> |
| PlugPath | | PowerDeliveryPath | | AltModePath<PartnerPath> |
+--------------+ +---------------------+ +----------------------------+
| |
v +---+ +------------------+
+-------------------------+ | |
| AltMode<PlugPath> | v v
| AltModePath<PlugPath> | +----------------------+ +--------------------+
+-------------------------+ | SourceCapabilities | | SinkCapabilities |
| CapabilitiesPath | | CapabilitiesPath |
+----------------------+ +--------------------+
| |
v v
+-------------------+ +-------------------+
| SourcePdo | | SinkPdo |
| PdoPath | | PdoPath |
| | | |
| -FixedSupply | | -FixedSupply |
| -Battery | | -Battery |
| -VariableSupply | | -VariableSupply |
| -ProgramSupply | | -ProgramSupply |
+-------------------+ +-------------------+
§Device
s and DevicePath
s
The above diagram has two lines for each node: a Device
and its
DevicePath
, respectively.
DevicePath
s are structured types that can be converted on-demand to and
from subdirectories of /sys/class/typec
. For instance,
/sys/class/typec/port0/port0-partner
is a PortPath { port: 0 }
.
Device
s, on the other hand, are what you get when you actually open a
DevicePath
and grants access to its properties. If the device becomes
unavailable at any point, then accessing the properties will start returning
errors.
NOTE: Device
s and DevicePath
s do not necessarily correspond to
physical devices. Some of them, such as Port
s, do correspond to a
physical counterpart, but others like PowerDelivery
map to USB-C concepts
instead.
§DeviceEntry
A DeviceEntry
is simply an unopened child of another device:
let port: Port = get_a_port_from_somewhere();
let partner_entry: DeviceEntry<'_, Partner> = port.partner();
let partner_path: &PartnerPath = partner_entry.path();
let partner: Partner = partner_entry.open()?;
§DeviceCollection
A DeviceCollection
is a handle that lets you iterate over multiple child
DeviceEntry
s:
let ports: DeviceCollection<'_, Port> = Port::collection()?;
let port0: Port = ports.get(0)?;
for entry in ports.iter()? {
let entry: DeviceEntry<'_, Port> = entry?;
let path: &PortPath = entry.path();
let port: Port = entry.open()?;
let alt_modes: DeviceCollection<'_, AltMode<PortPath>> = port.alt_modes();
}
let ports_vec: Vec<DeviceEntry<'_, Port>> = ports.list()?;
If you know you’re going to immediately call DeviceEntry::open
, then you
can just use DeviceCollection::iter_opened
or
DeviceCollection::list_opened
:
let ports: DeviceCollection<'_, Port> = Port::collection()?;
for port in ports.iter_opened()? {
let port: Port = port?;
}
let ports_vec: Vec<Port> = ports.list_opened()?;
§DevicePathCollection
A DevicePathCollection
is akin to a DeviceCollection
but instead creates
child paths relative to a parent:
let ports: DevicePathCollection<PortPath> = PortPath::collection();
let port0: PortPath = ports.get(0);
let port01 = port0.alt_modes().get(1);
(Note that these paths, by nature of being paths, do not necessarily correspond to a device that exists on disk.)
§Opening paths directly
If you already have a DevicePath
that you’d like to immediately open, you
can use Device::open
:
let port: Port = Port::open(PortPath { port: 0 })?;
NOTE: You may wonder what the purpose of the entire DeviceCollection
and DeviceEntry
API for getting single devices if you could just construct
a DevicePath
and open it individually. The answer is that, in most cases,
you don’t actually know a single device you’re interested in ahead of time, and
DeviceCollection
s provide a race-free way to slowly make your way down the
tree and observe interesting devices.
§Properties
Device
s all have a variety of properties on them, which are
implementations of either PropertyReadable
or PropertyWritable
returned
by getter methods. This is how you can access various information about the
devices:
let port: Port = Port::collection()?.get(0)?;
let data_role: RoleSelection<DataRole> = port.data_role().get()?;
let power_role: RoleSelection<PowerRole> = port.power_role().get()?;
println!("Port 0's data role is: {data_role:?}");
println!("Port 0's power role is: {power_role:?}");
Many of the properties directly correspond to the sysfs paths, so you can use the kernel’s documentation on them as a point of reference:
- https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-typec
- https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-usb_power_delivery
Modules§
- sysfs
- Accessing the sysfs.
- types
- Various types returned from
sysfs
’s properties. - watcher
- Watching for devices.