use portable atomics and other similar libraries

This commit is contained in:
Corwin 2024-02-17 02:34:39 +00:00
parent 0dab252379
commit 2b4c4459e0
No known key found for this signature in database
15 changed files with 202 additions and 661 deletions

View file

@ -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"

View file

@ -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 {}

View file

@ -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() {

View file

@ -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();

View file

@ -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 {
let irq = INTERRUPTS_ENABLED.get();
INTERRUPTS_ENABLED.set(0);
irq
}
// prevents the contents of the function from being reordered before IME is disabled. unsafe fn release(token: RawRestoreState) {
crate::sync::memory_write_hint(&mut f); INTERRUPTS_ENABLED.set(token);
}
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 static NUM_VBLANKS: AtomicUsize = AtomicUsize::new(0); // overflows after 2.27 years
static HAS_CREATED_INTERRUPT: Static<bool> = Static::new(false); 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);
});
}
} }

View file

@ -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

View file

@ -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)]

View file

@ -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]);

View file

@ -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);
} }
} }

View file

@ -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),

View file

@ -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(

View file

@ -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> {}

View file

@ -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`

View file

@ -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);
}
}

View file

@ -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()
}) })