Crate yuca

Source
Expand description

§yuca

crates.io docs.rs build status

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   |
                                 +-------------------+    +-------------------+


§Devices and DevicePaths

The above diagram has two lines for each node: a Device and its DevicePath, respectively.

DevicePaths 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 }.

Devices, 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: Devices and DevicePaths do not necessarily correspond to physical devices. Some of them, such as Ports, 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 DeviceEntrys:

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 DeviceCollections provide a race-free way to slowly make your way down the tree and observe interesting devices.

§Properties

Devices 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.

Enums§

Error

Type Aliases§

Result