1 // Copyright 2024 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! typec_class_utils
16
17 use crate::usb_pd_utils::*;
18 use anyhow::{bail, Context, Result};
19 use log::warn;
20 use regex::Regex;
21 use std::{
22 fs,
23 io::Write,
24 path::{Path, PathBuf},
25 };
26
27 pub const TYPEC_SYSFS_PATH: &str = "/sys/class/typec";
28
29 pub const MODE_REGEX: &str = "^mode[0-9]+$";
30 pub const PLUG_ALT_MODE_REGEX: &str = "^port[0-9]+\\-plug[0-9]+\\.[0-9]+$";
31 pub const PLUG_REGEX: &str = "^port[0-9]+\\-plug[0-9]+$";
32 pub const PORT_REGEX: &str = "^port[0-9]+$";
33 pub const PARTNER_ALT_MODE_REGEX: &str = "^port[0-9]+-partner\\.[0-9]+$";
34 pub const PARTNER_PDO_REGEX: &str = "^pd[0-9]+$";
35 pub const PDO_CAPABILITIES_REGEX: &str = "^(sink|source)-capabilities$";
36 pub const PDO_TYPE_REGEX: &str =
37 "^[0-9]+:(battery|fixed_supply|programmable_supply|variable_supply)$";
38 pub const USB_PORT_REGEX: &str = "^usb[0-9]+\\-port[0-9]+$";
39 pub const USB_DEVICE_REGEX: &str = "^[0-9]+\\-[0-9]+(\\.[0-9]+)*$";
40
41 pub const INDENT_STEP: usize = 2;
42
43 const ERR_READ_DIR: &str = "Failed to iterate over files in directory";
44 const ERR_READ_FILE: &str = "Cannot read file from path";
45 const ERR_FILE_NAME_FROM_PATH: &str = "Cannot get file name at path";
46 const ERR_FILE_NAME_UTF_INVALID: &str = "The file name is UTF-8 invalid at path";
47 const ERR_DIR_ENTRY_ACCESS: &str = "Cannot access entry in directory";
48 const ERR_WRITE_TO_BUFFER: &str = "Cannot write to output buffer. Attempted to print";
49 const ERR_PATH_NOT_DIR_OR_SYMLINK: &str = "The path is not a directory or a symbolic link";
50
51 pub struct OutputWriter<W: Write> {
52 buffer: W,
53 indent: usize,
54 }
55
56 impl<W: Write> OutputWriter<W> {
new(buffer: W, indent: usize) -> Self57 pub fn new(buffer: W, indent: usize) -> Self {
58 Self { buffer, indent }
59 }
60
61 /// Prints a string with indentation.
print_str_indent(&mut self, str_to_print: &str) -> Result<()>62 fn print_str_indent(&mut self, str_to_print: &str) -> Result<()> {
63 writeln!(self.buffer, "{:indent$}{str_to_print}", "", indent = self.indent)
64 .with_context(|| format!("{ERR_WRITE_TO_BUFFER} {:?}", str_to_print))?;
65 Ok(())
66 }
67
68 /// Prints a file's contents in a "name: content" format and also adds indentation to multiline strings.
print_file_formatted(&mut self, file_path: &Path) -> Result<()>69 fn print_file_formatted(&mut self, file_path: &Path) -> Result<()> {
70 let file_name = get_path_basename(file_path)?;
71 let file_contents = fs::read_to_string(file_path)
72 .with_context(|| format!("{ERR_READ_FILE} {:?}", file_path))?;
73 let formatted_contents = file_contents
74 .trim_ascii_end()
75 .replace("\n", format!("\n{:indent$}", "", indent = self.indent).as_str());
76 writeln!(
77 self.buffer,
78 "{:indent$}{file_name}: {formatted_contents}",
79 "",
80 indent = self.indent
81 )
82 .with_context(|| format!("{ERR_WRITE_TO_BUFFER} {:?}", formatted_contents))?;
83 Ok(())
84 }
85
86 /// Prints the name of the directory followed by all the files in it in a "name: content" format.
87 /// The files are sorted alphabetically by path to ensure consistency.
print_dir_files(&mut self, dir_path: &Path) -> Result<()>88 pub fn print_dir_files(&mut self, dir_path: &Path) -> Result<()> {
89 let dir_name = get_path_basename(dir_path)?;
90 self.print_str_indent(dir_name)?;
91
92 for path in get_sorted_paths_from_dir(dir_path, |path| path.is_file())? {
93 self.indent += INDENT_STEP;
94 let _ = self.print_file_formatted(&path).inspect_err(|err| warn!("{:?}: {err}", path));
95 self.indent -= INDENT_STEP;
96 }
97
98 Ok(())
99 }
100 }
101
102 /// Retrieves a sorted vector of paths in the given directory. Filters the paths according to `path_filter`.
103 /// Logs the errors and ignores faulty entries/paths.
get_sorted_paths_from_dir( dir_path: &Path, path_filter: fn(&PathBuf) -> bool, ) -> Result<Vec<PathBuf>>104 fn get_sorted_paths_from_dir(
105 dir_path: &Path,
106 path_filter: fn(&PathBuf) -> bool,
107 ) -> Result<Vec<PathBuf>> {
108 let mut sorted_paths: Vec<PathBuf> = fs::read_dir(dir_path)
109 .with_context(|| format!("{ERR_READ_DIR} {:?}", dir_path))?
110 .filter_map(|entry| {
111 entry
112 .inspect_err(|err| warn!("{ERR_DIR_ENTRY_ACCESS} {:?}: {err}", dir_path))
113 .map(|entry| entry.path())
114 .ok()
115 })
116 .filter(path_filter)
117 .collect();
118 sorted_paths.sort();
119 Ok(sorted_paths)
120 }
121
122 /// Returns the rightmost element of a path.
get_path_basename(path: &Path) -> Result<&str>123 fn get_path_basename(path: &Path) -> Result<&str> {
124 let basename = path
125 .file_name()
126 .with_context(|| format!("{ERR_FILE_NAME_FROM_PATH} {:?}", path))?
127 .to_str()
128 .with_context(|| format!("{ERR_FILE_NAME_UTF_INVALID} {:?}", path))?;
129 Ok(basename)
130 }
131
132 /// Decodes and prints VDOs from the files specified relative to `identity_path_buf` path.
133 /// * `filename_vdo_fields_arr` - Is an array of tuples containing the name of the file where the VDO can be found and the VDO description array (`&[VdoField]`).
print_decoded_vdos_from_files<W: Write>( identity_path_buf: &mut PathBuf, filename_vdo_fields_arr: &[(&str, &[VdoField])], out_writer: &mut OutputWriter<W>, )134 pub fn print_decoded_vdos_from_files<W: Write>(
135 identity_path_buf: &mut PathBuf,
136 filename_vdo_fields_arr: &[(&str, &[VdoField])],
137 out_writer: &mut OutputWriter<W>,
138 ) {
139 for (filename, vdo_fields) in filename_vdo_fields_arr {
140 identity_path_buf.push(filename);
141 let _ = print_vdo(identity_path_buf, vdo_fields, out_writer)
142 .inspect_err(|err| warn!("{:#}", err));
143 identity_path_buf.pop();
144 }
145 }
146
147 /// Prints the identity information of a USB device.
148 /// This function reads and decodes the identity descriptors of a USB device,
149 /// including its ID header, certification status, and product information.
150 /// The output is formatted and written using the provided `OutputWriter`.
151 /// # Arguments
152 /// * `dev_path` - A reference to the Path of the device's directory.
153 /// * `out_writer` - A mutable reference to an OutputWriter for writing the output.
154 /// # Returns
155 /// * `Result<()>` - A Result indicating success or failure of the printing operation.
print_identity<W: Write>(dev_path: &Path, out_writer: &mut OutputWriter<W>) -> Result<()>156 pub fn print_identity<W: Write>(dev_path: &Path, out_writer: &mut OutputWriter<W>) -> Result<()> {
157 out_writer.print_str_indent("identity").inspect_err(|err| warn!("{:#}", err)).ok();
158
159 out_writer.indent += INDENT_STEP;
160
161 let filename_vdo_fields_arr: &[(&str, &[VdoField]); 3] = match get_pd_revision(dev_path) {
162 PdRev::Pd20 => &[
163 ("id_header", pd20_data::ID_HEADER_VDO),
164 ("cert_stat", pd20_data::CERT_STAT_VDO),
165 ("product", pd20_data::PRODUCT_VDO),
166 ],
167 PdRev::Pd30 => &[
168 ("id_header", pd30_data::ID_HEADER_VDO),
169 ("cert_stat", pd30_data::CERT_STAT_VDO),
170 ("product", pd30_data::PRODUCT_VDO),
171 ],
172 PdRev::Pd31 => &[
173 ("id_header", pd31_data::ID_HEADER_VDO),
174 ("cert_stat", pd31_data::CERT_STAT_VDO),
175 ("product", pd31_data::PRODUCT_VDO),
176 ],
177 PdRev::None => &[("id_header", &[]), ("cert_stat", &[]), ("product", &[])],
178 };
179 print_decoded_vdos_from_files(
180 &mut dev_path.join("identity"),
181 filename_vdo_fields_arr,
182 out_writer,
183 );
184 out_writer.indent -= INDENT_STEP;
185
186 Ok(())
187 }
188
189 /// Prints the contents of an identity directory including VDO fields which are determined by product type.
print_partner_identity<W: Write>( partner_path: &Path, out_writer: &mut OutputWriter<W>, ) -> Result<()>190 pub fn print_partner_identity<W: Write>(
191 partner_path: &Path,
192 out_writer: &mut OutputWriter<W>,
193 ) -> Result<()> {
194 let mut identity_path_buf: PathBuf = partner_path.join("identity");
195 if !(identity_path_buf.is_dir() | identity_path_buf.is_symlink()) {
196 bail!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", identity_path_buf);
197 }
198
199 let _ = print_identity(partner_path, out_writer).inspect_err(|err| warn!("{:#}", err));
200
201 out_writer.indent += INDENT_STEP;
202
203 let pd_rev = get_pd_revision(partner_path);
204
205 let vdo_fields_arr: [&[VdoField]; 3] = match get_partner_product_type(partner_path, pd_rev) {
206 ProductType::Ama(PdRev::Pd20) => [pd20_data::AMA_VDO, &[], &[]],
207 ProductType::Vpd(PdRev::Pd30) => [pd30_data::VPD_VDO, &[], &[]],
208 ProductType::Ama(PdRev::Pd30) => [pd30_data::AMA_VDO, &[], &[]],
209 ProductType::Port((PortType::Ufp, PdRev::Pd30)) => {
210 [pd30_data::UFP_VDO1, pd30_data::UFP_VDO2, &[]]
211 }
212 ProductType::Port((PortType::Dfp, PdRev::Pd30)) => [pd30_data::DFP_VDO, &[], &[]],
213 ProductType::Port((PortType::Drd, PdRev::Pd30)) => {
214 [pd30_data::UFP_VDO1, pd30_data::UFP_VDO2, pd30_data::DFP_VDO]
215 }
216 ProductType::Port((PortType::Ufp, PdRev::Pd31)) => [pd31_data::UFP_VDO, &[], &[]],
217 ProductType::Port((PortType::Dfp, PdRev::Pd31)) => [pd31_data::DFP_VDO, &[], &[]],
218 ProductType::Port((PortType::Drd, PdRev::Pd31)) => {
219 [pd31_data::UFP_VDO, &[], pd31_data::DFP_VDO]
220 }
221 _ => [&[], &[], &[]],
222 };
223 let filenames = ["product_type_vdo1", "product_type_vdo2", "product_type_vdo3"];
224 let filename_vdo_fields_arr: Vec<(&str, &[VdoField])> =
225 filenames.into_iter().zip(vdo_fields_arr).collect();
226
227 print_decoded_vdos_from_files(&mut identity_path_buf, &filename_vdo_fields_arr, out_writer);
228
229 out_writer.indent -= INDENT_STEP;
230
231 Ok(())
232 }
233
234 /// Reads the usb_power_delivery_revision file in a given directory
235 /// and returns a PdRev enum noting the supported PD Revision.
get_pd_revision(dir_path: &Path) -> PdRev236 pub fn get_pd_revision(dir_path: &Path) -> PdRev {
237 let pd_path: PathBuf = dir_path.join("usb_power_delivery_revision");
238 let pd_revision_str = fs::read_to_string(&pd_path)
239 .inspect_err(|err| warn!("{ERR_READ_FILE} {:?} {:#}", &pd_path, err))
240 .unwrap_or_default();
241 match pd_revision_str.trim_ascii() {
242 "2.0" => PdRev::Pd20,
243 "3.0" => PdRev::Pd30,
244 "3.1" => PdRev::Pd31,
245 _ => PdRev::None,
246 }
247 }
248
249 /// Uses the id_header VDO and USB PD revision to decode what type of device is being parsed.
250 /// In case of failure returns ProductType::Other.
get_partner_product_type(partner_dir: &Path, pd_rev: PdRev) -> ProductType251 pub fn get_partner_product_type(partner_dir: &Path, pd_rev: PdRev) -> ProductType {
252 let id_header_vdo_path: PathBuf = partner_dir.join("identity").join("id_header");
253
254 let id_header_vdo = match read_vdo(&id_header_vdo_path) {
255 Ok(vdo) => vdo,
256 Err(err) => {
257 warn!("{:#}", err);
258 return ProductType::Other;
259 }
260 };
261
262 match pd_rev {
263 PdRev::Pd20 => {
264 // Alternate Mode Adapter (AMA) is the only partner product type in the
265 // USB PD 2.0 specification.
266 match id_header_vdo & UFP_PRODUCT_TYPE_MASK {
267 pd20_data::AMA_COMP => ProductType::Ama(PdRev::Pd20),
268 _ => ProductType::Other,
269 }
270 }
271 PdRev::Pd30 => {
272 // In USB PD 3.0 a partner can be an upstream facing port (UFP),
273 // downstream facing port (DFP), or a dual-role data port (DRD).
274 // Information about UFP/DFP are in different fields, so they are checked
275 // separately then compared to determine a partner's product type.
276 // Separate from UFP/DFP, they can support AMA/VPD as a UFP type.
277
278 let ufp_supported = match id_header_vdo & UFP_PRODUCT_TYPE_MASK {
279 pd30_data::HUB_COMP => true,
280 pd30_data::PERIPHERAL_COMP => true,
281 pd30_data::AMA_COMP => return ProductType::Ama(PdRev::Pd30),
282 pd30_data::VPD_COMP => return ProductType::Vpd(PdRev::Pd30),
283 _ => false,
284 };
285
286 let dfp_supported = matches!(
287 id_header_vdo & DFP_PRODUCT_TYPE_MASK,
288 pd30_data::DFP_HUB_COMP | pd30_data::DFP_HOST_COMP | pd30_data::POWER_BRICK_COMP
289 );
290
291 match (ufp_supported, dfp_supported) {
292 (true, true) => ProductType::Port((PortType::Drd, PdRev::Pd30)),
293 (true, false) => ProductType::Port((PortType::Ufp, PdRev::Pd30)),
294 (false, true) => ProductType::Port((PortType::Dfp, PdRev::Pd30)),
295 _ => ProductType::Other,
296 }
297 }
298 PdRev::Pd31 => {
299 // Similar to USB PD 3.0, USB PD 3.1 can have a partner which is both UFP
300 // and DFP (DRD). AMA product type is no longer present in PD 3.1.
301 let ufp_supported = matches!(
302 id_header_vdo & UFP_PRODUCT_TYPE_MASK,
303 pd31_data::HUB_COMP | pd31_data::PERIPHERAL_COMP
304 );
305
306 let dfp_supported = matches!(
307 id_header_vdo & DFP_PRODUCT_TYPE_MASK,
308 pd31_data::DFP_HOST_COMP | pd31_data::DFP_HUB_COMP | pd31_data::POWER_BRICK_COMP
309 );
310
311 match (ufp_supported, dfp_supported) {
312 (true, true) => ProductType::Port((PortType::Drd, PdRev::Pd31)),
313 (true, false) => ProductType::Port((PortType::Ufp, PdRev::Pd31)),
314 (false, true) => ProductType::Port((PortType::Dfp, PdRev::Pd31)),
315 _ => ProductType::Other,
316 }
317 }
318 _ => ProductType::Other,
319 }
320 }
321
322 /// Reads a vdo value from a text file and converts it to a u32 variable. Then, prints out the values of each field according to the vdo_description.
print_vdo<W: Write>( vdo_path: &Path, vdo_description: &[VdoField], out_writer: &mut OutputWriter<W>, ) -> Result<()>323 pub fn print_vdo<W: Write>(
324 vdo_path: &Path,
325 vdo_description: &[VdoField],
326 out_writer: &mut OutputWriter<W>,
327 ) -> Result<()> {
328 let vdo = read_vdo(vdo_path)?;
329 let vdo_str = format!("{}: 0x{:x}", get_path_basename(vdo_path)?, vdo);
330 let _ = out_writer.print_str_indent(&vdo_str).inspect_err(|err| warn!("{:#}", err));
331 out_writer.indent += INDENT_STEP;
332 for vdo_field in vdo_description {
333 let _ = out_writer
334 .print_str_indent(&vdo_field.decode_vdo(vdo))
335 .inspect_err(|err| warn!("{:#}", err)); // log error but continue iterating.
336 }
337 out_writer.indent -= INDENT_STEP;
338 Ok(())
339 }
340
341 /// Reads a file containing a 32 bit VDO value and loads it into a u32.
read_vdo(vdo_path: &Path) -> Result<u32>342 pub fn read_vdo(vdo_path: &Path) -> Result<u32> {
343 let vdo_str =
344 fs::read_to_string(vdo_path).with_context(|| format!("{ERR_READ_FILE} {:?}", vdo_path))?;
345 let vdo_str = vdo_str.trim_ascii();
346 let vdo: u32 =
347 u32::from_str_radix(vdo_str.trim_start_matches("0x"), 16).with_context(|| {
348 format!("Cannot convert string read from path: {:?} to u32: {vdo_str}", vdo_path)
349 })?;
350 Ok(vdo)
351 }
352
353 // Prints the immediate files in an alternate mode directory, then prints the files in each mode subdirectory.
print_alt_mode<W: Write>(alt_mode_dir_path: &Path, out_writer: &mut OutputWriter<W>)354 pub fn print_alt_mode<W: Write>(alt_mode_dir_path: &Path, out_writer: &mut OutputWriter<W>) {
355 if !(alt_mode_dir_path.is_dir() | alt_mode_dir_path.is_symlink()) {
356 warn!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", alt_mode_dir_path);
357 return;
358 }
359
360 let _ = out_writer.print_dir_files(alt_mode_dir_path).inspect_err(|err| warn!("{:#}", err));
361 out_writer.indent += INDENT_STEP;
362 let _ = parse_dirs_and_execute(
363 alt_mode_dir_path,
364 out_writer,
365 MODE_REGEX,
366 |path: &Path, out_writer: &mut OutputWriter<W>| {
367 let _ = out_writer.print_dir_files(path).inspect_err(|err| warn!("{:#}", err));
368 },
369 )
370 .inspect_err(|err| warn!("{:#}", err));
371 out_writer.indent -= INDENT_STEP;
372 }
373
374 // Prints detailed information about the PDOs given at capabilities_dir_path, including available voltages and currents.
print_pdo_capabilities<W: Write>(capabilities: &Path, out_writer: &mut OutputWriter<W>)375 pub fn print_pdo_capabilities<W: Write>(capabilities: &Path, out_writer: &mut OutputWriter<W>) {
376 let _ = out_writer.print_dir_files(capabilities).inspect_err(|err| warn!("{:#}", err));
377 out_writer.indent += INDENT_STEP;
378 let _ = parse_dirs_and_execute(
379 capabilities,
380 out_writer,
381 PDO_TYPE_REGEX,
382 |path: &Path, out_writer: &mut OutputWriter<W>| {
383 let _ = out_writer.print_dir_files(path).inspect_err(|err| warn!("{:#}", err));
384 },
385 )
386 .inspect_err(|err| warn!("{:#}", err));
387 out_writer.indent -= INDENT_STEP;
388 }
389
390 // Prints the immediate files in a PDO data directory, then call
391 // print_pdo_capabilities to print more detailed PDO information.
print_pdos<W: Write>(pdo_dir_path: &Path, out_writer: &mut OutputWriter<W>)392 pub fn print_pdos<W: Write>(pdo_dir_path: &Path, out_writer: &mut OutputWriter<W>) {
393 if !(pdo_dir_path.is_dir() | pdo_dir_path.is_symlink()) {
394 warn!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", pdo_dir_path);
395 return;
396 }
397
398 let _ = out_writer.print_dir_files(pdo_dir_path).inspect_err(|err| warn!("{:#}", err));
399 out_writer.indent += INDENT_STEP;
400 let _ = parse_dirs_and_execute(
401 pdo_dir_path,
402 out_writer,
403 PDO_CAPABILITIES_REGEX,
404 print_pdo_capabilities,
405 )
406 .inspect_err(|err| warn!("{:#}", err));
407 out_writer.indent -= INDENT_STEP;
408 }
409
410 /// Prints the immediate information in the partner directory, then prints identity, alternate mode, and power data objects (PDO) information.
print_partner<W: Write>(port_path: &Path, out_writer: &mut OutputWriter<W>) -> Result<()>411 pub fn print_partner<W: Write>(port_path: &Path, out_writer: &mut OutputWriter<W>) -> Result<()> {
412 let partner_dir_name = [get_path_basename(port_path)?, "-partner"].concat();
413 let partner_path_buf: PathBuf = port_path.join(partner_dir_name);
414
415 if !(partner_path_buf.is_dir() | partner_path_buf.is_symlink()) {
416 bail!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", partner_path_buf);
417 }
418
419 let _ = out_writer.print_dir_files(&partner_path_buf).inspect_err(|err| warn!("{:#}", err));
420
421 out_writer.indent += INDENT_STEP;
422
423 let _ =
424 print_partner_identity(&partner_path_buf, out_writer).inspect_err(|err| warn!("{:#}", err));
425
426 let _ = parse_dirs_and_execute(
427 &partner_path_buf,
428 out_writer,
429 PARTNER_ALT_MODE_REGEX,
430 print_alt_mode,
431 )
432 .inspect_err(|err| warn!("{:#}", err));
433
434 let _ = parse_dirs_and_execute(&partner_path_buf, out_writer, PARTNER_PDO_REGEX, print_pdos)
435 .inspect_err(|err| warn!("{:#}", err));
436
437 out_writer.indent -= INDENT_STEP;
438 Ok(())
439 }
440
441 /// Determines the product type of a USB cable by reading its identity descriptors.
442 /// This function reads the "id_header" file within the "identity" directory of the cable's path
443 /// to determine its product type based on the USB PD revision and product mask.
444 /// # Arguments
445 /// * `dir` - A reference to the Path of the cable's directory.
446 /// # Returns
447 /// * `ProductType` - The determined product type of the cable.
get_cable_product_type(dir: &Path) -> ProductType448 pub fn get_cable_product_type(dir: &Path) -> ProductType {
449 let id_header = match read_vdo(&dir.join("identity").join("id_header")) {
450 Err(err) => {
451 warn!("get cable type failed to read vdo: {:#}", err);
452 return ProductType::Other;
453 }
454 Ok(id_header) => id_header,
455 };
456
457 let pd = get_pd_revision(dir);
458 let prod_mask = id_header & UFP_PRODUCT_TYPE_MASK;
459
460 // All USB PD 3.1/3.0/2.0 support passive cables.
461 if pd == PdRev::Pd20 && prod_mask == pd20_data::PASSIVE_CABLE_COMP
462 || pd == PdRev::Pd30 && prod_mask == pd30_data::PASSIVE_CABLE_COMP
463 || pd == PdRev::Pd31 && prod_mask == pd31_data::PASSIVE_CABLE_COMP
464 {
465 return ProductType::Cable((CableType::Passive, pd));
466 }
467
468 // All USB PD 3.1/3.0/2.0 support active cables.
469 if pd == PdRev::Pd20 && prod_mask == pd20_data::ACTIVE_CABLE_COMP
470 || pd == PdRev::Pd30 && prod_mask == pd30_data::ACTIVE_CABLE_COMP
471 || pd == PdRev::Pd31 && prod_mask == pd31_data::ACTIVE_CABLE_COMP
472 {
473 return ProductType::Cable((CableType::Active, pd));
474 }
475
476 // USB PD 3.1 also supports Vconn Powered Devices (VPD).
477 if pd == PdRev::Pd31 && prod_mask == pd31_data::VPD_COMP {
478 return ProductType::Cable((CableType::Vpd, pd));
479 }
480
481 ProductType::Other
482 }
483
484 /// Prints the identity information of a USB cable.
485 /// This function prints the identity information of a USB cable, including its product type and
486 /// decoded VDOs (Vendor-Defined Objects) based on its PD revision.
487 /// # Arguments
488 /// * `cable` - A reference to the Path of the cable's directory.
489 /// * `out_writer` - A mutable reference to an OutputWriter for writing the output.
print_cable_identity<W: Write>(cable: &Path, out_writer: &mut OutputWriter<W>)490 pub fn print_cable_identity<W: Write>(cable: &Path, out_writer: &mut OutputWriter<W>) {
491 let mut identity = cable.join("identity");
492 if !(identity.is_dir() | identity.is_symlink()) {
493 warn!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", identity);
494 return;
495 }
496
497 let _ = print_identity(cable, out_writer).inspect_err(|err| warn!("{:#}", err));
498
499 out_writer.indent += INDENT_STEP;
500
501 let vdos: [&[VdoField]; 3] = match get_cable_product_type(cable) {
502 ProductType::Cable(cable_type) => match cable_type {
503 (CableType::Passive, PdRev::Pd20) => [pd20_data::PASSIVE_VDO, &[], &[]],
504 (CableType::Active, PdRev::Pd20) => [pd20_data::ACTIVE_VDO, &[], &[]],
505 (CableType::Passive, PdRev::Pd30) => [pd30_data::PASSIVE_VDO, &[], &[]],
506 (CableType::Active, PdRev::Pd30) => {
507 [pd30_data::ACTIVE_VDO1, pd30_data::ACTIVE_VDO2, &[]]
508 }
509 (CableType::Passive, PdRev::Pd31) => [pd31_data::PASSIVE_VDO, &[], &[]],
510 (CableType::Active, PdRev::Pd31) => {
511 [pd31_data::ACTIVE_VDO1, pd31_data::ACTIVE_VDO2, &[]]
512 }
513 (CableType::Vpd, PdRev::Pd31) => [pd31_data::VPD_VDO, &[], &[]],
514 _ => [&[], &[], &[]],
515 },
516 _ => [&[], &[], &[]],
517 };
518
519 let descriptions = ["product_type_vdo1", "product_type_vdo2", "product_type_vdo3"];
520 let to_print: Vec<(&str, &[VdoField])> = descriptions.into_iter().zip(vdos).collect();
521
522 print_decoded_vdos_from_files(&mut identity, &to_print, out_writer);
523
524 out_writer.indent -= INDENT_STEP;
525 }
526
527 /// Prints information about a USB cable plug.
528 /// This function prints information about a USB cable plug, including its alternate modes.
529 /// # Arguments
530 /// * `plug` - A reference to the Path of the plug's directory.
531 /// * `out_writer` - A mutable reference to an OutputWriter for writing the output.
print_plug_info<W: Write>(plug: &Path, out_writer: &mut OutputWriter<W>)532 pub fn print_plug_info<W: Write>(plug: &Path, out_writer: &mut OutputWriter<W>) {
533 if !(plug.is_dir() | plug.is_symlink()) {
534 warn!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", plug);
535 return;
536 }
537
538 let _ = out_writer.print_dir_files(plug);
539 out_writer.indent += INDENT_STEP;
540 let _ = parse_dirs_and_execute(plug, out_writer, PLUG_ALT_MODE_REGEX, print_alt_mode)
541 .inspect_err(|err| warn!("{:#}", err));
542 out_writer.indent -= INDENT_STEP;
543 }
544
545 /// Prints information about a USB cable connected to a port.
546 /// This function prints information about a USB cable connected to a port, including its identity,
547 /// plug information, and alternate modes.
548 /// # Arguments
549 /// * `port` - A reference to the Path of the port's directory.
550 /// * `out_writer` - A mutable reference to an OutputWriter for writing the output.
551 /// # Returns
552 /// * `Result<()>` - A Result indicating success or failure.
print_cable<W: Write>(port: &Path, out_writer: &mut OutputWriter<W>) -> Result<()>553 pub fn print_cable<W: Write>(port: &Path, out_writer: &mut OutputWriter<W>) -> Result<()> {
554 let cable_path = format!("{}-cable", get_path_basename(port)?);
555 let cable_dir = port.join(cable_path);
556
557 if !(cable_dir.is_dir() | cable_dir.is_symlink()) {
558 bail!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", cable_dir);
559 }
560
561 out_writer.print_dir_files(&cable_dir).inspect_err(|err| warn!("{:#}", err)).ok();
562
563 out_writer.indent += INDENT_STEP;
564 print_cable_identity(&cable_dir, out_writer);
565 let _ = parse_dirs_and_execute(&cable_dir, out_writer, PLUG_REGEX, print_plug_info)
566 .inspect_err(|err| warn!("{:#}", err));
567 out_writer.indent -= INDENT_STEP;
568
569 Ok(())
570 }
571
572 /// Prints the panel and horizontal_position in the physical_location directory of a port.
print_physical_location<W: Write>( port_path: &Path, out_writer: &mut OutputWriter<W>, ) -> Result<()>573 pub fn print_physical_location<W: Write>(
574 port_path: &Path,
575 out_writer: &mut OutputWriter<W>,
576 ) -> Result<()> {
577 let physical_location_dir = port_path.join("physical_location");
578 if !(physical_location_dir.is_dir() | physical_location_dir.is_symlink()) {
579 bail!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", physical_location_dir);
580 }
581 out_writer.print_str_indent("physical_location")?;
582
583 out_writer.indent += INDENT_STEP;
584 out_writer.print_file_formatted(&physical_location_dir.join("panel"))?;
585 out_writer.print_file_formatted(&physical_location_dir.join("horizontal_position"))?;
586 out_writer.indent -= INDENT_STEP;
587
588 Ok(())
589 }
590
591 // Prints the `busnum`, `devnum`, `devpath` in the usb device directory, which are minimal
592 // info needed to map to corresponding peripheral.
print_usb_device_info<W: Write>(usb_device: &Path, out_writer: &mut OutputWriter<W>)593 pub fn print_usb_device_info<W: Write>(usb_device: &Path, out_writer: &mut OutputWriter<W>) {
594 out_writer.print_str_indent("usb_device").inspect_err(|err| warn!("{:#}", err)).ok();
595
596 out_writer.indent += INDENT_STEP;
597 out_writer
598 .print_file_formatted(&usb_device.join("busnum"))
599 .inspect_err(|err| warn!("{:#}", err))
600 .ok();
601 out_writer
602 .print_file_formatted(&usb_device.join("devnum"))
603 .inspect_err(|err| warn!("{:#}", err))
604 .ok();
605 out_writer
606 .print_file_formatted(&usb_device.join("devpath"))
607 .inspect_err(|err| warn!("{:#}", err))
608 .ok();
609 parse_dirs_and_execute(usb_device, out_writer, USB_DEVICE_REGEX, print_usb_device_info)
610 .inspect_err(|err| warn!("{:#}", err))
611 .ok();
612 out_writer.indent -= INDENT_STEP;
613 }
614
615 // Finds and prints information about the usb device in the usb port directory.
print_usb_device<W: Write>(usb_port: &Path, out_writer: &mut OutputWriter<W>)616 pub fn print_usb_device<W: Write>(usb_port: &Path, out_writer: &mut OutputWriter<W>) {
617 let usb_device_dir = usb_port.join("device");
618 if !(usb_device_dir.is_dir() | usb_device_dir.is_symlink()) {
619 warn!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", usb_device_dir);
620 return;
621 }
622
623 print_usb_device_info(&usb_device_dir, out_writer);
624 }
625
626 /// Finds the type-c port's linked usb ports and print relevant usb subsystem information.
print_usb_subsystem<W: Write>( port_path: &Path, out_writer: &mut OutputWriter<W>, ) -> Result<()>627 pub fn print_usb_subsystem<W: Write>(
628 port_path: &Path,
629 out_writer: &mut OutputWriter<W>,
630 ) -> Result<()> {
631 let _ = parse_dirs_and_execute(port_path, out_writer, USB_PORT_REGEX, print_usb_device);
632 Ok(())
633 }
634
635 /// Prints `connector_id` and `status` in the drm_connector directory which is a symlink
636 /// to the corresponding DP connector.
print_drm_subsystem<W: Write>( port_path: &Path, out_writer: &mut OutputWriter<W>, ) -> Result<()>637 pub fn print_drm_subsystem<W: Write>(
638 port_path: &Path,
639 out_writer: &mut OutputWriter<W>,
640 ) -> Result<()> {
641 let drm_path = port_path.join("drm_connector");
642 if !(drm_path.is_dir() | drm_path.is_symlink()) {
643 bail!("{ERR_PATH_NOT_DIR_OR_SYMLINK}: {:?}", drm_path);
644 }
645 out_writer.print_str_indent("dp_connector")?;
646
647 out_writer.indent += INDENT_STEP;
648 out_writer.print_file_formatted(&drm_path.join("connector_id"))?;
649 out_writer.print_file_formatted(&drm_path.join("status"))?;
650 out_writer.indent -= INDENT_STEP;
651
652 Ok(())
653 }
654
655 /// Prints relevant type-c connector class information for the port located at the sysfs path "port_path".
print_port_info<W: Write>(port_path: &Path, out_writer: &mut OutputWriter<W>)656 pub fn print_port_info<W: Write>(port_path: &Path, out_writer: &mut OutputWriter<W>) {
657 let _ = out_writer.print_dir_files(port_path).inspect_err(|err| warn!("{:#}", err));
658 out_writer.indent += INDENT_STEP;
659 let _ = print_partner(port_path, out_writer).inspect_err(|err| warn!("{:#}", err));
660 print_cable(port_path, out_writer).inspect_err(|err| warn!("{:#}", err)).ok();
661 print_physical_location(port_path, out_writer).inspect_err(|err| warn!("{:#}", err)).ok();
662 print_usb_subsystem(port_path, out_writer).inspect_err(|err| warn!("{:#}", err)).ok();
663 print_drm_subsystem(port_path, out_writer).inspect_err(|err| warn!("{:#}", err)).ok();
664 out_writer.indent -= INDENT_STEP;
665 }
666
667 /// Looks at subdirectories of a given directory and executes a passed function on directories matching a given regular expression.
668 /// Directories are sorted alphabetically by path to ensure consistency in the order of processing.
parse_dirs_and_execute<W: Write>( dir_path: &Path, out_writer: &mut OutputWriter<W>, regex: &str, func: fn(&Path, &mut OutputWriter<W>), ) -> Result<()>669 pub fn parse_dirs_and_execute<W: Write>(
670 dir_path: &Path,
671 out_writer: &mut OutputWriter<W>,
672 regex: &str,
673 func: fn(&Path, &mut OutputWriter<W>),
674 ) -> Result<()> {
675 let re = Regex::new(regex).unwrap();
676 for path in get_sorted_paths_from_dir(dir_path, |path| path.is_dir() | path.is_symlink())? {
677 // Assumption: all the symlinks created by the Type-C connector class point to directories.
678 get_path_basename(&path)
679 .inspect_err(|err| warn!("{:#}", err))
680 .is_ok_and(|file_name| re.is_match(file_name))
681 .then(|| func(&path, out_writer));
682 }
683 Ok(())
684 }
685