diff --git a/tools/rust/Makefile b/tools/rust/Makefile
index 814e5b94447f..4f064c37f45c 100644
--- a/tools/rust/Makefile
+++ b/tools/rust/Makefile
@@ -6,6 +6,9 @@ XENBINDGEN=$(CURDIR)/xenbindgen
 # Path to the autogenerated Rust bindings crate
 CRATE_XENSYS=$(CURDIR)/xen-sys
 
+# Output folder for the autogenerated Rust files
+AUTOGEN_RS=$(CRATE_XENSYS)/src/autogen
+
 # Output folder for the autogenerated C headers
 #
 # Must contain autogenerated files only because they're all wiped on update
@@ -25,13 +28,18 @@ all install uninstall clean:
 # Remove all autogenerated files
 .PHONY: clean-autogen
 clean-autogen:
-	rm -rf "${AUTOGEN_C}"
+	rm -rf "${AUTOGEN_RS}" "${AUTOGEN_C}"
 
 # Refresh autogenerated files. Depending on clean-autogen is required in order
 # for removals of specification files to cause the removal of their
 # autogenerated files.
 .PHONY: update
 update: clean-autogen
+	# Update Rust bindings
+	cargo run --manifest-path "${XENBINDGEN}/Cargo.toml" -- --lang rust \
+	          --indir "${XENBINDGEN}/extra" --outdir "${AUTOGEN_RS}"
+	cargo fmt --manifest-path "${CRATE_XENSYS}/Cargo.toml"
+	
 	# Update C bindings
 	cargo run --manifest-path "${XENBINDGEN}/Cargo.toml" -- --lang c \
 	          --indir "${XENBINDGEN}/extra" --outdir "${AUTOGEN_C}"
diff --git a/tools/rust/xen-sys/src/lib.rs b/tools/rust/xen-sys/src/lib.rs
index 7b3ea6c8ad4c..efab54ee1025 100644
--- a/tools/rust/xen-sys/src/lib.rs
+++ b/tools/rust/xen-sys/src/lib.rs
@@ -5,6 +5,8 @@
 //! a few closely related primitives, like [`Align64`].
 #![no_std]
 
+pub mod autogen;
+
 use core::ops::{Deref, DerefMut};
 
 /// Wrapper for pointers and 64bit integers so they are _always_ aligned to 8
diff --git a/tools/rust/xenbindgen/src/main.rs b/tools/rust/xenbindgen/src/main.rs
index dbc610e420f2..a1cf47e26bf5 100644
--- a/tools/rust/xenbindgen/src/main.rs
+++ b/tools/rust/xenbindgen/src/main.rs
@@ -5,6 +5,7 @@
 mod spec;
 
 mod c_lang;
+mod rs_lang;
 
 use std::{io::Write, path::PathBuf};
 
