mirror of
https://github.com/italicsjenga/gba.git
synced 2024-12-23 19:01:30 +11:00
DMA first draft
This commit is contained in:
parent
91a958fc77
commit
51dbb749ef
|
@ -17,6 +17,8 @@ before_script:
|
||||||
- cargo install-update -a
|
- cargo install-update -a
|
||||||
|
|
||||||
script:
|
script:
|
||||||
|
# Travis seems to cache for some dumb reason, but we don't want that at all.
|
||||||
|
- rm -fr target
|
||||||
# Obtain the devkitPro tools, using `target/` as a temp directory
|
# Obtain the devkitPro tools, using `target/` as a temp directory
|
||||||
- mkdir -p target
|
- mkdir -p target
|
||||||
- cd target
|
- cd target
|
||||||
|
|
|
@ -46,7 +46,7 @@ fn main(_argc: isize, _argv: *const *const u8) -> isize {
|
||||||
let color_here = Mode3::read_pixel(px, py);
|
let color_here = Mode3::read_pixel(px, py);
|
||||||
if color_here != Some(BLACK) {
|
if color_here != Some(BLACK) {
|
||||||
// crashed into our own line, reset the screen
|
// crashed into our own line, reset the screen
|
||||||
Mode3::clear_to(BLACK);
|
Mode3::dma_clear_to(BLACK);
|
||||||
color = color.rotate_left(5);
|
color = color.rotate_left(5);
|
||||||
} else {
|
} else {
|
||||||
// draw the new part of the line
|
// draw the new part of the line
|
||||||
|
|
|
@ -11,4 +11,5 @@ use super::*;
|
||||||
use gba_proc_macro::register_bit;
|
use gba_proc_macro::register_bit;
|
||||||
|
|
||||||
pub mod display;
|
pub mod display;
|
||||||
|
pub mod dma;
|
||||||
pub mod keypad;
|
pub mod keypad;
|
||||||
|
|
209
src/io/dma.rs
Normal file
209
src/io/dma.rs
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
//! Module for using the four Direct Memory Access (DMA) units.
|
||||||
|
//!
|
||||||
|
//! The GBA has four DMA units, numbered 0 through 3. If you ever try to have
|
||||||
|
//! more than one active at once the lowest numbered DMA will take priority and
|
||||||
|
//! complete first. Any use of DMA halts the CPU's operation. DMA can also be
|
||||||
|
//! configured to activate automatically at certain times, and when configured
|
||||||
|
//! like that the CPU runs in between the automatic DMA activations. (This is
|
||||||
|
//! actually the intended method for doing sound.) Each DMA unit has an intended
|
||||||
|
//! use:
|
||||||
|
//!
|
||||||
|
//! * DMA0: highest priority, but can only read from internal memory.
|
||||||
|
//! * DMA1/DMA2: Intended for sound transfers.
|
||||||
|
//! * DMA3: Can be used to write into Game Pak ROM / FlashROM (not SRAM).
|
||||||
|
//!
|
||||||
|
//! ## DMA Anatomy
|
||||||
|
//!
|
||||||
|
//! Each DMA is utilized via a combination four IO registers:
|
||||||
|
//!
|
||||||
|
//! * **Source Address:** (`*const u32`) Where to read from. DMA0 can only read
|
||||||
|
//! from internal memory, the other units can read from any non-SRAM memory.
|
||||||
|
//! * **Destination Address:** (`*mut u32`) Where to write to. DMA0/1/2 can only
|
||||||
|
//! write to internal memory, DMA3 can write to any non-SRAM memory.
|
||||||
|
//! * **Word Count:** (`u16`) How many units to transfer. Despite being called
|
||||||
|
//! "word count" you can also use DMA to transfer half-words. DMA0/1/2 are
|
||||||
|
//! limited to a 14-bit counter value, DMA3 allowed the full 16-bit range to
|
||||||
|
//! be used for the counter. Note that even when transferring half-words you
|
||||||
|
//! MUST have both Source and Destination be 32-bit aligned.
|
||||||
|
//! * **Control:** (`DMAControlSetting`) This is one of those fiddly bit-flag
|
||||||
|
//! registers with all sorts of settings. See the type for more info.
|
||||||
|
//!
|
||||||
|
//! Note that Source, Destination, and Count are all read-only, while the
|
||||||
|
//! Control is read/write. When a DMA unit is _Enabled_ it copies the relevent
|
||||||
|
//! Source, Destination, and Count values into its own internal registers (so a
|
||||||
|
//! second Enable will reuse the old values). If the DMA _Repeats_ it re-copies
|
||||||
|
//! the Count, and also the Destination if
|
||||||
|
//! `DMADestAddressControl::IncrementReload` is configured in the Control, but
|
||||||
|
//! not the Source.
|
||||||
|
//!
|
||||||
|
//! When the DMA completes the Enable bit will be cleared from the Control,
|
||||||
|
//! unless the Repeat bit is set in the Control, in which case the Enable bit is
|
||||||
|
//! left active and the DMA will automatically activate again at the right time
|
||||||
|
//! (depending on the Start Timing setting). You have to manually turn off the
|
||||||
|
//! correct bit to stop the DMA unit.
|
||||||
|
//!
|
||||||
|
//! ## Safety
|
||||||
|
//!
|
||||||
|
//! As you might have noticed by now, utilizing DMA can be very fiddly. It moves
|
||||||
|
//! around bytes with no concern for the type system, including the `Clone` and
|
||||||
|
//! `Copy` traits that Rust relies on. Use of DMA can be made _somewhat safe_
|
||||||
|
//! via wrapper methods (such as those we've provided), but it's fundamentally
|
||||||
|
//! an unsafe thing to use.
|
||||||
|
//!
|
||||||
|
//! ## DMA Can Cause Subtle Bugs
|
||||||
|
//!
|
||||||
|
//! Since the CPU is halted while DMA is active you can miss out on interrupts
|
||||||
|
//! that should have fired. This can cause any number of unintended effects. DMA
|
||||||
|
//! is primarily intended for loading large amounts of graphical data from ROM,
|
||||||
|
//! or loading sound data at regulated intervals to avoid pops and crackles. It
|
||||||
|
//! _can_ be used for general purpose bulk transfers but you are advised to use
|
||||||
|
//! restraint.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
newtype! {
|
||||||
|
/// Allows you to configure a DMA unit.
|
||||||
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
|
||||||
|
DMAControlSetting, u16
|
||||||
|
}
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
impl DMAControlSetting {
|
||||||
|
pub const DEST_ADDR_CONTROL_MASK: u16 = 0b11 << 5;
|
||||||
|
pub fn dest_address_control(self) -> DMADestAddressControl {
|
||||||
|
// TODO: constify
|
||||||
|
match self.0 & Self::DEST_ADDR_CONTROL_MASK {
|
||||||
|
0 => DMADestAddressControl::Increment,
|
||||||
|
1 => DMADestAddressControl::Decrement,
|
||||||
|
2 => DMADestAddressControl::Fixed,
|
||||||
|
3 => DMADestAddressControl::IncrementReload,
|
||||||
|
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn with_dest_address_control(self, new_control: DMADestAddressControl) -> Self {
|
||||||
|
Self((self.0 & !Self::DEST_ADDR_CONTROL_MASK) | ((new_control as u16) << 5))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SRC_ADDR_CONTROL_MASK: u16 = 0b11 << 7;
|
||||||
|
pub fn src_address_control(self) -> DMASrcAddressControl {
|
||||||
|
// TODO: constify
|
||||||
|
match self.0 & Self::SRC_ADDR_CONTROL_MASK {
|
||||||
|
0 => DMASrcAddressControl::Increment,
|
||||||
|
1 => DMASrcAddressControl::Decrement,
|
||||||
|
2 => DMASrcAddressControl::Fixed,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn with_src_address_control(self, new_control: DMASrcAddressControl) -> Self {
|
||||||
|
Self((self.0 & !Self::SRC_ADDR_CONTROL_MASK) | ((new_control as u16) << 7))
|
||||||
|
}
|
||||||
|
|
||||||
|
register_bit!(REPEAT, u16, 1 << 9, repeat);
|
||||||
|
register_bit!(TRANSFER_U32, u16, 1 << 10, transfer_u32);
|
||||||
|
// TODO: Game Pak DRQ? (bit 11) DMA3 only, and requires specific hardware
|
||||||
|
|
||||||
|
pub const START_TIMING_MASK: u16 = 0b11 << 12;
|
||||||
|
pub fn start_timing(self) -> DMAStartTiming {
|
||||||
|
// TODO: constify
|
||||||
|
match self.0 & Self::DEST_ADDR_CONTROL_MASK {
|
||||||
|
0 => DMAStartTiming::Immediate,
|
||||||
|
1 => DMAStartTiming::VBlank,
|
||||||
|
2 => DMAStartTiming::HBlank,
|
||||||
|
3 => DMAStartTiming::Special,
|
||||||
|
_ => unsafe { core::hint::unreachable_unchecked() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn with_start_timing(self, new_control: DMAStartTiming) -> Self {
|
||||||
|
Self((self.0 & !Self::START_TIMING_MASK) | ((new_control as u16) << 12))
|
||||||
|
}
|
||||||
|
|
||||||
|
register_bit!(IRQ_AT_END, u16, 1 << 14, irq_at_end);
|
||||||
|
register_bit!(ENABLE, u16, 1 << 15, enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets how the destination address should be adjusted per data transfer.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum DMADestAddressControl {
|
||||||
|
/// Offset +1
|
||||||
|
Increment = 0,
|
||||||
|
/// Offset -1
|
||||||
|
Decrement = 1,
|
||||||
|
/// No change
|
||||||
|
Fixed = 2,
|
||||||
|
/// Offset +1 per transfer and auto-reset to base when the DMA repeats.
|
||||||
|
IncrementReload = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets how the source address should be adjusted per data transfer.
|
||||||
|
///
|
||||||
|
/// Note that only 0,1,2 are allowed, 3 is prohibited.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum DMASrcAddressControl {
|
||||||
|
/// Offset +1
|
||||||
|
Increment = 0,
|
||||||
|
/// Offset -1
|
||||||
|
Decrement = 1,
|
||||||
|
/// No change
|
||||||
|
Fixed = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets when the DMA should activate.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[repr(u16)]
|
||||||
|
pub enum DMAStartTiming {
|
||||||
|
/// Causes the DMA to start as soon as possible (2 wait cycles after enabled)
|
||||||
|
Immediate = 0,
|
||||||
|
/// Start at VBlank
|
||||||
|
VBlank = 1,
|
||||||
|
/// Start at HBlank
|
||||||
|
HBlank = 2,
|
||||||
|
/// The special timing depends on the DMA it's used with:
|
||||||
|
/// * 0: Prohibited
|
||||||
|
/// * 1/2: Sound FIFO,
|
||||||
|
/// * 3: Video Capture, for transferring from memory/camera into VRAM
|
||||||
|
Special = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is the "general purpose" DMA unit, with the fewest limits.
|
||||||
|
pub struct DMA3;
|
||||||
|
impl DMA3 {
|
||||||
|
/// DMA 3 Source Address, read only.
|
||||||
|
const DMA3SAD: VolAddress<*const u32> = unsafe { VolAddress::new_unchecked(0x400_00D4) };
|
||||||
|
/// DMA 3 Destination Address, read only.
|
||||||
|
const DMA3DAD: VolAddress<*mut u32> = unsafe { VolAddress::new_unchecked(0x400_00D8) };
|
||||||
|
/// DMA 3 Word Count, read only.
|
||||||
|
const DMA3CNT_L: VolAddress<u16> = unsafe { VolAddress::new_unchecked(0x400_00DC) };
|
||||||
|
/// DMA 3 Control, read/write.
|
||||||
|
const DMA3CNT_H: VolAddress<DMAControlSetting> = unsafe { VolAddress::new_unchecked(0x400_00DE) };
|
||||||
|
|
||||||
|
/// Fills `count` slots (starting at `dest`) with the value at `src`.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// Both pointers must be aligned, and all positions specified for writing
|
||||||
|
/// must be valid for writing.
|
||||||
|
pub unsafe fn fill32(src: *const u32, dest: *mut u32, count: u16) {
|
||||||
|
const FILL_CONTROL: DMAControlSetting = DMAControlSetting::new()
|
||||||
|
.with_src_address_control(DMASrcAddressControl::Fixed)
|
||||||
|
.with_transfer_u32(true)
|
||||||
|
.with_enable(true);
|
||||||
|
// TODO: destination checking against SRAM
|
||||||
|
Self::DMA3SAD.write(src);
|
||||||
|
Self::DMA3DAD.write(dest);
|
||||||
|
Self::DMA3CNT_L.write(count);
|
||||||
|
Self::DMA3CNT_H.write(FILL_CONTROL);
|
||||||
|
// 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
|
||||||
|
// 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
|
||||||
|
// each other.
|
||||||
|
asm!(/* ASM */ "NOP
|
||||||
|
NOP"
|
||||||
|
:/* OUT */ // none
|
||||||
|
:/* INP */ // none
|
||||||
|
:/* CLO */ // none
|
||||||
|
:/* OPT */ "volatile"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
13
src/video.rs
13
src/video.rs
|
@ -48,9 +48,11 @@ impl Mode3 {
|
||||||
pub const VRAM: VolAddressBlock<Color> =
|
pub const VRAM: VolAddressBlock<Color> =
|
||||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) };
|
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT) };
|
||||||
|
|
||||||
|
const MODE3_U32_COUNT: u16 = (Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 2) as u16;
|
||||||
|
|
||||||
/// private iterator over the pixels, two at a time
|
/// private iterator over the pixels, two at a time
|
||||||
const BULK_ITER: VolAddressIter<u32> =
|
const BULK_ITER: VolAddressIter<u32> =
|
||||||
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::SCREEN_WIDTH * Self::SCREEN_HEIGHT / 2).iter() };
|
unsafe { VolAddressBlock::new_unchecked(VolAddress::new_unchecked(VRAM_BASE_USIZE), Self::MODE3_U32_COUNT as usize).iter() };
|
||||||
|
|
||||||
/// Reads the pixel at the given (col,row).
|
/// Reads the pixel at the given (col,row).
|
||||||
///
|
///
|
||||||
|
@ -79,5 +81,12 @@ impl Mode3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: dma_clear_to?
|
/// Clears the whole screen to the desired color using DMA3.
|
||||||
|
pub fn dma_clear_to(color: Color) {
|
||||||
|
use crate::io::dma::DMA3;
|
||||||
|
|
||||||
|
let color32 = color.0 as u32;
|
||||||
|
let bulk_color = color32 << 16 | color32;
|
||||||
|
unsafe { DMA3::fill32(&bulk_color, VRAM_BASE_USIZE as *mut u32, Self::MODE3_U32_COUNT) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue