1use 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 }
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}