@@ -35,6 +36,8 @@ struct Cli {
 enum Lang {
     #[doc(hidden)]
     C,
+    #[doc(hidden)]
+    Rust,
 }
 
 fn main() {
@@ -66,6 +69,7 @@ fn main() {
     };
 
     let (extension, parser): (&str, fn(&OutFileDef) -> String) = match cli.lang {
+        Lang::Rust => (".rs", rs_lang::parse),
         Lang::C => (".h", c_lang::parse),
     };
 
diff --git a/tools/rust/xenbindgen/src/rs_lang.rs b/tools/rust/xenbindgen/src/rs_lang.rs
new file mode 100644
index 000000000000..cd5ce2989d51
--- /dev/null
+++ b/tools/rust/xenbindgen/src/rs_lang.rs
@@ -0,0 +1,227 @@
+//! Rust backend
+//!
+//! A backend for the Rust programming language. Enums, structs and bitmaps
+//! are native (with the latter being available via the `bitflags` crate.
+//!
+//! 64bit primitives and pointers are wrapped in a _magic_ type called `Align64`.
+//! This type is expected to exist in the target crate and is meant to provide
+//! ergonomic mechanisms to, create, access and modify its contents. Its whole
+//! purpose is make those types have 64bit size and be 64bit aligned even on
+//! 32bit targets.
+use std::fmt::Write;
+
+use crate::spec::{BitmapDef, EnumDef, IncludeDef, OutFileDef, StructDef, Typ};
+
+use convert_case::{Case, Casing};
+use log::{debug, trace};
+
+/// An abstract indentation level. 0 is no indentation, 1 is [`INDENT_WIDTH`]
+/// and so on.
+#[derive(Copy, Clone)]
+struct Indentation(usize);
+
+/// Default width of each level of indentation
+const INDENT_WIDTH: usize = 4;
+
+/// Convert an IDL type into its Rust type.
+fn typ_rs(typ: &Typ) -> String {
+    match typ {
+        Typ::Ptr(x) => format!("crate::Align64<*mut {}>", typ_rs(x)),
+        Typ::Enum(x) | Typ::Struct(x) | Typ::Bitmap(x) => x.to_case(Case::Pascal),
+        Typ::Array(x, len) => format!("[{}; {}]", typ_rs(x), len),
+        Typ::U8 => String::from("u8"),
+        Typ::U16 => String::from("u16"),
+        Typ::U32 => String::from("u32"),
+        Typ::U64 => String::from("crate::Align64<u64>"),
+        Typ::I8 => String::from("i8"),
+        Typ::I16 => String::from("i16"),
+        Typ::I32 => String::from("i32"),
+        Typ::I64 => String::from("crate::Align64<i64>"),
+    }
+}
+
+/// Add a comment to a struct or a field.
+fn comment(out: &mut String, comment: &str, ind: Indentation) {
+    let spaces = " ".repeat(INDENT_WIDTH * ind.0);
+    for line in comment.split('\n') {
+        write!(out, "{spaces}///").unwrap();
+        if !line.is_empty() {
+            write!(out, " {line}").unwrap();
+        }
+        writeln!(out).unwrap();
+    }
+}
+
+/// Perform external inclusion in the form of a Rust _use_ statement.
+fn includegen(out: &mut String, def: &IncludeDef) {
+    if def.imports.is_empty() {
+        return;
+    }
+
+    let refered_pascal: Vec<String> = def
+        .imports
+        .iter()
+        .map(|s| s.to_case(Case::Pascal))
+        .collect();
+
+    writeln!(
+        out,
+        "use super::{}::{{{}}};",
+        &def.from,
+        refered_pascal.join(",")
+    )
+    .unwrap();
+
+    writeln!(out).unwrap();
+}
+
+/// Write a Rust-compatible struct onto `out`
+fn structgen(out: &mut String, def: &StructDef) {
+    debug!("struct {}", def.name);
+
+    comment(out, &def.description, Indentation(0));
+    writeln!(out, "#[repr(C)]").unwrap();
+    writeln!(out, "#[derive(Clone, Debug, Default)]").unwrap();
+    write!(out, "pub struct {}", def.name.to_case(Case::Pascal)).unwrap();
+
+    if def.fields.is_empty() {
+        // zero-sized struct
+        writeln!(out, ";\n").unwrap();
+        return;
+    }
+
+    writeln!(out, " {{").unwrap();
+
+    for f in &def.fields {
+        trace!("  field {} type={:?}", f.name, f.typ);
+
+        comment(out, &f.description, Indentation(1));
+        writeln!(
+            out,
+            "    pub {}: {},",
+            f.name.to_case(Case::Snake),
+            typ_rs(&f.typ)
+        )
+        .unwrap();
+    }
+    writeln!(out, "}}\n").unwrap();
+}
+
+/// Write a Rust-compatible enum onto `out`
+fn enumgen(out: &mut String, def: &EnumDef) {
+    debug!("enum {}", def.name);
+
+    comment(out, &def.description, Indentation(0));
+
+    // If the underlying type is 64bits things get trickier. What we want
+    // in that case is to use u64 for the underlying type, but set
+    // align(8) on the overall repr. Otherwise 32bit platforms have the wrong
+    // alignment.
+    let repr: &str = if def.typ == Typ::U64 || def.typ == Typ::I64 {
+        "align(8)"
+    } else {
+        &typ_rs(&def.typ)
+    };
+
+    writeln!(out, "#[repr({repr})]").unwrap();
+    writeln!(out, "#[derive(Clone, Debug, Default, PartialEq, Eq)]").unwrap();
+    writeln!(out, "pub enum {} {{", def.name.to_case(Case::Pascal)).unwrap();
+    for (i, f) in def.variants.iter().enumerate() {
+        trace!("  variant {}={}", f.name, f.value);
+
+        comment(out, &f.description, Indentation(1));
+        if i == 0 {
+            writeln!(out, "    #[default]").unwrap();
+        }
+        writeln!(out, "    {} = {},", f.name.to_case(Case::Pascal), f.value).unwrap();
+    }
+    writeln!(out, "}}\n").unwrap();
+}
+
+/// Write a Rust-compatible bitmap onto `out`.
+///
+/// NOTE: Uses the bitflags crate underneath
+fn bitmapgen(out: &mut String, def: &BitmapDef) {
+    debug!("bitmap {}", def.name);
+
+    writeln!(out, "bitflags! {{").unwrap();
+    comment(out, &def.description, Indentation(1));
+
+    // If the underlying type is 64bits things get trickier. What we want
+    // in that case is to use u64 for the underlying type, but set
+    // align(8) on the overall repr.
+    let is_64bits = def.typ == Typ::U64 || def.typ == Typ::I64;
+    let repr = if is_64bits { "align(8)" } else { "C" };
+
+    writeln!(out, "    #[repr({repr})]").unwrap();
+    writeln!(
+        out,
+        "    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]"
+    )
+    .unwrap();
+    writeln!(
+        out,
+        "    pub struct {}: {} {{",
+        def.name.from_case(Case::Snake).to_case(Case::Pascal),
+        if is_64bits {
+            "u64".to_owned()
+        } else {
+            typ_rs(&def.typ)
+        }
+    )
+    .unwrap();
+
+    for f in &def.bits {
+        trace!("  shift {}={}", f.name, f.shift);
+
+        comment(out, &f.description, Indentation(2));
+        writeln!(
+            out,
+            "        const {} = 1 << {};",
+            f.name.from_case(Case::Snake).to_case(Case::Pascal),
+            f.shift
+        )
+        .unwrap();
+    }
+    writeln!(out, "    }}").unwrap();
+
+    writeln!(out, "}}\n").unwrap();
+}
+
+/// Generates a single `.rs` file.
+///
+/// `filedef` is a language-agnostic high level description of what the output
+/// must contain. The function returns the contents of the new
+///
+/// # Aborts
+/// Aborts the process with `rc=1` on known illegal specifications.
+pub fn parse(filedef: &OutFileDef) -> String {
+    let mut out = String::new();
+
+    writeln!(out, "//! {}", &filedef.name).unwrap();
+    writeln!(out, "//!").unwrap();
+    writeln!(out, "//! AUTOGENERATED. DO NOT MODIFY").unwrap();
+    writeln!(out).unwrap();
+
+    if !filedef.bitmaps.is_empty() {
+        writeln!(out, "use bitflags::bitflags;\n").unwrap();
+    }
+
+    for def in &filedef.includes {
+        includegen(&mut out, def);
+    }
+
+    for def in &filedef.bitmaps {
+        bitmapgen(&mut out, def);
+    }
+
+    for def in &filedef.enums {
+        enumgen(&mut out, def);
+    }
+
+    for def in &filedef.structs {
+        structgen(&mut out, def);
+    }
+
+    out
+}
