arm_sysregs_xml/
lib.rs

1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use serde::{Deserialize, Deserializer, de::Unexpected};
16
17#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
18pub struct RegisterPage {
19    pub registers: Registers,
20    pub timestamp: String,
21    pub commit_id: String,
22}
23
24#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
25pub struct Registers {
26    pub register: Register,
27}
28
29#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
30pub struct Register {
31    #[serde(rename = "@execution_state")]
32    pub execution_state: Option<ExecutionState>,
33    #[serde(rename = "@is_register", deserialize_with = "titlecase_bool")]
34    pub is_register: bool,
35    #[serde(rename = "@is_internal", deserialize_with = "titlecase_bool")]
36    pub is_internal: bool,
37    #[serde(rename = "@is_stub_entry", deserialize_with = "titlecase_bool")]
38    pub is_stub_entry: bool,
39    pub reg_short_name: String,
40    pub reg_long_name: String,
41    pub reg_condition: Option<RegCondition>,
42    pub power_domain_text: Option<Text>,
43    pub reg_reset_value: RegResetValue,
44    pub reg_mappings: RegMappings,
45    pub reg_purpose: RegPurpose,
46    pub reg_groups: RegGroups,
47    pub reg_configuration: Option<RegConfiguration>,
48    pub reg_attributes: RegAttributes,
49    pub reg_fieldsets: RegFieldsets,
50    pub access_mechanisms: AccessMechanisms,
51    pub arch_variants: ArchVariants,
52    #[serde(default)]
53    pub reg_address: Vec<RegAddress>,
54}
55
56#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
57pub enum ExecutionState {
58    AArch32,
59    AArch64,
60    External,
61}
62
63#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
64pub struct RegCondition {
65    #[serde(rename = "@otherwise")]
66    pub otherwise: Option<String>,
67    #[serde(rename = "$value", default)]
68    pub condition: Vec<TextEntry>,
69}
70
71#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
72pub struct RegResetValue {
73    #[serde(default)]
74    pub reg_reset_limited_to_el: Vec<String>,
75    pub reg_reset_special_text: Option<RegResetSpecialText>,
76}
77
78#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
79pub struct RegResetSpecialText {}
80
81#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
82pub struct RegMappings {
83    #[serde(default)]
84    pub reg_mapping: Vec<RegMapping>,
85}
86
87#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
88pub struct RegMapping {
89    pub mapped_name: MappedName,
90    pub mapped_type: String,
91    pub mapped_execution_state: ExecutionState,
92    pub mapped_from_startbit: Option<u8>,
93    pub mapped_from_endbit: Option<u8>,
94    pub mapped_to_startbit: Option<u8>,
95    pub mapped_to_endbit: Option<u8>,
96    pub mapped_from_rangeset: Option<MappedFromRangeset>,
97}
98
99#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
100pub struct MappedName {
101    #[serde(rename = "@filename")]
102    pub filename: String,
103    #[serde(rename = "$text")]
104    pub name: String,
105}
106
107#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
108pub struct MappedFromRangeset {
109    #[serde(rename = "@output")]
110    pub output: String,
111    pub range: Vec<Range>,
112}
113
114#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
115pub struct Range {
116    pub msb: u8,
117    pub lsb: u8,
118}
119
120#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
121pub struct RegPurpose {
122    pub purpose_text: Vec<Text>,
123}
124
125#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
126pub struct RegGroups {
127    pub reg_group: Vec<String>,
128}
129
130#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
131pub struct RegConfiguration {
132    #[serde(default)]
133    pub configuration_text: Vec<ConfigurationText>,
134}
135
136#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
137pub struct ConfigurationText {}
138
139#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
140pub struct RegAttributes {
141    pub attributes_text: Vec<Text>,
142}
143
144#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
145pub struct RegFieldsets {
146    #[serde(default)]
147    pub fields: Vec<Fields>,
148    #[serde(default)]
149    pub reg_fieldset: Vec<RegFieldset>,
150}
151
152#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
153pub struct Fields {
154    #[serde(rename = "@id")]
155    pub id: Option<String>,
156    #[serde(rename = "@length")]
157    pub length: Option<u8>,
158    pub fields_condition: Option<String>,
159    pub text_before_fields: Text,
160    pub field: Vec<Field>,
161}
162
163#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
164pub struct Field {
165    #[serde(rename = "@id")]
166    pub id: String,
167    #[serde(rename = "@has_partial_fieldset", deserialize_with = "titlecase_bool")]
168    pub has_partial_fieldset: bool,
169    #[serde(
170        rename = "@is_linked_to_partial_fieldset",
171        deserialize_with = "titlecase_bool"
172    )]
173    pub is_linked_to_partial_fieldset: bool,
174    #[serde(
175        rename = "@is_access_restriction_possible",
176        deserialize_with = "titlecase_bool"
177    )]
178    pub is_access_restriction_possible: bool,
179    #[serde(rename = "@is_variable_length", deserialize_with = "titlecase_bool")]
180    pub is_variable_length: bool,
181    #[serde(rename = "@is_constant_value", deserialize_with = "titlecase_bool")]
182    pub is_constant_value: bool,
183    #[serde(rename = "@is_partial_field", deserialize_with = "titlecase_bool")]
184    pub is_partial_field: bool,
185    #[serde(
186        rename = "@is_conditional_field_name",
187        deserialize_with = "titlecase_bool"
188    )]
189    pub is_conditional_field_name: bool,
190    #[serde(rename = "@rwtype")]
191    pub rwtype: Option<String>,
192    pub field_name: Option<String>,
193    pub field_msb: u8,
194    pub field_lsb: u8,
195    pub rel_range: String,
196    pub field_description: Vec<FieldDescription>,
197}
198
199#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
200pub struct FieldDescription {
201    #[serde(rename = "@order")]
202    pub order: Order,
203    #[serde(rename = "$value", default)]
204    pub description: Vec<TextEntry>,
205}
206
207#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
208pub struct Text {
209    #[serde(rename = "$value", default)]
210    pub text: Vec<TextEntry>,
211}
212
213#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
214#[serde(rename_all = "kebab-case")]
215pub enum TextEntry {
216    #[serde(rename = "$text")]
217    String(String),
218    ArmDefinedWord(String),
219    List(List),
220    Note(Text),
221    Para(Para),
222    Table(Table),
223}
224
225#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
226pub struct Para {
227    // TODO
228}
229
230#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
231pub struct List {
232    pub listitem: Vec<ListItem>,
233}
234
235#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
236pub struct ListItem {}
237
238#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
239pub struct Table {}
240
241#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
242#[serde(rename_all = "lowercase")]
243pub enum Order {
244    After,
245    Before,
246}
247
248#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
249pub struct RegFieldset {
250    #[serde(rename = "@length")]
251    pub length: u8,
252    pub fields_condition: Option<String>,
253    pub fieldat: Vec<FieldAt>,
254}
255
256#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
257pub struct FieldAt {
258    #[serde(rename = "@id")]
259    pub id: String,
260    #[serde(rename = "@label")]
261    pub label: Option<String>,
262    #[serde(rename = "@msb")]
263    pub msb: u8,
264    #[serde(rename = "@lsb")]
265    pub lsb: u8,
266}
267
268#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
269pub struct AccessMechanisms {
270    #[serde(default)]
271    pub access_mechanism: Vec<AccessMechanism>,
272}
273
274#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
275pub struct AccessMechanism {
276    #[serde(rename = "@accessor")]
277    pub accessor: Option<String>,
278    #[serde(rename = "@type")]
279    pub type_: AccessMechanismType,
280    #[serde(rename = "@table_id")]
281    pub table_id: Option<String>,
282    pub encoding: Option<Encoding>,
283    pub access_permission: Option<AccessPermission>,
284    pub access_header: Option<AccessHeader>,
285}
286
287#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
288pub enum AccessMechanismType {
289    BlockAccessAbstract,
290    SystemAccessor,
291}
292
293#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
294pub struct Encoding {
295    pub access_instruction: String,
296    pub enc: Vec<Enc>,
297}
298
299#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
300pub struct Enc {
301    #[serde(rename = "@n")]
302    pub n: EncName,
303    #[serde(rename = "@v")]
304    pub v: String,
305}
306
307impl Enc {
308    pub fn parse_value(&self) -> Option<u8> {
309        let (prefix, rest) = self.v.split_at_checked(2)?;
310        if prefix == "0b" {
311            u8::from_str_radix(rest, 2).ok()
312        } else {
313            None
314        }
315    }
316}
317
318#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
319#[serde(rename_all = "lowercase")]
320pub enum EncName {
321    Coproc,
322    #[serde(rename = "CRd")]
323    CRd,
324    #[serde(rename = "CRm")]
325    CRm,
326    #[serde(rename = "CRn")]
327    CRn,
328    #[serde(rename = "M")]
329    M,
330    #[serde(rename = "M1")]
331    M1,
332    Op0,
333    Op1,
334    Op2,
335    Opc1,
336    Opc2,
337    #[serde(rename = "R")]
338    R,
339    Reg,
340}
341
342#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
343pub struct AccessPermission {
344    pub ps: Ps,
345}
346
347#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
348pub struct AccessHeader {}
349
350#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
351pub struct Ps {
352    #[serde(rename = "@name")]
353    pub name: String,
354    #[serde(rename = "@sections")]
355    pub sections: usize,
356    #[serde(rename = "@secttype")]
357    pub secttype: String,
358    pub pstext: String,
359}
360
361#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
362pub struct RegAddress {
363    #[serde(rename = "@external_access", deserialize_with = "titlecase_bool")]
364    pub external_access: bool,
365    #[serde(rename = "@mem_map_access", deserialize_with = "titlecase_bool")]
366    pub mem_map_access: bool,
367    #[serde(
368        rename = "@block_access",
369        deserialize_with = "titlecase_bool_option",
370        default
371    )]
372    pub block_access: Option<bool>,
373    #[serde(
374        rename = "@memory_access",
375        deserialize_with = "titlecase_bool_option",
376        default
377    )]
378    pub memory_access: Option<bool>,
379    #[serde(rename = "@table_id")]
380    pub table_id: Option<String>,
381    #[serde(rename = "@power_domain")]
382    pub power_domain: Option<String>,
383    pub reg_component: Option<String>,
384    pub reg_frame: Option<String>,
385    pub reg_offset: RegOffset,
386    pub reg_instance: Option<String>,
387    pub reg_access: RegAccess,
388}
389
390#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
391pub struct RegOffset {
392    pub hexnumber: String,
393}
394
395impl RegOffset {
396    pub fn parse_hex(&self) -> Option<u64> {
397        let (prefix, rest) = self.hexnumber.split_at_checked(2)?;
398        if prefix == "0x" {
399            u64::from_str_radix(rest, 16).ok()
400        } else {
401            None
402        }
403    }
404}
405
406#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
407pub struct RegAccess {
408    pub reg_access_state: Vec<RegAccessState>,
409}
410
411#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
412pub struct RegAccessState {
413    pub reg_access_level: Option<String>,
414    pub reg_access_type: String,
415}
416
417#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
418pub struct ArchVariants {
419    #[serde(default)]
420    pub arch_variant: Vec<ArchVariant>,
421}
422
423#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
424pub struct ArchVariant {
425    #[serde(rename = "@name")]
426    pub name: String,
427}
428
429fn titlecase_bool<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
430    match String::deserialize(deserializer)?.as_ref() {
431        "True" => Ok(true),
432        "False" => Ok(false),
433        other => Err(serde::de::Error::invalid_value(
434            Unexpected::Str(other),
435            &"True or False",
436        )),
437    }
438}
439
440fn titlecase_bool_option<'de, D: Deserializer<'de>>(
441    deserializer: D,
442) -> Result<Option<bool>, D::Error> {
443    match <Option<String>>::deserialize(deserializer)?.as_deref() {
444        Some("True") => Ok(Some(true)),
445        Some("False") => Ok(Some(false)),
446        None => Ok(None),
447        Some(other) => Err(serde::de::Error::invalid_value(
448            Unexpected::Str(other),
449            &"True or False",
450        )),
451    }
452}
453
454#[cfg(test)]
455mod tests {
456    use super::*;
457    use quick_xml::de;
458    use std::{
459        fs::{File, read_dir},
460        io::BufReader,
461    };
462
463    #[test]
464    fn parse_reg_purpose() {
465        let reg_purpose: RegPurpose = de::from_str(
466            "<reg_purpose><purpose_text><para>foo</para></purpose_text></reg_purpose>",
467        )
468        .unwrap();
469        assert_eq!(
470            reg_purpose,
471            RegPurpose {
472                purpose_text: vec![Text {
473                    text: vec![TextEntry::Para(Para {})]
474                }]
475            }
476        );
477    }
478
479    #[test]
480    fn parse_reg_condition_string() {
481        let reg_condition: RegCondition =
482            de::from_str("<reg_condition>foo</reg_condition>").unwrap();
483        assert_eq!(
484            reg_condition,
485            RegCondition {
486                otherwise: None,
487                condition: vec![TextEntry::String("foo".to_string())]
488            }
489        );
490    }
491
492    #[test]
493    fn parse_reg_condition_para() {
494        let reg_condition: RegCondition =
495            de::from_str("<reg_condition><para>foo</para></reg_condition>").unwrap();
496        assert_eq!(
497            reg_condition,
498            RegCondition {
499                otherwise: None,
500                condition: vec![TextEntry::Para(Para {})]
501            }
502        );
503    }
504
505    #[test]
506    fn parse_field_description_empty() {
507        let field_description: FieldDescription =
508            de::from_str("<field_description order=\"before\"/>").unwrap();
509        assert_eq!(
510            field_description,
511            FieldDescription {
512                order: Order::Before,
513                description: vec![],
514            }
515        );
516    }
517
518    #[test]
519    fn parse_field_description_para() {
520        let field_description: FieldDescription = de::from_str(
521            "<field_description order=\"before\"><para>foo</para></field_description>",
522        )
523        .unwrap();
524        assert_eq!(
525            field_description,
526            FieldDescription {
527                order: Order::Before,
528                description: vec![TextEntry::Para(Para {})],
529            }
530        );
531    }
532
533    #[test]
534    fn parse_reg_reset_value_empty() {
535        let reg_reset_value: RegResetValue =
536            de::from_str("<reg_reset_value></reg_reset_value>").unwrap();
537        assert_eq!(
538            reg_reset_value,
539            RegResetValue {
540                reg_reset_limited_to_el: vec![],
541                reg_reset_special_text: None
542            }
543        );
544    }
545
546    #[test]
547    fn parse_reg_attributes() {
548        let reg_attributes: RegAttributes = de::from_str(
549            "<reg_attributes><attributes_text><para>foo</para></attributes_text></reg_attributes>",
550        )
551        .unwrap();
552        assert_eq!(
553            reg_attributes,
554            RegAttributes {
555                attributes_text: vec![Text {
556                    text: vec![TextEntry::Para(Para {})]
557                }]
558            }
559        );
560    }
561
562    #[test]
563    fn parse_text_before_fields() {
564        let text_before_fields: Text =
565            de::from_str("<text_before_fields><para>foo</para></text_before_fields>").unwrap();
566        assert_eq!(
567            text_before_fields,
568            Text {
569                text: vec![TextEntry::Para(Para {})]
570            }
571        );
572    }
573
574    #[test]
575    fn parse_hexnumber() {
576        let reg_offset: RegOffset =
577            de::from_str("<reg_offset><hexnumber>0x18</hexnumber></reg_offset>").unwrap();
578        assert_eq!(reg_offset.parse_hex(), Some(0x18));
579    }
580
581    #[test]
582    fn parse_enc() {
583        let enc: Enc = de::from_str("<enc n=\"coproc\" v=\"0b1101\"/>").unwrap();
584        assert_eq!(enc.n, EncName::Coproc);
585        assert_eq!(enc.parse_value(), Some(0b1101));
586    }
587
588    #[test]
589    #[ignore]
590    fn parse_all() {
591        let mut failed = 0;
592        let mut succeeded = 0;
593        for entry in read_dir("SysReg_xml_A_profile-2025-06/SysReg_xml_A_profile-2025-06").unwrap()
594        {
595            let entry = entry.unwrap();
596            let filename = entry.file_name().into_string().unwrap();
597            if filename.ends_with(".xml")
598                && !filename.ends_with("index.xml")
599                && ![
600                    "amu.xml",
601                    "architecture_info.xml",
602                    "instructions.xml",
603                    "notice.xml",
604                    "pmu.xml",
605                ]
606                .contains(&filename.as_str())
607            {
608                if let Err(e) = de::from_reader::<_, RegisterPage>(BufReader::new(
609                    File::open(entry.path()).unwrap(),
610                )) {
611                    println!("{filename}:");
612                    println!("{e}");
613                    failed += 1;
614                } else {
615                    succeeded += 1;
616                }
617            }
618        }
619        println!("{succeeded} succeeded");
620        assert_eq!(failed, 0);
621    }
622}