Skip to content
Snippets Groups Projects
Commit 1cce9b6f authored by Geo Ster's avatar Geo Ster
Browse files

Groundwork for IOP DMA implementation

* The DMA routine on the IOP works similarly to the PSX version with a
few additions. There are 7 channels from the PSX and an additional 6 new
PS2 exclusive ones. One the PSX, each channel has 3 registers used
to configure and use it and 3 global registers.

* The PS2 contains all the older DMA registers, but it add 6 more channels
and duplicates the global registers (DPCR now has a counterpart called DPCR2)
This is done because each global register can control up to 7 channels.
An additional register on each channel (tadr) and 2 additional
global registers have been added as well. For now we don't really
care to implement them, only read/write to them.

* For reading and writing to the registers structs are used to prevent
the usage of switch and if statements.
parent 42172fa2
No related branches found
No related tags found
No related merge requests found
......@@ -14,7 +14,8 @@ add_executable(${PROJECT_NAME}
"src/main.cc"
"src/common/manager.cc"
"src/cpu/ee/ee.cc"
"src/cpu/iop/iop.cc")
"src/cpu/iop/iop.cc"
"src/cpu/iop/dma.cc")
# Link to all the required conan libs
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
......
......@@ -17,11 +17,12 @@ ComponentManager::ComponentManager()
/* Load the BIOS in our memory */
/* NOTE: Must make a GUI for this someday */
this->read_bios();
read_bios();
/* Finally construct our components */
ee = std::make_unique<ee::EmotionEngine>(this);
iop = std::make_unique<iop::IOProcessor>(this);
iop_dma = std::make_unique<iop::DMAController>(this);
}
ComponentManager::~ComponentManager()
......
#pragma once
#include <common/memory.h>
#include <cpu/iop/dma.h>
#include <fmt/core.h>
#include <memory>
#include <vector>
......@@ -41,6 +42,7 @@ public:
/* Components */
std::unique_ptr<ee::EmotionEngine> ee;
std::unique_ptr<iop::IOProcessor> iop;
std::unique_ptr<iop::DMAController> iop_dma;
/* Memory - Registers */
uint8_t* ram, * iop_ram, * bios;
......@@ -67,6 +69,8 @@ T ComponentManager::read(uint32_t addr, Component comp)
return *(T*)&bios[ee::BIOS.offset(vaddr)];
else if (iop::RAM.contains(vaddr))
return *(T*)&iop_ram[iop::RAM.offset(vaddr)];
else if (iop::DMA1.contains(vaddr) || iop::DMA2.contains(vaddr))
return iop_dma->read(vaddr);
else /* Otherwise handle specific address diferently. */
{
switch (vaddr)
......@@ -99,6 +103,9 @@ T ComponentManager::read(uint32_t addr, Component comp)
}
return 0;
}
case 0x1f801450: /* Unknown */
case 0xfffe0130: /* Cache control */
return 0;
default:
fmt::print("[{}] {:d}bit read from unknown address {:#x}\n", component_name[comp], sizeof(T) * 8, addr);
return 0;
......@@ -117,6 +124,8 @@ void ComponentManager::write(uint32_t addr, T data, Component comp)
*(T*)&ram[ee::RAM.offset(vaddr)] = data;
else if (iop::RAM.contains(vaddr))
*(T*)&iop_ram[iop::RAM.offset(vaddr)] = data;
else if (iop::DMA1.contains(vaddr) || iop::DMA2.contains(vaddr))
iop_dma->write(vaddr, data);
else
{
switch (vaddr)
......@@ -144,6 +153,9 @@ void ComponentManager::write(uint32_t addr, T data, Component comp)
MCH_DRD = data;
break;
}
case 0x1f801450:
case 0xfffe0130: /* Cache control */
return;
default:
fmt::print("[{}] {:d}bit write {:#x} to unknown address {:#x}\n", component_name[comp], sizeof(T) * 8, data, addr);
break;
......
......@@ -58,5 +58,6 @@ namespace iop
constexpr Range KSEG0 = Range(0x80000000, 512 * 1024 * 1024LL);
constexpr Range KSEG1 = Range(0xA0000000, 512 * 1024 * 1024LL);
constexpr Range KSEG2 = Range(0xC0000000, 1024 * 1024 * 1024LL);
constexpr Range DMA1 = Range(0x1f801080, 0x80LL), DMA2 = Range(0x1f801500, 0x80LL);
constexpr Range INTERRUPT = Range(0x1f801070, 8);
};
};
\ No newline at end of file
......@@ -16,26 +16,6 @@ namespace iop
void DMAController::transfer_finished(DMAChannels dma_channel)
{
DMAChannel& channel = channels[dma_channel];
/* IRQ flags in Bit(24+n) are set upon DMAn completion -
but caution - they are set ONLY if enabled in Bit(16+n).*/
if (irq.enable & (1 << dma_channel) || irq.master_enable)
{
irq.flags |= 1 << dma_channel;
}
/* The master flag is a simple readonly flag that follows the following rules:
IF b15=1 OR (b23=1 AND (b16-22 AND b24-30)>0) THEN b31=1 ELSE b31=0
Upon 0-to-1 transition of Bit31, the IRQ3 flag (in Port 1F801070h) gets set.
Bit24-30 are acknowledged (reset to zero) when writing a "1" to that bits */
bool previous = irq.master_flag;
irq.master_flag = irq.force || (irq.master_enable && ((irq.enable & irq.flags) > 0));
if (irq.master_flag && !previous)
{
irq_pending = true;
}
}
void DMAController::start(DMAChannels dma_channel)
......@@ -43,12 +23,11 @@ namespace iop
DMAChannel& channel = channels[dma_channel];
/* Start linked list copy routine. */
if (channel.control.sync_mode == SyncType::Linked_List)
{
list_copy(dma_channel);
}
else /* Start block copy routine. */
switch (channel.channel_ctrl.transfer_mode)
{
case TransferMode::Linked_List:
list_copy(dma_channel); break;
default:
block_copy(dma_channel);
}
......@@ -66,103 +45,60 @@ namespace iop
uint32_t DMAController::read(uint32_t address)
{
uint32_t off = iop::DMA.offset(address);
/* Get channel information from address. */
uint32_t channel_num = (off & 0x70) >> 4;
uint32_t offset = off & 0xf;
/* One of the main channels is enabled. */
bool group = address & 0x100;
uint16_t channel_num = (address & 0x70) >> 4;
uint16_t offset = (address & 0xf) >> 2;
uint32_t* ptr = nullptr;
if (channel_num >= 0 && channel_num <= 6)
{
DMAChannel& channel = channels[channel_num];
switch (offset)
{
case 0:
return channel.base;
case 4:
return channel.block.value;
case 8:
return channel.control.value;
default:
fmt::print("[IOP DMA] Unhandled DMA read at offset: {:#x}\n", off);
std::abort();
}
} /* One of the primary registers is selected. */
ptr = (uint32_t*)&channels[channel_num + group * 7] + offset;
}
else if (channel_num == 7)
{
switch (offset)
{
case 0:
return control;
case 4:
return irq.value;
default:
fmt::print("[IOP DMA] Unhandled DMA read at offset: {:#x}\n", offset);
std::abort();
}
ptr = (uint32_t*)&globals + 2 * offset + group;
}
else
{
fmt::print("[IOP DMA] Invalid channel number {:d}\n", channel_num);
std::abort();
}
fmt::print("[IOP DMA] Invalid channel number {:d}\n", channel_num);
return 0;
fmt::print("[IOP DMA] Reading {:#x} from DMA channel {:d} at address {:#x}\n", *ptr, channel_num, address);
return *ptr;
}
void DMAController::write(uint32_t address, uint32_t val)
void DMAController::write(uint32_t address, uint32_t data)
{
uint32_t off = iop::DMA.offset(address);
/* Get channel information from address. */
uint32_t channel_num = (off & 0x70) >> 4;
uint32_t offset = off & 0xf;
bool group = address & 0x100;
uint16_t channel_num = (address & 0x70) >> 4;
uint16_t offset = (address & 0xf) >> 2;
fmt::print("[IOP DMA] Writing {:#x} to DMA channel {:d} at address {:#x}\n", data, channel_num, address);
uint32_t active_channel = INT_MAX;
/* One of the main channels is enabled. */
if (channel_num >= 0 && channel_num <= 6)
{
DMAChannel channel = channels[channel_num];
switch (offset) {
case 0:
channel.base = val & 0xffffff;
break;
case 4:
channel.block.value = val;
break;
case 8:
channel.control.value = val;
break;
default:
fmt::print("[IOP DMA] Unhandled DMA channel write at offset: {:#x}\n", off);
std::abort();
}
/* Check if the channel was just activated. */
bool trigger = true;
if (channel.control.sync_mode == SyncType::Manual)
trigger = channel.control.trigger;
if (channel.control.enable && trigger)
active_channel = channel_num;
} /* One of the primary registers is selected. */
auto ptr = (uint32_t*)&channels[channel_num + group * 7] + offset;
*ptr = data;
}
else if (channel_num == 7)
{
switch (offset)
auto ptr = (uint32_t*)&globals + 2 * offset + group;
*ptr = data;
if (offset == 4 && !group)
{
case 0:
control = val;
break;
case 4:
irq.value = val;
irq.master_flag = irq.force || (irq.master_enable && ((irq.enable & irq.flags) > 0));
break;
default:
fmt::print("[IOP DMA] Unhandled DMA write at offset: {:#x}\n", offset);
std::abort();
/* Handle special case for writing to DICR */
auto& irq = globals.dicr;
irq.master_flag = irq.force || (irq.master_enable && ((irq.enable & irq.flags) > 0));
}
}
/* Start DMA if a channel was just activated. */
if (active_channel != INT_MAX)
start((DMAChannels)active_channel);
else
{
fmt::print("[IOP DMA] Invalid channel number {:d}\n", channel_num);
std::abort();
}
}
}
\ No newline at end of file
#pragma once
#include <cstdint>
class ComponentManager;
namespace iop
{
enum class SyncType : uint32_t
enum class TransferMode : uint32_t
{
Manual = 0,
Request = 1,
Linked_List = 2
Burst,
Slice,
Linked_List,
Chain
};
enum DMAChannels : uint32_t
{
MDECin = 0x0,
MDECout = 0x1,
GPU = 0x2,
CDROM = 0x3,
SPU = 0x4,
PIO = 0x5,
OTC = 0x6
MDECin,
MDECout,
SIF2,
CDVD,
SPU1,
PIO,
OTC,
SPU2,
DEV9,
SIF0,
SIF1,
SIO2in,
SIO2out
};
/* General info about DMA for each channel. */
......@@ -31,7 +40,7 @@ namespace iop
uint32_t addr_step : 1;
uint32_t : 6;
uint32_t chop_enable : 1;
SyncType sync_mode : 2;
TransferMode transfer_mode : 2;
uint32_t : 5;
uint32_t chop_dma : 3;
uint32_t : 1;
......@@ -50,8 +59,8 @@ namespace iop
uint32_t value;
struct
{
uint16_t block_size;
uint16_t block_count; /* Only used in Request sync mode. */
uint16_t size; /* Block size in words. */
uint16_t count; /* Only used in Request sync mode. */
};
};
......@@ -59,17 +68,18 @@ namespace iop
struct DMAChannel
{
uint32_t base;
DMABlockReg block;
DMAControlReg control;
DMABlockReg block_ctrl;
DMAControlReg channel_ctrl;
uint32_t tadr;
};
/* DMA Interrupt Register. */
union DMAIRQReg
union DICR
{
uint32_t value;
struct
{
uint32_t : 15;
uint32_t completion : 7;
uint32_t : 8;
uint32_t force : 1;
uint32_t enable : 7;
uint32_t master_enable : 1;
......@@ -78,18 +88,33 @@ namespace iop
};
};
union ListPacket
union DICR2
{
uint32_t value;
struct
{
uint32_t next_addr : 24;
uint32_t size : 8;
uint32_t tag : 13;
uint32_t : 3;
uint32_t mask : 6;
uint32_t : 2;
uint32_t flags : 6;
uint32_t : 2;
};
};
/* The standalone registers */
struct DMAGlobals
{
uint32_t dpcr[2];
DICR dicr;
DICR2 dicr2;
uint32_t pad1; /* Added so we can read the struct nicely using pointer arithmetic */
uint32_t dmacen;
uint32_t pad2;
uint32_t dmacinten;
};
/* A class that manages all DMA routines. */
class ComponentManager;
class DMAController {
public:
DMAController(ComponentManager* manager);
......@@ -105,11 +130,10 @@ namespace iop
void write(uint32_t address, uint32_t data);
public:
uint32_t control;
DMAIRQReg irq;
DMAChannel channels[7];
DMAChannel channels[13] = {};
DMAGlobals globals = {};
bool irq_pending = false;
ComponentManager* manager;
ComponentManager* manager = nullptr;
};
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
#include <cstring>
#include <fmt/color.h>
#ifndef NDEBUG
#ifdef NDEBUG
#define log(x) (void)0
#else
constexpr fmt::v8::text_style BOLD = fg(fmt::color::forest_green) | fmt::emphasis::bold;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment