fuchsia_driver_dev/
lib.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{Context, Result, anyhow, format_err};
6use {fidl_fuchsia_driver_development as fdd, fidl_fuchsia_driver_framework as fdf};
7
8#[derive(Debug)]
9pub struct Device(pub fdd::NodeInfo);
10
11impl Device {
12    /// Gets the full moniker name of the device.
13    pub fn get_moniker(&self) -> Result<&str> {
14        let moniker = self.0.moniker.as_ref();
15        Ok(moniker.ok_or_else(|| format_err!("Missing moniker"))?)
16    }
17
18    /// Gets the full identifying path name of the device.
19    pub fn get_full_name(&self) -> Result<&str> {
20        self.get_moniker()
21    }
22
23    /// Gets the last ordinal of the device's moniker.
24    ///
25    /// For a `moniker` value of "this.is.a.moniker.foo.bar", "bar" will be returned.
26    pub fn extract_name(&self) -> Result<&str> {
27        let moniker = self.get_moniker()?;
28        let (_, name) = moniker.rsplit_once('.').unwrap_or(("", &moniker));
29        Ok(name)
30    }
31}
32
33impl std::convert::From<fdd::NodeInfo> for Device {
34    fn from(device_info: fdd::NodeInfo) -> Device {
35        Device(device_info)
36    }
37}
38
39/// Combines pagination results into a single vector.
40pub async fn get_device_info(
41    service: &fdd::ManagerProxy,
42    device_filter: &[String],
43    exact_match: bool,
44) -> Result<Vec<fdd::NodeInfo>> {
45    let (iterator, iterator_server) =
46        fidl::endpoints::create_proxy::<fdd::NodeInfoIteratorMarker>();
47
48    service
49        .get_node_info(device_filter, iterator_server, exact_match)
50        .context("FIDL call to get device info failed")?;
51
52    let mut info_result = Vec::new();
53
54    'outer: loop {
55        // To minimize round trips we request several results in one go.
56        let device_info_futures = vec![
57            iterator.get_next(),
58            iterator.get_next(),
59            iterator.get_next(),
60            iterator.get_next(),
61            iterator.get_next(),
62            iterator.get_next(),
63            iterator.get_next(),
64            iterator.get_next(),
65            iterator.get_next(),
66            iterator.get_next(),
67            iterator.get_next(),
68            iterator.get_next(),
69        ];
70        let device_info_result = futures::future::join_all(device_info_futures).await;
71        for result in device_info_result {
72            let mut device_info = result.context("FIDL call to get device info failed")?;
73            if device_info.len() == 0 {
74                break 'outer;
75            }
76            info_result.append(&mut device_info)
77        }
78    }
79    Ok(info_result)
80}
81
82/// Combines pagination results into a single vector.
83pub async fn get_driver_info(
84    service: &fdd::ManagerProxy,
85    driver_filter: &[String],
86) -> Result<Vec<fdf::DriverInfo>> {
87    let (iterator, iterator_server) =
88        fidl::endpoints::create_proxy::<fdd::DriverInfoIteratorMarker>();
89
90    service
91        .get_driver_info(driver_filter, iterator_server)
92        .context("FIDL call to get driver info failed")?;
93
94    let mut info_result = Vec::new();
95    'outer: loop {
96        // To minimize round trips we request several results in one go.
97        let driver_info_futures = vec![
98            iterator.get_next(),
99            iterator.get_next(),
100            iterator.get_next(),
101            iterator.get_next(),
102            iterator.get_next(),
103            iterator.get_next(),
104            iterator.get_next(),
105            iterator.get_next(),
106        ];
107        let driver_info_result = futures::future::join_all(driver_info_futures).await;
108        for result in driver_info_result {
109            let mut driver_info = result.context("FIDL call to get driver info failed")?;
110            if driver_info.len() == 0 {
111                break 'outer;
112            }
113            info_result.append(&mut driver_info)
114        }
115    }
116    Ok(info_result)
117}
118
119/// Combines pagination results into a single vector.
120pub async fn get_driver_host_info(service: &fdd::ManagerProxy) -> Result<Vec<fdd::DriverHostInfo>> {
121    let (iterator, iterator_server) =
122        fidl::endpoints::create_proxy::<fdd::DriverHostInfoIteratorMarker>();
123
124    service
125        .get_driver_host_info(iterator_server)
126        .context("FIDL call to get driver host info failed")?;
127
128    let mut info_result = Vec::new();
129    loop {
130        let mut info =
131            iterator.get_next().await.context("FIDL call to get driver host info failed")?;
132        if info.is_empty() {
133            break;
134        }
135        info_result.append(&mut info)
136    }
137    Ok(info_result)
138}
139
140/// Combines pagination results into a single vector.
141pub async fn get_composite_node_specs(
142    service: &fdd::ManagerProxy,
143    name_filter: Option<String>,
144) -> Result<Vec<fdf::CompositeInfo>> {
145    let (iterator, iterator_server) =
146        fidl::endpoints::create_proxy::<fdd::CompositeNodeSpecIteratorMarker>();
147
148    service
149        .get_composite_node_specs(name_filter.as_deref(), iterator_server)
150        .context("FIDL call to get node groups failed")?;
151
152    let mut info_result = Vec::new();
153    loop {
154        let mut node_groups =
155            iterator.get_next().await.context("FIDL call to get node groups failed")?;
156        if node_groups.is_empty() {
157            break;
158        }
159        info_result.append(&mut node_groups)
160    }
161    Ok(info_result)
162}
163
164/// Gets the desired DriverInfo instance.
165///
166/// Filter based on the driver's URL.
167/// For example: "fuchsia-boot://domain/#meta/foo.cm"
168///
169/// # Arguments
170/// * `driver_filter` - Filter to the driver that matches the given filter.
171pub async fn get_driver_by_filter(
172    driver_filter: &String,
173    driver_development_proxy: &fdd::ManagerProxy,
174) -> Result<fdf::DriverInfo> {
175    let filter_list: [String; 1] = [driver_filter.to_string()];
176    let driver_list = get_driver_info(&driver_development_proxy, &filter_list).await?;
177    if driver_list.len() != 1 {
178        return Err(anyhow!(
179            "There should be exactly one match for '{}'. Found {}.",
180            driver_filter,
181            driver_list.len()
182        ));
183    }
184    let mut driver_info: Option<fdf::DriverInfo> = None;
185
186    // Confirm this is the correct match.
187    let driver = &driver_list[0];
188    if let Some(ref url) = driver.url {
189        if url == driver_filter {
190            driver_info = Some(driver.clone());
191        }
192    }
193    match driver_info {
194        Some(driver) => Ok(driver),
195        _ => Err(anyhow!("Did not find matching driver for: {}", driver_filter)),
196    }
197}
198
199/// Gets the driver that is bound to the given device.
200///
201/// Is able to fuzzy match on the device's topological path, where the shortest match
202/// will be the one chosen.
203///
204/// # Arguments
205/// * `device_topo_path` - The device's topological path. e.g. sys/platform/.../device
206pub async fn get_driver_by_device(
207    device_topo_path: &String,
208    driver_development_proxy: &fdd::ManagerProxy,
209) -> Result<fdf::DriverInfo> {
210    let device_filter: [String; 1] = [device_topo_path.to_string()];
211    let mut device_list =
212        get_device_info(&driver_development_proxy, &device_filter, /* exact_match= */ true).await?;
213    if device_list.len() != 1 {
214        let fuzzy_device_list = get_device_info(
215            &driver_development_proxy,
216            &device_filter,
217            /* exact_match= */ false,
218        )
219        .await?;
220        if fuzzy_device_list.len() == 0 {
221            return Err(anyhow!("No devices matched the query: {}", device_topo_path));
222        } else if fuzzy_device_list.len() > 1 {
223            let mut builder = "Found multiple matches. Did you mean one of these?\n\n".to_string();
224            for item in fuzzy_device_list {
225                let device: Device = item.into();
226                // We don't appear to have a string builder crate in-tree.
227                builder = format!("{}{}\n", builder, device.get_full_name()?);
228            }
229            return Err(anyhow!(builder));
230        }
231        device_list = fuzzy_device_list;
232    }
233
234    let found_device = device_list.remove(0);
235    match found_device.bound_driver_url {
236        Some(ref driver_filter) => {
237            get_driver_by_filter(&driver_filter, &driver_development_proxy).await
238        }
239        _ => Err(anyhow!("Did not find driver for device {}", &device_topo_path)),
240    }
241}
242
243/// Gets the devices that are bound to the given driver.
244///
245/// Filter based on the driver's URL.
246/// For example: "fuchsia-boot://domain/#meta/foo.cm"
247///
248/// # Arguments
249/// * `driver_filter` - Filter to the driver that matches the given filter.
250pub async fn get_devices_by_driver(
251    driver_filter: &String,
252    driver_development_proxy: &fdd::ManagerProxy,
253) -> Result<Vec<Device>> {
254    let driver_info = get_driver_by_filter(driver_filter, &driver_development_proxy);
255    let empty: [String; 0] = [];
256    let device_list =
257        get_device_info(&driver_development_proxy, &empty, /* exact_match= */ false);
258
259    let (driver_info, device_list) = futures::join!(driver_info, device_list);
260    let (driver_info, device_list) = (driver_info?, device_list?);
261
262    let mut matches: Vec<Device> = Vec::new();
263    for device_item in device_list.into_iter() {
264        let device: Device = device_item.into();
265        if let (Some(bound_driver_url), Some(url)) = (&device.0.bound_driver_url, &driver_info.url)
266        {
267            if &url == &bound_driver_url {
268                matches.push(device);
269            }
270        }
271    }
272    Ok(matches)
273}