133 lines
4.2 KiB
Rust
133 lines
4.2 KiB
Rust
use crate::device;
|
|
use crate::ui;
|
|
use rand::Rng;
|
|
|
|
const SI_DRAM_ADDR_REG: usize = 0;
|
|
const SI_PIF_ADDR_RD64B_REG: usize = 1;
|
|
//const SI_R2_REG: usize = 2;
|
|
//const SI_R3_REG: usize = 3;
|
|
const SI_PIF_ADDR_WR64B_REG: usize = 4;
|
|
//const SI_R5_REG: usize = 5;
|
|
pub const SI_STATUS_REG: usize = 6;
|
|
pub const SI_REGS_COUNT: usize = 7;
|
|
|
|
pub const SI_STATUS_DMA_BUSY: u32 = 1 << 0;
|
|
pub const SI_STATUS_IO_BUSY: u32 = 1 << 1;
|
|
//const SI_STATUS_DMA_ERROR: u32 = 1 << 3;
|
|
const SI_STATUS_INTERRUPT: u32 = 1 << 12;
|
|
|
|
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
pub enum DmaDir {
|
|
None,
|
|
Write,
|
|
Read,
|
|
}
|
|
|
|
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
|
pub struct Si {
|
|
pub regs: [u32; SI_REGS_COUNT],
|
|
pub dma_dir: DmaDir,
|
|
}
|
|
|
|
pub fn read_regs(
|
|
device: &mut device::Device,
|
|
address: u64,
|
|
_access_size: device::memory::AccessSize,
|
|
) -> u32 {
|
|
device::cop0::add_cycles(device, 20);
|
|
device.si.regs[((address & 0xFFFF) >> 2) as usize]
|
|
}
|
|
|
|
fn randomize_interrupt_time(rng: &mut rand::rngs::Xoshiro256PlusPlus) -> u64 {
|
|
rng.next_u64() % 0x100
|
|
}
|
|
|
|
fn dma_read(device: &mut device::Device) {
|
|
device.si.dma_dir = DmaDir::Read;
|
|
|
|
let duration = device::pif::update_pif_ram(device);
|
|
|
|
device.si.regs[SI_STATUS_REG] |= SI_STATUS_DMA_BUSY;
|
|
|
|
let length = duration + randomize_interrupt_time(&mut device.rng);
|
|
|
|
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
|
|
}
|
|
|
|
fn dma_write(device: &mut device::Device) {
|
|
device.si.dma_dir = DmaDir::Write;
|
|
|
|
copy_pif_rdram(device);
|
|
|
|
device.si.regs[SI_STATUS_REG] |= SI_STATUS_DMA_BUSY;
|
|
|
|
let length = 6000 + randomize_interrupt_time(&mut device.rng); //based on https://github.com/rasky/n64-systembench
|
|
|
|
device::events::create_event(device, device::events::EVENT_TYPE_SI, length)
|
|
}
|
|
|
|
pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u32) {
|
|
let reg = (address & 0xFFFF) >> 2;
|
|
match reg as usize {
|
|
SI_STATUS_REG => {
|
|
device.si.regs[reg as usize] &= !SI_STATUS_INTERRUPT;
|
|
device::mi::clear_rcp_interrupt(device, device::mi::MI_INTR_SI)
|
|
}
|
|
SI_PIF_ADDR_RD64B_REG => dma_read(device),
|
|
SI_PIF_ADDR_WR64B_REG => dma_write(device),
|
|
_ => device::memory::masked_write_32(&mut device.si.regs[reg as usize], value, mask),
|
|
}
|
|
}
|
|
|
|
//rdram is in native endian format, and pif memory is in big endian format
|
|
fn copy_pif_rdram(device: &mut device::Device) {
|
|
let dram_addr = device.si.regs[SI_DRAM_ADDR_REG] as usize & device::rdram::RDRAM_MASK;
|
|
if device.si.dma_dir == DmaDir::Write {
|
|
let mut i = 0;
|
|
while i < device::pif::PIF_RAM_SIZE {
|
|
let data = u32::from_ne_bytes(
|
|
device
|
|
.rdram
|
|
.mem
|
|
.get(dram_addr + i..dram_addr + i + 4)
|
|
.unwrap_or(&[0; 4])
|
|
.try_into()
|
|
.unwrap_or_default(),
|
|
);
|
|
device.pif.ram[i..i + 4].copy_from_slice(&data.to_be_bytes());
|
|
i += 4;
|
|
}
|
|
} else if device.si.dma_dir == DmaDir::Read {
|
|
// check RDP before SI writes to RDRAM
|
|
ui::video::check_framebuffers(dram_addr as u32, device::pif::PIF_RAM_SIZE as u32);
|
|
let mut i = 0;
|
|
while i < device::pif::PIF_RAM_SIZE {
|
|
let data = u32::from_be_bytes(device.pif.ram[i..i + 4].try_into().unwrap());
|
|
device
|
|
.rdram
|
|
.mem
|
|
.get_mut(dram_addr + i..dram_addr + i + 4)
|
|
.unwrap_or(&mut [0; 4])
|
|
.copy_from_slice(&data.to_ne_bytes());
|
|
i += 4;
|
|
}
|
|
} else {
|
|
panic!("si dma unknown")
|
|
}
|
|
}
|
|
|
|
pub fn dma_event(device: &mut device::Device) {
|
|
if device.si.dma_dir == DmaDir::Write {
|
|
device::pif::process_ram(device);
|
|
} else if device.si.dma_dir == DmaDir::Read {
|
|
device::si::copy_pif_rdram(device);
|
|
} else {
|
|
panic!("si dma unknown")
|
|
}
|
|
device.si.dma_dir = DmaDir::None;
|
|
device.si.regs[SI_STATUS_REG] &= !(SI_STATUS_DMA_BUSY | SI_STATUS_IO_BUSY);
|
|
device.si.regs[SI_STATUS_REG] |= SI_STATUS_INTERRUPT;
|
|
|
|
device::mi::set_rcp_interrupt(device, device::mi::MI_INTR_SI)
|
|
}
|