mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 19:01:30 +11:00
Implement a sync
API for working with global mutable state. (#107)
* Add IntelliJ workspace files to the .gitignore. * Add a simple make_example script for Linux. * Create a `sync` module with many GBA-specific sync utilties. * Fix overflow error in debug mode in the hello_world crate. * Fixes to DMA. * Code cleanup for the sync module. * Run rustfmt on new sync code. * Fix up some names and documentation in the sync module. * Add a few changes suggested by thomcc for the locks. * Added needed compiler fences to `InitOnce::try_get`. * Change the error in `RawMutex::raw_unlock` to better reflect the cause. * Add a proper issue link to the __sync_synchronize hack. * Disable interrupts during `InitOnce::try_get`. * Fix some bad wording in the comments for `InitOnce::try_get` * Use the new target in `cfg` checks to see if we're on GBA. * Change registers used for transfer_align4_arm for the different target. * Cleanup on sync_api changes for the target change.
This commit is contained in:
parent
5114f89448
commit
2aa59bb341
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -14,4 +14,8 @@ Cargo.lock
|
||||||
crt0.o
|
crt0.o
|
||||||
|
|
||||||
# Don't track VSCode Workspace settings
|
# Don't track VSCode Workspace settings
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
|
# Don't track IntelliJ Workspaces
|
||||||
|
/.idea
|
||||||
|
/*.ida
|
||||||
|
|
|
@ -52,8 +52,8 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
let this_frame_keys = read_key_input();
|
let this_frame_keys = read_key_input();
|
||||||
|
|
||||||
// adjust game state and wait for vblank
|
// adjust game state and wait for vblank
|
||||||
px = px.wrapping_add(2 * this_frame_keys.x_tribool() as usize);
|
px = px.wrapping_add((2 * this_frame_keys.x_tribool() as i32) as usize);
|
||||||
py = py.wrapping_add(2 * this_frame_keys.y_tribool() as usize);
|
py = py.wrapping_add((2 * this_frame_keys.y_tribool() as i32) as usize);
|
||||||
if this_frame_keys.l() {
|
if this_frame_keys.l() {
|
||||||
color = Color(color.0.rotate_left(5));
|
color = Color(color.0.rotate_left(5));
|
||||||
}
|
}
|
||||||
|
|
11
make_example.sh
Executable file
11
make_example.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
if [ "$1" = "" ]; then
|
||||||
|
echo "Usage: $0 [example to build]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cargo build --example $1 --release || exit 1
|
||||||
|
arm-none-eabi-objcopy -O binary target/thumbv4-none-agb/release/examples/$1 target/$1.gba || exit 1
|
||||||
|
gbafix target/$1.gba || exit 1
|
||||||
|
echo "ROM built successfully!"
|
120
src/bios.rs
120
src/bios.rs
|
@ -8,7 +8,7 @@
|
||||||
//! whatever value is necessary for that function). Some functions also perform
|
//! whatever value is necessary for that function). Some functions also perform
|
||||||
//! necessary checks to save you from yourself, such as not dividing by zero.
|
//! necessary checks to save you from yourself, such as not dividing by zero.
|
||||||
|
|
||||||
#![cfg_attr(not(all(target_vendor = "nintendo", target_env = "agb")), allow(unused_variables))]
|
#![cfg_attr(not(target_arch = "arm"), allow(unused_variables))]
|
||||||
|
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -55,11 +55,11 @@ use io::irq::IrqFlags;
|
||||||
/// perform UB, but such a scenario might exist.
|
/// perform UB, but such a scenario might exist.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn soft_reset() -> ! {
|
pub unsafe fn soft_reset() -> ! {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
asm!("swi 0x00", options(noreturn))
|
asm!("swi 0x00", options(noreturn))
|
||||||
}
|
}
|
||||||
|
@ -92,11 +92,11 @@ pub unsafe fn soft_reset() -> ! {
|
||||||
/// that. If you do then you return to nothing and have a bad time.
|
/// that. If you do then you return to nothing and have a bad time.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) {
|
pub unsafe fn register_ram_reset(flags: RegisterRAMResetFlags) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
asm!("swi 0x01", in("r0") flags.0);
|
asm!("swi 0x01", in("r0") flags.0);
|
||||||
}
|
}
|
||||||
|
@ -126,11 +126,11 @@ impl RegisterRAMResetFlags {
|
||||||
/// any enabled interrupt triggers.
|
/// any enabled interrupt triggers.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn halt() {
|
pub fn halt() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x02");
|
asm!("swi 0x02");
|
||||||
|
@ -148,11 +148,11 @@ pub fn halt() {
|
||||||
/// optional externals such as rumble and infra-red.
|
/// optional externals such as rumble and infra-red.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn stop() {
|
pub fn stop() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x03");
|
asm!("swi 0x03");
|
||||||
|
@ -175,11 +175,11 @@ pub fn stop() {
|
||||||
/// the usual interrupt acknowledgement.
|
/// the usual interrupt acknowledgement.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) {
|
pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(
|
asm!(
|
||||||
|
@ -198,11 +198,11 @@ pub fn interrupt_wait(ignore_current_flags: bool, target_flags: IrqFlags) {
|
||||||
/// [`interrupt_wait`](interrupt_wait) outlines.
|
/// [`interrupt_wait`](interrupt_wait) outlines.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn vblank_interrupt_wait() {
|
pub fn vblank_interrupt_wait() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!(
|
asm!(
|
||||||
|
@ -222,11 +222,11 @@ pub fn vblank_interrupt_wait() {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
pub fn div_rem(numerator: i32, denominator: i32) -> (i32, i32) {
|
||||||
assert!(denominator != 0);
|
assert!(denominator != 0);
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
(numerator / denominator, numerator % denominator)
|
(numerator / denominator, numerator % denominator)
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let div_out: i32;
|
let div_out: i32;
|
||||||
let rem_out: i32;
|
let rem_out: i32;
|
||||||
|
@ -265,11 +265,11 @@ pub fn rem(numerator: i32, denominator: i32) -> i32 {
|
||||||
/// by `2n` bits to get `n` more bits of fractional precision in your output.
|
/// by `2n` bits to get `n` more bits of fractional precision in your output.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn sqrt(val: u32) -> u16 {
|
pub fn sqrt(val: u32) -> u16 {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
0 // TODO: simulate this properly when not on GBA
|
0 // TODO: simulate this properly when not on GBA
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let out: u32;
|
let out: u32;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -293,11 +293,11 @@ pub fn sqrt(val: u32) -> u16 {
|
||||||
/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`.
|
/// Accuracy suffers if `theta` is less than `-pi/4` or greater than `pi/4`.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn atan(theta: i16) -> i16 {
|
pub fn atan(theta: i16) -> i16 {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
0 // TODO: simulate this properly when not on GBA
|
0 // TODO: simulate this properly when not on GBA
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let out: i16;
|
let out: i16;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -322,11 +322,11 @@ pub fn atan(theta: i16) -> i16 {
|
||||||
/// integral, 14 bits for fractional.
|
/// integral, 14 bits for fractional.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn atan2(y: i16, x: i16) -> u16 {
|
pub fn atan2(y: i16, x: i16) -> u16 {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
0 // TODO: simulate this properly when not on GBA
|
0 // TODO: simulate this properly when not on GBA
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let out: u16;
|
let out: u16;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -353,11 +353,11 @@ pub fn atan2(y: i16, x: i16) -> u16 {
|
||||||
/// * Both pointers must be aligned
|
/// * Both pointers must be aligned
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) {
|
pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_source: bool) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let control = count + ((fixed_source as u32) << 24);
|
let control = count + ((fixed_source as u32) << 24);
|
||||||
asm!(
|
asm!(
|
||||||
|
@ -380,11 +380,11 @@ pub unsafe fn cpu_set16(src: *const u16, dest: *mut u16, count: u32, fixed_sourc
|
||||||
/// * Both pointers must be aligned
|
/// * Both pointers must be aligned
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
let control = count + ((fixed_source as u32) << 24) + (1 << 26);
|
||||||
asm!(
|
asm!(
|
||||||
|
@ -408,11 +408,11 @@ pub unsafe fn cpu_set32(src: *const u32, dest: *mut u32, count: u32, fixed_sourc
|
||||||
/// * Both pointers must be aligned
|
/// * Both pointers must be aligned
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_source: bool) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let control = count + ((fixed_source as u32) << 24);
|
let control = count + ((fixed_source as u32) << 24);
|
||||||
asm!(
|
asm!(
|
||||||
|
@ -434,11 +434,11 @@ pub unsafe fn cpu_fast_set(src: *const u32, dest: *mut u32, count: u32, fixed_so
|
||||||
/// some other value I guess you're probably running on an emulator that just
|
/// some other value I guess you're probably running on an emulator that just
|
||||||
/// broke the fourth wall.
|
/// broke the fourth wall.
|
||||||
pub fn get_bios_checksum() -> u32 {
|
pub fn get_bios_checksum() -> u32 {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
let out: u32;
|
let out: u32;
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -473,11 +473,11 @@ pub fn get_bios_checksum() -> u32 {
|
||||||
///
|
///
|
||||||
/// The final sound level setting will be `level` * `0x200`.
|
/// The final sound level setting will be `level` * `0x200`.
|
||||||
pub fn sound_bias(level: u32) {
|
pub fn sound_bias(level: u32) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x19", in("r0") level);
|
asm!("swi 0x19", in("r0") level);
|
||||||
|
@ -513,11 +513,11 @@ pub fn sound_bias(level: u32) {
|
||||||
/// * 10: 40137
|
/// * 10: 40137
|
||||||
/// * 11: 42048
|
/// * 11: 42048
|
||||||
pub fn sound_driver_mode(mode: u32) {
|
pub fn sound_driver_mode(mode: u32) {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x1B", in("r0") mode);
|
asm!("swi 0x1B", in("r0") mode);
|
||||||
|
@ -535,11 +535,11 @@ pub fn sound_driver_mode(mode: u32) {
|
||||||
/// executed." --what?
|
/// executed." --what?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn sound_driver_main() {
|
pub fn sound_driver_main() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x1C");
|
asm!("swi 0x1C");
|
||||||
|
@ -553,11 +553,11 @@ pub fn sound_driver_main() {
|
||||||
/// vblank interrupt (every 1/60th of a second).
|
/// vblank interrupt (every 1/60th of a second).
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn sound_driver_vsync() {
|
pub fn sound_driver_vsync() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x1D");
|
asm!("swi 0x1D");
|
||||||
|
@ -573,11 +573,11 @@ pub fn sound_driver_vsync() {
|
||||||
/// --what?
|
/// --what?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn sound_channel_clear() {
|
pub fn sound_channel_clear() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x1E");
|
asm!("swi 0x1E");
|
||||||
|
@ -596,11 +596,11 @@ pub fn sound_channel_clear() {
|
||||||
/// noise.
|
/// noise.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn sound_driver_vsync_off() {
|
pub fn sound_driver_vsync_off() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x28");
|
asm!("swi 0x28");
|
||||||
|
@ -615,11 +615,11 @@ pub fn sound_driver_vsync_off() {
|
||||||
/// interrupt followed by a `sound_driver_vsync` within 2/60th of a second.
|
/// interrupt followed by a `sound_driver_vsync` within 2/60th of a second.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn sound_driver_vsync_on() {
|
pub fn sound_driver_vsync_on() {
|
||||||
#[cfg(not(all(target_vendor = "nintendo", target_env = "agb")))]
|
#[cfg(not(target_arch = "arm"))]
|
||||||
{
|
{
|
||||||
unimplemented!()
|
unimplemented!("This function is not supported on this target.")
|
||||||
}
|
}
|
||||||
#[cfg(all(target_vendor = "nintendo", target_env = "agb"))]
|
#[cfg(target_arch = "arm")]
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
asm!("swi 0x29");
|
asm!("swi 0x29");
|
||||||
|
|
|
@ -143,7 +143,8 @@ impl DMA0 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to read from.
|
/// The source pointer must be aligned and valid to read from.
|
||||||
pub unsafe fn set_source(src: *const u32) {
|
pub unsafe fn set_source(src: *const u32) {
|
||||||
Self::DMA0SAD.write(src)
|
crate::sync::memory_read_hint(src);
|
||||||
|
Self::DMA0SAD.write(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the destination register.
|
/// Assigns the destination register.
|
||||||
|
@ -154,7 +155,8 @@ impl DMA0 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to write to.
|
/// The source pointer must be aligned and valid to write to.
|
||||||
pub unsafe fn set_dest(dest: *mut u32) {
|
pub unsafe fn set_dest(dest: *mut u32) {
|
||||||
Self::DMA0DAD.write(dest)
|
Self::DMA0DAD.write(dest);
|
||||||
|
crate::sync::memory_write_hint(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the count register.
|
/// Assigns the count register.
|
||||||
|
@ -204,7 +206,8 @@ impl DMA1 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to read from.
|
/// The source pointer must be aligned and valid to read from.
|
||||||
pub unsafe fn set_source(src: *const u32) {
|
pub unsafe fn set_source(src: *const u32) {
|
||||||
Self::DMA1SAD.write(src)
|
crate::sync::memory_read_hint(src);
|
||||||
|
Self::DMA1SAD.write(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the destination register.
|
/// Assigns the destination register.
|
||||||
|
@ -215,7 +218,8 @@ impl DMA1 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to write to.
|
/// The source pointer must be aligned and valid to write to.
|
||||||
pub unsafe fn set_dest(dest: *mut u32) {
|
pub unsafe fn set_dest(dest: *mut u32) {
|
||||||
Self::DMA1DAD.write(dest)
|
Self::DMA1DAD.write(dest);
|
||||||
|
crate::sync::memory_write_hint(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the count register.
|
/// Assigns the count register.
|
||||||
|
@ -265,7 +269,8 @@ impl DMA2 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to read from.
|
/// The source pointer must be aligned and valid to read from.
|
||||||
pub unsafe fn set_source(src: *const u32) {
|
pub unsafe fn set_source(src: *const u32) {
|
||||||
Self::DMA2SAD.write(src)
|
crate::sync::memory_read_hint(src);
|
||||||
|
Self::DMA2SAD.write(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the destination register.
|
/// Assigns the destination register.
|
||||||
|
@ -276,7 +281,8 @@ impl DMA2 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to write to.
|
/// The source pointer must be aligned and valid to write to.
|
||||||
pub unsafe fn set_dest(dest: *mut u32) {
|
pub unsafe fn set_dest(dest: *mut u32) {
|
||||||
Self::DMA2DAD.write(dest)
|
Self::DMA2DAD.write(dest);
|
||||||
|
crate::sync::memory_write_hint(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the count register.
|
/// Assigns the count register.
|
||||||
|
@ -327,7 +333,8 @@ impl DMA3 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to read from.
|
/// The source pointer must be aligned and valid to read from.
|
||||||
pub unsafe fn set_source(src: *const u32) {
|
pub unsafe fn set_source(src: *const u32) {
|
||||||
Self::DMA3SAD.write(src)
|
crate::sync::memory_read_hint(src);
|
||||||
|
Self::DMA3SAD.write(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the destination register.
|
/// Assigns the destination register.
|
||||||
|
@ -338,7 +345,8 @@ impl DMA3 {
|
||||||
///
|
///
|
||||||
/// The source pointer must be aligned and valid to write to.
|
/// The source pointer must be aligned and valid to write to.
|
||||||
pub unsafe fn set_dest(dest: *mut u32) {
|
pub unsafe fn set_dest(dest: *mut u32) {
|
||||||
Self::DMA3DAD.write(dest)
|
Self::DMA3DAD.write(dest);
|
||||||
|
crate::sync::memory_write_hint(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assigns the count register.
|
/// Assigns the count register.
|
||||||
|
@ -380,18 +388,24 @@ impl DMA3 {
|
||||||
.with_use_32bit(true)
|
.with_use_32bit(true)
|
||||||
.with_enabled(true);
|
.with_enabled(true);
|
||||||
// TODO: destination checking against SRAM
|
// TODO: destination checking against SRAM
|
||||||
|
crate::sync::memory_read_hint(src);
|
||||||
Self::DMA3SAD.write(src);
|
Self::DMA3SAD.write(src);
|
||||||
Self::DMA3DAD.write(dest);
|
Self::DMA3DAD.write(dest);
|
||||||
Self::DMA3CNT_L.write(count);
|
Self::DMA3CNT_L.write(count);
|
||||||
Self::DMA3CNT_H.write(FILL_CONTROL);
|
Self::DMA3CNT_H.write(FILL_CONTROL);
|
||||||
|
crate::sync::memory_write_hint(dest);
|
||||||
|
|
||||||
// Note(Lokathor): Once DMA is set to activate it takes 2 cycles for it to
|
// Note(Lokathor): Once DMA is set to activate it takes 2 cycles for it to
|
||||||
// kick in. You can do any non-DMA thing you like before that, but since
|
// kick in. You can do any non-DMA thing you like before that, but since
|
||||||
// it's only two cycles we just insert two NOP instructions to ensure that
|
// it's only two cycles we just insert two NOP instructions to ensure that
|
||||||
// successive calls to `fill32` or other DMA methods don't interfere with
|
// successive calls to `fill32` or other DMA methods don't interfere with
|
||||||
// each other.
|
// each other.
|
||||||
asm!("
|
asm!(
|
||||||
|
"
|
||||||
NOP
|
NOP
|
||||||
NOP
|
NOP
|
||||||
", options(nomem, nostack));
|
",
|
||||||
|
options(nomem, nostack)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![cfg_attr(not(test), no_std)]
|
#![cfg_attr(not(test), no_std)]
|
||||||
#![feature(asm)]
|
#![feature(asm, isa_attribute)]
|
||||||
#![allow(clippy::cast_lossless)]
|
#![allow(clippy::cast_lossless)]
|
||||||
#![deny(clippy::float_arithmetic)]
|
#![deny(clippy::float_arithmetic)]
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
@ -44,6 +44,8 @@ pub mod sram;
|
||||||
|
|
||||||
pub mod mgba;
|
pub mod mgba;
|
||||||
|
|
||||||
|
pub mod sync;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
/// This marks the end of the `.data` and `.bss` sections in IWRAM.
|
/// This marks the end of the `.data` and `.bss` sections in IWRAM.
|
||||||
///
|
///
|
||||||
|
|
63
src/sync.rs
Normal file
63
src/sync.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
//! A module containing functions and utilities useful for synchronizing state.
|
||||||
|
|
||||||
|
use crate::io::irq::{IrqEnableSetting, IME};
|
||||||
|
|
||||||
|
mod locks;
|
||||||
|
mod statics;
|
||||||
|
|
||||||
|
pub use locks::*;
|
||||||
|
pub use statics::*;
|
||||||
|
|
||||||
|
/// Marks that a pointer is read without actually reading from this.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// This uses an [`asm!`] instruction that marks the parameter as being read
|
||||||
|
/// and written, requiring the compiler to treat this function as if anything
|
||||||
|
/// could be done to it.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn memory_write_hint<T>(val: *mut T) {
|
||||||
|
unsafe { asm!("/* {0} */", in(reg) val, options(nostack)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An internal function used as a temporary hack to get `compiler_fence`
|
||||||
|
/// working. While this call is not properly inlined, working is better than
|
||||||
|
/// not working at all.
|
||||||
|
///
|
||||||
|
/// This seems to be a problem caused by Rust issue #62256:
|
||||||
|
/// <https://github.com/rust-lang/rust/issues/62256>
|
||||||
|
///
|
||||||
|
/// Not public API, obviously.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[deprecated]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[no_mangle]
|
||||||
|
#[inline(always)]
|
||||||
|
pub unsafe extern "C" fn __sync_synchronize() {}
|
||||||
|
|
||||||
|
/// Runs a function with IRQs disabled.
|
||||||
|
///
|
||||||
|
/// This should not be done without good reason, as IRQs are usually important
|
||||||
|
/// for game functionality.
|
||||||
|
pub fn with_irqs_disabled<T>(mut func: impl FnOnce() -> T) -> T {
|
||||||
|
let current_ime = IME.read();
|
||||||
|
IME.write(IrqEnableSetting::IRQ_NO);
|
||||||
|
// prevents the contents of the function from being reordered before IME is disabled.
|
||||||
|
memory_write_hint(&mut func);
|
||||||
|
|
||||||
|
let mut result = func();
|
||||||
|
|
||||||
|
// prevents the contents of the function from being reordered after IME is reenabled.
|
||||||
|
memory_write_hint(&mut result);
|
||||||
|
IME.write(current_ime);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
202
src/sync/locks.rs
Normal file
202
src/sync/locks.rs
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
use super::*;
|
||||||
|
use core::{
|
||||||
|
cell::UnsafeCell,
|
||||||
|
mem::MaybeUninit,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
ptr,
|
||||||
|
sync::atomic::{compiler_fence, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn already_locked() -> ! {
|
||||||
|
panic!("This lock has already been locked by another thread.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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, opps.
|
||||||
|
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 {}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
with_irqs_disabled(|| -> 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> 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> {}
|
261
src/sync/statics.rs
Normal file
261
src/sync/statics.rs
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
#![cfg_attr(not(target_arch = "arm"), allow(unused_variables))]
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use core::{cell::UnsafeCell, mem, mem::MaybeUninit, 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.
|
||||||
|
#[cfg(target_arch = "arm")]
|
||||||
|
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. Obviously.
|
||||||
|
} 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 <= 36 && align % 4 == 0 {
|
||||||
|
// We can do the same up to 36 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.
|
||||||
|
with_irqs_disabled(|| ptr::write_volatile(dst, ptr::read_volatile(src)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "arm")]
|
||||||
|
#[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 == 5, 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "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 == 20, we have to switch to ARM due to lack of
|
||||||
|
// accessible registers in THUMB mode.
|
||||||
|
asm!(
|
||||||
|
"ldmia {0}!, {{r2-r5,r8}}",
|
||||||
|
"stmia {1}!, {{r2-r5,r8}}",
|
||||||
|
inout(reg) src, inout(reg) dst,
|
||||||
|
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _,
|
||||||
|
)
|
||||||
|
} else if size <= 24 {
|
||||||
|
asm!(
|
||||||
|
"ldmia {0}!, {{r2-r5,r8-r9}}",
|
||||||
|
"stmia {1}!, {{r2-r5,r8-r9}}",
|
||||||
|
inout(reg) src, inout(reg) dst,
|
||||||
|
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r9") _,
|
||||||
|
)
|
||||||
|
} else if size <= 28 {
|
||||||
|
asm!(
|
||||||
|
"ldmia {0}!, {{r2-r5,r8-r10}}",
|
||||||
|
"stmia {1}!, {{r2-r5,r8-r10}}",
|
||||||
|
inout(reg) src, inout(reg) dst,
|
||||||
|
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r9") _,
|
||||||
|
out("r10") _,
|
||||||
|
)
|
||||||
|
} else if size <= 32 {
|
||||||
|
asm!(
|
||||||
|
"ldmia {0}!, {{r2-r5,r8-r10,r12}}",
|
||||||
|
"stmia {1}!, {{r2-r5,r8-r10,r12}}",
|
||||||
|
inout(reg) src, inout(reg) dst,
|
||||||
|
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r9") _,
|
||||||
|
out("r10") _, out("r12") _,
|
||||||
|
)
|
||||||
|
} else if size <= 36 {
|
||||||
|
asm!(
|
||||||
|
"ldmia {0}!, {{r2-r5,r8-r10,r12,r14}}",
|
||||||
|
"stmia {1}!, {{r2-r5,r8-r10,r12,r14}}",
|
||||||
|
inout(reg) src, inout(reg) dst,
|
||||||
|
out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r9") _,
|
||||||
|
out("r10") _, out("r12") _, out("r14") _,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
unimplemented!("Copy too large for use of ldmia/stmia.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The internal function for swapping the current value of a [`Static`] with
|
||||||
|
/// another value.
|
||||||
|
#[cfg(target_arch = "arm")]
|
||||||
|
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
|
||||||
|
with_irqs_disabled(|| {
|
||||||
|
let cur = ptr::read_volatile(dst);
|
||||||
|
ptr::write_volatile(dst, ptr::read_volatile(src));
|
||||||
|
cur
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "arm")]
|
||||||
|
#[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(target_arch = "arm")]
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "arm"))]
|
||||||
|
unsafe fn exchange<T>(dst: *mut T, src: *const T) -> T {
|
||||||
|
unimplemented!("This function is not supported on this target.")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "arm"))]
|
||||||
|
unsafe fn transfer<T: Copy>(dst: *mut T, src: *const T) {
|
||||||
|
unimplemented!("This function is not supported on this target.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 [`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 36 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 [`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.
|
||||||
|
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: MaybeUninit<T> = 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> {}
|
Loading…
Reference in a new issue