Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement special register access mode to write to AVR protected registers #784

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- Add `svd::Device` validation after parsing by `serde`
- Add `skip-crate-attributes` config flag
- Better display parsing errors
- Add an `AVR` target which includes generic code to access configuration change protected registers in a more convenient way

## [v0.31.2] - 2023-11-29

Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub enum Target {
XtensaLX,
#[cfg_attr(feature = "serde", serde(rename = "mips"))]
Mips,
#[cfg_attr(feature = "serde", serde(rename = "avr"))]
Avr,
#[cfg_attr(feature = "serde", serde(rename = "none"))]
None,
}
Expand All @@ -58,6 +60,7 @@ impl std::fmt::Display for Target {
Target::RISCV => "riscv",
Target::XtensaLX => "xtensa-lx",
Target::Mips => "mips",
Target::Avr => "avr",
Target::None => "none",
})
}
Expand All @@ -71,6 +74,7 @@ impl Target {
"riscv" => Target::RISCV,
"xtensa-lx" => Target::XtensaLX,
"mips" => Target::Mips,
"avr" => Target::Avr,
"none" => Target::None,
_ => bail!("unknown target {}", s),
})
Expand Down
7 changes: 7 additions & 0 deletions src/generate/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result<Toke

let generic_file = include_str!("generic.rs");
let generic_atomic_file = include_str!("generic_atomic.rs");
let avr_ccp_file = include_str!("generic_avr_ccp.rs");
if config.generic_mod {
let mut file = File::create(
config
Expand All @@ -157,6 +158,9 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result<Toke
}
writeln!(file, "\n{generic_atomic_file}")?;
}
if config.target == Target::Avr {
writeln!(file, "\n{}", avr_ccp_file)?;
}

if !config.make_mod {
out.extend(quote! {
Expand All @@ -174,6 +178,9 @@ pub fn render(d: &Device, config: &Config, device_x: &mut String) -> Result<Toke
}
syn::parse_file(generic_atomic_file)?.to_tokens(&mut tokens);
}
if config.target == Target::Avr {
syn::parse_file(avr_ccp_file)?.to_tokens(&mut tokens);
}

out.extend(quote! {
#[allow(unused_imports)]
Expand Down
127 changes: 127 additions & 0 deletions src/generate/generic_avr_ccp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/// Trait implemented by registers that can be used to unlock another
/// configuration change protected register.
///
/// A type reference to an unlock register needs to be defined in every
/// implementation of a [`Protected`]
pub trait UnlockRegister {
/// A raw pointer to the location of the CCP register in data space
const PTR: *mut u8;
}

/// Trait to mark a register as configuration change protected on xmega based
/// AVR cores.
///
/// To write into a configuration change protected register, the CPU first has
/// to issue a write a defined magic value to a register called `CCP` in the
/// `CPU` block of the core. After this write access has been performed, the
/// protected register has to be written within the next four instruction for
/// the write to take effect.
pub trait Protected {
/// The CCP [`UnlockRegister`] that needs to be written with the
/// [`Self::MAGIC`] value to unlock writes to the protected register.
type CcpReg: UnlockRegister;

/// The magic value that needs to be written into the configuration change
/// protection register [`Self::CcpReg`] to unlock writes to the protected
/// register
const MAGIC: u8;
}

/// Trait implemented by [`Writable`] and [`Protected`] registers which
/// allows writing to the protected register by first writing a magic value to
/// the CCP register.
pub trait ProtectedWritable<REG>
where
REG: Writable + Protected
{
/// Write to a CCP protected register by unlocking it first.
///
/// Refer to [`Reg::write`] for usage.
fn write_protected<F>(&self, f: F)
where
F: FnOnce(&mut W<REG>) -> &mut W<REG>;
}

impl<REG> ProtectedWritable<REG> for Reg<REG>
where
REG: RegisterSpec<Ux = u8> + Writable + Resettable + Protected
{
/// Unlocks and then writes bits to a `Writable` register.
///
/// Refer to [`Reg::write`] for usage.
#[inline(always)]
fn write_protected<F>(&self, f: F)
where
F: FnOnce(&mut W<REG>) -> &mut W<REG>
{
let val = f(&mut W::<REG>::from(W {
bits: REG::RESET_VALUE & !REG::ONE_TO_MODIFY_FIELDS_BITMAP
| REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
})).bits;

unsafe {
core::arch::asm!(
// Write the CCP register with the desired magic
"ldi {magicreg}, {magic}",
"out {ccpreg}, {magicreg}",

// Then immediately write the protected register
"st X, {perval}",

magic = const REG::MAGIC,
ccpreg = const unsafe { core::mem::transmute::<_, i16>(REG::CcpReg::PTR) as i32 },

in("X") self.register.as_ptr(),
perval = in(reg) val,

magicreg = out (reg) _ // mark the magicreg as clobbered
);
}
}
}

impl<REG: RegisterSpec<Ux = u8> + Readable + Writable + Protected> Reg<REG> {
/// Modifies the contents of a protected register by reading and then
/// unlocking and writing it.
///
/// Refer to [`Reg::modify`] for usage.
#[inline(always)]
pub fn modify_protected<F>(&self, f: F)
where
for<'w> F: FnOnce(&R<REG>, &'w mut W<REG>) -> &'w mut W<REG>,
{
let bits = self.register.get();
let val = f(
&R::<REG>::from(R {
bits,
_reg: marker::PhantomData,
}),
&mut W::<REG>::from(W {
bits: bits & !REG::ONE_TO_MODIFY_FIELDS_BITMAP
| REG::ZERO_TO_MODIFY_FIELDS_BITMAP,
_reg: marker::PhantomData,
}),
)
.bits;

unsafe {
core::arch::asm!(
// Write the CCP register with the desired magic
"ldi {magicreg}, {magic}",
"out {ccpreg}, {magicreg}",

// Then immediately write the protected register
"st X, {perval}",

magic = const REG::MAGIC,
ccpreg = const unsafe { core::mem::transmute::<_, i16>(REG::CcpReg::PTR) as i32 },

in("X") self.register.as_ptr(),
perval = in(reg) val,

magicreg = out (reg) _ // mark the magicreg as clobbered
);
}
}
}
2 changes: 2 additions & 0 deletions src/generate/interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ pub fn render(
});
}
Target::Mips => {}
Target::Avr => {}
Target::None => {}
}

Expand Down Expand Up @@ -364,6 +365,7 @@ pub fn render(
&& target != Target::Msp430
&& target != Target::XtensaLX
&& target != Target::Mips
&& target != Target::Avr
{
mod_items.extend(quote! {
#[cfg(feature = "rt")]
Expand Down
Loading