blob: ba4a0eeba4e558f41f30a1ce000c7fba2230cec7 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{anyhow, Context, Error},
bind::interpreter::{
common::BytecodeError,
decode_bind_rules::{DecodedCompositeBindRules, DecodedRules},
match_bind::{match_bind, DeviceProperties, MatchBindData},
},
cm_rust::FidlIntoNative,
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_driver_development as fdd,
fidl_fuchsia_driver_index as fdi, fidl_fuchsia_io as fio,
futures::TryFutureExt,
};
// Cached drivers don't exist yet so we allow dead code.
#[derive(Copy, Clone, Debug)]
#[allow(dead_code)]
pub enum DriverPackageType {
Boot = 0,
Base = 1,
Cached = 2,
Universe = 3,
}
// TODO(fxbug.dev/92329): Use `fallback` and remove dead code attribute.
#[allow(dead_code)]
pub struct ResolvedDriver {
pub component_url: url::Url,
pub v1_driver_path: Option<String>,
pub bind_rules: DecodedRules,
pub bind_bytecode: Vec<u8>,
pub colocate: bool,
pub fallback: bool,
pub package_type: DriverPackageType,
}
impl std::fmt::Display for ResolvedDriver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.component_url)
}
}
impl ResolvedDriver {
pub fn get_libname(&self) -> String {
let mut libname = self.component_url.clone();
libname.set_fragment(self.v1_driver_path.as_deref());
libname.into_string()
}
pub async fn resolve(
component_url: url::Url,
resolver: &fidl_fuchsia_pkg::PackageResolverProxy,
package_type: DriverPackageType,
) -> Result<ResolvedDriver, fuchsia_zircon::Status> {
let (dir, dir_server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>()
.map_err(|e| {
log::warn!("Failed to create DirectoryMarker Proxy: {}", e);
fuchsia_zircon::Status::INTERNAL
})?;
let mut base_url = component_url.clone();
base_url.set_fragment(None);
let res = resolver
.resolve(&base_url.as_str(), dir_server_end)
.map_err(|e| {
log::warn!("Resolve call failed: {}", e);
fuchsia_zircon::Status::INTERNAL
})
.await?;
res.map_err(|e| {
log::warn!("{}: Failed to resolve package: {:?}", component_url.as_str(), e);
map_resolve_err_to_zx_status(e)
})?;
let driver = load_driver(&dir, component_url.clone(), package_type)
.map_err(|e| {
log::warn!("Could not load driver: {}", e);
fuchsia_zircon::Status::INTERNAL
})
.await?;
return driver.ok_or_else(|| {
log::warn!("{}: Component was not a driver-component", component_url.as_str());
fuchsia_zircon::Status::INTERNAL
});
}
pub fn matches(
&self,
properties: &DeviceProperties,
) -> Result<Option<fdi::MatchedDriver>, bind::interpreter::common::BytecodeError> {
match &self.bind_rules {
DecodedRules::Normal(rules) => {
let matches = match_bind(
MatchBindData {
symbol_table: &rules.symbol_table,
instructions: &rules.instructions,
},
properties,
)
.map_err(|e| {
log::error!("Driver {}: bind error: {}", self, e);
e
})?;
if !matches {
return Ok(None);
}
Ok(Some(fdi::MatchedDriver::Driver(self.create_matched_driver_info())))
}
DecodedRules::Composite(composite) => {
let result = matches_composite_device(composite, properties).map_err(|e| {
log::error!("Driver {}: bind error: {}", self, e);
e
})?;
if result.is_none() {
return Ok(None);
}
let node_index = result.unwrap();
let mut node_names = vec![];
node_names.push(composite.symbol_table[&composite.primary_node.name_id].clone());
for node in &composite.additional_nodes {
node_names.push(composite.symbol_table[&node.name_id].clone());
}
Ok(Some(fdi::MatchedDriver::CompositeDriver(fdi::MatchedCompositeInfo {
node_index: Some(node_index),
num_nodes: Some((composite.additional_nodes.len() + 1) as u32),
composite_name: Some(composite.symbol_table[&composite.device_name_id].clone()),
node_names: Some(node_names),
driver_info: Some(self.create_matched_driver_info()),
..fdi::MatchedCompositeInfo::EMPTY
})))
}
}
}
fn get_driver_url(&self) -> Option<String> {
match self.v1_driver_path.as_ref() {
Some(p) => {
let mut driver_url = self.component_url.clone();
driver_url.set_fragment(Some(p));
Some(driver_url.to_string())
}
None => None,
}
}
fn create_matched_driver_info(&self) -> fdi::MatchedDriverInfo {
fdi::MatchedDriverInfo {
url: Some(self.component_url.as_str().to_string()),
driver_url: self.get_driver_url(),
colocate: Some(self.colocate),
package_type: fdi::DriverPackageType::from_primitive(self.package_type as u8),
is_fallback: Some(self.fallback),
..fdi::MatchedDriverInfo::EMPTY
}
}
pub fn create_driver_info(&self) -> fdd::DriverInfo {
let bind_rules = match &self.bind_rules {
DecodedRules::Normal(_) => {
Some(fdd::BindRulesBytecode::BytecodeV2(self.bind_bytecode.clone()))
}
// TODO(fxbug.dev/85651): Support composite bytecode in DriverInfo.
DecodedRules::Composite(_) => None,
};
fdd::DriverInfo {
url: Some(self.component_url.clone().to_string()),
libname: Some(self.get_libname()),
bind_rules: bind_rules,
package_type: fdi::DriverPackageType::from_primitive(self.package_type as u8),
..fdd::DriverInfo::EMPTY
}
}
}
fn matches_composite_device(
rules: &DecodedCompositeBindRules,
properties: &DeviceProperties,
) -> Result<Option<u32>, BytecodeError> {
let matches = match_bind(
MatchBindData {
symbol_table: &rules.symbol_table,
instructions: &rules.primary_node.instructions,
},
properties,
)?;
if matches {
return Ok(Some(0));
}
for (i, node) in rules.additional_nodes.iter().enumerate() {
let matches = match_bind(
MatchBindData { symbol_table: &rules.symbol_table, instructions: &node.instructions },
properties,
)?;
if matches {
return Ok(Some((i + 1) as u32));
}
}
Ok(None)
}
// Load the `component_url` driver out of `dir` which should be the root directory
// of that component. Will return Ok(None) if `component_url` is a valid component
// but it's not a driver component.
pub async fn load_driver(
dir: &fio::DirectoryProxy,
component_url: url::Url,
package_type: DriverPackageType,
) -> Result<Option<ResolvedDriver>, Error> {
let component = fuchsia_fs::open_file(
&dir,
std::path::Path::new(
component_url
.fragment()
.ok_or(anyhow!("{}: URL is missing fragment", component_url.as_str()))?,
),
fio::OpenFlags::RIGHT_READABLE,
)
.with_context(|| {
format!("{}: Failed to open component manifest file", component_url.as_str())
})?;
let component: fdecl::Component = fuchsia_fs::read_file_fidl(&component)
.await
.with_context(|| format!("{}: Failed to read component", component_url.as_str()))?;
let component: cm_rust::ComponentDecl = component.fidl_into_native();
let runner = match component.get_runner() {
Some(r) => r,
None => return Ok(None),
};
if runner.str() != "driver" {
return Ok(None);
}
let bind_path = get_rules_string_value(&component, "bind")
.ok_or(anyhow!("{}: Missing bind path", component_url.as_str()))?;
let bind = fuchsia_fs::open_file(
&dir,
std::path::Path::new(&bind_path),
fio::OpenFlags::RIGHT_READABLE,
)
.with_context(|| format!("{}: Failed to open bind", component_url.as_str()))?;
let bind = fuchsia_fs::read_file_bytes(&bind)
.await
.with_context(|| format!("{}: Failed to read bind", component_url.as_str()))?;
let bind_rules = DecodedRules::new(bind.clone())
.with_context(|| format!("{}: Failed to parse bind", component_url.as_str()))?;
let v1_driver_path = get_rules_string_value(&component, "compat");
let fallback = get_rules_string_value(&component, "fallback");
let fallback = match fallback {
Some(s) => s == "true",
None => false,
};
let colocate = get_rules_string_value(&component, "colocate");
let colocate = match colocate {
Some(s) => s == "true",
None => false,
};
Ok(Some(ResolvedDriver {
component_url: component_url,
v1_driver_path: v1_driver_path,
bind_rules: bind_rules,
bind_bytecode: bind,
colocate: colocate,
fallback,
package_type,
}))
}
fn get_rules_string_value(component: &cm_rust::ComponentDecl, key: &str) -> Option<String> {
for entry in component.program.as_ref()?.info.entries.as_ref()? {
if entry.key == key {
match entry.value.as_ref()?.as_ref() {
fidl_fuchsia_data::DictionaryValue::Str(s) => {
return Some(s.to_string());
}
_ => {
return None;
}
}
}
}
return None;
}
fn map_resolve_err_to_zx_status(
resolve_error: fidl_fuchsia_pkg::ResolveError,
) -> fuchsia_zircon::Status {
match resolve_error {
fidl_fuchsia_pkg::ResolveError::Internal => fuchsia_zircon::Status::INTERNAL,
fidl_fuchsia_pkg::ResolveError::AccessDenied => fuchsia_zircon::Status::ACCESS_DENIED,
fidl_fuchsia_pkg::ResolveError::Io => fuchsia_zircon::Status::IO,
fidl_fuchsia_pkg::ResolveError::BlobNotFound => fuchsia_zircon::Status::NOT_FOUND,
fidl_fuchsia_pkg::ResolveError::PackageNotFound => fuchsia_zircon::Status::NOT_FOUND,
fidl_fuchsia_pkg::ResolveError::RepoNotFound => fuchsia_zircon::Status::NOT_FOUND,
fidl_fuchsia_pkg::ResolveError::NoSpace => fuchsia_zircon::Status::NO_SPACE,
fidl_fuchsia_pkg::ResolveError::UnavailableBlob => fuchsia_zircon::Status::UNAVAILABLE,
fidl_fuchsia_pkg::ResolveError::UnavailableRepoMetadata => {
fuchsia_zircon::Status::UNAVAILABLE
}
fidl_fuchsia_pkg::ResolveError::InvalidUrl => fuchsia_zircon::Status::INVALID_ARGS,
}
}