diff --git a/tools/rust/xenbindgen/src/c_lang.rs b/tools/rust/xenbindgen/src/c_lang.rs
index 597e0ed41362..f15feca8df91 100644
--- a/tools/rust/xenbindgen/src/c_lang.rs
+++ b/tools/rust/xenbindgen/src/c_lang.rs
@@ -17,10 +17,10 @@
 
 use std::fmt::Write;
 
-use crate::spec::{OutFileDef, StructDef, Typ};
+use crate::spec::{EnumDef, OutFileDef, StructDef, Typ};
 
 use convert_case::{Case, Casing};
-use log::{debug, trace};
+use log::{debug, error, trace};
 
 /// An abstract indentation level. 0 is no indentation, 1 is [`INDENT_WIDTH`]
 /// and so on.
@@ -31,7 +31,7 @@ struct Indentation(usize);
 const INDENT_WIDTH: usize = 4;
 
 /// Create a C-compatible struct field. Without the terminating semicolon.
-fn structfield(typ: &Typ, name: &str) -> String {
+fn structfield(filedef: &OutFileDef, typ: &Typ, name: &str) -> String {
     match typ {
         Typ::Ptr(x) => {
             let t: &Typ = x;
@@ -51,7 +51,20 @@ fn structfield(typ: &Typ, name: &str) -> String {
             )
         }
         Typ::Struct(x) => format!("struct {x} {name}"),
-        Typ::Array(x, len) => format!("{}{name}[{len}]", structfield(x, "")),
+        Typ::Enum(x) => {
+            // C can't use an enum as a field and fix its width. Look for its
+            // underlying layout and use that type instead.
+            let Some(e) = filedef.enums.iter().find(|y| *x == y.name) else {
+                error!("Can't find enum {x}. Typo?");
+                std::process::exit(1);
+            };
+            format!(
+                "{} /* See {} */",
+                structfield(filedef, &e.typ, name),
+                e.name
+            )
+        }
+        Typ::Array(x, len) => format!("{}{name}[{len}]", structfield(filedef, x, "")),
         Typ::U8 => format!("uint8_t {name}"),
         Typ::U16 => format!("uint16_t {name}"),
         Typ::U32 => format!("uint32_t {name}"),
@@ -83,7 +96,7 @@ fn comment(out: &mut String, comment: &str, ind: Indentation) {
 }
 
 /// Write a C-compatible struct onto `out`
-fn structgen(out: &mut String, def: &StructDef) {
+fn structgen(out: &mut String, filedef: &OutFileDef, def: &StructDef) {
     debug!("struct {}", def.name);
 
     comment(out, &def.description, Indentation(0));
@@ -92,7 +105,33 @@ fn structgen(out: &mut String, def: &StructDef) {
         trace!("  field {} type={:?}", f.name, f.typ);
 
         comment(out, &f.description, Indentation(1));
-        writeln!(out, "    {};", structfield(&f.typ, &f.name),).unwrap();
+        writeln!(out, "    {};", structfield(filedef, &f.typ, &f.name),).unwrap();
+    }
+    writeln!(out, "}};").unwrap();
+    writeln!(out).unwrap();
+}
+
+/// Write a C-compatible enum onto `out`
+///
+/// This is a generator for the enum _type_, not an instantiation of a bitmap
+/// in a struct field. Use [`structfield`] for that.
+fn enumgen(out: &mut String, def: &EnumDef) {
+    debug!("enum {}", def.name);
+
+    comment(out, &def.description, Indentation(0));
+    writeln!(out, "enum {} {{", def.name).unwrap();
+    for f in &def.variants {
+        trace!("  variant {}={}", f.name, f.value);
+
+        comment(out, &f.description, Indentation(1));
+        writeln!(
+            out,
+            "    {}_{} = {},",
+            def.name.from_case(Case::Snake).to_case(Case::UpperSnake),
+            f.name.from_case(Case::Snake).to_case(Case::UpperSnake),
+            f.value
+        )
+        .unwrap();
     }
     writeln!(out, "}};").unwrap();
     writeln!(out).unwrap();
@@ -117,8 +156,12 @@ pub fn parse(filedef: &OutFileDef) -> String {
     writeln!(out, "#ifndef __XEN_AUTOGEN_{name}_H").unwrap();
     writeln!(out, "#define __XEN_AUTOGEN_{name}_H\n").unwrap();
 
+    for def in &filedef.enums {
+        enumgen(&mut out, def);
+    }
+
     for def in &filedef.structs {
-        structgen(&mut out, def);
+        structgen(&mut out, filedef, def);
     }
 
     writeln!(out, "#endif /* __XEN_AUTOGEN_{name}_H */\n").unwrap();
diff --git a/tools/rust/xenbindgen/src/spec.rs b/tools/rust/xenbindgen/src/spec.rs
index e183378329ad..f6cfedad2150 100644
--- a/tools/rust/xenbindgen/src/spec.rs
+++ b/tools/rust/xenbindgen/src/spec.rs
@@ -28,6 +28,7 @@ use log::{debug, info};
 #[derive(Debug, serde::Deserialize, PartialEq)]
 #[serde(rename_all = "lowercase", tag = "tag", content = "args")]
 pub enum Typ {
+    Enum(String),
     Struct(String),
     U8,
     U16,
@@ -66,11 +67,47 @@ pub struct FieldDef {
     pub typ: Typ,
 }
 
+/// Description of a lang-agnostic enumerated type.
+#[derive(Debug, serde::Deserialize)]
+pub struct EnumDef {
+    /// snake-cased name of this enumeration.
+    ///
+    /// Must be converted to whatever is idiomatic in the target language.
+    pub name: String,
+    /// Description of what the type is for.
+    ///
+    /// Must be turned into documentation in the autogenerated file.
+    pub description: String,
+    /// Width of the type given as an equivalent primitive unsigned integer
+    /// of the same width.
+    pub typ: Typ,
+    /// List of variants present in this enum.
+    ///
+    /// The backend must export all of these under the same namespace if
+    /// possible.
+    pub variants: Vec<VariantDef>,
+}
+
+/// A lang-agnostic description of a single variant of an enumerated type.
+#[derive(Debug, serde::Deserialize)]
+pub struct VariantDef {
+    /// Name of this variant. Depending on the backend, the name might be
+    /// prefixed by the name of its type (as is commonly done in C).
+    pub name: String,
+    /// Meaning of this variant in the context of its type.
+    pub description: String,
+    /// Actual value associated with this variant. Must be explicit to enable
+    /// deprecation of variants.
+    pub value: u64,
+}
+
 /// A language-agnostic specification.
 #[derive(Debug, serde::Deserialize)]
 struct InFileDef {
     /// List of structs described in this input specification.
     structs: Option<Vec<StructDef>>,
+    /// List of lang-agnostic enumerated descriptions.
+    enums: Option<Vec<EnumDef>>,
 }
 
 /// Description of an abstract output (i.e: `.rs`, `.h`, etc).
@@ -82,6 +119,10 @@ pub struct OutFileDef {
     pub name: String,
     /// List of structs described by all input spec files merged on this file.
     pub structs: Vec<StructDef>,
+    /// List of enumerated descriptions.
+    ///
+    /// Implementation is lang-specific.
+    pub enums: Vec<EnumDef>,
 }
 
 impl OutFileDef {
@@ -97,6 +138,7 @@ impl OutFileDef {
         let mut ret = Self {
             name,
             structs: Vec::new(),
+            enums: Vec::new(),
         };
 
         for entry in from_ioerr(dir.read_dir())? {
@@ -107,6 +149,9 @@ impl OutFileDef {
             if let Some(structs) = filedef.structs {
                 ret.structs.extend(structs);
             }
+            if let Some(enums) = filedef.enums {
+                ret.enums.extend(enums);
+            }
         }
 
         Ok(ret)
