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.as...@vates.tech>
---
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


Reply via email to