use core::arch::asm;
use core::cell::UnsafeCell;
use core::mem;
use core::ptr;
unsafe fn transfer<T: Copy>(dst: *mut T, src: *const T) {
let align = mem::align_of::<T>();
let size = mem::size_of::<T>();
if size == 0 {
} else if size <= 16 && align % 4 == 0 {
transfer_align4_thumb(dst, src);
} else if size <= 40 && align % 4 == 0 {
transfer_align4_arm(dst, src);
} else if size <= 2 && align % 2 == 0 {
asm!(
"ldrh {2},[{0}]",
"strh {2},[{1}]",
in(reg) src, in(reg) dst, out(reg) _,
);
} else if size == 1 {
asm!(
"ldrb {2},[{0}]",
"strb {2},[{1}]",
in(reg) src, in(reg) dst, out(reg) _,
);
} else {
crate::interrupt::free(|_| ptr::write_volatile(dst, ptr::read_volatile(src)));
}
}
#[allow(unused_assignments)]
unsafe fn transfer_align4_thumb<T: Copy>(mut dst: *mut T, mut src: *const T) {
let size = mem::size_of::<T>();
if size <= 4 {
asm!(
"ldr {2},[{0}]",
"str {2},[{1}]",
inout(reg) src, in(reg) dst, out(reg) _,
);
} else if size <= 8 {
asm!(
"ldmia {0}!, {{r2-r3}}",
"stmia {1}!, {{r2-r3}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _,
);
} else if size <= 12 {
asm!(
"ldmia {0}!, {{r2-r4}}",
"stmia {1}!, {{r2-r4}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _,
);
} else if size <= 16 {
asm!(
"ldmia {0}!, {{r2-r5}}",
"stmia {1}!, {{r2-r5}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _,
);
} else {
unimplemented!("This should be done via transfer_arm.");
}
}
#[cfg_attr(not(doc), instruction_set(arm::a32))]
#[allow(unused_assignments)]
unsafe fn transfer_align4_arm<T: Copy>(mut dst: *mut T, mut src: *const T) {
let size = mem::size_of::<T>();
if size <= 16 {
unimplemented!("This should be done via transfer_thumb.");
} else if size <= 20 {
asm!(
"ldmia {0}!, {{r2-r5,r7}}",
"stmia {1}!, {{r2-r5,r7}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r7") _,
);
} else if size <= 24 {
asm!(
"ldmia {0}!, {{r2-r5,r7-r8}}",
"stmia {1}!, {{r2-r5,r7-r8}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r7") _,
out("r8") _,
);
} else if size <= 28 {
asm!(
"ldmia {0}!, {{r2-r5,r7-r9}}",
"stmia {1}!, {{r2-r5,r7-r9}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r7") _,
out("r8") _, out("r9") _,
);
} else if size <= 32 {
asm!(
"ldmia {0}!, {{r2-r5,r7-r10}}",
"stmia {1}!, {{r2-r5,r7-r10}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r7") _,
out("r8") _, out("r9") _, out("r10") _,
);
} else if size <= 36 {
asm!(
"ldmia {0}!, {{r2-r5,r7-r10,r12}}",
"stmia {1}!, {{r2-r5,r7-r10,r12}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r7") _,
out("r8") _, out("r9") _, out("r10") _, out("r12") _,
);
} else if size <= 40 {
asm!(
"ldmia {0}!, {{r2-r5,r7-r10,r12,r14}}",
"stmia {1}!, {{r2-r5,r7-r10,r12,r14}}",
inout(reg) src, inout(reg) dst,
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r7") _,
out("r8") _, out("r9") _, out("r10") _, out("r12") _, out("r14") _,
);
} else {
unimplemented!("Copy too large for use of ldmia/stmia.");
}
}
unsafe fn exchange<T>(dst: *mut T, src: *const T) -> T {
let align = mem::align_of::<T>();
let size = mem::size_of::<T>();
if size == 0 {
ptr::read(dst)
} else if size <= 4 && align % 4 == 0 {
let val = ptr::read(src as *const u32);
let new_val = exchange_align4_arm(dst, val);
ptr::read(&new_val as *const _ as *const T)
} else if size == 1 {
let val = ptr::read(src as *const u8);
let new_val = exchange_align1_arm(dst, val);
ptr::read(&new_val as *const _ as *const T)
} else {
crate::interrupt::free(|_| {
let cur = ptr::read_volatile(dst);
ptr::write_volatile(dst, ptr::read_volatile(src));
cur
})
}
}
#[cfg_attr(not(doc), instruction_set(arm::a32))]
unsafe fn exchange_align4_arm<T>(dst: *mut T, i: u32) -> u32 {
let out;
asm!("swp {2}, {1}, [{0}]", in(reg) dst, in(reg) i, lateout(reg) out);
out
}
#[cfg_attr(not(doc), instruction_set(arm::a32))]
unsafe fn exchange_align1_arm<T>(dst: *mut T, i: u8) -> u8 {
let out;
asm!("swpb {2}, {1}, [{0}]", in(reg) dst, in(reg) i, lateout(reg) out);
out
}
pub struct Static<T> {
data: UnsafeCell<T>,
}
impl<T> Static<T> {
pub const fn new(val: T) -> Self {
Static {
data: UnsafeCell::new(val),
}
}
#[allow(clippy::needless_pass_by_value)] pub fn replace(&self, val: T) -> T {
unsafe { exchange(self.data.get(), &val) }
}
pub fn into_inner(self) -> T {
self.data.into_inner()
}
}
impl<T: Copy> Static<T> {
pub fn write(&self, val: T) {
unsafe { transfer(self.data.get(), &val) }
}
pub fn read(&self) -> T {
unsafe {
let mut out: mem::MaybeUninit<T> = mem::MaybeUninit::uninit();
transfer(out.as_mut_ptr(), self.data.get());
out.assume_init()
}
}
}
impl<T: Default> Default for Static<T> {
fn default() -> Self {
Static::new(T::default())
}
}
unsafe impl<T> Send for Static<T> {}
unsafe impl<T> Sync for Static<T> {}
#[cfg(test)]
mod test {
use crate::interrupt::Interrupt;
use crate::sync::Static;
use crate::timer::Divider;
use crate::Gba;
fn write_read_concurrency_test_impl<const COUNT: usize>(gba: &mut Gba) {
let sentinel = [0x12345678; COUNT];
let value: Static<[u32; COUNT]> = Static::new(sentinel);
let mut timer = gba.timers.timers().timer2;
timer.set_cascade(false);
timer.set_divider(Divider::Divider1);
timer.set_overflow_amount(1049);
timer.set_interrupt(true);
timer.set_enabled(true);
let _int = crate::interrupt::add_interrupt_handler(Interrupt::Timer2, |_| {
value.write(sentinel);
});
let mut interrupt_seen = false;
let mut no_interrupt_seen = false;
for i in 0..250000 {
let new_value = [i; COUNT];
value.write(new_value);
let current = value.read();
if current == new_value {
no_interrupt_seen = true;
} else if current == sentinel {
interrupt_seen = true;
} else {
panic!("Unexpected value found in `Static`.");
}
if interrupt_seen && no_interrupt_seen {
timer.set_enabled(false);
return;
}
if i % 8192 == 0 && i != 0 {
timer.set_overflow_amount(1049 + (i / 64) as u16);
}
}
panic!("Concurrency test timed out: {}", COUNT)
}
#[test_case]
fn write_read_concurrency_test(gba: &mut Gba) {
write_read_concurrency_test_impl::<1>(gba);
write_read_concurrency_test_impl::<2>(gba);
write_read_concurrency_test_impl::<3>(gba);
write_read_concurrency_test_impl::<4>(gba);
write_read_concurrency_test_impl::<5>(gba);
write_read_concurrency_test_impl::<6>(gba);
write_read_concurrency_test_impl::<7>(gba);
write_read_concurrency_test_impl::<8>(gba);
write_read_concurrency_test_impl::<9>(gba);
write_read_concurrency_test_impl::<10>(gba);
}
}