use crate::mdec;
use crate::memory::Result;
use super::interrupts::InterruptRequester;
use super::BusLine;
bitflags::bitflags! {
#[derive(Default, Debug)]
struct ChannelControl: u32 {
const DIRECTION_FROM_RAM = 0b00000000000000000000000000000001;
const ADDRESS_STEP_DIRECTION = 0b00000000000000000000000000000010;
const CHOPPING_ENABLED = 0b00000000000000000000000100000000;
const SYNC_MODE = 0b00000000000000000000011000000000;
const CHOPPING_DMA_WINDOW_SIZE = 0b00000000000001110000000000000000;
const CHOPPING_CPU_WINDOW_SIZE = 0b00000000011100000000000000000000;
const START_BUSY = 0b00000001000000000000000000000000;
const START_TRIGGER = 0b00010000000000000000000000000000;
const UNKNOWN1 = 0b00100000000000000000000000000000;
const UNKNOWN2 = 0b01000000000000000000000000000000;
}
}
impl ChannelControl {
fn address_step(&self) -> i32 {
if self.intersects(Self::ADDRESS_STEP_DIRECTION) {
-4
} else {
4
}
}
fn sync_mode(&self) -> u32 {
(self.bits() & Self::SYNC_MODE.bits()) >> 9
}
fn in_progress(&self) -> bool {
self.intersects(Self::START_BUSY)
}
fn finish_transfer(&mut self) {
self.remove(Self::START_BUSY)
}
fn chopping_dma_window_size(&self) -> u32 {
1 << ((self.bits() & Self::CHOPPING_DMA_WINDOW_SIZE.bits()) >> 16)
}
fn chopping_cpu_window_size(&self) -> u8 {
1 << ((self.bits() & Self::CHOPPING_CPU_WINDOW_SIZE.bits()) >> 20)
}
}
bitflags::bitflags! {
#[derive(Default, Debug)]
struct DmaInterruptRegister: u32 {
const UNKNOWN = 0b00000000000000000000000000111111;
const FORCE_IRQ = 0b00000000000000001000000000000000;
const IRQ_ENABLE = 0b00000000011111110000000000000000;
const IRQ_MASTER_ENABLE = 0b00000000100000000000000000000000;
const IRQ_FLAGS = 0b01111111000000000000000000000000;
const IRQ_MASTER_FLAG = 0b10000000000000000000000000000000;
}
}
impl DmaInterruptRegister {
#[inline]
fn master_flag(&self) -> bool {
self.intersects(Self::IRQ_MASTER_FLAG)
}
#[inline]
fn request_interrupt(&mut self, channel: u32) {
assert!(channel < 7);
if (self.bits() >> 16) & (1 << channel) != 0 {
log::info!("requesting interrupt channel {}", channel);
*self |= Self::from_bits_retain(1 << (channel + 24));
}
}
#[inline]
fn compute_irq_master_flag(&self) -> bool {
self.intersects(DmaInterruptRegister::FORCE_IRQ)
|| (self.intersects(DmaInterruptRegister::IRQ_MASTER_ENABLE)
&& (((self.bits() & DmaInterruptRegister::IRQ_ENABLE.bits()) >> 16)
& ((self.bits() & DmaInterruptRegister::IRQ_FLAGS.bits()) >> 24)
!= 0))
}
}
#[derive(Default)]
struct DmaChannel {
base_address: u32,
block_control: u32,
channel_control: ChannelControl,
}
impl DmaChannel {
fn read(&mut self, addr: u32) -> u32 {
match addr {
0x0 => {
let out = self.base_address;
log::info!("Dma base address read {:08X}", out);
out
}
0x4 => {
let out = self.block_control;
log::info!("Dma block control read {:08X}", out);
out
}
0x8 => {
let out = self.channel_control.bits();
log::info!("Dma channel read control {:08X}", out);
out
}
0xC => {
log::info!("Dma channel read control mirror 0xC");
self.read(0x8)
}
_ => unreachable!(),
}
}
fn write(&mut self, addr: u32, data: u32) {
match addr {
0x0 => {
log::info!("Dma channel base address {:08X}", data);
self.base_address = data & 0xFFFFFF;
}
0x4 => {
log::info!("Dma channel block control {:08X}", data);
self.block_control = data;
}
0x8 => {
log::info!("Dma channel control write {:08X}", data);
self.channel_control = ChannelControl::from_bits_retain(data);
log::info!("Dma channel control write {:?}", self.channel_control);
}
0xC => {
log::info!("Dma channel control write mirror 0xC");
self.write(0x8, data)
}
_ => unreachable!(),
}
}
}
pub struct Dma {
control: u32,
interrupt: DmaInterruptRegister,
channels: [DmaChannel; 7],
}
impl Default for Dma {
fn default() -> Self {
Self {
control: 0x07654321,
interrupt: Default::default(),
channels: Default::default(),
}
}
}
impl Dma {
fn perform_mdec_in_channel0_dma(
channel: &mut DmaChannel,
dma_bus: &mut super::DmaBus,
) -> (u32, bool) {
assert!(channel
.channel_control
.intersects(ChannelControl::DIRECTION_FROM_RAM));
assert!(channel.channel_control.address_step() == 4);
assert!(channel.channel_control.sync_mode() == 1);
assert!(!channel
.channel_control
.intersects(ChannelControl::CHOPPING_ENABLED));
let block_size = channel.block_control & 0xFFFF;
let blocks = channel.block_control >> 16;
let mut address = channel.base_address & 0xFFFFFC;
for _ in 0..block_size {
let data = dma_bus.main_ram.read_u32(address).unwrap();
dma_bus.mdec.write_u32(0, data).unwrap();
address += 4;
}
let blocks = blocks.saturating_sub(1);
channel.block_control &= 0xFFFF;
channel.block_control |= blocks << 16;
channel.base_address = address;
(block_size, blocks == 0)
}
fn perform_mdec_out_channel1_dma(
channel: &mut DmaChannel,
dma_bus: &mut super::DmaBus,
) -> (u32, bool) {
assert!(!channel
.channel_control
.intersects(ChannelControl::DIRECTION_FROM_RAM));
assert!(channel.channel_control.address_step() == 4);
assert!(channel.channel_control.sync_mode() == 1);
assert!(!channel
.channel_control
.intersects(ChannelControl::CHOPPING_ENABLED));
let block_size = channel.block_control & 0xFFFF;
let blocks = channel.block_control >> 16;
let mut address = channel.base_address & 0xFFFFFC;
for _ in 0..block_size {
let fifo_state = dma_bus.mdec.fifo_current_state();
let data = dma_bus.mdec.read_fifo();
let row_size = if fifo_state.is_24bit { 6 } else { 4 };
let block_size = row_size * 8;
let offset = match fifo_state.block_type {
mdec::BlockType::Y1 | mdec::BlockType::Y3 => {
let base_index = fifo_state.index as i32 / row_size;
base_index * row_size
}
mdec::BlockType::Y2 | mdec::BlockType::Y4 => {
let base_index = fifo_state.index as i32 / row_size;
base_index * row_size + row_size - block_size
}
mdec::BlockType::YCr => 0,
_ => unreachable!(),
};
let effective_address = (address as i32 + (offset * 4)) as u32;
dma_bus.main_ram.write_u32(effective_address, data).unwrap();
address += 4;
}
let blocks = blocks.saturating_sub(1);
channel.block_control &= 0xFFFF;
channel.block_control |= blocks << 16;
channel.base_address = address;
(block_size, blocks == 0)
}
fn perform_gpu_channel2_dma(
channel: &mut DmaChannel,
dma_bus: &mut super::DmaBus,
) -> (u32, bool) {
match channel.channel_control.sync_mode() {
1 => {
let direction_from_main_ram = channel
.channel_control
.intersects(ChannelControl::DIRECTION_FROM_RAM);
let address_step = channel.channel_control.address_step();
let block_size = channel.block_control & 0xFFFF;
let blocks = channel.block_control >> 16;
let mut address = channel.base_address & 0xFFFFFC;
if direction_from_main_ram {
for _ in 0..block_size {
let data = dma_bus.main_ram.read_u32(address).unwrap();
dma_bus.gpu.write_u32(0, data).unwrap();
address = (address as i32 + address_step) as u32;
}
} else {
for _ in 0..block_size {
let data = dma_bus.gpu.read_u32(0).unwrap();
dma_bus.main_ram.write_u32(address, data).unwrap();
address = (address as i32 + address_step) as u32;
}
}
let blocks = blocks - 1;
channel.block_control &= 0xFFFF;
channel.block_control |= blocks << 16;
channel.base_address = address;
(block_size, blocks == 0)
}
2 => {
assert!(channel.channel_control.address_step() == 4);
let mut linked_entry_addr = channel.base_address & 0xFFFFFC;
let mut linked_list_data = dma_bus.main_ram.read_u32(linked_entry_addr).unwrap();
let mut n_entries = linked_list_data >> 24;
log::info!(
"got {} entries, from data {:08X} located at address {:08X}",
n_entries,
linked_list_data,
linked_entry_addr
);
while n_entries == 0 && linked_list_data & 0xFFFFFF != 0xFFFFFF {
linked_entry_addr = linked_list_data & 0xFFFFFC;
linked_list_data = dma_bus.main_ram.read_u32(linked_entry_addr).unwrap();
n_entries = linked_list_data >> 24;
if n_entries != 0 {
channel.base_address = linked_entry_addr & 0xFFFFFC;
return (0, false);
}
log::trace!(
"skipping: got {} entries, from data {:08X} located at address {:08X}",
n_entries,
linked_list_data,
linked_entry_addr
);
}
for i in 1..(n_entries + 1) {
let cmd = dma_bus
.main_ram
.read_u32(linked_entry_addr + i * 4)
.unwrap();
dma_bus.gpu.write_u32(0, cmd).unwrap();
}
channel.base_address = linked_list_data & 0xFFFFFF;
(n_entries + 1, channel.base_address == 0xFFFFFF)
}
_ => unreachable!("{}", channel.channel_control.sync_mode()),
}
}
fn perform_cdrom_channel3_dma(
channel: &mut DmaChannel,
dma_bus: &mut super::DmaBus,
) -> (u32, bool) {
assert!(!channel
.channel_control
.intersects(ChannelControl::DIRECTION_FROM_RAM));
assert!(channel.channel_control.address_step() == 4);
assert!(channel.channel_control.sync_mode() == 0);
if !channel
.channel_control
.intersects(ChannelControl::START_TRIGGER)
{
return (0, true);
}
let chopping = channel
.channel_control
.intersects(ChannelControl::CHOPPING_ENABLED);
if chopping {
log::info!(
"chopping enabled: CPU window: {:02X}, DMA window: {:02X}",
channel.channel_control.chopping_cpu_window_size(),
channel.channel_control.chopping_dma_window_size()
);
}
let mut block_size = channel.block_control & 0xFFFF;
if block_size == 0 {
block_size = 0x10000;
}
log::info!("CD-ROM DMA: block size: {:04X}", block_size);
let mut address = channel.base_address & 0xFFFFFC;
for _ in 0..block_size {
let mut data = dma_bus.cdrom.read_u8(2).unwrap() as u32;
data |= (dma_bus.cdrom.read_u8(2).unwrap() as u32) << 8;
data |= (dma_bus.cdrom.read_u8(2).unwrap() as u32) << 16;
data |= (dma_bus.cdrom.read_u8(2).unwrap() as u32) << 24;
dma_bus.main_ram.write_u32(address, data).unwrap();
address += 4;
}
if chopping {
channel.block_control = 0;
channel.base_address = address;
}
(block_size * 30, true)
}
fn perform_spu_channel4_dma(
channel: &mut DmaChannel,
dma_bus: &mut super::DmaBus,
) -> (u32, bool) {
assert!(channel.channel_control.sync_mode() != 2);
assert!(!channel
.channel_control
.intersects(ChannelControl::CHOPPING_ENABLED));
let direction_from_main_ram = channel
.channel_control
.intersects(ChannelControl::DIRECTION_FROM_RAM);
if !dma_bus.spu.is_ready_for_dma(direction_from_main_ram) {
return (0, false);
}
let address_step = channel.channel_control.address_step();
let block_size = channel.block_control & 0xFFFF;
let mut blocks = channel.block_control >> 16;
let mut address = channel.base_address & 0xFFFFFC;
if direction_from_main_ram {
let mut block = Vec::with_capacity(block_size as usize);
for _ in 0..block_size {
let data = dma_bus.main_ram.read_u32(address).unwrap();
block.push(data);
address = (address as i32 + address_step) as u32;
}
dma_bus.spu.dma_write_buf(&block);
} else {
let block = dma_bus.spu.dma_read_buf(block_size as usize);
for data in block {
dma_bus.main_ram.write_u32(address, data).unwrap();
address = (address as i32 + address_step) as u32;
}
}
if channel.channel_control.sync_mode() == 1 {
blocks -= 1;
channel.block_control &= 0xFFFF;
channel.block_control |= blocks << 16;
channel.base_address = address;
}
let finished = blocks == 0 || channel.channel_control.sync_mode() == 0;
if finished {
dma_bus.spu.finish_dma();
}
(block_size, finished)
}
fn perform_otc_channel6_dma(
channel: &mut DmaChannel,
dma_bus: &mut super::DmaBus,
) -> (u32, bool) {
assert!(!channel
.channel_control
.intersects(ChannelControl::DIRECTION_FROM_RAM));
assert!(channel.channel_control.address_step() == -4);
assert!(channel.channel_control.sync_mode() == 0);
if !channel
.channel_control
.intersects(ChannelControl::START_TRIGGER)
{
return (0, true);
}
let chopping = channel
.channel_control
.intersects(ChannelControl::CHOPPING_ENABLED);
let mut current = channel.base_address & 0xFFFFFC;
let mut n_entries = channel.block_control & 0xFFFF;
if n_entries == 0 {
n_entries = 0x10000;
}
for _ in 0..(n_entries - 1) {
let next = current - 4;
dma_bus.main_ram.write_u32(current, next).unwrap();
current = next;
}
dma_bus.main_ram.write_u32(current, 0xFFFFFF).unwrap();
if chopping {
channel.block_control = 0;
channel.base_address = current;
}
(n_entries, true)
}
}
impl Dma {
pub(super) fn needs_to_run(&self) -> bool {
self.channels.iter().enumerate().any(|(i, channel)| {
let channel_enabled = (self.control >> (i * 4)) & 0b1000 != 0;
channel_enabled && channel.channel_control.in_progress()
})
}
fn get_channels_order_to_run<'a>(&self, out_order: &'a mut [usize]) -> &'a [usize] {
let mut enabled_channels = [(0, 0); 7];
let mut total_enabled = 0;
self.channels
.iter()
.enumerate()
.filter_map(|(i, channel)| {
let channel_enabled = (self.control >> (i * 4)) & 0b1000 != 0;
let priority = (self.control >> (i * 4)) & 0b111;
if channel_enabled && channel.channel_control.in_progress() {
Some((i, priority))
} else {
None
}
})
.for_each(|(i, priority)| {
enabled_channels[total_enabled] = (i, priority);
total_enabled += 1;
});
if total_enabled == 0 {
return &[];
}
enabled_channels[..total_enabled]
.sort_unstable_by_key(|(i, priority)| *priority as i32 * 100 - *i as i32);
let size = out_order.len().min(total_enabled);
for i in 0..size {
out_order[i] = enabled_channels[i].0;
}
&out_order[..total_enabled]
}
pub(super) fn clock_dma(
&mut self,
dma_bus: &mut super::DmaBus,
interrupt_requester: &mut impl InterruptRequester,
) -> u32 {
let mut cpu_cycles = 0;
let mut channels_order = [0; 7];
let channels_to_run = self.get_channels_order_to_run(&mut channels_order);
for &i in channels_to_run {
let channel = &mut self.channels[i];
log::trace!("channel {} doing DMA", i);
let (cycles_to_delay, finished) = match i {
0 => Self::perform_mdec_in_channel0_dma(channel, dma_bus),
1 => Self::perform_mdec_out_channel1_dma(channel, dma_bus),
2 => Self::perform_gpu_channel2_dma(channel, dma_bus),
3 => Self::perform_cdrom_channel3_dma(channel, dma_bus),
4 => Self::perform_spu_channel4_dma(channel, dma_bus),
5 => todo!("DMA channel PIO 5"),
6 => Self::perform_otc_channel6_dma(channel, dma_bus),
_ => unreachable!(),
};
if cycles_to_delay == 0 {
continue;
}
cpu_cycles = cycles_to_delay;
channel
.channel_control
.remove(ChannelControl::START_TRIGGER);
if finished {
channel.channel_control.finish_transfer();
self.interrupt.request_interrupt(i as u32);
}
break;
}
let new_master_flag = self.interrupt.compute_irq_master_flag();
if new_master_flag && !self.interrupt.master_flag() {
interrupt_requester.request_dma();
}
self.interrupt
.set(DmaInterruptRegister::IRQ_MASTER_FLAG, new_master_flag);
cpu_cycles
}
}
impl BusLine for Dma {
fn read_u32(&mut self, addr: u32) -> Result<u32> {
let r = match addr {
0x80..=0xEF => {
let channel_index = (addr >> 4) - 8;
log::info!("DMA, reading from channel {}", channel_index);
self.channels[channel_index as usize].read(addr & 0xF)
}
0xF0 => self.control,
0xF4 => self.interrupt.bits(),
_ => unreachable!(),
};
Ok(r)
}
fn write_u32(&mut self, addr: u32, mut data: u32) -> Result<()> {
match addr {
0x80..=0xEF => {
let channel_index = (addr >> 4) - 8;
log::info!("DMA, writing to channel {}", channel_index);
if channel_index == 6 && addr & 0xF == 8 {
data &= 0b0101_0001_0000_0000_0000_0000_0000_0000;
data |= 2;
}
self.channels[channel_index as usize].write(addr & 0xF, data)
}
0xF0 => {
log::info!("DMA control {:08X}", data);
self.control = data
}
0xF4 => {
let old_interrupt = self.interrupt.bits();
let new_data = data & 0xFFFFFF;
let irq_flags_reset = data & 0x7F000000;
let irq_enable_mask = ((old_interrupt >> 16) & 0x7F) ^ 0x7F;
let irq_flags_reset = irq_flags_reset | (irq_enable_mask << 24);
let new_interrupt = ((old_interrupt & 0xFF000000) & !irq_flags_reset) | new_data;
self.interrupt = DmaInterruptRegister::from_bits_retain(new_interrupt);
log::info!(
"DMA interrupt input: {:08X}, result: {:08X}, {:?}",
data,
self.interrupt.bits(),
self.interrupt
);
}
_ => unreachable!(),
}
Ok(())
}
fn read_u8(&mut self, addr: u32) -> Result<u8> {
let u32_data = self.read_u32(addr & 0xFFFFFFFC)?;
let shift = (addr & 3) * 8;
Ok(((u32_data >> shift) & 0xFF) as u8)
}
fn write_u8(&mut self, addr: u32, data: u8) -> Result<()> {
match addr {
0x80..=0xF3 | 0xF7 => {
let aligned_addr = addr & 0xFFFFFFFC;
let current_u32 = self.read_u32(aligned_addr)?;
let shift = (addr & 3) * 8;
let new_u32 = (current_u32 & !(0xFF << shift)) | ((data as u32) << shift);
self.write_u32(aligned_addr, new_u32)?;
}
0xF4..=0xF6 => {
let current_u32 = self.read_u32(0xF4)?;
let shift = (addr & 3) * 8;
let new_u32 = (current_u32 & !(0xFF << shift)) | ((data as u32) << shift);
let new_u32 = new_u32 & !0xFF000000;
self.write_u32(0xF4, new_u32)?;
}
_ => unreachable!(),
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_channels_order_no_channels_enabled() {
let dma = Dma::default();
let mut channels_order = [0; 7];
let channels_order = dma.get_channels_order_to_run(&mut channels_order);
assert_eq!(channels_order, &[]);
}
#[test]
fn get_channels_order_one_channel_enabled() {
let mut dma = Dma {
control: 0b0000_0000_0000_0000_0000_0000_0000_1000, ..Dma::default()
};
dma.channels[0].channel_control = ChannelControl::START_BUSY;
let mut channels_order = [0; 7];
let channels_order = dma.get_channels_order_to_run(&mut channels_order);
assert_eq!(channels_order, &[0]);
}
#[test]
fn get_channels_order_multiple_channels_enabled_same_priority() {
let mut dma = Dma {
control: 0b0000_0000_0000_0000_1000_1000_1000_1000, ..Dma::default()
};
dma.channels[0].channel_control = ChannelControl::START_BUSY;
dma.channels[1].channel_control = ChannelControl::START_BUSY;
dma.channels[2].channel_control = ChannelControl::START_BUSY;
dma.channels[3].channel_control = ChannelControl::START_BUSY;
let mut channels_order = [0; 7];
let channels_order = dma.get_channels_order_to_run(&mut channels_order);
assert_eq!(channels_order, &[3, 2, 1, 0]);
}
#[test]
fn get_channels_order_multiple_channels_enabled_different_priority() {
let mut dma = Dma {
control: 0b0000_0000_0000_0000_1010_1001_0000_0000, ..Dma::default()
};
dma.channels[2].channel_control = ChannelControl::START_BUSY;
dma.channels[3].channel_control = ChannelControl::START_BUSY;
let mut channels_order = [0; 7];
let channels_order = dma.get_channels_order_to_run(&mut channels_order);
assert_eq!(channels_order, &[2, 3]);
}
#[test]
fn get_channels_order_all_channels_enabled_different_priority() {
let mut dma = Dma {
control: 0b1111_1110_1101_1100_1011_1010_1001_1000, ..Dma::default()
};
for i in 0..7 {
dma.channels[i].channel_control = ChannelControl::START_BUSY;
}
let mut channels_order = [0; 7];
let channels_order = dma.get_channels_order_to_run(&mut channels_order);
assert_eq!(channels_order, &[0, 1, 2, 3, 4, 5, 6]);
}
}