[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [RFC PATCH 9/9] tools: Introduce abi-tool
abi-tool is a small Rust tool that is able to parse ABI yaml files and generate C stubs for performing hypercalls. Signed-off-by: Teddy Astie <teddy.astie@xxxxxxxxxx> --- Usage : ./abi-tool < abi.yaml > abi.h --- xen/tools/abi-tool/.gitignore | 1 + xen/tools/abi-tool/Cargo.lock | 145 ++++++++++++++++++++++++++ xen/tools/abi-tool/Cargo.toml | 11 ++ xen/tools/abi-tool/src/abi.rs | 23 ++++ xen/tools/abi-tool/src/c_lang.rs | 173 +++++++++++++++++++++++++++++++ xen/tools/abi-tool/src/main.rs | 17 +++ xen/tools/abi-tool/src/spec.rs | 61 +++++++++++ 7 files changed, 431 insertions(+) create mode 100644 xen/tools/abi-tool/.gitignore create mode 100644 xen/tools/abi-tool/Cargo.lock create mode 100644 xen/tools/abi-tool/Cargo.toml create mode 100644 xen/tools/abi-tool/src/abi.rs create mode 100644 xen/tools/abi-tool/src/c_lang.rs create mode 100644 xen/tools/abi-tool/src/main.rs create mode 100644 xen/tools/abi-tool/src/spec.rs diff --git a/xen/tools/abi-tool/.gitignore b/xen/tools/abi-tool/.gitignore new file mode 100644 index 0000000000..1de565933b --- /dev/null +++ b/xen/tools/abi-tool/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/xen/tools/abi-tool/Cargo.lock b/xen/tools/abi-tool/Cargo.lock new file mode 100644 index 0000000000..056a68f20f --- /dev/null +++ b/xen/tools/abi-tool/Cargo.lock @@ -0,0 +1,145 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "abi-tool" +version = "0.1.0" +dependencies = [ + "anyhow", + "bimap", + "indexmap", + "serde", + "serde_yaml", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" + +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "arbitrary", + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" diff --git a/xen/tools/abi-tool/Cargo.toml b/xen/tools/abi-tool/Cargo.toml new file mode 100644 index 0000000000..eee8ad18a9 --- /dev/null +++ b/xen/tools/abi-tool/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "abi-tool" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.98" +bimap = { version = "0.6.3", features = ["serde"] } +indexmap = { version = "2.10.0", features = ["arbitrary", "serde"] } +serde = { version = "1.0.219", features = ["derive"] } +serde_yaml = "0.9.34" diff --git a/xen/tools/abi-tool/src/abi.rs b/xen/tools/abi-tool/src/abi.rs new file mode 100644 index 0000000000..9ca427e821 --- /dev/null +++ b/xen/tools/abi-tool/src/abi.rs @@ -0,0 +1,23 @@ +pub trait XenABI { + fn get_register_name(id: u8) -> &'static str; +} + +pub struct Amd64ABI; + +impl XenABI for Amd64ABI { + fn get_register_name(id: u8) -> &'static str { + match id { + 0 => "rax", + 1 => "rdi", + 2 => "rsi", + 3 => "r8", + 4 => "r9", + 5 => "r10", + 6 => "r11", + 7 => "r12", + 8 => "r13", + + _ => panic!("Unexpected register id: {id}"), + } + } +} diff --git a/xen/tools/abi-tool/src/c_lang.rs b/xen/tools/abi-tool/src/c_lang.rs new file mode 100644 index 0000000000..20389d305d --- /dev/null +++ b/xen/tools/abi-tool/src/c_lang.rs @@ -0,0 +1,173 @@ +use std::{collections::HashMap, fmt::Write}; + +use crate::{ + abi::{Amd64ABI, XenABI}, + spec::{AbiSpec, CType, HypercallOp}, +}; + +fn emit_register_variable<ABI: XenABI>( + w: &mut impl Write, + id: u8, + value: Option<&str>, +) -> anyhow::Result<()> { + write!( + w, + " register {ctype} reg{id} __asm__(\"{reg}\")", + ctype = if id == 0 { "long" } else { "uint64_t" }, + reg = ABI::get_register_name(id) + )?; + if let Some(value) = value { + write!(w, " = {value}")?; + } + writeln!(w, ";")?; + + Ok(()) +} + +fn emit_hypercall<ABI: XenABI>( + w: &mut impl Write, + op: &HypercallOp, + ident: usize, + instruction: &str, +) -> anyhow::Result<()> { + let start = format!("{:ident$}__asm__ volatile (\"{instruction}\" ", ""); + let pad = start.len(); + + /* All the exclusive inputs. */ + let reg_input = op + .input + .right_values() + .filter(|&input| !op.output.contains_right(input)) + .map(|input| format!("\"r\"(reg{input})")) + .collect::<Box<[_]>>() + .join(", "); + + /* Outputs that are also inputs are transformed into +r, the rest is =r */ + let reg_output = op + .output + .right_values() + .chain(&[0]) /* 0 is always a input/output */ + .map(|output| { + if *output == 0 || op.input.contains_right(output) { + format!("\"+r\"(reg{output})") + } else { + format!("\"=r\"(reg{output})") + } + }) + .collect::<Box<[_]>>() + .join(", "); + + writeln!(w, "{:ident$}{start}: {reg_output}", "")?; + writeln!(w, "{:ident$}{:pad$}: {reg_input}", "", "")?; + writeln!(w, "{:ident$}{:pad$}: \"memory\");", "", "")?; + + Ok(()) +} + +fn generate_hypercall_function( + w: &mut impl Write, + hypercall_name: &str, + op: &HypercallOp, + function_name: &str, + subop_index: Option<u32>, +) -> anyhow::Result<()> { + writeln!(w, "static inline")?; + + eprintln!("Processing {hypercall_name}.{function_name}"); + let annotations = op.c_lang.clone().unwrap_or_default(); + + assert!( + annotations.cstruct.is_some() || op.output.is_empty(), + "struct-less wrappers doesn't allow outputs, please use a C structure" + ); + + // Match each input register with its C value. + let input_map: HashMap<u8, String> = op + .input + .iter() + .map(|(name, &id)| { + if annotations.params.contains_key(name) { + (id, name.clone()) + } else { + // Struct may have a custom mapping + let field = annotations.mapping.get(name).unwrap_or(name); + + (id, format!("param->{field}")) + } + }) + .collect(); + + write!(w, "long {function_name}(enum xen_hypercall_vendor vendor")?; + let pad = 6 + function_name.len(); + + if let Some(cstruct) = &annotations.cstruct { + write!(w, ",\n{:pad$}struct {cstruct} *param", "",)?; + } + + for (name, CType(ctype)) in &annotations.params { + write!(w, ",\n{:pad$}{ctype} {name}", "",)?; + } + + writeln!(w, ")")?; + + writeln!(w, "{{")?; + + for id in op.used_registers() { + // If it is a input, we need to set it here. + let value = match (id, subop_index) { + /* Hypercall index */ + (0, _) => Some(format!( + "__HYPERVISOR_FASTABI_MASK | __HYPERVISOR_{hypercall_name}_op" + )), + /* Sub-operation index */ + (1, Some(subop_index)) => Some(format!("{subop_index}")), + /* Other input parameter */ + (id, _) => input_map.get(&id).cloned(), + }; + + emit_register_variable::<Amd64ABI>(w, id, value.as_deref())?; + } + writeln!(w)?; + + writeln!(w, " if ( vendor == Intel )")?; + emit_hypercall::<Amd64ABI>(w, op, 4, "vmcall")?; + writeln!(w, " else")?; + emit_hypercall::<Amd64ABI>(w, op, 4, "vmmcall")?; + + writeln!(w, "")?; + + for (field, output) in &op.output { + let field = annotations.mapping.get(field).unwrap_or(field); + + writeln!(w, " param->{field} = reg{output};")?; + } + + writeln!(w, " return reg0;")?; + + writeln!(w, "}}")?; + + Ok(()) +} + +pub fn generate_code(w: &mut impl Write, spec: AbiSpec) -> anyhow::Result<()> { + writeln!(w, "/* SPDX-License-Identifier: MIT */")?; + writeln!(w, "/* AUTOGENERATED. DO NOT MODIFY */")?; + writeln!(w)?; + + if let Some(op) = spec.direct { + let function_name = ["xen_hypercall", &spec.name].join("_"); + + generate_hypercall_function(w, &spec.name, &op, &function_name, None)?; + writeln!(w)?; + } + + for (name, mut subop) in spec.subops { + let function_name = ["xen_hypercall", &spec.name, &name].join("_"); + subop.op.input.insert("subop_index".to_string(), 1); + + generate_hypercall_function(w, &spec.name, &subop.op, &function_name, Some(subop.index))?; + writeln!(w)?; + } + + Ok(()) +} diff --git a/xen/tools/abi-tool/src/main.rs b/xen/tools/abi-tool/src/main.rs new file mode 100644 index 0000000000..dda85c24d5 --- /dev/null +++ b/xen/tools/abi-tool/src/main.rs @@ -0,0 +1,17 @@ +use std::io::{Read, stdin}; + +pub mod abi; +pub mod c_lang; +pub mod spec; + +fn main() { + let mut buffer = String::new(); + stdin().read_to_string(&mut buffer).unwrap(); + + let abi_spec: spec::AbiSpec = serde_yaml::from_str(&buffer).unwrap(); + + let mut buffer = String::new(); + + c_lang::generate_code(&mut buffer, abi_spec).unwrap(); + print!("{buffer}"); +} diff --git a/xen/tools/abi-tool/src/spec.rs b/xen/tools/abi-tool/src/spec.rs new file mode 100644 index 0000000000..e5fb2c85d2 --- /dev/null +++ b/xen/tools/abi-tool/src/spec.rs @@ -0,0 +1,61 @@ +use std::collections::{BTreeSet, HashMap}; + +use bimap::BiBTreeMap; +use indexmap::IndexMap; /* use indexmap to keep consistent ordering */ +use serde::Deserialize; + +fn default_ctype() -> String { + "uint64_t".into() +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Hash)] +pub struct CType(#[serde(default = "default_ctype")] pub String); + +#[derive(Clone, Debug, Default, Deserialize)] +pub struct CAnnotations { + #[serde(rename = "struct")] + pub cstruct: Option<String>, + #[serde(default)] + pub mapping: HashMap<String, String>, + #[serde(default)] + pub params: HashMap<String, CType>, +} + +#[derive(Debug, Deserialize)] +pub struct HypercallOp { + #[serde(default)] + pub input: BiBTreeMap<String, u8>, + #[serde(default)] + pub output: BiBTreeMap<String, u8>, + + pub c_lang: Option<CAnnotations>, +} + +impl HypercallOp { + pub fn used_registers(&self) -> BTreeSet<u8> { + self.input + .right_values() + .chain(self.output.right_values()) + .chain(&[0]) + .cloned() + .collect() + } +} + +#[derive(Debug, Deserialize)] +pub struct HypercallSubOp { + pub index: u32, + #[serde(flatten)] + pub op: HypercallOp, +} + +#[derive(Debug, Deserialize)] +pub struct AbiSpec { + pub hypercall_index: u32, + pub name: String, + + pub direct: Option<HypercallOp>, + + #[serde(default)] + pub subops: IndexMap<String, HypercallSubOp>, +} -- 2.50.1 Teddy Astie | Vates XCP-ng Developer XCP-ng & Xen Orchestra - Vates solutions web: https://vates.tech
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |