blob: 48d8a7cfd5f51b56f689c86e56358ea3a9dca525 [file] [log] [blame]
// Copyright 2019 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.
//! This is a migration tool to help move from C-macro style bind rules to using the bind compiler.
use regex::Regex;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(StructOpt, Debug)]
struct Opt {
#[structopt(parse(from_os_str))]
input: PathBuf,
}
#[derive(Debug)]
struct DriverArgs {
driver_name: String,
driver_ops: String,
vendor: String,
version: String,
num_ops: usize,
match_start: usize,
match_end: usize,
}
impl TryFrom<&str> for DriverArgs {
type Error = &'static str;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let begin_re =
Regex::new(r"ZIRCON_DRIVER_BEGIN\(([^,]*),([^,]*),([^,]*),([^,]*),([^\)]*)\)").unwrap();
let header = begin_re.captures(input).ok_or("Couldn't find driver begin macro")?;
let get_group_as_string = |i: usize| -> Result<String, Self::Error> {
Ok(header.get(i).ok_or("Driver begin macro regex failed")?.as_str().trim().to_string())
};
let driver_name = get_group_as_string(1)?;
let driver_ops = get_group_as_string(2)?;
let vendor = get_group_as_string(3)?;
let version = get_group_as_string(4)?;
let num_ops = get_group_as_string(5)?
.parse::<usize>()
.map_err(|_| "Couldn't parse number of bind ops")?;
let mat = header.get(0).ok_or("Driver begin macro regex failed")?;
Ok(DriverArgs {
driver_name,
driver_ops,
vendor,
version,
num_ops,
match_start: mat.start(),
match_end: mat.end(),
})
}
}
#[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"),
}
}
}
#[derive(Debug, PartialEq, Eq, Hash)]
enum Library {
Acpi,
Bluetooth,
Composite,
Pci,
Platform,
Serial,
Test,
Usb,
Wlan,
}
impl Library {
fn name(&self) -> &str {
match self {
Library::Acpi => "fuchsia.acpi",
Library::Bluetooth => "fuchsia.bluetooth",
Library::Composite => "fuchsia.composite",
Library::Pci => "fuchsia.pci",
Library::Platform => "fuchsia.platform",
Library::Serial => "fuchsia.serial",
Library::Test => "fuchsia.test",
Library::Usb => "fuchsia.usb",
Library::Wlan => "fuchsia.wlan",
}
}
fn build_target(&self) -> &str {
match self {
Library::Acpi => "//src/devices/bind/fuchsia.acpi",
Library::Bluetooth => "//src/devices/bind/fuchsia.bluetooth",
Library::Composite => "//src/devices/bind/fuchsia.composite",
Library::Pci => "//src/devices/bind/fuchsia.pci",
Library::Platform => "//src/devices/bind/fuchsia.platform",
Library::Serial => "//src/devices/bind/fuchsia.serial",
Library::Test => "//src/devices/bind/fuchsia.test",
Library::Usb => "//src/devices/bind/fuchsia.usb",
Library::Wlan => "//src/devices/bind/fuchsia.wlan",
}
}
}
struct Migrator {
libraries: HashSet<Library>,
}
impl Migrator {
fn process_build_file(&self, input: PathBuf) -> Result<Vec<PathBuf>, &'static str> {
let mut output_path = input.clone();
let mut file = File::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 build file")?;
let driver_module_re = Regex::new("driver_module").unwrap();
let sources_re = Regex::new(r"sources = \[([^\]]*)\]").unwrap();
let module =
driver_module_re.find(&contents).ok_or("Couldn't find driver_module in build file")?;
let sources = sources_re
.captures(&contents[module.start()..])
.ok_or("Couldn't find sources in driver_module target")?;
let source_files = sources.get(1).ok_or("Couldn't find sources in driver_module target")?;
let mut result = vec![];
for source in source_files.as_str().split(",") {
let trimmed = source.trim();
let unquoted = trimmed.trim_matches('"');
if !unquoted.is_empty() {
output_path.set_file_name(unquoted);
result.push(output_path.clone());
}
}
Ok(result)
}
fn insert_build_rule(&self, driver_name: &str, input: PathBuf) -> Result<(), &'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 build file")?;
let import_re = Regex::new(r"import\([^\)]*\)\n").unwrap();
let mut iter = import_re.find_iter(&contents);
let first_import = iter.next().ok_or("Couldn't find import list in build file")?;
let last_import = iter.last().unwrap_or(first_import);
let driver_module_re = Regex::new("driver_module").unwrap();
let module =
driver_module_re.find(&contents).ok_or("Couldn't find driver_module in build file")?;
let deps_re = Regex::new(r"deps = \[").unwrap();
let deps =
deps_re.find(&contents[module.start()..]).ok_or("Couldn't find driver_module deps")?;
let deps_start = module.start() + deps.start();
let deps_end = module.start() + deps.end();
let mut output = String::new();
output.push_str(&contents[..first_import.start()]);
output.push_str("import(\"//build/bind/bind.gni\")\n");
output.push_str(&contents[first_import.start()..last_import.end()]);
output.push_str("\n");
output.push_str(format!("bind_rules(\"{}_bind\") {{\n", driver_name).as_str());
output.push_str(format!(" rules = \"{}.bind\"\n", driver_name).as_str());
output.push_str(format!(" output = \"{}_bind.h\"\n", driver_name).as_str());
output.push_str(format!(" tests = \"tests.json\"\n").as_str());
if !self.libraries.is_empty() {
output.push_str(" deps = [\n");
for library in &self.libraries {
output.push_str(format!(" \"{}\",\n", library.build_target()).as_str());
}
output.push_str(" ]\n");
}
output.push_str("}\n");
output.push_str(&contents[last_import.end()..deps_start]);
output.push_str(format!("deps = [\n \":{}_bind\",", driver_name).as_str());
output.push_str(&contents[deps_end..]);
file.seek(SeekFrom::Start(0)).map_err(|_| "Failed to seek to beginning of build file")?;
file.set_len(0).map_err(|_| "Failed to truncate build file")?;
file.write_all(output.as_bytes()).map_err(|_| "Failed to write back to build file")?;
Ok(())
}
// Best effort approach to rename key/values into their bindc equivalents. If the replacement
// requires a library, keep track of that.
fn rename<'a>(&mut self, original: &'a str) -> &'a str {
match original {
"BIND_PROTOCOL" => "fuchsia.BIND_PROTOCOL",
"ZX_PROTOCOL_ACPI" => {
self.libraries.insert(Library::Acpi);
"fuchsia.acpi.BIND_PROTOCOL.DEVICE"
}
"ZX_PROTOCOL_COMPOSITE" => {
self.libraries.insert(Library::Composite);
"fuchsia.composite.BIND_PROTOCOL.DEVICE"
}
"ZX_PROTOCOL_PCI" => {
self.libraries.insert(Library::Pci);
"fuchsia.pci.BIND_PROTOCOL.DEVICE"
}
"ZX_PROTOCOL_USB_INTERFACE" => {
self.libraries.insert(Library::Usb);
"fuchsia.usb.BIND_PROTOCOL.INTERFACE"
}
"ZX_PROTOCOL_USB_FUNCTION" => {
self.libraries.insert(Library::Usb);
"fuchsia.usb.BIND_PROTOCOL.FUNCTION"
}
"ZX_PROTOCOL_SERIAL" => {
self.libraries.insert(Library::Serial);
"fuchsia.serial.BIND_PROTOCOL.DEVICE"
}
"ZX_PROTOCOL_SERIAL_IMPL" => {
self.libraries.insert(Library::Serial);
"fuchsia.serial.BIND_PROTOCOL.IMPL"
}
"ZX_PROTOCOL_SERIAL_IMPL_ASYNC" => {
self.libraries.insert(Library::Serial);
"fuchsia.serial.BIND_PROTOCOL.IMPL_ASYNC"
}
"ZX_PROTOCOL_TEST" => {
self.libraries.insert(Library::Test);
"fuchsia.test.BIND_PROTOCOL.DEVICE"
}
"ZX_PROTOCOL_TEST_COMPAT_CHILD" => {
self.libraries.insert(Library::Test);
"fuchsia.test.BIND_PROTOCOL.COMPAT_CHILD"
}
"ZX_PROTOCOL_TEST_POWER_CHILD" => {
self.libraries.insert(Library::Test);
"fuchsia.test.BIND_PROTOCOL.POWER_CHILD"
}
"ZX_PROTOCOL_TEST_PARENT" => {
self.libraries.insert(Library::Test);
"fuchsia.test.BIND_PROTOCOL.PARENT"
}
"ZX_PROTOCOL_WLANPHY" => {
self.libraries.insert(Library::Wlan);
"fuchsia.wlan.BIND_PROTOCOL.PHY"
}
"ZX_PROTOCOL_WLANPHY_IMPL" => {
self.libraries.insert(Library::Wlan);
"fuchsia.wlan.BIND_PROTOCOL.PHY_IMPL"
}
"ZX_PROTOCOL_WLANIF" => {
self.libraries.insert(Library::Wlan);
"fuchsia.wlan.BIND_PROTOCOL.IF"
}
"ZX_PROTOCOL_WLANIF_IMPL" => {
self.libraries.insert(Library::Wlan);
"fuchsia.wlan.BIND_PROTOCOL.IF_IMPL"
}
"ZX_PROTOCOL_WLANMAC" => {
self.libraries.insert(Library::Wlan);
"fuchsia.wlan.BIND_PROTOCOL.MAC"
}
"ZX_PROTOCOL_BT_HCI" => {
self.libraries.insert(Library::Bluetooth);
"fuchsia.bluetooth.BIND_PROTOCOL.HCI"
}
"ZX_PROTOCOL_BT_EMULATOR" => {
self.libraries.insert(Library::Bluetooth);
"fuchsia.bluetooth.BIND_PROTOCOL.EMULATOR"
}
"ZX_PROTOCOL_BT_TRANSPORT" => {
self.libraries.insert(Library::Bluetooth);
"fuchsia.bluetooth.BIND_PROTOCOL.TRANSPORT"
}
"ZX_PROTOCOL_BT_HOST" => {
self.libraries.insert(Library::Bluetooth);
"fuchsia.bluetooth.BIND_PROTOCOL.HOST"
}
"ZX_PROTOCOL_BT_GATT_SVC" => {
self.libraries.insert(Library::Bluetooth);
"fuchsia.bluetooth.BIND_PROTOCOL.GATT_SVC"
}
"BIND_PCI_VID" => {
self.libraries.insert(Library::Pci);
"fuchsia.BIND_PCI_VID"
}
"BIND_PCI_DID" => {
self.libraries.insert(Library::Pci);
"fuchsia.BIND_PCI_DID"
}
"BIND_USB_DID" => {
self.libraries.insert(Library::Usb);
"fuchsia.BIND_USB_DID"
}
"BIND_USB_PID" => {
self.libraries.insert(Library::Usb);
"fuchsia.BIND_USB_PID"
}
"BIND_USB_CLASS" => {
self.libraries.insert(Library::Usb);
"fuchsia.BIND_USB_CLASS"
}
"BIND_USB_SUBCLASS" => {
self.libraries.insert(Library::Usb);
"fuchsia.BIND_USB_SUBCLASS"
}
"BIND_PLATFORM_DEV_VID" => {
self.libraries.insert(Library::Platform);
"fuchsia.BIND_PLATFORM_DEV_VID"
}
"BIND_PLATFORM_DEV_PID" => "fuchsia.BIND_PLATFORM_DEV_PID",
"BIND_PLATFORM_DEV_DID" => "fuchsia.BIND_PLATFORM_DEV_DID",
"ATHEROS_VID" => "fuchsia.pci.BIND_PCI_VID.ATHEROS",
"INTEL_VID" => "fuchsia.pci.BIND_PCI_VID.INTEL",
_ => original,
}
}
fn process_source_file(&mut self, input: PathBuf) -> Result<Option<String>, &'static str> {
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(input.clone())
.map_err(|_| "Failed to open build file")?;
let mut contents = String::new();
file.read_to_string(&mut contents).map_err(|_| "Failed to read build file")?;
// These regular expressions will fail if there are additional parentheses or commas in the
// macros, but we're okay with that since this whole tool is best effort.
let include_re = Regex::new(r"(?m)^#include <ddk/binding.h>$").unwrap();
let end_re = Regex::new(r"ZIRCON_DRIVER_END\([^\)]*\)").unwrap();
let op_re = Regex::new(r"BI_[A-Z_]*\([^\)]*\)").unwrap();
let abort_re = Regex::new(r"BI_ABORT\(\)").unwrap();
let abort_if_re = Regex::new(r"BI_ABORT_IF\(([A-Z][A-Z]),([^,]*),([^\)]*)\)").unwrap();
let match_if_re = Regex::new(r"BI_MATCH_IF\(([A-Z][A-Z]),([^,]*),([^\)]*)\)").unwrap();
let args = DriverArgs::try_from(contents.as_str());
if !args.is_ok() {
return Ok(None);
}
let args = args?;
let mut bind_rules = String::new();
let mut count = 0;
let mut iter = op_re.find_iter(&contents);
while let Some(mat) = iter.next() {
if abort_re.is_match(mat.as_str()) {
bind_rules.push_str("abort;")
} else if let Some(caps) = abort_if_re.captures(mat.as_str()) {
let condition = Condition::try_from(caps.get(1).unwrap().as_str());
let lhs = self.rename(caps.get(2).unwrap().as_str().trim());
let rhs = self.rename(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_re.captures(mat.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 = self.rename(caps.get(3).unwrap().as_str().trim());
bind_rules.push_str(format!("accept {} {{\n", self.rename(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(mat) = iter.next() {
if let Some(caps) = match_if_re.captures(mat.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 = self.rename(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");
}
count += 1;
}
bind_rules.push_str("}\n");
} else {
println!("The migration tool doesn't handle this bind op: {}", mat.as_str());
return Err("Unhandled bind op");
}
count += 1;
}
assert_eq!(count, args.num_ops);
let mut source_output = String::new();
let mut bind_output_file_path = input.clone();
bind_output_file_path.set_file_name(format!("{}_bind.h", args.driver_name).as_str());
let include = include_re.find(&contents).ok_or("Couldn't find binding include")?;
source_output.push_str(&contents[..include.start()]);
if let Some(full_path) = bind_output_file_path.to_str() {
source_output.push_str(format!("#include \"{}\"\n", full_path).as_str());
} else {
source_output.push_str(format!("#include \"{}_bind.h\"\n", args.driver_name).as_str());
}
source_output.push_str(&contents[include.end()..args.match_start]);
source_output.push_str(
format!(
"ZIRCON_DRIVER({}, {}, {}, {});\n",
args.driver_name, args.driver_ops, args.vendor, args.version
)
.as_str(),
);
let end = end_re.find(&contents).ok_or("Couldn't find end of driver macro")?;
source_output.push_str(&contents[end.end()..]);
file.seek(SeekFrom::Start(0)).map_err(|_| "Failed to seek to beginning of source file")?;
file.set_len(0).map_err(|_| "Failed to truncate source file")?;
file.write_all(source_output.as_bytes()).unwrap();
let mut bind_file_data = String::new();
bind_file_data.push_str("// Copyright 2020 The Fuchsia Authors. All rights reserved.\n");
bind_file_data.push_str(
"// Use of this source code is governed by a BSD-style license that can be\n",
);
bind_file_data.push_str("// found in the LICENSE file.\n\n");
for library in &self.libraries {
bind_file_data.push_str(format!("using {};\n", library.name()).as_str());
}
if !self.libraries.is_empty() {
bind_file_data.push_str("\n");
}
bind_file_data.push_str(bind_rules.as_str());
let mut bind_file_path = input;
bind_file_path.set_file_name(format!("{}.bind", args.driver_name).as_str());
let mut bind_file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(bind_file_path)
.map_err(|_| "Failed to open bind file")?;
bind_file
.write_all(bind_file_data.as_bytes())
.map_err(|_| "Failed to write to bind file")?;
Ok(Some(args.driver_name))
}
}
fn main() -> Result<(), &'static str> {
let opt = Opt::from_iter(std::env::args());
let mut migrator = Migrator { libraries: HashSet::new() };
for source_file in migrator.process_build_file(opt.input.clone())? {
if let Some(source_file_str) = source_file.to_str() {
println!("Checking {}", source_file_str);
}
let processed = migrator.process_source_file(source_file)?;
if let Some(driver_name) = processed {
migrator.insert_build_rule(&driver_name, opt.input)?;
return Ok(());
}
}
Err("Couldn't find a ZIRCON_DRIVER_BEGIN macro")
}