Skip to content

Commit e15dcf0

Browse files
committed
feat(Config): add udev matching for composite device configs
This change adds a new 'udev' property to the 'matches' section of the composite device config, allowing you to match composite devices based on a pre-defined syspath.
1 parent 7a54be8 commit e15dcf0

File tree

3 files changed

+165
-0
lines changed

3 files changed

+165
-0
lines changed

rootfs/usr/share/inputplumber/schema/composite_device_v1.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@
106106
"properties": {
107107
"dmi_data": {
108108
"$ref": "#/definitions/DMIMatch"
109+
},
110+
"udev": {
111+
"$ref": "#/definitions/Udev"
109112
}
110113
},
111114
"title": "Match"

src/config/mod.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ pub struct CompositeDeviceConfigOptions {
193193
pub struct Match {
194194
#[serde(skip_serializing_if = "Option::is_none")]
195195
pub dmi_data: Option<DMIMatch>,
196+
#[serde(skip_serializing_if = "Option::is_none")]
197+
pub udev: Option<Udev>,
196198
}
197199

198200
/// Match DMI data for loading a [CompositeDevice]
@@ -905,6 +907,138 @@ impl CompositeDeviceConfig {
905907
let conf = match_config.clone();
906908
let mut has_matches = false;
907909

910+
if let Some(udev) = match_config.udev {
911+
let Some(sys_path) = udev.sys_path else {
912+
log::warn!("Match using udev MUST define 'sys_path'");
913+
return None;
914+
};
915+
916+
let device = UdevDevice::from_syspath(sys_path.as_str());
917+
918+
if let Some(subsystem_pattern) = udev.subsystem {
919+
let subsystem = device.subsystem();
920+
if !glob_match(&subsystem_pattern, &subsystem) {
921+
continue;
922+
}
923+
has_matches = true;
924+
}
925+
926+
if let Some(driver_pattern) = udev.driver {
927+
let drivers = device.drivers();
928+
let mut has_matching_driver = false;
929+
for driver in drivers {
930+
if !glob_match(&driver_pattern, &driver) {
931+
continue;
932+
}
933+
has_matching_driver = true;
934+
break;
935+
}
936+
has_matches = has_matching_driver;
937+
}
938+
939+
if let Some(sys_name_pattern) = udev.sys_name {
940+
let sys_name = device.sysname();
941+
if !glob_match(&sys_name_pattern, &sys_name) {
942+
continue;
943+
}
944+
has_matches = true;
945+
}
946+
947+
if let Some(dev_node_pattern) = udev.dev_node {
948+
let dev_node = device.devnode();
949+
if !glob_match(&dev_node_pattern, &dev_node) {
950+
continue;
951+
}
952+
has_matches = true;
953+
}
954+
955+
if let Some(dev_path_pattern) = udev.dev_path {
956+
let dev_path = device.devpath();
957+
if !glob_match(&dev_path_pattern, &dev_path) {
958+
continue;
959+
}
960+
has_matches = true;
961+
}
962+
963+
if let Some(vendor_id_pattern) = udev.vendor_id {
964+
let vendor_id = device.id_vendor().to_string();
965+
if !glob_match(&vendor_id_pattern, &vendor_id) {
966+
continue;
967+
}
968+
has_matches = true;
969+
}
970+
971+
if let Some(product_id_pattern) = udev.product_id {
972+
let product_id = device.id_product().to_string();
973+
if !glob_match(&product_id_pattern, &product_id) {
974+
continue;
975+
}
976+
has_matches = true;
977+
}
978+
979+
if let Some(iface_num_pattern) = udev.interface_number {
980+
let iface_num = device.interface_number().to_string();
981+
if !glob_match(&iface_num_pattern, &iface_num) {
982+
continue;
983+
}
984+
has_matches = true;
985+
}
986+
987+
if let Some(attribute_patterns) = udev.attributes {
988+
let attributes = device.get_attributes();
989+
let mut all_attributes_match = true;
990+
991+
// All attribute patterns in the config must match
992+
for attr_pattern in attribute_patterns {
993+
let attr_name = attr_pattern.name;
994+
let Some(attr_value) = attributes.get(&attr_name) else {
995+
// If the attribute was not found, this is not a match
996+
all_attributes_match = false;
997+
break;
998+
};
999+
1000+
let Some(value_pattern) = attr_pattern.value else {
1001+
// If no value was given, then assume '**' pattern
1002+
continue;
1003+
};
1004+
1005+
if !glob_match(&value_pattern, attr_value) {
1006+
all_attributes_match = false;
1007+
break;
1008+
}
1009+
}
1010+
1011+
has_matches = all_attributes_match;
1012+
}
1013+
1014+
if let Some(property_patterns) = udev.properties {
1015+
let properties = device.get_properties();
1016+
let mut all_properties_match = true;
1017+
1018+
// All property patterns in the config must match
1019+
for prop_pattern in property_patterns {
1020+
let prop_name = prop_pattern.name;
1021+
let Some(prop_value) = properties.get(&prop_name) else {
1022+
// If the attribute was not found, this is not a match
1023+
all_properties_match = false;
1024+
break;
1025+
};
1026+
1027+
let Some(value_pattern) = prop_pattern.value else {
1028+
// If no value was given, then assume '**' pattern
1029+
continue;
1030+
};
1031+
1032+
if !glob_match(&value_pattern, prop_value) {
1033+
all_properties_match = false;
1034+
break;
1035+
}
1036+
}
1037+
1038+
has_matches = all_properties_match;
1039+
}
1040+
}
1041+
9081042
if let Some(dmi_config) = match_config.dmi_data {
9091043
if let Some(cpu_vendor) = dmi_config.cpu_vendor {
9101044
if !glob_match(

src/udev/device.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,34 @@ impl UdevDevice {
427427
UdevDevice::from_devnode(base_path, name)
428428
}
429429

430+
/// Returns a UdevDevice object from the given syspath.
431+
/// e.g. UdevDevice::from_syspath("/sys/firmware/devicetree/base");
432+
pub fn from_syspath(path: &str) -> Self {
433+
let path = PathBuf::from(path);
434+
let device = match ::udev::Device::from_syspath(path.as_path()) {
435+
Ok(dev) => dev,
436+
Err(_) => return Default::default(),
437+
};
438+
439+
Self {
440+
devnode: device
441+
.devnode()
442+
.map(|p| p.to_string_lossy().to_string())
443+
.unwrap_or_default(),
444+
subsystem: device
445+
.subsystem()
446+
.map(|s| s.to_string_lossy().to_string())
447+
.unwrap_or_default(),
448+
syspath: device.syspath().to_string_lossy().to_string(),
449+
sysname: device.sysname().to_string_lossy().to_string(),
450+
name: Some(device.name().to_string()),
451+
vendor_id: None,
452+
product_id: None,
453+
bus_type: None,
454+
properties: Default::default(),
455+
}
456+
}
457+
430458
/// Returns a udev::Device from the stored syspath.
431459
pub fn get_device(&self) -> Result<::udev::Device, Box<dyn Error + Send + Sync>> {
432460
match ::udev::Device::from_syspath(Path::new(self.syspath.as_str())) {

0 commit comments

Comments
 (0)