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

Lay the foundation for the DMAC

* The BIOS now continues by initializing the DMA Controller.
This is one of the most important hardware components of the PS2,
as it assists the EE with transfering data where it needs to be. I've
even read that at times it can do more work than the EE itself.

* Since the DMAC isn't used at this stage, we only really have to
implement its registers and reads/writes to them, which is pretty easy.
However one register D_CTRL is a bit quirky in a sense that writes to it
clear/reverse its bits, not overwrite them.

* To emulate this, an additional struct is added to the register unions
and bitwise operators are used to write to the upper and lower parts of
the register appropriately. You can look into the source code for more details.

* This allows the EE to start initializing the VU1 which is quite exciting!
parent 1f7dedbe
No related branches found
No related tags found
No related merge requests found
......@@ -22,6 +22,7 @@ add_executable(${PROJECT_NAME}
"src/cpu/iop/intr.cc"
"src/gs/gif.cc"
"src/gs/gs.cc"
"src/cpu/ee/dmac.cc"
)
# Link to all the required conan libs
......
#include <common/emulator.h>
#include <cpu/ee/ee.h>
#include <cpu/ee/dmac.h>
#include <cpu/iop/iop.h>
#include <gs/gif.h>
#include <gs/gs.h>
......@@ -24,6 +25,7 @@ namespace common
iop_dma = std::make_unique<iop::DMAController>(this);
gif = std::make_unique<gs::GIF>(this);
gs = std::make_unique<gs::GraphicsSynthesizer>(this);
dmac = std::make_unique<ee::DMAController>(this);
}
Emulator::~Emulator()
......
......@@ -9,6 +9,7 @@
namespace ee
{
class EmotionEngine;
struct DMAController;
}
namespace iop
......@@ -73,6 +74,7 @@ namespace common
std::unique_ptr<iop::DMAController> iop_dma;
std::unique_ptr<gs::GIF> gif;
std::unique_ptr<gs::GraphicsSynthesizer> gs;
std::unique_ptr<ee::DMAController> dmac;
/* Memory - Registers */
uint8_t* bios;
......
#include <cpu/ee/dmac.h>
#include <common/emulator.h>
#include <cassert>
inline uint32_t get_channel(uint32_t value)
{
switch (value)
{
case 0x80: return 0;
case 0x90: return 1;
case 0xa0: return 2;
case 0xb0: return 3;
case 0xb4: return 4;
case 0xc0: return 5;
case 0xc4: return 6;
case 0xc8: return 7;
case 0xd0: return 8;
case 0xd4: return 9;
default:
fmt::print("[DMAC] Invalid channel id provided {:#x}, aborting...\n", value);
std::abort();
}
}
constexpr const char* REGS[] =
{
"Dn_CHCR",
"Dn_MADR",
"Dn_QWC",
"Dn_TADR",
"Dn_ASR0",
"Dn_ASR1",
"", "",
"Dn_SADR"
};
constexpr const char* GLOBALS[] =
{
"D_CTRL",
"D_STAT",
"D_PCR",
"D_SQWC",
"D_RBSR",
"D_RBOR",
"D_STADT",
};
namespace ee
{
DMAController::DMAController(common::Emulator* parent) :
emulator(parent)
{
constexpr uint32_t addresses[] =
{ 0x10008000, 0x10009000, 0x1000a000, 0x1000b000, 0x1000b400,
0x1000c000, 0x1000c400, 0x1000c800, 0x1000d000, 0x1000d400 };
/* Register channel handlers */
for (auto addr : addresses)
{
emulator->add_handler(addr, this, &DMAController::read_channel, &DMAController::write_channel);
/* Sadly the last register eats up a whole page itself */
emulator->add_handler(addr + 0x80, this, &DMAController::read_channel, &DMAController::write_channel);
}
/* Register global register handlers */
emulator->add_handler(0x1000E000, this, &DMAController::read_global, &DMAController::write_global);
}
uint32_t DMAController::read_channel(uint32_t addr)
{
assert((addr & 0xff) <= 0x80);
uint32_t id = (addr >> 8) & 0xff;
uint32_t channel = get_channel(id);
uint32_t offset = (addr >> 4) & 0xf;
auto ptr = (uint32_t*)&channels[channel] + offset;
fmt::print("[DMAC] Reading {:#x} from {} of channel {:d}\n", *ptr, REGS[offset], channel);
return *ptr;
}
void DMAController::write_channel(uint32_t addr, uint32_t data)
{
assert((addr & 0xff) <= 0x80);
uint32_t id = (addr >> 8) & 0xff;
uint32_t channel = get_channel(id);
uint32_t offset = (addr >> 4) & 0xf;
auto ptr = (uint32_t*)&channels[channel] + offset;
fmt::print("[DMAC] Writing {:#x} to {} of channel {:d}\n", data, REGS[offset], channel);
*ptr = data;
}
uint32_t DMAController::read_global(uint32_t addr)
{
assert(addr <= 0x1000E060);
uint32_t offset = (addr >> 4) & 0xf;
auto ptr = (uint32_t*)&globals + offset;
fmt::print("[DMAC] Reading {:#x} from {}\n", *ptr, GLOBALS[offset]);
return *ptr;
}
void DMAController::write_global(uint32_t addr, uint32_t data)
{
assert(addr <= 0x1000E060);
uint32_t offset = (addr >> 4) & 0xf;
auto ptr = (uint32_t*)&globals + offset;
fmt::print("[DMAC] Writing {:#x} to {}\n", data, GLOBALS[offset]);
if (offset == 1) /* D_STAT */
{
/* The lower bits are cleared while the upper ones are reversed */
globals.d_stat.clear &= ~(data & 0xffff);
globals.d_stat.reverse ^= (data >> 16);
}
else
{
*ptr = data;
}
}
}
\ No newline at end of file
#pragma once
#include <common/component.h>
namespace common
{
class Emulator;
}
namespace ee
{
union TagAddr
{
uint32_t value;
struct
{
uint32_t address : 30;
uint32_t mem_select : 1;
};
};
struct DMACChannel
{
uint32_t control;
uint32_t address;
uint32_t qword_count;
TagAddr tag_address;
TagAddr saved_tag_address[2];
uint32_t padding[2];
uint32_t scratchpad_address;
};
/* Writing to this is a pain */
union DSTAT
{
uint32_t value;
struct
{
uint32_t channel_irq : 10; /* Clear with 1 */
uint32_t : 3;
uint32_t dma_stall : 1; /* Clear with 1 */
uint32_t mfifo_empty : 1; /* Clear with 1 */
uint32_t bus_error : 1; /* Clear with 1 */
uint32_t channel_irq_mask : 10; /* Reverse with 1 */
uint32_t : 3;
uint32_t stall_irq_mask : 1; /* Reverse with 1 */
uint32_t mfifo_irq_mask : 1; /* Reverse with 1 */
uint32_t : 1;
};
/* If you notice above the lower 16bits are cleared when 1 is written to them
while the upper 16bits are reversed. So I'm making this struct to better
implement this behaviour */
struct
{
uint32_t clear : 16;
uint32_t reverse : 16;
};
};
struct DMACGlobals
{
uint32_t d_ctrl;
DSTAT d_stat;
uint32_t d_pcr;
uint32_t d_sqwc;
uint32_t d_rbsr;
uint32_t d_rbor;
uint32_t d_stadr;
};
struct DMAController : public common::Component
{
DMAController(common::Emulator* parent);
~DMAController() = default;
/* I/O communication */
uint32_t read_channel(uint32_t addr);
void write_channel(uint32_t addr, uint32_t data);
uint32_t read_global(uint32_t addr);
void write_global(uint32_t addr, uint32_t data);
private:
common::Emulator* emulator;
/* Channels / Global registers */
DMACChannel channels[10] = {};
DMACGlobals globals = {};
};
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
#include <cstring>
#include <fmt/color.h>
#ifndef NDEBUG
#ifdef NDEBUG
#define log(...) ((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