| /*++ |
| Licensed under the Apache-2.0 license. |
| --*/ |
| |
| use std::{collections::HashMap, rc::Rc, str::FromStr}; |
| |
| use proc_macro2::{Ident, Literal, TokenStream}; |
| use quote::{format_ident, quote}; |
| use ureg_schema::{Enum, EnumVariant, FieldType, Register, RegisterType, ValidatedRegisterBlock}; |
| |
| fn tweak_keywords(s: &str) -> &str { |
| match s { |
| "as" => "as_", |
| "break" => "break_", |
| "const" => "const_", |
| "continue" => "continue_", |
| "crate" => "crate_", |
| "else" => "else_", |
| "fn" => "fn_", |
| "for" => "for_", |
| "if" => "if_", |
| "impl" => "impl_", |
| "in" => "in_", |
| "let" => "let_", |
| "loop" => "loop_", |
| "match" => "match_", |
| "mod" => "mod_", |
| "move" => "move_", |
| "mut" => "mut_", |
| "pub" => "pub_", |
| "ref" => "ref_", |
| "return" => "return_", |
| "self" => "self_", |
| "Self" => "Self_", |
| "static" => "static_", |
| "struct" => "struct_", |
| "super" => "super_", |
| "trait" => "trait_", |
| "true" => "true_", |
| "type" => "type_", |
| "unsafe" => "unsafe_", |
| "use" => "use_", |
| "where" => "where_", |
| "while" => "while_", |
| "async" => "async_", |
| "await" => "await_", |
| "dyn" => "dyn_", |
| "abstract" => "abstract_", |
| "become" => "become_", |
| "box" => "box_", |
| "do" => "do_", |
| "final" => "final_", |
| "macro" => "macro_", |
| "override" => "override_", |
| "priv" => "priv_", |
| "typeof" => "typeof_", |
| "unsized" => "unsized_", |
| "virtual" => "virtual_", |
| "yield" => "yield_", |
| s => s, |
| } |
| } |
| |
| fn snake_ident(name: &str) -> Ident { |
| let mut result = String::new(); |
| if let Some(c) = name.chars().next() { |
| if c.is_ascii_digit() { |
| result.push('_'); |
| } |
| } |
| let mut prev = None; |
| for c in name.chars() { |
| if c.is_ascii_whitespace() || c.is_ascii_punctuation() { |
| if prev != Some('_') { |
| result.push('_'); |
| } |
| prev = Some('_'); |
| continue; |
| } |
| if let Some(prev) = prev { |
| if (prev.is_ascii_lowercase() || prev.is_ascii_digit()) && c.is_ascii_uppercase() { |
| result.push('_'); |
| } |
| } |
| prev = Some(c); |
| result.push(c.to_ascii_lowercase()); |
| } |
| |
| format_ident!("{}", tweak_keywords(result.trim_end_matches('_'))) |
| } |
| #[cfg(test)] |
| mod snake_ident_tests { |
| use crate::*; |
| |
| #[test] |
| fn test_snake_ident() { |
| assert_eq!("_8_bits", snake_ident("8 bits").to_string()); |
| assert_eq!("_16_bits", snake_ident("16_Bits").to_string()); |
| assert_eq!("_16_bits", snake_ident("16Bits").to_string()); |
| assert_eq!("_16bits", snake_ident("16bits").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("fooBarBaz").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("FooBarBaz").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("foo bar baz").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("foo_bar_baz").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("FOO BAR BAZ").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("FOO_BAR_BAZ").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("FOO BAR BAZ.").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("FOO BAR.BAZ.").to_string()); |
| assert_eq!("foo_bar_baz", snake_ident("FOO BAR..BAZ.").to_string()); |
| |
| assert_eq!("fn_", snake_ident("fn").to_string()); |
| assert_eq!("fn_", snake_ident("FN").to_string()); |
| } |
| } |
| |
| fn camel_ident(name: &str) -> Ident { |
| let mut result = String::new(); |
| if let Some(c) = name.chars().next() { |
| if c.is_ascii_digit() { |
| result.push('_'); |
| } |
| } |
| let mut upper_next = true; |
| for c in name.chars() { |
| if c.is_ascii_punctuation() || c.is_ascii_whitespace() { |
| upper_next = true; |
| } else { |
| result.push(if upper_next { |
| c.to_ascii_uppercase() |
| } else { |
| c.to_ascii_lowercase() |
| }); |
| upper_next = false; |
| } |
| } |
| format_ident!("{}", tweak_keywords(&result)) |
| } |
| |
| #[cfg(test)] |
| mod camel_ident_tests { |
| use crate::*; |
| |
| #[test] |
| fn test_camel_ident() { |
| assert_eq!("_8Bits", camel_ident("8 bits").to_string()); |
| assert_eq!("_16Bits", camel_ident("16_bits").to_string()); |
| assert_eq!("FooBarBaz", camel_ident("foo bar baz").to_string()); |
| assert_eq!("FooBarBaz", camel_ident("foo_bar_baz").to_string()); |
| assert_eq!("FooBarBaz", camel_ident("FOO BAR BAZ").to_string()); |
| assert_eq!("FooBarBaz", camel_ident("FOO_BAR_BAZ").to_string()); |
| assert_eq!("FooBarBaz", camel_ident("FOO BAR BAZ.").to_string()); |
| assert_eq!("FooBarBaz", camel_ident("FOO BAR.BAZ.").to_string()); |
| assert_eq!("Self_", camel_ident("self").to_string()); |
| } |
| } |
| |
| fn generate_enum(e: &Enum) -> TokenStream { |
| let mut variant_tokens = TokenStream::new(); |
| let mut accessor_tokens = TokenStream::new(); |
| let mut from_u32_tokens = TokenStream::new(); |
| let mut variant_map: HashMap<u32, &EnumVariant> = e |
| .variants |
| .iter() |
| .map(|variant| (variant.value, variant)) |
| .collect(); |
| for i in 0..(1u32 << e.bit_width) { |
| let variant = variant_map.remove(&i); |
| |
| let variant_ident = match variant { |
| Some(variant) => camel_ident(&variant.name), |
| None => format_ident!("Reserved{}", i), |
| }; |
| let variant_value = Literal::u32_unsuffixed(i); |
| variant_tokens.extend(quote! { |
| #variant_ident = #variant_value, |
| }); |
| from_u32_tokens.extend(quote! { |
| #variant_value => Ok(Self::#variant_ident), |
| }); |
| if let Some(variant) = variant { |
| let accessor_ident = snake_ident(&variant.name); |
| accessor_tokens.extend(quote! { |
| #[inline(always)] |
| pub fn #accessor_ident(&self) -> bool { |
| *self == Self::#variant_ident |
| } |
| }); |
| } |
| } |
| |
| // unwrap is safe because this came from a ValidatedRegisterBlock |
| let enum_name = camel_ident(e.name.as_ref().unwrap()); |
| quote! { |
| #[derive(Clone, Copy, Eq, PartialEq)] |
| #[repr(u32)] |
| pub enum #enum_name { |
| #variant_tokens |
| } |
| impl #enum_name { |
| #accessor_tokens |
| } |
| impl TryFrom<u32> for #enum_name { |
| type Error = (); |
| #[inline(always)] |
| fn try_from(val: u32) -> Result<#enum_name, ()> { |
| match val { |
| #from_u32_tokens |
| _ => Err(()), |
| } |
| } |
| } |
| impl From<#enum_name> for u32 { |
| fn from(val: #enum_name) -> Self { |
| val as u32 |
| } |
| } |
| } |
| } |
| |
| fn generate_enum_selector(e: &Enum) -> TokenStream { |
| let enum_name = camel_ident(e.name.as_ref().unwrap()); |
| let mut selector_tokens = TokenStream::new(); |
| for variant in e.variants.iter() { |
| let selector_ident = snake_ident(&variant.name); |
| let variant_ident = camel_ident(&variant.name); |
| selector_tokens.extend(quote! { |
| #[inline(always)] |
| pub fn #selector_ident(&self) -> super::#enum_name { |
| super::#enum_name::#variant_ident |
| } |
| }); |
| } |
| |
| let selector_name = format_ident!("{}Selector", enum_name); |
| quote! { |
| pub struct #selector_name(); |
| impl #selector_name { |
| #selector_tokens |
| } |
| } |
| } |
| |
| fn generate_enums<'a>(enums: impl Iterator<Item = &'a Enum>) -> TokenStream { |
| let mut enum_tokens = TokenStream::new(); |
| let mut selector_tokens = TokenStream::new(); |
| let mut enums: Vec<_> = enums.collect(); |
| enums.sort_by_key(|e| &e.name); |
| for e in enums { |
| enum_tokens.extend(generate_enum(e)); |
| selector_tokens.extend(generate_enum_selector(e)); |
| } |
| quote! { |
| #enum_tokens |
| pub mod selector { |
| #selector_tokens |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod generate_enums_test { |
| use ureg_schema::EnumVariant; |
| |
| use crate::*; |
| |
| #[test] |
| fn test_generate_enums() { |
| assert_eq!( |
| generate_enums( |
| [Enum { |
| name: Some("Pull DIR".into()), |
| bit_width: 2, |
| variants: vec![ |
| EnumVariant { |
| name: "down".to_string(), |
| value: 0 |
| }, |
| EnumVariant { |
| name: "UP".to_string(), |
| value: 1 |
| }, |
| EnumVariant { |
| name: "Hi Z".to_string(), |
| value: 2 |
| }, |
| ] |
| }] |
| .iter() |
| ) |
| .to_string(), |
| quote! { |
| #[derive (Clone , Copy , Eq , PartialEq)] |
| #[repr(u32)] |
| pub enum PullDir { |
| Down = 0, |
| Up = 1, |
| HiZ = 2, |
| Reserved3 = 3, |
| } |
| impl PullDir { |
| #[inline(always)] |
| pub fn down(&self) -> bool { *self == Self::Down } |
| #[inline(always)] |
| pub fn up(&self) -> bool { *self == Self::Up } |
| #[inline(always)] |
| pub fn hi_z(&self) -> bool { *self == Self::HiZ } |
| } |
| impl TryFrom<u32> for PullDir { |
| type Error = (); |
| #[inline (always)] fn try_from(val : u32) -> Result<PullDir, ()> { |
| match val { |
| 0 => Ok (Self :: Down), |
| 1 => Ok (Self :: Up), |
| 2 => Ok (Self :: HiZ), |
| 3 => Ok (Self :: Reserved3), |
| _ => Err (()), |
| } |
| } |
| } |
| impl From<PullDir> for u32 { |
| fn from(val : PullDir) -> Self { |
| val as u32 |
| } |
| } |
| pub mod selector { |
| pub struct PullDirSelector(); |
| impl PullDirSelector { |
| #[inline(always)] |
| pub fn down(&self) -> super::PullDir { super::PullDir::Down } |
| #[inline(always)] |
| pub fn up(&self) -> super::PullDir { super::PullDir::Up } |
| #[inline(always)] |
| pub fn hi_z(&self) -> super::PullDir { super::PullDir::HiZ } |
| } |
| } |
| } |
| .to_string() |
| ); |
| } |
| } |
| |
| fn hex_literal(val: u64) -> Literal { |
| if val > 9 { |
| Literal::from_str(&format! {"0x{val:x}"}).unwrap() |
| } else { |
| Literal::u64_unsuffixed(val) |
| } |
| } |
| |
| fn read_val_ident(reg_type: &RegisterType) -> Ident { |
| format_ident!("{}ReadVal", camel_ident(reg_type.name.as_ref().unwrap())) |
| } |
| fn write_val_ident(reg_type: &RegisterType) -> Ident { |
| format_ident!("{}WriteVal", camel_ident(reg_type.name.as_ref().unwrap())) |
| } |
| |
| fn generate_register(reg: &RegisterType) -> TokenStream { |
| let read_val_ident = read_val_ident(reg); |
| let write_val_ident = write_val_ident(reg); |
| let raw_type = format_ident!("{}", reg.width.rust_primitive_name()); |
| |
| let mut read_val_tokens = TokenStream::new(); |
| let mut write_val_tokens = TokenStream::new(); |
| |
| for field in reg.fields.iter() { |
| let field_ident = snake_ident(&field.name); |
| let position = Literal::u64_unsuffixed(field.position.into()); |
| let mask = hex_literal((1u64 << field.width) - 1); |
| let access_expr = quote! { |
| (self.0 >> #position) & #mask |
| }; |
| let comment = &field.comment.replace("<br>", "\n"); |
| if field.ty.can_read() { |
| read_val_tokens.extend(quote! { |
| #[doc = #comment] |
| #[inline(always)] |
| }); |
| if let Some(ref enum_type) = field.enum_type { |
| let enum_type_ident = camel_ident(enum_type.name.as_ref().unwrap()); |
| read_val_tokens.extend(quote! { |
| pub fn #field_ident(&self) -> super::enums::#enum_type_ident { |
| super::enums::#enum_type_ident::try_from(#access_expr).unwrap() |
| } |
| }); |
| } else if field.width == 1 { |
| read_val_tokens.extend(quote! { |
| pub fn #field_ident(&self) -> bool { |
| (#access_expr) != 0 |
| } |
| }); |
| } else { |
| read_val_tokens.extend(quote! { |
| pub fn #field_ident(&self) -> #raw_type { |
| #access_expr |
| } |
| }); |
| } |
| } |
| if field.ty.can_write() { |
| write_val_tokens.extend(quote! { |
| #[doc = #comment] |
| #[inline(always)] |
| }); |
| if let Some(ref enum_type) = field.enum_type { |
| let enum_type_ident = camel_ident(enum_type.name.as_ref().unwrap()); |
| let enum_selector_type = format_ident!("{}Selector", enum_type_ident); |
| write_val_tokens.extend(quote! { |
| pub fn #field_ident(self, f: impl FnOnce(super::enums::selector::#enum_selector_type) -> super::enums::#enum_type_ident) -> Self { |
| Self((self.0 & !(#mask << #position)) | (#raw_type::from(f(super::enums::selector::#enum_selector_type())) << #position)) |
| } |
| }); |
| } else if field.width == 1 { |
| write_val_tokens.extend(quote! { |
| pub fn #field_ident(self, val: bool) -> Self { |
| Self((self.0 & !(#mask << #position)) | (#raw_type::from(val) << #position)) |
| } |
| }); |
| } else { |
| write_val_tokens.extend(quote! { |
| pub fn #field_ident(self, val: #raw_type) -> Self { |
| Self((self.0 & !(#mask << #position)) | ((val & #mask) << #position)) |
| } |
| }); |
| } |
| } |
| if field.ty == FieldType::W1C && field.width == 1 { |
| let field_clear_ident = format_ident!("{}_clear", field_ident); |
| write_val_tokens.extend(quote! { |
| #[doc = #comment] |
| #[inline(always)] |
| pub fn #field_clear_ident(self) -> Self { |
| Self(self.0 | (1 << #position)) |
| } |
| }); |
| } |
| if field.ty == FieldType::W1S && field.width == 1 { |
| let field_set_ident = format_ident!("{}_set", field_ident); |
| write_val_tokens.extend(quote! { |
| #[doc = #comment] |
| #[inline(always)] |
| pub fn #field_set_ident(self) -> Self { |
| Self(self.0 | (1 << #position)) |
| } |
| }); |
| } |
| } |
| |
| let mut result = TokenStream::new(); |
| if !read_val_tokens.is_empty() { |
| let modify_fn_tokens = if !write_val_tokens.is_empty() { |
| quote! { |
| /// Construct a WriteVal that can be used to modify the contents of this register value. |
| pub fn modify(self) -> #write_val_ident { |
| #write_val_ident(self.0) |
| } |
| } |
| } else { |
| quote! {} |
| }; |
| result.extend(quote! { |
| #[derive(Clone, Copy)] |
| pub struct #read_val_ident(#raw_type); |
| impl #read_val_ident{ |
| #read_val_tokens |
| #modify_fn_tokens |
| } |
| impl From<#raw_type> for #read_val_ident { |
| fn from(val: #raw_type) -> Self { |
| Self(val) |
| } |
| } |
| impl From<#read_val_ident> for #raw_type { |
| fn from(val: #read_val_ident) -> #raw_type { |
| val.0 |
| } |
| } |
| |
| }); |
| } |
| if !write_val_tokens.is_empty() { |
| result.extend(quote! { |
| #[derive(Clone, Copy)] |
| pub struct #write_val_ident(#raw_type); |
| impl #write_val_ident{ |
| #write_val_tokens |
| } |
| impl From<#raw_type> for #write_val_ident { |
| fn from(val: #raw_type) -> Self { |
| Self(val) |
| } |
| } |
| impl From<#write_val_ident> for #raw_type { |
| fn from(val: #write_val_ident) -> #raw_type { |
| val.0 |
| } |
| } |
| }); |
| } |
| result |
| } |
| |
| fn generate_register_types<'a>(regs: impl Iterator<Item = &'a RegisterType>) -> TokenStream { |
| let mut regs: Vec<_> = regs.collect(); |
| regs.sort_by_key(|e| &e.name); |
| let mut tokens = TokenStream::new(); |
| for reg in regs { |
| if !has_single_32_bit_field(reg) { |
| tokens.extend(generate_register(reg)); |
| } |
| } |
| tokens |
| } |
| |
| fn has_single_32_bit_field(t: &RegisterType) -> bool { |
| t.fields.len() == 1 |
| && t.fields[0].enum_type.is_none() |
| && t.fields[0].position == 0 |
| && t.fields[0].width == 32 |
| } |
| |
| fn read_write_types(t: &RegisterType, options: &OptionsInternal) -> (TokenStream, TokenStream) { |
| // TODO: This should be using #reg_raw_type instead of u32 |
| if has_single_32_bit_field(t) { |
| (quote! { u32 }, quote! { u32 }) |
| } else { |
| if let Some(extern_type) = options.extern_types.get(t) { |
| return ( |
| extern_type.read_val_type.clone(), |
| extern_type.write_val_type.clone(), |
| ); |
| } |
| let read_type = read_val_ident(t); |
| let write_type = write_val_ident(t); |
| let module_path = &options.module_path; |
| ( |
| quote! { #module_path::regs::#read_type }, |
| quote! { #module_path::regs::#write_type }, |
| ) |
| } |
| } |
| |
| fn generate_array_type( |
| mut remaining_dimensions: impl Iterator<Item = u64>, |
| reg_type_tokens: TokenStream, |
| ) -> TokenStream { |
| match remaining_dimensions.next() { |
| Some(array_dimension) => { |
| let array_dimension = Literal::u64_unsuffixed(array_dimension); |
| let inner = generate_array_type(remaining_dimensions, reg_type_tokens); |
| quote! { ureg::Array<#array_dimension, #inner> } |
| } |
| None => reg_type_tokens, |
| } |
| } |
| |
| fn generate_block_registers( |
| registers: &[Rc<Register>], |
| raw_ptr_type: &Ident, |
| meta_tokens: &mut TokenStream, |
| block_tokens: &mut TokenStream, |
| meta_prefix: &str, |
| options: &OptionsInternal, |
| ) { |
| for reg in registers.iter() { |
| let reg_raw_type = format_ident!("{}", reg.ty.width.rust_primitive_name()); |
| let reg_name = snake_ident(®.name); |
| let mut reg_meta_name = format_ident!("{}{}", meta_prefix, camel_ident(®.name)); |
| if registers.len() == 1 && camel_ident(®.name) == meta_prefix { |
| reg_meta_name = camel_ident(®.name); |
| } |
| if reg.ty.name.is_none() { |
| continue; |
| } |
| let default_val = hex_literal(reg.default_val); |
| let (read_type, write_type) = read_write_types(®.ty, options); |
| let ptr_offset = hex_literal(reg.offset); |
| let can_read = reg.ty.fields.iter().any(|f| f.ty.can_read()); |
| let can_write = reg.ty.fields.iter().any(|f| f.ty.can_write()); |
| let can_clear = reg.ty.fields.iter().any(|f| f.ty.can_clear()); |
| let can_set = reg.ty.fields.iter().any(|f| f.ty.can_set()); |
| meta_tokens.extend(quote! { |
| #[derive(Clone, Copy)] |
| pub struct #reg_meta_name(); |
| impl ureg::RegType for #reg_meta_name { |
| type Raw = #reg_raw_type; |
| } |
| }); |
| if can_read { |
| meta_tokens.extend(quote! { |
| impl ureg::ReadableReg for #reg_meta_name { |
| type ReadVal = #read_type; |
| } |
| }); |
| } |
| if can_write || can_clear || can_set { |
| meta_tokens.extend(quote! { |
| impl ureg::WritableReg for #reg_meta_name { |
| type WriteVal = #write_type; |
| } |
| }); |
| meta_tokens.extend(quote! { |
| impl ureg::ResettableReg for #reg_meta_name { |
| const RESET_VAL: Self::Raw = #default_val; |
| } |
| }); |
| } |
| let module_path = &options.module_path; |
| let read_type_str = read_type |
| .to_string() |
| .replace(' ', "") |
| .replace("crate::", ""); |
| let write_type_str = write_type |
| .to_string() |
| .replace(' ', "") |
| .replace("crate::", ""); |
| let comment = format!( |
| "{}\n\nRead value: [`{read_type_str}`]; Write value: [`{write_type_str}`]", |
| reg.comment.replace("<br>", "\n") |
| ); |
| |
| let result_type = generate_array_type( |
| reg.array_dimensions.iter().cloned(), |
| quote! { |
| ureg::RegRef<#module_path::meta::#reg_meta_name> |
| }, |
| ); |
| let constructor = if reg.array_dimensions.is_empty() { |
| quote! { ureg::RegRef::new } |
| } else { |
| quote! { ureg::Array::new } |
| }; |
| block_tokens.extend(quote!{ |
| #[doc = #comment] |
| pub fn #reg_name(&self) -> #result_type { |
| unsafe { #constructor(self.0.wrapping_add(#ptr_offset / core::mem::size_of::<#raw_ptr_type>())) } |
| } |
| }); |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct ExternType { |
| read_val_type: TokenStream, |
| write_val_type: TokenStream, |
| } |
| |
| pub struct OptionsInternal { |
| module_path: TokenStream, |
| is_root_module: bool, |
| |
| // TODO: This should probably be a const reference |
| extern_types: HashMap<Rc<RegisterType>, ExternType>, |
| } |
| |
| #[derive(Default)] |
| pub struct Options { |
| /// If the generated code is not at the base of the crate, this should |
| /// be set to the prefix. |
| pub module: TokenStream, |
| |
| pub extern_types: HashMap<Rc<RegisterType>, ExternType>, |
| } |
| impl Options { |
| fn compile(self) -> OptionsInternal { |
| if self.module.is_empty() { |
| OptionsInternal { |
| module_path: quote! {crate}, |
| is_root_module: true, |
| extern_types: self.extern_types, |
| } |
| } else { |
| let module = self.module; |
| OptionsInternal { |
| module_path: quote! {crate::#module}, |
| is_root_module: false, |
| extern_types: self.extern_types, |
| } |
| } |
| } |
| } |
| |
| pub fn build_extern_types( |
| block: &ValidatedRegisterBlock, |
| module: TokenStream, |
| extern_types: &mut HashMap<Rc<RegisterType>, ExternType>, |
| ) { |
| for ty in block.block().declared_register_types.iter() { |
| if ty.name.is_none() { |
| continue; |
| } |
| let read_val_ident = read_val_ident(ty); |
| let write_val_ident = write_val_ident(ty); |
| extern_types.insert( |
| ty.clone(), |
| ExternType { |
| read_val_type: quote! { #module::regs::#read_val_ident }, |
| write_val_type: quote! { #module::regs::#write_val_ident }, |
| }, |
| ); |
| } |
| } |
| |
| pub fn generate_code(block: &ValidatedRegisterBlock, options: Options) -> TokenStream { |
| let options = options.compile(); |
| let enum_tokens = generate_enums(block.enum_types().values().map(AsRef::as_ref)); |
| let mut reg_tokens = generate_register_types( |
| block |
| .register_types() |
| .values() |
| .filter(|t| !options.extern_types.contains_key(*t)) |
| .map(AsRef::as_ref), |
| ); |
| reg_tokens.extend(generate_register_types( |
| block |
| .block() |
| .declared_register_types |
| .iter() |
| .map(AsRef::as_ref), |
| )); |
| |
| let mut subblock_type_tokens = TokenStream::new(); |
| let mut block_inner_tokens = TokenStream::new(); |
| let mut meta_tokens = TokenStream::new(); |
| let mut block_tokens = TokenStream::new(); |
| |
| if !block.block().registers.is_empty() { |
| let max_reg_width = block |
| .block() |
| .registers |
| .iter() |
| .map(|r| r.ty.width) |
| .max() |
| .unwrap(); |
| let raw_ptr_type = format_ident!("{}", max_reg_width.rust_primitive_name()); |
| |
| for instance in block.block().instances.iter() { |
| let name = snake_ident(&instance.name); |
| let addr = hex_literal(instance.address.into()); |
| // TODO: Should this be unsafe? |
| block_inner_tokens.extend(quote! { |
| pub fn #name() -> Self { unsafe { Self::new(#addr as *mut #raw_ptr_type) } } |
| }); |
| } |
| generate_block_registers( |
| &block.block().registers, |
| &raw_ptr_type, |
| &mut meta_tokens, |
| &mut block_inner_tokens, |
| "", |
| &options, |
| ); |
| |
| for block_array in block.block().sub_arrays.iter() { |
| let mut subblock_tokens = TokenStream::new(); |
| let subblock_name = format_ident!("{}Block", camel_ident(&block_array.name)); |
| let subblock_fn_name = snake_ident(&block_array.name); |
| let meta_prefix = camel_ident(&block_array.name).to_string(); |
| generate_block_registers( |
| &block_array.registers, |
| &raw_ptr_type, |
| &mut meta_tokens, |
| &mut subblock_tokens, |
| &meta_prefix, |
| &options, |
| ); |
| |
| subblock_type_tokens.extend(quote! { |
| #[derive(Clone, Copy)] |
| pub struct #subblock_name(*mut #raw_ptr_type); |
| impl #subblock_name { |
| #subblock_tokens |
| } |
| }); |
| let start_offset = hex_literal(block_array.start_offset); |
| let stride = hex_literal(block_array.stride); |
| let array_len = block_array.len; |
| block_inner_tokens.extend(quote! { |
| pub fn #subblock_fn_name(&self, index: usize) -> #subblock_name { |
| assert!(index < #array_len); |
| #subblock_name(unsafe { self.0.add((#start_offset + index * #stride) / core::mem::size_of::<#raw_ptr_type>()) }) |
| } |
| }); |
| } |
| block_tokens = quote! { |
| #[derive(Clone, Copy)] |
| pub struct RegisterBlock(*mut #raw_ptr_type); |
| impl RegisterBlock { |
| /// # Safety |
| /// |
| /// The caller is responsible for ensuring that ptr is valid for |
| /// volatile reads and writes at any of the offsets in this register |
| /// block. |
| pub unsafe fn new(ptr: *mut #raw_ptr_type) -> Self { |
| Self(ptr) |
| } |
| #block_inner_tokens |
| } |
| } |
| } |
| let no_std_header = if options.is_root_module { |
| quote! { #![no_std] } |
| } else { |
| // You can't set no_std in a module |
| quote! {} |
| }; |
| |
| quote! { |
| #no_std_header |
| |
| #![allow(clippy::erasing_op)] |
| #![allow(clippy::identity_op)] |
| |
| #block_tokens |
| |
| #subblock_type_tokens |
| |
| pub mod regs { |
| //! Types that represent the values held by registers. |
| #reg_tokens |
| } |
| |
| pub mod enums { |
| //! Enumerations used by some register fields. |
| #enum_tokens |
| } |
| |
| pub mod meta { |
| //! Additional metadata needed by ureg. |
| #meta_tokens |
| } |
| |
| } |
| } |