avr_boot/
spm.rs

1//! Low level API for calling bootloader functions
2
3use crate::Address;
4use crate::*;
5
6use cfg_if::cfg_if;
7#[allow(unused_imports)]
8use core::arch::asm;
9
10/// Store a whole page into program memory by erasing the page, filling the buffer,
11/// and writing the buffer to the program memory.  
12/// `address` must be page aligned.
13pub fn store_page<'a>(address: impl Into<Address>, data: impl Into<&'a DataPage>) {
14    let page_address: Address = address.into();
15
16    erase_page(page_address);
17    copy_to_buffer(data);
18    write_page(page_address);
19    rww_enable();
20}
21
22/// Erase the page from program memory
23///
24/// The PCPAGE part of the address is used to address the page, the PCWORD part must be zero
25#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
26pub fn erase_page(address: impl Into<Address>) {
27    let page_address: Address = address.into();
28    let z_address: u16 = page_address.into_page_aligned().into();
29
30    busy_wait();
31    rampz(page_address.ramp());
32    cfg_if! {
33        if #[cfg(all(target_arch = "avr", not(doc)))] {
34            unsafe {
35                asm!(
36                    "
37                    out {SPMCSR} r24
38                    spm
39                    ",
40                    in("r24") PAGE_ERASE,
41                    in("Z") z_address,
42                    SPMCSR = const SPMCSR_ADDR - 0x20,
43                );
44            }
45        }
46    }
47}
48
49/// Write data to the page buffer
50///
51/// Only the PCWORD part of the address actually matters, the size of which varies according to SPM_PAGESIZE_BYTES
52#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
53pub fn fill_page(address: impl Into<Address>, data: u16) {
54    let page_address: Address = address.into();
55    let z_address: u16 = page_address.into();
56
57    busy_wait();
58    cfg_if! {
59        if #[cfg(all(target_arch = "avr", not(doc)))] {
60            unsafe {
61                asm!(
62                    "
63                    movw r0 {data}
64                    out {SPMCSR} r24
65                    spm
66                    eor	r1, r1
67                    ",
68                    data = in(reg_iw) data,
69                    in("r24") PAGE_FILL,
70                    in("Z") z_address,
71                    SPMCSR = const SPMCSR_ADDR - 0x20,
72                )
73            }
74        }
75    }
76}
77
78/// Write the page from the buffer to the program memory
79///
80/// The PCPAGE part of the address is used to address the page, the PCWORD part must be zero
81#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
82pub fn write_page(address: impl Into<Address>) {
83    let page_address: Address = address.into();
84    let z_address: u16 = page_address.into_page_aligned().into();
85
86    busy_wait();
87    rampz(page_address.ramp());
88    cfg_if! {
89        if #[cfg(all(target_arch = "avr", not(doc)))] {
90            unsafe {
91                asm!(
92                    "
93                    out {SPMCSR} r24
94                    spm
95                    ",
96                    in("r24") PAGE_WRITE,
97                    in("Z") z_address,
98                    SPMCSR = const SPMCSR_ADDR - 0x20,
99                )
100            }
101        }
102    }
103}
104
105/// Fill the whole buffer at once
106///
107#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
108pub fn copy_to_buffer<'a>(data: impl Into<&'a DataPage>) {
109    busy_wait();
110    rampz(0);
111    cfg_if! {
112        if #[cfg(all(target_arch = "avr", not(doc)))] {
113            unsafe {
114                asm!(
115                    "
116                    1:                       
117                        ld      r0,         X+  // Load r0r1 pair with data from X pointer
118                        ld      r1,         X+
119                        out {SPMCSR} r24
120                        spm                     // call spm(PAGE_FILL) (r24 is always 1st byte argument)
121                        adiw    Z,          2   // increment Z
122                        subi    {words},    1   // decrement counter
123                        brne    1b              // loop until counter reaches 0
124
125                        clr	    r1
126                    ",
127
128                    words = inout(reg) SPM_PAGESIZE_WORDS as u8 => _,
129                    in("r24") PAGE_FILL,
130                    inout("X") data.into().as_ptr() => _,
131                    inout("Z") 0u16 => _,
132                    SPMCSR = const SPMCSR_ADDR - 0x20,
133                )
134            }
135        }
136    }
137}
138
139#[cfg_attr(not(target_arch = "avr"), allow(unused_variables))]
140pub fn lock_bits_set(lock_bits: u8) {
141    rampz(0);
142    cfg_if! {
143        if #[cfg(all(target_arch = "avr", not(doc)))] {
144            let value = !lock_bits;
145            unsafe {
146                asm!(
147                    "
148                    mov r0 {value}
149                    out {SPMCSR} r24
150                    spm
151                    ",
152                    value = in(reg) value,
153                    in("r24") LOCK_BITS_SET,
154                    in("Z") 0x0001u16,
155                    SPMCSR = const SPMCSR_ADDR - 0x20,
156                )
157            }
158        }
159    }
160}
161
162/// Re-enable the RWW section after programming, to enable it to be read
163#[cfg(rww_enable)]
164pub fn rww_enable() {
165    busy_wait();
166    cfg_if! {
167        if #[cfg(all(target_arch = "avr", not(doc)))] {
168            unsafe {
169                asm!(
170                    "
171                    out {SPMCSR} r24
172                    spm
173                    ",
174                    in("r24") RWW_ENABLE,
175                    SPMCSR = const SPMCSR_ADDR - 0x20,
176                );
177            }
178        }
179    }
180}
181
182/// Empty function for devices without a RWW section
183#[cfg(not(rww_enable))]
184pub fn rww_enable() {}
185
186/// Wait for the current SPM operation to complete.
187///
188/// On devices with a RWW section, the CPU is not halted during the SPM operation if the RWW section is being written to.
189/// Therefore it is important that we make sure the operation is complete before trying to do the next operation.
190pub fn busy_wait() {
191    cfg_if! {
192        if #[cfg(all(target_arch = "avr", not(doc)))] {
193           while unsafe { core::ptr::read_volatile(SPMCSR) } & PAGE_FILL != 0 {}
194        }
195    }
196}
197
198#[cfg_attr(
199    not(all(target_arch = "avr", extended_addressing)),
200    allow(unused_variables)
201)]
202fn rampz(value: u8) {
203    cfg_if! {
204        if #[cfg(all(target_arch = "avr", extended_addressing, not(doc)))] {
205            unsafe {
206                core::ptr::write_volatile(crate::RAMPZ, value);
207            }
208        }
209    }
210}