mirror of
https://github.com/italicsjenga/agb.git
synced 2025-01-03 21:51:34 +11:00
use portable atomics and other similar libraries
This commit is contained in:
parent
0dab252379
commit
2b4c4459e0
|
@ -19,9 +19,11 @@ agb_sound_converter = { version = "0.19.1", path = "../agb-sound-converter" }
|
||||||
agb_macros = { version = "0.19.1", path = "../agb-macros" }
|
agb_macros = { version = "0.19.1", path = "../agb-macros" }
|
||||||
agb_fixnum = { version = "0.19.1", path = "../agb-fixnum" }
|
agb_fixnum = { version = "0.19.1", path = "../agb-fixnum" }
|
||||||
agb_hashmap = { version = "0.19.1", path = "../agb-hashmap" }
|
agb_hashmap = { version = "0.19.1", path = "../agb-hashmap" }
|
||||||
bare-metal = "1"
|
|
||||||
bilge = "0.2"
|
bilge = "0.2"
|
||||||
qrcodegen-no-heap = "1.8"
|
qrcodegen-no-heap = "1.8"
|
||||||
|
portable-atomic = { version = "1.6.0", default-features = false, features = ["unsafe-assume-single-core"] }
|
||||||
|
once_cell = { version = "1.19.0", default-features = false, features = ["critical-section"] }
|
||||||
|
critical-section = { version = "1.1.2", features = ["restore-state-u16"] }
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
default-target = "thumbv4t-none-eabi"
|
default-target = "thumbv4t-none-eabi"
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use agb::sync::Static;
|
use portable_atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
static COUNT: Static<u32> = Static::new(0);
|
static COUNT: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
#[agb::entry]
|
#[agb::entry]
|
||||||
fn main(_gba: agb::Gba) -> ! {
|
fn main(_gba: agb::Gba) -> ! {
|
||||||
let _a = unsafe {
|
let _a = unsafe {
|
||||||
agb::interrupt::add_interrupt_handler(agb::interrupt::Interrupt::VBlank, |_| {
|
agb::interrupt::add_interrupt_handler(agb::interrupt::Interrupt::VBlank, |_| {
|
||||||
let cur_count = COUNT.read();
|
let cur_count = COUNT.load(Ordering::SeqCst);
|
||||||
agb::println!("Hello, world, frame = {}", cur_count);
|
agb::println!("Hello, world, frame = {}", cur_count);
|
||||||
COUNT.write(cur_count + 1);
|
COUNT.store(cur_count + 1, Ordering::SeqCst);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
loop {}
|
loop {}
|
||||||
|
|
|
@ -9,9 +9,9 @@ use agb::{
|
||||||
tiled::{RegularBackgroundSize, TileFormat},
|
tiled::{RegularBackgroundSize, TileFormat},
|
||||||
},
|
},
|
||||||
fixnum::FixedNum,
|
fixnum::FixedNum,
|
||||||
interrupt::{free, Interrupt},
|
interrupt::Interrupt,
|
||||||
};
|
};
|
||||||
use bare_metal::{CriticalSection, Mutex};
|
use critical_section::{CriticalSection, Mutex};
|
||||||
|
|
||||||
struct BackCosines {
|
struct BackCosines {
|
||||||
cosines: [u16; 32],
|
cosines: [u16; 32],
|
||||||
|
@ -37,7 +37,7 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
let _a = unsafe {
|
let _a = unsafe {
|
||||||
agb::interrupt::add_interrupt_handler(Interrupt::HBlank, |key: CriticalSection| {
|
agb::interrupt::add_interrupt_handler(Interrupt::HBlank, |key: CriticalSection| {
|
||||||
let mut back = BACK.borrow(key).borrow_mut();
|
let mut back = BACK.borrow_ref_mut(key);
|
||||||
let deflection = back.cosines[back.row % 32];
|
let deflection = back.cosines[back.row % 32];
|
||||||
((0x0400_0010) as *mut u16).write_volatile(deflection);
|
((0x0400_0010) as *mut u16).write_volatile(deflection);
|
||||||
back.row += 1;
|
back.row += 1;
|
||||||
|
@ -49,8 +49,8 @@ fn main(mut gba: agb::Gba) -> ! {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
vblank.wait_for_vblank();
|
vblank.wait_for_vblank();
|
||||||
free(|key| {
|
critical_section::with(|key| {
|
||||||
let mut back = BACK.borrow(key).borrow_mut();
|
let mut back = BACK.borrow_ref_mut(key);
|
||||||
back.row = 0;
|
back.row = 0;
|
||||||
time += 1;
|
time += 1;
|
||||||
for (r, a) in back.cosines.iter_mut().enumerate() {
|
for (r, a) in back.cosines.iter_mut().enumerate() {
|
||||||
|
|
|
@ -191,7 +191,7 @@ pub(crate) fn dma3_exclusive<R>(f: impl FnOnce() -> R) -> R {
|
||||||
const DMA1_CTRL_HI: MemoryMapped<u16> = unsafe { MemoryMapped::new(dma_control_addr(1) + 2) };
|
const DMA1_CTRL_HI: MemoryMapped<u16> = unsafe { MemoryMapped::new(dma_control_addr(1) + 2) };
|
||||||
const DMA2_CTRL_HI: MemoryMapped<u16> = unsafe { MemoryMapped::new(dma_control_addr(2) + 2) };
|
const DMA2_CTRL_HI: MemoryMapped<u16> = unsafe { MemoryMapped::new(dma_control_addr(2) + 2) };
|
||||||
|
|
||||||
crate::interrupt::free(|_| {
|
critical_section::with(|_| {
|
||||||
let dma0_ctl = DMA0_CTRL_HI.get();
|
let dma0_ctl = DMA0_CTRL_HI.get();
|
||||||
let dma1_ctl = DMA1_CTRL_HI.get();
|
let dma1_ctl = DMA1_CTRL_HI.get();
|
||||||
let dma2_ctl = DMA2_CTRL_HI.get();
|
let dma2_ctl = DMA2_CTRL_HI.get();
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use core::{cell::Cell, marker::PhantomPinned, pin::Pin};
|
use core::{cell::Cell, marker::PhantomPinned, pin::Pin};
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use bare_metal::CriticalSection;
|
use critical_section::{CriticalSection, RawRestoreState};
|
||||||
|
use portable_atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||||
|
|
||||||
use crate::{display::DISPLAY_STATUS, memory_mapped::MemoryMapped, sync::Static};
|
use crate::{display::DISPLAY_STATUS, memory_mapped::MemoryMapped};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Interrupt {
|
pub enum Interrupt {
|
||||||
|
@ -234,7 +235,7 @@ fn interrupt_to_root(interrupt: Interrupt) -> &'static InterruptRoot {
|
||||||
/// * The closure must be static because forgetting the interrupt handler would
|
/// * The closure must be static because forgetting the interrupt handler would
|
||||||
/// cause a use after free.
|
/// cause a use after free.
|
||||||
///
|
///
|
||||||
/// [`CriticalSection`]: bare_metal::CriticalSection
|
/// [`CriticalSection`]: critical_section::CriticalSection
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
|
@ -242,7 +243,7 @@ fn interrupt_to_root(interrupt: Interrupt) -> &'static InterruptRoot {
|
||||||
/// # #![no_std]
|
/// # #![no_std]
|
||||||
/// # #![no_main]
|
/// # #![no_main]
|
||||||
/// # fn foo() {
|
/// # fn foo() {
|
||||||
/// use bare_metal::CriticalSection;
|
/// use critical_section::CriticalSection;
|
||||||
/// use agb::interrupt::{add_interrupt_handler, Interrupt};
|
/// use agb::interrupt::{add_interrupt_handler, Interrupt};
|
||||||
/// // Safety: doesn't allocate
|
/// // Safety: doesn't allocate
|
||||||
/// let _a = unsafe {
|
/// let _a = unsafe {
|
||||||
|
@ -257,7 +258,7 @@ pub unsafe fn add_interrupt_handler(
|
||||||
handler: impl Fn(CriticalSection) + Send + Sync + 'static,
|
handler: impl Fn(CriticalSection) + Send + Sync + 'static,
|
||||||
) -> InterruptHandler {
|
) -> InterruptHandler {
|
||||||
fn do_with_inner(interrupt: Interrupt, inner: Pin<Box<InterruptInner>>) -> InterruptHandler {
|
fn do_with_inner(interrupt: Interrupt, inner: Pin<Box<InterruptInner>>) -> InterruptHandler {
|
||||||
free(|_| {
|
critical_section::with(|_| {
|
||||||
let root = interrupt_to_root(interrupt);
|
let root = interrupt_to_root(interrupt);
|
||||||
root.add();
|
root.add();
|
||||||
let mut c = root.next.get();
|
let mut c = root.next.get();
|
||||||
|
@ -283,32 +284,23 @@ pub unsafe fn add_interrupt_handler(
|
||||||
do_with_inner(interrupt, inner)
|
do_with_inner(interrupt, inner)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How you can access mutexes outside of interrupts by being given a
|
struct MyCriticalSection;
|
||||||
/// [`CriticalSection`]
|
critical_section::set_impl!(MyCriticalSection);
|
||||||
///
|
|
||||||
/// [`CriticalSection`]: bare_metal::CriticalSection
|
|
||||||
pub fn free<F, R>(mut f: F) -> R
|
|
||||||
where
|
|
||||||
F: FnOnce(CriticalSection) -> R,
|
|
||||||
{
|
|
||||||
let enabled = INTERRUPTS_ENABLED.get();
|
|
||||||
|
|
||||||
disable_interrupts();
|
unsafe impl critical_section::Impl for MyCriticalSection {
|
||||||
|
unsafe fn acquire() -> RawRestoreState {
|
||||||
// prevents the contents of the function from being reordered before IME is disabled.
|
let irq = INTERRUPTS_ENABLED.get();
|
||||||
crate::sync::memory_write_hint(&mut f);
|
INTERRUPTS_ENABLED.set(0);
|
||||||
|
irq
|
||||||
let mut r = f(unsafe { CriticalSection::new() });
|
|
||||||
|
|
||||||
// prevents the contents of the function from being reordered after IME is re-enabled.
|
|
||||||
crate::sync::memory_write_hint(&mut r);
|
|
||||||
|
|
||||||
INTERRUPTS_ENABLED.set(enabled);
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static NUM_VBLANKS: Static<usize> = Static::new(0); // overflows after 2.27 years
|
unsafe fn release(token: RawRestoreState) {
|
||||||
static HAS_CREATED_INTERRUPT: Static<bool> = Static::new(false);
|
INTERRUPTS_ENABLED.set(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static NUM_VBLANKS: AtomicUsize = AtomicUsize::new(0); // overflows after 2.27 years
|
||||||
|
static HAS_CREATED_INTERRUPT: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct VBlank {
|
pub struct VBlank {
|
||||||
|
@ -320,29 +312,28 @@ impl VBlank {
|
||||||
/// interrupt syscall.
|
/// interrupt syscall.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get() -> Self {
|
pub fn get() -> Self {
|
||||||
if !HAS_CREATED_INTERRUPT.read() {
|
if !HAS_CREATED_INTERRUPT.swap(true, Ordering::SeqCst) {
|
||||||
// safety: we don't allocate in the interrupt
|
// safety: we don't allocate in the interrupt
|
||||||
let handler = unsafe {
|
let handler = unsafe {
|
||||||
add_interrupt_handler(Interrupt::VBlank, |_| {
|
add_interrupt_handler(Interrupt::VBlank, |_| {
|
||||||
NUM_VBLANKS.write(NUM_VBLANKS.read() + 1);
|
NUM_VBLANKS.store(NUM_VBLANKS.load(Ordering::SeqCst) + 1, Ordering::SeqCst);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
core::mem::forget(handler);
|
core::mem::forget(handler);
|
||||||
|
|
||||||
HAS_CREATED_INTERRUPT.write(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VBlank {
|
VBlank {
|
||||||
last_waited_number: Cell::new(NUM_VBLANKS.read()),
|
last_waited_number: Cell::new(NUM_VBLANKS.load(Ordering::SeqCst)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Pauses CPU until vblank interrupt is triggered where code execution is
|
/// Pauses CPU until vblank interrupt is triggered where code execution is
|
||||||
/// resumed.
|
/// resumed.
|
||||||
pub fn wait_for_vblank(&self) {
|
pub fn wait_for_vblank(&self) {
|
||||||
let last_waited_number = self.last_waited_number.get();
|
let last_waited_number = self.last_waited_number.get();
|
||||||
self.last_waited_number.set(NUM_VBLANKS.read() + 1);
|
self.last_waited_number
|
||||||
|
.set(NUM_VBLANKS.load(Ordering::SeqCst) + 1);
|
||||||
|
|
||||||
if last_waited_number < NUM_VBLANKS.read() {
|
if last_waited_number < NUM_VBLANKS.load(Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,4 +374,11 @@ mod tests {
|
||||||
"interrupt table should be able to store gamepak interrupt"
|
"interrupt table should be able to store gamepak interrupt"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case]
|
||||||
|
fn interrupts_disabled_in_critical_section(_gba: &mut crate::Gba) {
|
||||||
|
critical_section::with(|_| {
|
||||||
|
assert_eq!(INTERRUPTS_ENABLED.get(), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ mod single;
|
||||||
/// Implements sound output.
|
/// Implements sound output.
|
||||||
pub mod sound;
|
pub mod sound;
|
||||||
/// A module containing functions and utilities useful for synchronizing state.
|
/// A module containing functions and utilities useful for synchronizing state.
|
||||||
pub mod sync;
|
mod sync;
|
||||||
/// System BIOS calls / syscalls.
|
/// System BIOS calls / syscalls.
|
||||||
pub mod syscall;
|
pub mod syscall;
|
||||||
/// Interactions with the internal timers
|
/// Interactions with the internal timers
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
use core::cell::RefCell;
|
use portable_atomic::{AtomicU128, Ordering};
|
||||||
|
|
||||||
use bare_metal::Mutex;
|
|
||||||
|
|
||||||
use crate::interrupt::free;
|
|
||||||
|
|
||||||
/// A fast pseudo-random number generator. Note that the output of the
|
/// A fast pseudo-random number generator. Note that the output of the
|
||||||
/// random number generator for a given seed is guaranteed stable
|
/// random number generator for a given seed is guaranteed stable
|
||||||
|
@ -58,13 +54,18 @@ impl Default for RandomNumberGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static GLOBAL_RNG: Mutex<RefCell<RandomNumberGenerator>> =
|
static GLOBAL_RNG: AtomicU128 =
|
||||||
Mutex::new(RefCell::new(RandomNumberGenerator::new()));
|
AtomicU128::new(unsafe { core::mem::transmute(RandomNumberGenerator::new().state) });
|
||||||
|
|
||||||
/// Using a global random number generator, provides the next random number
|
/// Using a global random number generator, provides the next random number
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn gen() -> i32 {
|
pub fn gen() -> i32 {
|
||||||
free(|cs| GLOBAL_RNG.borrow(cs).borrow_mut().gen())
|
let data: u128 = GLOBAL_RNG.load(Ordering::SeqCst);
|
||||||
|
let data_u32: [u32; 4] = unsafe { core::mem::transmute(data) };
|
||||||
|
let mut rng = RandomNumberGenerator { state: data_u32 };
|
||||||
|
let value = rng.gen();
|
||||||
|
GLOBAL_RNG.store(unsafe { core::mem::transmute(rng.state) }, Ordering::SeqCst);
|
||||||
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -5,11 +5,13 @@
|
||||||
|
|
||||||
// TODO: Setup cartridge read timings for faster Flash access.
|
// TODO: Setup cartridge read timings for faster Flash access.
|
||||||
|
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use portable_atomic::{AtomicU8, Ordering};
|
||||||
|
|
||||||
use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray};
|
use crate::memory_mapped::{MemoryMapped, MemoryMapped1DArray};
|
||||||
use crate::save::asm_utils::*;
|
use crate::save::asm_utils::*;
|
||||||
use crate::save::utils::Timeout;
|
use crate::save::utils::Timeout;
|
||||||
use crate::save::{Error, MediaInfo, MediaType, RawSaveAccess};
|
use crate::save::{Error, MediaInfo, MediaType, RawSaveAccess};
|
||||||
use crate::sync::{InitOnce, Static};
|
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
|
|
||||||
// Volatile address ports for flash
|
// Volatile address ports for flash
|
||||||
|
@ -45,14 +47,14 @@ fn issue_flash_command(c2: u8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple thing to avoid excessive bank switches
|
/// A simple thing to avoid excessive bank switches
|
||||||
static CURRENT_BANK: Static<u8> = Static::new(!0);
|
static CURRENT_BANK: AtomicU8 = AtomicU8::new(!0);
|
||||||
fn set_bank(bank: u8) -> Result<(), Error> {
|
fn set_bank(bank: u8) -> Result<(), Error> {
|
||||||
if bank == 0xFF {
|
if bank == 0xFF {
|
||||||
Err(Error::OutOfBounds)
|
Err(Error::OutOfBounds)
|
||||||
} else if bank != CURRENT_BANK.read() {
|
} else if bank != CURRENT_BANK.load(Ordering::SeqCst) {
|
||||||
issue_flash_command(CMD_SET_BANK);
|
issue_flash_command(CMD_SET_BANK);
|
||||||
FLASH_PORT_BANK.set(bank);
|
FLASH_PORT_BANK.set(bank);
|
||||||
CURRENT_BANK.write(bank);
|
CURRENT_BANK.store(bank, Ordering::SeqCst);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -239,10 +241,12 @@ impl FlashChipType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static CHIP_INFO: InitOnce<&'static ChipInfo> = InitOnce::new();
|
|
||||||
|
static CHIP_INFO: OnceCell<&'static ChipInfo> = OnceCell::new();
|
||||||
|
|
||||||
fn cached_chip_info() -> Result<&'static ChipInfo, Error> {
|
fn cached_chip_info() -> Result<&'static ChipInfo, Error> {
|
||||||
CHIP_INFO
|
CHIP_INFO
|
||||||
.try_get(|| -> Result<_, Error> { Ok(FlashChipType::detect()?.chip_info()) })
|
.get_or_try_init(|| -> Result<_, Error> { Ok(FlashChipType::detect()?.chip_info()) })
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,7 +384,7 @@ impl ChipInfo {
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
timeout: &mut Timeout,
|
timeout: &mut Timeout,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
crate::interrupt::free(|_| {
|
critical_section::with(|_| {
|
||||||
issue_flash_command(CMD_WRITE);
|
issue_flash_command(CMD_WRITE);
|
||||||
for i in 0..128 {
|
for i in 0..128 {
|
||||||
FLASH_DATA.set(offset + i, buf[i]);
|
FLASH_DATA.set(offset + i, buf[i]);
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
//! size.
|
//! size.
|
||||||
|
|
||||||
use crate::save::utils::Timeout;
|
use crate::save::utils::Timeout;
|
||||||
use crate::sync::{Mutex, RawMutexGuard};
|
use crate::sync::{Lock, RawLockGuard};
|
||||||
use crate::timer::Timer;
|
use crate::timer::Timer;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ trait RawSaveAccess: Sync {
|
||||||
fn write(&self, offset: usize, buffer: &[u8], timeout: &mut Timeout) -> Result<(), Error>;
|
fn write(&self, offset: usize, buffer: &[u8], timeout: &mut Timeout) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CURRENT_SAVE_ACCESS: Mutex<Option<&'static dyn RawSaveAccess>> = Mutex::new(None);
|
static CURRENT_SAVE_ACCESS: Lock<Option<&'static dyn RawSaveAccess>> = Lock::new(None);
|
||||||
|
|
||||||
fn set_save_implementation(access_impl: &'static dyn RawSaveAccess) {
|
fn set_save_implementation(access_impl: &'static dyn RawSaveAccess) {
|
||||||
let mut access = CURRENT_SAVE_ACCESS.lock();
|
let mut access = CURRENT_SAVE_ACCESS.lock();
|
||||||
|
@ -196,7 +196,7 @@ fn get_save_implementation() -> Option<&'static dyn RawSaveAccess> {
|
||||||
|
|
||||||
/// Allows reading and writing of save media.
|
/// Allows reading and writing of save media.
|
||||||
pub struct SaveData {
|
pub struct SaveData {
|
||||||
_lock: RawMutexGuard<'static>,
|
_lock: RawLockGuard<'static>,
|
||||||
access: &'static dyn RawSaveAccess,
|
access: &'static dyn RawSaveAccess,
|
||||||
info: &'static MediaInfo,
|
info: &'static MediaInfo,
|
||||||
timeout: utils::Timeout,
|
timeout: utils::Timeout,
|
||||||
|
@ -356,19 +356,19 @@ mod marker {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn emit_eeprom_marker() {
|
pub fn emit_eeprom_marker() {
|
||||||
crate::sync::memory_read_hint(&EEPROM);
|
core::hint::black_box(&EEPROM);
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn emit_sram_marker() {
|
pub fn emit_sram_marker() {
|
||||||
crate::sync::memory_read_hint(&SRAM);
|
core::hint::black_box(&SRAM);
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn emit_flash_512k_marker() {
|
pub fn emit_flash_512k_marker() {
|
||||||
crate::sync::memory_read_hint(&FLASH512K);
|
core::hint::black_box(&FLASH512K);
|
||||||
}
|
}
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn emit_flash_1m_marker() {
|
pub fn emit_flash_1m_marker() {
|
||||||
crate::sync::memory_read_hint(&FLASH1M);
|
core::hint::black_box(&FLASH1M);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
//! A package containing useful utilities for writing save accessors.
|
//! A package containing useful utilities for writing save accessors.
|
||||||
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use crate::sync::{RawMutex, RawMutexGuard};
|
use crate::{
|
||||||
use crate::timer::{Divider, Timer};
|
sync::{RawLock, RawLockGuard},
|
||||||
|
timer::{Divider, Timer},
|
||||||
|
};
|
||||||
|
|
||||||
/// A timeout type used to prevent hardware errors in save media from hanging
|
/// A timeout type used to prevent hardware errors in save media from hanging
|
||||||
/// the game.
|
/// the game.
|
||||||
|
@ -50,8 +52,8 @@ impl Drop for Timeout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lock_media_access() -> Result<RawMutexGuard<'static>, Error> {
|
pub fn lock_media_access() -> Result<RawLockGuard<'static>, Error> {
|
||||||
static LOCK: RawMutex = RawMutex::new();
|
static LOCK: RawLock = RawLock::new();
|
||||||
match LOCK.try_lock() {
|
match LOCK.try_lock() {
|
||||||
Some(x) => Ok(x),
|
Some(x) => Ok(x),
|
||||||
None => Err(Error::MediaInUse),
|
None => Err(Error::MediaInUse),
|
||||||
|
|
|
@ -4,7 +4,7 @@ use core::pin::Pin;
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use bare_metal::{CriticalSection, Mutex};
|
use critical_section::{CriticalSection, Mutex};
|
||||||
|
|
||||||
use super::hw::LeftOrRight;
|
use super::hw::LeftOrRight;
|
||||||
use super::{hw, Frequency};
|
use super::{hw, Frequency};
|
||||||
|
@ -13,7 +13,6 @@ use super::{SoundChannel, SoundPriority};
|
||||||
use crate::InternalAllocator;
|
use crate::InternalAllocator;
|
||||||
use crate::{
|
use crate::{
|
||||||
fixnum::Num,
|
fixnum::Num,
|
||||||
interrupt::free,
|
|
||||||
interrupt::{add_interrupt_handler, InterruptHandler},
|
interrupt::{add_interrupt_handler, InterruptHandler},
|
||||||
timer::Divider,
|
timer::Divider,
|
||||||
timer::Timer,
|
timer::Timer,
|
||||||
|
@ -395,11 +394,11 @@ impl MixerBuffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_calculate(&self) -> bool {
|
fn should_calculate(&self) -> bool {
|
||||||
free(|cs| self.state.borrow(cs).borrow().should_calculate())
|
critical_section::with(|cs| self.state.borrow_ref_mut(cs).should_calculate())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn swap(&self, cs: CriticalSection) {
|
fn swap(&self, cs: CriticalSection) {
|
||||||
let buffer = self.state.borrow(cs).borrow_mut().playing_advanced();
|
let buffer = self.state.borrow_ref_mut(cs).playing_advanced();
|
||||||
|
|
||||||
let left_buffer = buffer;
|
let left_buffer = buffer;
|
||||||
// SAFETY: starting pointer is fine, resulting pointer also fine because buffer has length buffer_size() * 2 by construction
|
// SAFETY: starting pointer is fine, resulting pointer also fine because buffer has length buffer_size() * 2 by construction
|
||||||
|
@ -435,7 +434,8 @@ impl MixerBuffer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let write_buffer = free(|cs| self.state.borrow(cs).borrow_mut().active_advanced());
|
let write_buffer =
|
||||||
|
critical_section::with(|cs| self.state.borrow_ref_mut(cs).active_advanced());
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
agb_rs__mixer_collapse(
|
agb_rs__mixer_collapse(
|
||||||
|
|
|
@ -1,223 +0,0 @@
|
||||||
use crate::sync::Static;
|
|
||||||
use core::cell::UnsafeCell;
|
|
||||||
use core::mem::MaybeUninit;
|
|
||||||
use core::ops::{Deref, DerefMut};
|
|
||||||
use core::ptr;
|
|
||||||
use core::sync::atomic::{compiler_fence, Ordering};
|
|
||||||
|
|
||||||
#[inline(never)]
|
|
||||||
fn already_locked() -> ! {
|
|
||||||
panic!("IRQ and main thread are attempting to access the same Mutex!")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mutex that prevents code from running in both an IRQ and normal code at
|
|
||||||
/// the same time.
|
|
||||||
///
|
|
||||||
/// Note that this does not support blocking like a typical mutex, and instead
|
|
||||||
/// mainly exists for memory safety reasons.
|
|
||||||
pub struct RawMutex(Static<bool>);
|
|
||||||
impl RawMutex {
|
|
||||||
/// Creates a new lock.
|
|
||||||
#[must_use]
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
RawMutex(Static::new(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Locks the mutex and returns whether a lock was successfully acquired.
|
|
||||||
fn raw_lock(&self) -> bool {
|
|
||||||
if self.0.replace(true) {
|
|
||||||
// value was already true, oops.
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
// prevent any weird reordering, and continue
|
|
||||||
compiler_fence(Ordering::Acquire);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unlocks the mutex.
|
|
||||||
fn raw_unlock(&self) {
|
|
||||||
compiler_fence(Ordering::Release);
|
|
||||||
if !self.0.replace(false) {
|
|
||||||
panic!("Internal error: Attempt to unlock a `RawMutex` which is not locked.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a guard for this lock, or panics if there is another lock active.
|
|
||||||
pub fn lock(&self) -> RawMutexGuard<'_> {
|
|
||||||
self.try_lock().unwrap_or_else(|| already_locked())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a guard for this lock, or `None` if there is another lock active.
|
|
||||||
pub fn try_lock(&self) -> Option<RawMutexGuard<'_>> {
|
|
||||||
if self.raw_lock() {
|
|
||||||
Some(RawMutexGuard(self))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe impl Send for RawMutex {}
|
|
||||||
unsafe impl Sync for RawMutex {}
|
|
||||||
|
|
||||||
impl Default for RawMutex {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A guard representing an active lock on an [`RawMutex`].
|
|
||||||
pub struct RawMutexGuard<'a>(&'a RawMutex);
|
|
||||||
impl<'a> Drop for RawMutexGuard<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.0.raw_unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A mutex that protects an object from being accessed in both an IRQ and
|
|
||||||
/// normal code at once.
|
|
||||||
///
|
|
||||||
/// Note that this does not support blocking like a typical mutex, and instead
|
|
||||||
/// mainly exists for memory safety reasons.
|
|
||||||
pub struct Mutex<T> {
|
|
||||||
raw: RawMutex,
|
|
||||||
data: UnsafeCell<T>,
|
|
||||||
}
|
|
||||||
impl<T> Mutex<T> {
|
|
||||||
/// Creates a new lock containing a given value.
|
|
||||||
#[must_use]
|
|
||||||
pub const fn new(t: T) -> Self {
|
|
||||||
Mutex {
|
|
||||||
raw: RawMutex::new(),
|
|
||||||
data: UnsafeCell::new(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a guard for this lock, or panics if there is another lock active.
|
|
||||||
pub fn lock(&self) -> MutexGuard<'_, T> {
|
|
||||||
self.try_lock().unwrap_or_else(|| already_locked())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a guard for this lock or `None` if there is another lock active.
|
|
||||||
pub fn try_lock(&self) -> Option<MutexGuard<'_, T>> {
|
|
||||||
if self.raw.raw_lock() {
|
|
||||||
Some(MutexGuard {
|
|
||||||
underlying: self,
|
|
||||||
ptr: self.data.get(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe impl<T> Send for Mutex<T> {}
|
|
||||||
unsafe impl<T> Sync for Mutex<T> {}
|
|
||||||
|
|
||||||
/// A guard representing an active lock on an [`Mutex`].
|
|
||||||
pub struct MutexGuard<'a, T> {
|
|
||||||
underlying: &'a Mutex<T>,
|
|
||||||
ptr: *mut T,
|
|
||||||
}
|
|
||||||
impl<'a, T> Drop for MutexGuard<'a, T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.underlying.raw.raw_unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T> Deref for MutexGuard<'a, T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { &*self.ptr }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, T> DerefMut for MutexGuard<'a, T> {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
unsafe { &mut *self.ptr }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Void {}
|
|
||||||
|
|
||||||
/// A helper type that ensures a particular value is only initialized once.
|
|
||||||
pub struct InitOnce<T> {
|
|
||||||
is_initialized: Static<bool>,
|
|
||||||
value: UnsafeCell<MaybeUninit<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> InitOnce<T> {
|
|
||||||
/// Creates a new uninitialized object.
|
|
||||||
#[must_use]
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
InitOnce {
|
|
||||||
is_initialized: Static::new(false),
|
|
||||||
value: UnsafeCell::new(MaybeUninit::uninit()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the contents of this state, or initializes it if it has not already
|
|
||||||
/// been initialized.
|
|
||||||
///
|
|
||||||
/// The initializer function is guaranteed to only be called once.
|
|
||||||
///
|
|
||||||
/// This function disables IRQs while it is initializing the inner value.
|
|
||||||
/// While this can cause audio skipping and other similar issues, it is
|
|
||||||
/// not normally a problem as interrupts will only be disabled once per
|
|
||||||
/// `InitOnce` during the life cycle of the program.
|
|
||||||
pub fn get(&self, initializer: impl FnOnce() -> T) -> &T {
|
|
||||||
match self.try_get(|| -> Result<T, Void> { Ok(initializer()) }) {
|
|
||||||
Ok(v) => v,
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the contents of this state, or initializes it if it has not already
|
|
||||||
/// been initialized.
|
|
||||||
///
|
|
||||||
/// The initializer function is guaranteed to only be called once if it
|
|
||||||
/// returns `Ok`. If it returns `Err`, it will be called again in the
|
|
||||||
/// future until an attempt at initialization succeeds.
|
|
||||||
///
|
|
||||||
/// This function disables IRQs while it is initializing the inner value.
|
|
||||||
/// While this can cause audio skipping and other similar issues, it is
|
|
||||||
/// not normally a problem as interrupts will only be disabled once per
|
|
||||||
/// `InitOnce` during the life cycle of the program.
|
|
||||||
pub fn try_get<E>(&self, initializer: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
|
|
||||||
unsafe {
|
|
||||||
if !self.is_initialized.read() {
|
|
||||||
// We disable interrupts to make this simpler, since this is likely to
|
|
||||||
// only occur once in a program anyway.
|
|
||||||
crate::interrupt::free(|_| -> Result<(), E> {
|
|
||||||
// We check again to make sure this function wasn't called in an
|
|
||||||
// interrupt between the first check and when interrupts were
|
|
||||||
// actually disabled.
|
|
||||||
if !self.is_initialized.read() {
|
|
||||||
// Do the actual initialization.
|
|
||||||
ptr::write_volatile((*self.value.get()).as_mut_ptr(), initializer()?);
|
|
||||||
self.is_initialized.write(true);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
compiler_fence(Ordering::Acquire);
|
|
||||||
Ok(&*(*self.value.get()).as_mut_ptr())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for InitOnce<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for InitOnce<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.is_initialized.read() {
|
|
||||||
// drop the value inside the `MaybeUninit`
|
|
||||||
unsafe {
|
|
||||||
ptr::read((*self.value.get()).as_ptr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unsafe impl<T: Send> Send for InitOnce<T> {}
|
|
||||||
unsafe impl<T: Sync> Sync for InitOnce<T> {}
|
|
|
@ -1,29 +1,123 @@
|
||||||
mod locks;
|
use core::cell::UnsafeCell;
|
||||||
mod statics;
|
use core::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
pub use locks::*;
|
use portable_atomic::{AtomicBool, Ordering};
|
||||||
pub use statics::*;
|
|
||||||
|
|
||||||
use core::arch::asm;
|
#[inline(never)]
|
||||||
|
fn already_locked() -> ! {
|
||||||
/// Marks that a pointer is read without actually reading from this.
|
panic!("IRQ and main thread are attempting to access the same Lock!")
|
||||||
///
|
|
||||||
/// This uses an [`asm!`] instruction that marks the parameter as being read,
|
|
||||||
/// requiring the compiler to treat this function as if anything could be
|
|
||||||
/// done to it.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn memory_read_hint<T>(val: *const T) {
|
|
||||||
unsafe { asm!("/* {0} */", in(reg) val, options(readonly, nostack)) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks that a pointer is read or written to without actually writing to it.
|
/// A lock that prevents code from running in both an IRQ and normal code at
|
||||||
|
/// the same time.
|
||||||
///
|
///
|
||||||
/// This uses an [`asm!`] instruction that marks the parameter as being read
|
/// Note that this does not support blocking like a typical mutex, and instead
|
||||||
/// and written, requiring the compiler to treat this function as if anything
|
/// mainly exists for memory safety reasons.
|
||||||
/// could be done to it.
|
pub struct RawLock(AtomicBool);
|
||||||
#[inline(always)]
|
impl RawLock {
|
||||||
pub fn memory_write_hint<T>(val: *mut T) {
|
/// Creates a new lock.
|
||||||
unsafe { asm!("/* {0} */", in(reg) val, options(nostack)) }
|
#[must_use]
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
RawLock(AtomicBool::new(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Locks the lock and returns whether a lock was successfully acquired.
|
||||||
|
fn raw_lock(&self) -> bool {
|
||||||
|
if self.0.swap(true, Ordering::Acquire) {
|
||||||
|
// value was already true, oops.
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
// prevent any weird reordering, and continue
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unlocks the lock.
|
||||||
|
fn raw_unlock(&self) {
|
||||||
|
if !self.0.swap(false, Ordering::Release) {
|
||||||
|
panic!("Internal error: Attempt to unlock a `RawLock` which is not locked.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a guard for this lock, or `None` if there is another lock active.
|
||||||
|
pub fn try_lock(&self) -> Option<RawLockGuard<'_>> {
|
||||||
|
if self.raw_lock() {
|
||||||
|
Some(RawLockGuard(self))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe impl Send for RawLock {}
|
||||||
|
unsafe impl Sync for RawLock {}
|
||||||
|
|
||||||
|
/// A guard representing an active lock on an [`RawLock`].
|
||||||
|
pub struct RawLockGuard<'a>(&'a RawLock);
|
||||||
|
impl<'a> Drop for RawLockGuard<'a> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.0.raw_unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lock that protects an object from being accessed in both an IRQ and
|
||||||
|
/// normal code at once.
|
||||||
|
///
|
||||||
|
/// Note that this does not support blocking like a typical mutex, and instead
|
||||||
|
/// mainly exists for memory safety reasons.
|
||||||
|
pub struct Lock<T> {
|
||||||
|
raw: RawLock,
|
||||||
|
data: UnsafeCell<T>,
|
||||||
|
}
|
||||||
|
impl<T> Lock<T> {
|
||||||
|
/// Creates a new lock containing a given value.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(t: T) -> Self {
|
||||||
|
Lock {
|
||||||
|
raw: RawLock::new(),
|
||||||
|
data: UnsafeCell::new(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a guard for this lock, or panics if there is another lock active.
|
||||||
|
pub fn lock(&self) -> LockGuard<'_, T> {
|
||||||
|
self.try_lock().unwrap_or_else(|| already_locked())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a guard for this lock or `None` if there is another lock active.
|
||||||
|
pub fn try_lock(&self) -> Option<LockGuard<'_, T>> {
|
||||||
|
if self.raw.raw_lock() {
|
||||||
|
Some(LockGuard {
|
||||||
|
underlying: self,
|
||||||
|
ptr: self.data.get(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe impl<T> Send for Lock<T> {}
|
||||||
|
unsafe impl<T> Sync for Lock<T> {}
|
||||||
|
|
||||||
|
/// A guard representing an active lock on an [`Lock`].
|
||||||
|
pub struct LockGuard<'a, T> {
|
||||||
|
underlying: &'a Lock<T>,
|
||||||
|
ptr: *mut T,
|
||||||
|
}
|
||||||
|
impl<'a, T> Drop for LockGuard<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.underlying.raw.raw_unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> Deref for LockGuard<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*self.ptr }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, T> DerefMut for LockGuard<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
unsafe { &mut *self.ptr }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An internal function used as a temporary hack to get `compiler_fence`
|
/// An internal function used as a temporary hack to get `compiler_fence`
|
||||||
|
|
|
@ -1,337 +0,0 @@
|
||||||
use core::arch::asm;
|
|
||||||
use core::cell::UnsafeCell;
|
|
||||||
use core::mem;
|
|
||||||
use core::ptr;
|
|
||||||
|
|
||||||
/// The internal function for replacing a `Copy` (really `!Drop`) value in a
|
|
||||||
/// [`Static`]. This uses assembly to use an `stmia` instruction to ensure
|
|
||||||
/// an IRQ cannot occur during the write operation.
|
|
||||||
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 {
|
|
||||||
// Do nothing with ZSTs.
|
|
||||||
} else if size <= 16 && align % 4 == 0 {
|
|
||||||
// We can do an 4-byte aligned transfer up to 16 bytes.
|
|
||||||
transfer_align4_thumb(dst, src);
|
|
||||||
} else if size <= 40 && align % 4 == 0 {
|
|
||||||
// We can do the same up to 40 bytes, but we need to switch to ARM.
|
|
||||||
transfer_align4_arm(dst, src);
|
|
||||||
} else if size <= 2 && align % 2 == 0 {
|
|
||||||
// We can do a 2-byte aligned transfer up to 2 bytes.
|
|
||||||
asm!(
|
|
||||||
"ldrh {2},[{0}]",
|
|
||||||
"strh {2},[{1}]",
|
|
||||||
in(reg) src, in(reg) dst, out(reg) _,
|
|
||||||
);
|
|
||||||
} else if size == 1 {
|
|
||||||
// We can do a simple byte copy.
|
|
||||||
asm!(
|
|
||||||
"ldrb {2},[{0}]",
|
|
||||||
"strb {2},[{1}]",
|
|
||||||
in(reg) src, in(reg) dst, out(reg) _,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// When we don't have an optimized path, we just disable IRQs.
|
|
||||||
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 {
|
|
||||||
// We use assembly here regardless to just do the word aligned copy. This
|
|
||||||
// ensures it's done with a single ldr/str instruction.
|
|
||||||
asm!(
|
|
||||||
"ldr {2},[{0}]",
|
|
||||||
"str {2},[{1}]",
|
|
||||||
inout(reg) src, in(reg) dst, out(reg) _,
|
|
||||||
);
|
|
||||||
} else if size <= 8 {
|
|
||||||
// Starting at size == 8, we begin using ldmia/stmia to load/save multiple
|
|
||||||
// words in one instruction, avoiding IRQs from interrupting our operation.
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
|
||||||
// Starting at size == 16, we have to switch to ARM due to lack of
|
|
||||||
// accessible registers in THUMB mode.
|
|
||||||
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 {
|
|
||||||
// r13 is sp, and r15 is pc. Neither are usable
|
|
||||||
unimplemented!("Copy too large for use of ldmia/stmia.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The internal function for swapping the current value of a [`Static`] with
|
|
||||||
/// another value.
|
|
||||||
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 {
|
|
||||||
// Do nothing with ZSTs.
|
|
||||||
ptr::read(dst)
|
|
||||||
} else if size <= 4 && align % 4 == 0 {
|
|
||||||
// Swap a single word with the SWP instruction.
|
|
||||||
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 {
|
|
||||||
// Swap a byte with the SWPB instruction.
|
|
||||||
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 {
|
|
||||||
// fallback
|
|
||||||
crate::interrupt::free(|_| {
|
|
||||||
let cur = ptr::read_volatile(dst);
|
|
||||||
ptr::write_volatile(dst, ptr::read_volatile(src));
|
|
||||||
cur
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A helper that implements static variables.
|
|
||||||
///
|
|
||||||
/// It ensures that even if you use the same static variable in both an IRQ
|
|
||||||
/// and normal code, the IRQ will never observe an invalid value of the
|
|
||||||
/// variable.
|
|
||||||
///
|
|
||||||
/// This type only works with owned values. If you need to work with borrows,
|
|
||||||
/// consider using [`sync::Mutex`](`crate::sync::Mutex`) instead.
|
|
||||||
///
|
|
||||||
/// ## Performance
|
|
||||||
///
|
|
||||||
/// Writing or reading from a static variable is efficient under the following
|
|
||||||
/// conditions:
|
|
||||||
///
|
|
||||||
/// * The type is aligned to 4 bytes and can be stored in 40 bytes or less.
|
|
||||||
/// * The type is aligned to 2 bytes and can be stored in 2 bytes.
|
|
||||||
/// * The type is can be stored in a single byte.
|
|
||||||
///
|
|
||||||
/// Replacing the current value of the static variable is efficient under the
|
|
||||||
/// following conditions:
|
|
||||||
///
|
|
||||||
/// * The type is aligned to 4 bytes and can be stored in 4 bytes or less.
|
|
||||||
/// * The type is can be stored in a single byte.
|
|
||||||
///
|
|
||||||
/// When these conditions are not met, static variables are handled using a
|
|
||||||
/// fallback routine that disables IRQs and does a normal copy. This can be
|
|
||||||
/// dangerous as disabling IRQs can cause your program to miss out on important
|
|
||||||
/// interrupts such as V-Blank.
|
|
||||||
///
|
|
||||||
/// Consider using [`sync::Mutex`](`crate::sync::Mutex`) instead if you need to
|
|
||||||
/// use a large amount of operations that would cause IRQs to be disabled. Also
|
|
||||||
/// consider using `#[repr(align(4))]` to force proper alignment for your type.
|
|
||||||
pub struct Static<T> {
|
|
||||||
data: UnsafeCell<T>,
|
|
||||||
}
|
|
||||||
impl<T> Static<T> {
|
|
||||||
/// Creates a new static variable.
|
|
||||||
pub const fn new(val: T) -> Self {
|
|
||||||
Static {
|
|
||||||
data: UnsafeCell::new(val),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replaces the current value of the static variable with another, and
|
|
||||||
/// returns the old value.
|
|
||||||
#[allow(clippy::needless_pass_by_value)] // critical for safety
|
|
||||||
pub fn replace(&self, val: T) -> T {
|
|
||||||
unsafe { exchange(self.data.get(), &val) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extracts the interior value of the static variable.
|
|
||||||
pub fn into_inner(self) -> T {
|
|
||||||
self.data.into_inner()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Copy> Static<T> {
|
|
||||||
/// Writes a new value into this static variable.
|
|
||||||
pub fn write(&self, val: T) {
|
|
||||||
unsafe { transfer(self.data.get(), &val) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a value from this static variable.
|
|
||||||
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;
|
|
||||||
|
|
||||||
macro_rules! generate_concurrency_test {
|
|
||||||
($count:literal, $gba:ident) => {{
|
|
||||||
(|gba: &mut Gba| {
|
|
||||||
const SENTINEL: [u32; $count] = [0x12345678; $count];
|
|
||||||
static VALUE: Static<[u32; $count]> = Static::new(SENTINEL);
|
|
||||||
|
|
||||||
// set up a timer and an interrupt that uses the timer
|
|
||||||
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 = unsafe {
|
|
||||||
crate::interrupt::add_interrupt_handler(Interrupt::Timer2, |_| {
|
|
||||||
VALUE.write(SENTINEL);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
// the actual main test loop
|
|
||||||
let mut interrupt_seen = false;
|
|
||||||
let mut no_interrupt_seen = false;
|
|
||||||
for i in 0..250000 {
|
|
||||||
// write to the static
|
|
||||||
let new_value = [i; $count];
|
|
||||||
VALUE.write(new_value);
|
|
||||||
|
|
||||||
// check the current 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`.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// we return as soon as we've seen both the value written by the main thread
|
|
||||||
// and interrupt
|
|
||||||
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)
|
|
||||||
})($gba);
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case]
|
|
||||||
fn write_read_concurrency_test(gba: &mut Gba) {
|
|
||||||
generate_concurrency_test!(1, gba);
|
|
||||||
generate_concurrency_test!(2, gba);
|
|
||||||
generate_concurrency_test!(3, gba);
|
|
||||||
generate_concurrency_test!(4, gba);
|
|
||||||
generate_concurrency_test!(5, gba);
|
|
||||||
generate_concurrency_test!(6, gba);
|
|
||||||
generate_concurrency_test!(7, gba);
|
|
||||||
generate_concurrency_test!(8, gba);
|
|
||||||
generate_concurrency_test!(9, gba);
|
|
||||||
generate_concurrency_test!(10, gba);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
use agb::save::{Error, MediaInfo};
|
use agb::save::{Error, MediaInfo};
|
||||||
use agb::sync::InitOnce;
|
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
fn init_sram(gba: &mut agb::Gba) -> &'static MediaInfo {
|
fn init_sram(gba: &mut agb::Gba) -> &'static MediaInfo {
|
||||||
static ONCE: InitOnce<MediaInfo> = InitOnce::new();
|
static ONCE: OnceCell<MediaInfo> = OnceCell::new();
|
||||||
ONCE.get(|| {
|
ONCE.get_or_init(|| {
|
||||||
crate::save_setup(gba);
|
crate::save_setup(gba);
|
||||||
gba.save.access().unwrap().media_info().clone()
|
gba.save.access().unwrap().media_info().clone()
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue