blob: eb0cd01f61ba358a570c51b71d24371e24850819 [file] [log] [blame]
// Copyright 2021 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 crate::common::*;
use crate::composite_device_desc::*;
use crate::library::*;
use regex::Regex;
use std::collections::{HashMap, HashSet};
use std::fmt::Write;
use std::fs::OpenOptions;
use std::io::Read;
use std::path::PathBuf;
#[derive(Debug, PartialEq)]
enum Condition {
Always,
Equals,
NotEquals,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
}
impl TryFrom<&str> for Condition {
type Error = &'static str;
fn try_from(input: &str) -> Result<Self, Self::Error> {
match input {
"AL" => Ok(Condition::Always),
"EQ" => Ok(Condition::Equals),
"NE" => Ok(Condition::NotEquals),
"GT" => Ok(Condition::GreaterThan),
"LT" => Ok(Condition::LessThan),
"GE" => Ok(Condition::GreaterThanOrEqual),
"LE" => Ok(Condition::LessThanOrEqual),
_ => Err("Unrecognised condition"),
}
}
}
pub struct NodeData {
pub name: String,
pub bind_rules: String,
}
pub struct CompositeDeviceData {
pub libraries: HashSet<Library>,
pub nodes: Vec<NodeData>,
pub desc: CompositeDeviceDesc,
}
struct CompositeBindRules {
libraries: HashSet<Library>,
match_insts: HashMap<String, String>,
}
impl CompositeBindRules {
pub fn new() -> Self {
CompositeBindRules { libraries: HashSet::new(), match_insts: HashMap::new() }
}
pub fn add_match_insts(&mut self, insts: &str) -> Result<(), &'static str> {
let op_regex = Regex::new(r"BI_[A-Z_]*\([^\)]*\)").unwrap();
let abort_regex = Regex::new(r"BI_ABORT\(\)").unwrap();
let abort_if_regex = Regex::new(r"BI_ABORT_IF\(([A-Z][A-Z]),([^,]*),([^\)]*)\)").unwrap();
let match_if_regex = Regex::new(r"BI_MATCH_IF\(([A-Z][A-Z]),([^,]*),([^\)]*)\)").unwrap();
let mut bind_rules = String::new();
let mut iter = op_regex.find_iter(&insts);
while let Some(inst) = iter.next() {
if abort_regex.is_match(inst.as_str()) {
bind_rules.push_str("abort;")
} else if let Some(caps) = abort_if_regex.captures(inst.as_str()) {
let condition = Condition::try_from(caps.get(1).unwrap().as_str());
let lhs = rename_and_add(&mut self.libraries, caps.get(2).unwrap().as_str().trim());
let rhs = rename_and_add(&mut self.libraries, caps.get(3).unwrap().as_str().trim());
let rule = match condition {
Ok(Condition::Equals) => Ok(format!(" {} != {};\n", lhs, rhs)),
Ok(Condition::NotEquals) => Ok(format!(" {} == {};\n", lhs, rhs)),
_ => Err("Unsupported condition"),
}?;
bind_rules.push_str(rule.as_str());
} else if let Some(caps) = match_if_regex.captures(inst.as_str()) {
let condition = Condition::try_from(caps.get(1).unwrap().as_str());
let first_lhs = caps.get(2).unwrap().as_str().trim();
let rhs = rename_and_add(&mut self.libraries, caps.get(3).unwrap().as_str().trim());
bind_rules.push_str(
format!(" accept {} {{\n", rename_and_add(&mut self.libraries, first_lhs))
.as_str(),
);
bind_rules.push_str(format!(" {},\n", rhs).as_str());
// We only support BI_MATCH_IF when we can convert it to an accept. So every remaining
// op must be BI_MATCH_IF with the same LHS. We also only support EQ as the condition.
assert_eq!(condition, Ok(Condition::Equals));
while let Some(inst) = iter.next() {
if let Some(caps) = match_if_regex.captures(inst.as_str()) {
let condition = Condition::try_from(caps.get(1).unwrap().as_str());
assert_eq!(condition, Ok(Condition::Equals));
let lhs = caps.get(2).unwrap().as_str().trim();
let rhs = rename_and_add(
&mut self.libraries,
caps.get(3).unwrap().as_str().trim(),
);
assert_eq!(lhs, first_lhs);
bind_rules.push_str(format!(" {},\n", rhs).as_str());
} else {
return Err("Unsupported bind rules");
}
}
bind_rules.push_str(" }");
} else {
println!("The migration tool doesn't handle this bind op: {}", inst.as_str());
return Err("Unhandled bind op");
}
}
let name_regex = Regex::new(r"([A-Za-z0-9_])*\[\]").unwrap();
let mut match_inst_name = name_regex
.captures(insts)
.ok_or("unable to get zx_bind_inst[] name")?
.get(0)
.unwrap()
.as_str()
.to_string();
// Shed off the array brackets.
match_inst_name.truncate(match_inst_name.len() - 2);
self.match_insts.insert(match_inst_name, bind_rules);
Ok(())
}
}
pub fn process_source_file(input: PathBuf) -> Result<CompositeDeviceData, &'static str> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(input)
.map_err(|_| "Failed to open build file")?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|_| "Failed to read source file")?;
let bind_inst_regex = Regex::new(r"zx_bind_inst_t ([A-Za-z0-9_])*\[\] = \{([^}]*)\}").unwrap();
if !bind_inst_regex.is_match(&contents) {
return Err("No zx_bind_inst_t[] definition in source file");
}
// Add and migrate each bind_inst[] definition.
let mut composite_bind = CompositeBindRules::new();
let mut iter = bind_inst_regex.find_iter(&contents);
while let Some(inst) = iter.next() {
composite_bind.add_match_insts(inst.as_str())?;
}
// Get each device_fragment_part_t and match it to a migrated bind_inst[].
let mut fragment_parts: HashMap<String, String> = HashMap::new();
let part_regex = Regex::new(
r"device_fragment_part_t (?P<part_var>[A-Za-z0-9_]*)\[\] = \{\s*\{[^,]*, (?P<match_var>[A-Za-z0-9_]*)}",
)
.unwrap();
let mut iter = part_regex.captures_iter(&contents);
while let Some(part_cap) = iter.next() {
let part_var = capture_name(&part_cap, "part_var")?;
let match_var = capture_name(&part_cap, "match_var")?;
// Look up the zx_bind_inst_t[] with the matching variable name.
let instructions = composite_bind
.match_insts
.get(&match_var)
.ok_or("Undefined zx_bind_inst_t[] value in device_fragment_part_t")?;
fragment_parts.insert(part_var, instructions.to_string());
}
// Get device_fragment_t.
let fragment_list_regex = Regex::new(
r#"device_fragment_t ([A-Za-z0-9_]*)\[\] = \{\s*(\{"[^"]*", [^,]*, [A-Za-z0-9_]*\},*\s*)*\s*}"#,
).unwrap();
let fragment_regex =
Regex::new(r#"\{"(?P<node>[^"]*)", [^,]*, (?P<part_var>[A-Za-z0-9_]*)\}"#).unwrap();
let mut node_list: Vec<NodeData> = vec![];
let mut fragment_list_iter = fragment_list_regex.find_iter(&contents);
// TODO(spqchan): Support more than one fragment list.
let mut cap_iter = fragment_regex.captures_iter(
fragment_list_iter.next().ok_or("Unable to parse device_fragment_t")?.as_str(),
);
while let Some(fragment_cap) = cap_iter.next() {
let node_name = capture_name(&fragment_cap, "node")?;
let part_var = capture_name(&fragment_cap, "part_var")?;
// Push the instructions from the matching device_fragment_part_t.
let instructions = fragment_parts
.get(&part_var.to_string())
.ok_or("Undefined device_fragment_part_t value in device_fragment_t[]")?;
node_list
.push(NodeData { name: node_name.to_string(), bind_rules: instructions.to_string() });
}
if fragment_list_iter.next().is_some() {
return Err("The migration tool only support one fragment list in the code.");
}
// If a pbus device description is available, then we need to add a pdev node.
let device_desc = get_device_desc(&contents)?;
if let Some(pdev) = device_desc.pbus_desc.as_ref() {
let mut pdev_node_inst = String::new();
pdev_node_inst
.write_fmt(format_args!(
include_str!("templates/pdev_bind.template"),
pid = rename_and_add(&mut composite_bind.libraries, &pdev.pid),
vid = rename_and_add(&mut composite_bind.libraries, &pdev.vid),
did = rename_and_add(&mut composite_bind.libraries, &pdev.did),
instance_id = pdev.instance_id,
))
.map_err(|_| "Failed to format output")?;
node_list.insert(0, NodeData { name: "pdev".to_string(), bind_rules: pdev_node_inst });
composite_bind.libraries.insert(Library::Platform);
}
Ok(CompositeDeviceData {
libraries: composite_bind.libraries,
nodes: node_list,
desc: device_desc,
})
}