Merge branch 'sio2-sio0-impl' into master-merge-sio-impl

This commit is contained in:
Marco Satti 2018-07-28 22:51:13 +08:00
commit 6451b92db4
28 changed files with 627 additions and 233 deletions

View file

@ -229,9 +229,12 @@ set(COMMON_SRC_FILES
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio0/Sio0Registers.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/RSio2.cpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/RSio2.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2Packet.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2Registers.cpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2Registers.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2PortRegisters.cpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2PortRegisters.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2Ports.cpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Sio2/Sio2Ports.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Timers/IopTimersUnitRegisters.cpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Timers/IopTimersUnitRegisters.hpp"
"${CMAKE_SOURCE_DIR}/liborbum/src/Resources/Iop/Timers/IopTimersUnits.cpp"

View file

@ -268,6 +268,7 @@ struct Constants
struct SIO2
{
static constexpr int NUMBER_PORTS = 16;
static constexpr double SIO2_CLK_SPEED = 2000000.0; // 2 MHz. From here: https://en.wikipedia.org/wiki/PlayStation_2_technical_specifications.
};

View file

@ -54,7 +54,7 @@ void CEeTimers::tick_timer(const ControllerEvent::Type ce_type)
{
auto& r = core->get_resources();
// Update the timers which are set to count based on the type of event recieved.
// Update the timers which are set to count based on the type of event received.
for (auto& unit : r.ee.timers.units)
{
auto _lock = unit.mode->scope_lock();

View file

@ -343,26 +343,22 @@ std::optional<uptr> CIopCore::translate_address_data(const uptr virtual_address,
{
auto& r = core->get_resources();
#if 0 //defined(BUILD_DEBUG)
static const std::pair<uptr, uptr> DEBUG_VA_BREAKPOINT_RANGES[] =
{
std::make_pair(0xFFFFFFFF, 0xFFFFFFFF)
//std::make_pair(0xBF801040, 0xBF801050),
//std::make_pair(0xBF808200, 0xBF808300)
};
#if defined(BUILD_DEBUG)
static const std::pair<uptr, uptr> DEBUG_VA_BREAKPOINT_RANGES[] =
{
std::make_pair(0xBF808200, 0xBF80825C)};
for (const auto& range : DEBUG_VA_BREAKPOINT_RANGES)
{
if (virtual_address >= range.first && virtual_address <= range.second)
{
BOOST_LOG(Core::get_logger()) <<
boost::format("IOP MMU data breakpoint hit @ cycle = 0x%llX, PC = 0x%08X, VA = 0x%08X (%s).")
% DEBUG_LOOP_COUNTER
% r.iop.core.r3000.pc.read_uword()
% virtual_address
% ((rw_access == READ) ? "READ" : "WRITE");
}
}
for (const auto& range : DEBUG_VA_BREAKPOINT_RANGES)
{
if (virtual_address >= range.first && virtual_address <= range.second)
{
BOOST_LOG(Core::get_logger()) << boost::format("IOP MMU data breakpoint hit @ cycle = 0x%llX, PC = 0x%08X, VA = 0x%08X (%s).")
% DEBUG_LOOP_COUNTER
% r.iop.core.r3000.pc.read_uword()
% virtual_address
% ((rw_access == READ) ? "READ" : "WRITE");
}
}
#endif
// Check if a write is being performed with isolate cache turned on - don't run through the cache.

View file

@ -1,3 +1,5 @@
#include <boost/format.hpp>
#include "Controller/Iop/Sio0/CSio0.hpp"
#include "Core.hpp"
@ -45,10 +47,17 @@ int CSio0::time_to_ticks(const double time_us)
int CSio0::time_step(const int ticks_available)
{
auto& r = core->get_resources();
auto& ctrl = r.iop.sio0.ctrl;
// CTRL locked for entire duration.
auto _ctrl_lock = ctrl.scope_lock();
handle_reset_check();
handle_irq_check();
handle_transfer();
return ticks_available;
return 1;
}
void CSio0::handle_reset_check()
@ -56,17 +65,22 @@ void CSio0::handle_reset_check()
auto& r = core->get_resources();
auto& ctrl = r.iop.sio0.ctrl;
auto& stat = r.iop.sio0.stat;
auto& data = r.iop.sio0.data;
// Perform SIO0 reset (all relevant bits).
auto _stat_lock = stat.scope_lock();
// Perform SIO0 reset (all relevant bits and FIFO).
if (ctrl.extract_field(Sio0Register_Ctrl::SIO_RESET))
{
auto _ctrl_lock = ctrl.scope_lock();
auto _stat_lock = stat.scope_lock();
ctrl.insert_field(Sio0Register_Ctrl::RESET_IRQ, 0);
stat.insert_field(Sio0Register_Stat::TX_RDY, 1);
stat.insert_field(Sio0Register_Stat::TX_EMPTY, 1);
stat.insert_field(Sio0Register_Stat::IRQ, 0);
data.initialise();
// Now ready to transmit data.
// TODO: not totally correct...
stat.insert_field(Sio0Register_Stat::TX_RDY1, 1);
ctrl.insert_field(Sio0Register_Ctrl::SIO_RESET, 0);
}
}
@ -76,20 +90,54 @@ void CSio0::handle_irq_check()
auto& ctrl = r.iop.sio0.ctrl;
auto& stat = r.iop.sio0.stat;
auto _ctrl_lock = ctrl.scope_lock();
auto _stat_lock = stat.scope_lock();
// Reset IRQ bit if it has been acknowledged by IOP.
if (ctrl.extract_field(Sio0Register_Ctrl::RESET_IRQ))
{
auto _ctrl_lock = ctrl.scope_lock();
auto _stat_lock = stat.scope_lock();
stat.insert_field(Sio0Register_Stat::IRQ, 0);
ctrl.insert_field(Sio0Register_Ctrl::RESET_IRQ, 0);
}
// Raise IRQ on the ACK line going high (if enabled).
if (ctrl.extract_field(Sio0Register_Ctrl::ACK_INT_EN) && stat.extract_field(Sio0Register_Stat::DSR))
stat.insert_field(Sio0Register_Stat::IRQ, 1);
// Raise IOP INTC IRQ if requested.
if (stat.extract_field(Sio0Register_Stat::IRQ))
{
auto _lock = r.iop.intc.stat.scope_lock();
auto _stat_lock = r.iop.intc.stat.scope_lock();
r.iop.intc.stat.insert_field(IopIntcRegister_Stat::SIO0, 1);
}
}
}
void CSio0::handle_transfer()
{
auto& r = core->get_resources();
auto& command_queue = r.iop.sio0.data.command_queue;
auto& response_queue = r.iop.sio0.data.response_queue;
auto& stat = r.iop.sio0.stat;
// Sequential command/response action.
auto _stat_lock = stat.scope_lock();
// Check there is data to send first, TX_RDY2 changes depending on this.
if (!command_queue.has_read_available(1))
{
stat.insert_field(Sio0Register_Stat::TX_RDY2, 1);
return;
}
if (!response_queue.has_write_available(1))
return;
ubyte cmd = command_queue.read_ubyte();
BOOST_LOG(Core::get_logger()) << str(boost::format("~~~~~ SIO0 received cmd: 0x%02X") % cmd);
// TODO: properly implement, for now just send back 0x00 for all commands received.
response_queue.write_ubyte(0);
BOOST_LOG(Core::get_logger()) << "~~~~~ SIO0 sent response: 0x00 (not connected)";
stat.insert_field(Sio0Register_Stat::RX_NONEMPTY, 1);
}

View file

@ -12,6 +12,7 @@ public:
/// Converts a time duration into the number of ticks that would have occurred.
int time_to_ticks(const double time_us);
/// Steps through the SIO0 state.
int time_step(const int ticks_available);
/// Performs a reset if needed.
@ -19,4 +20,7 @@ public:
/// Handles raising IRQ's with the IOP and resetting the state.
void handle_irq_check();
/// Performs a send/receive of a command queued.
void handle_transfer();
};

View file

@ -1,10 +1,15 @@
#include <stdexcept>
#include <boost/format.hpp>
#include "Controller/Iop/Sio2/CSio2.hpp"
#include "Common/Constants.hpp"
#include "Core.hpp"
#include "Resources/RResources.hpp"
using Direction = Sio2Register_Ctrl::Direction;
CSio2::CSio2(Core* core) :
CController(core)
{
@ -50,50 +55,213 @@ int CSio2::time_step(const int ticks_available)
auto& r = core->get_resources();
auto& ctrl = r.iop.sio2.ctrl;
// Lock whole SIO2 operation while processing.
auto _ctrl_lock = ctrl.scope_lock();
if (ctrl.write_latch)
handle_ctrl_check();
handle_port_trasnfer();
return 1;
}
void CSio2::handle_ctrl_check()
{
auto& r = core->get_resources();
auto& ctrl = r.iop.sio2.ctrl;
if (ctrl.write_latch && !ctrl.transfer_started)
{
if (ctrl.extract_field(Sio2Register_Ctrl::RESET_DIR) > 0)
{
// Receive packet. Value should be 0x3BD.
if (ctrl.read_uword() != 0x3BD)
{
BOOST_LOG(Core::get_logger()) << boost::format("Careful, SIO2 ctrl recv value not normal: 0x%08X")
% ctrl.read_uword();
}
// Reset SIO2 port state and start transfer.
ctrl.transfer_port = 0;
ctrl.transfer_port_count = 0;
ctrl.transfer_started = true;
ctrl.transfer_direction = ctrl.get_direction();
// Clear the direction bit (no idea why... seems to be required).
ctrl.insert_field(Sio2Register_Ctrl::RESET_DIR, 0);
// Raise IOP IRQ.
auto _lock = r.iop.intc.stat.scope_lock();
r.iop.intc.stat.insert_field(IopIntcRegister_Stat::SIO2, 1);
}
else
{
// Send packet. Value should be 0x3BC.
if (ctrl.read_uword() != 0x3BC)
{
BOOST_LOG(Core::get_logger()) << boost::format("Careful, SIO2 ctrl send value not normal: 0x%08X")
% ctrl.read_uword();
}
// Perform SIO0 reset.
// Perform SIO0 reset before initiating transfer if in the TX direction.
if (ctrl.transfer_direction == Direction::TX)
handle_sio0_reset();
}
// Reset direction bits - they are write only (see register description).
ctrl.insert_field(Sio2Register_Ctrl::DIRECTION, 0);
ctrl.write_latch = false;
}
return 1;
}
void CSio2::handle_sio0_reset()
{
auto& r = core->get_resources();
auto& ctrl = r.iop.sio0.ctrl;
auto _lock = ctrl.scope_lock();
auto _lock = ctrl.scope_lock();
ctrl.insert_field(Sio0Register_Ctrl::SIO_RESET, 1);
}
void CSio2::handle_port_trasnfer()
{
auto& r = core->get_resources();
auto& ctrl = r.iop.sio2.ctrl;
auto& ports = r.iop.sio2.ports;
auto& port = ports[ctrl.transfer_port];
if (!ctrl.transfer_started)
return;
auto _port_ctrl3_lock = port.ctrl_3->scope_lock();
if (!port.ctrl_3->port_transfer_started)
{
static bool warned = false;
if (!port.ctrl_3->write_latch)
{
if (!warned)
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 TX/RX stalled: ctrl3 needs to be set by IOP first (write latch not set)";
warned = true;
}
return;
}
warned = false;
port.ctrl_3->port_transfer_started = true;
port.ctrl_3->write_latch = false;
BOOST_LOG(Core::get_logger()) << str(boost::format("~~~~~~ SIO2 TX port %d started") % ctrl.transfer_port);
}
switch (ctrl.transfer_direction)
{
case Direction::TX:
{
transfer_data_tx();
break;
}
case Direction::RX:
{
transfer_data_rx();
break;
}
default:
throw std::runtime_error("Could not determine SIO2 transfer direction.");
}
}
void CSio2::transfer_data_tx()
{
auto& r = core->get_resources();
auto& ctrl = r.iop.sio2.ctrl;
auto& ports = r.iop.sio2.ports;
auto& data_in = r.fifo_tosio2;
auto& sio0_data = r.iop.sio0.data;
auto& sio0_ctrl = r.iop.sio0.ctrl;
auto& sio0_stat = r.iop.sio0.stat;
auto& port = ports[ctrl.transfer_port];
size_t cmd_length = port.ctrl_3->extract_field(Sio2PortRegister_Ctrl3::CMDLEN);
// Send data to the SIO0.
if (ctrl.transfer_port_count != cmd_length)
{
if (!sio0_stat.extract_field(Sio0Register_Stat::TX_RDY1))
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 TX stalled: SIO0 stat.TX_RDY1 not set";
return;
}
if (!data_in.has_read_available(1))
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 TX stalled: no FIFO data";
return;
}
// If it's the first byte, perform some initialisation.
if (ctrl.transfer_port_count == 0)
{
// Set the SIO0 pad port first when the SIO0 is ready.
if (!sio0_stat.extract_field(Sio0Register_Stat::TX_RDY2))
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 TX stalled (init): SIO0 stat.TX_RDY2 not set";
return;
}
uhword sio0_padport = port.ctrl_3->extract_field(Sio2PortRegister_Ctrl3::PADPORT);
auto _sio0_ctrl_lock = sio0_ctrl.scope_lock();
sio0_ctrl.insert_field(Sio0Register_Ctrl::PORT, sio0_padport);
}
ubyte data = data_in.read_ubyte();
sio0_data.write_ubyte(data);
ctrl.transfer_port_count += 1;
}
// Finished with this port, move on to next.
if (ctrl.transfer_port_count == cmd_length)
{
BOOST_LOG(Core::get_logger()) << str(boost::format("~~~~~~ SIO2 TX port %d finished") % ctrl.transfer_port);
ctrl.transfer_port += 1;
ctrl.transfer_port_count = 0;
}
// All data sent, stop transfering.
if (ctrl.transfer_port == Constants::IOP::SIO2::NUMBER_PORTS)
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 TX all ports finished";
ctrl.transfer_started = false;
}
}
void CSio2::transfer_data_rx()
{
auto& r = core->get_resources();
auto& ctrl = r.iop.sio2.ctrl;
auto& ports = r.iop.sio2.ports;
auto& data_out = r.fifo_fromsio2;
auto& sio0_data = r.iop.sio0.data;
auto& sio0_stat = r.iop.sio0.stat;
auto& port = ports[ctrl.transfer_port];
size_t response_length = port.ctrl_3->extract_field(Sio2PortRegister_Ctrl3::BUFSZ);
// Receive data from the SIO0.
if (ctrl.transfer_port_count != response_length)
{
if (!data_out.has_write_available(1))
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 RX stalled: FIFO full";
return;
}
if (!sio0_stat.extract_field(Sio0Register_Stat::RX_NONEMPTY))
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 RX stalled: SIO0 stat.RX_NONEMPTY not set";
return;
}
ubyte data = sio0_data.read_ubyte();
data_out.write_ubyte(data);
ctrl.transfer_port_count += 1;
}
// Finished with this port, move on to next.
if (ctrl.transfer_port_count == response_length)
{
BOOST_LOG(Core::get_logger()) << str(boost::format("~~~~~~ SIO2 RX port %d finished") % ctrl.transfer_port);
ctrl.transfer_port += 1;
ctrl.transfer_port_count = 0;
}
// All data received, stop transfering, raise IRQ on RX finish.
if (ctrl.transfer_port == Constants::IOP::SIO2::NUMBER_PORTS)
{
BOOST_LOG(Core::get_logger()) << "~~~~~~ SIO2 RX all ports finished";
ctrl.transfer_started = false;
auto& intc_stat = r.iop.intc.stat;
auto _intc_lock = intc_stat.scope_lock();
intc_stat.insert_field(IopIntcRegister_Stat::SIO2, 1);
}
}

View file

@ -2,6 +2,15 @@
#include "Controller/CController.hpp"
/// SIO2 controller.
/// Basic operation from BIOS perspective:
/// 1. Set CTRL register value, for writing.
/// 2. For each port, set CTRL 1/2 registers (unused within emulator).
/// 3. For each port, set CTRL 3 registers.
/// 4. Send command data to the data_in FIFO.
/// 5. Turn on the SIO2 DMA channels for reading and writing.
/// 6. Set CTRL register value, for reading (based upon result from reading CTRL).
/// 7. Repeat.
class CSio2 : public CController
{
public:
@ -12,8 +21,22 @@ public:
/// Converts a time duration into the number of ticks that would have occurred.
int time_to_ticks(const double time_us);
/// Steps through the SIO2 state, initiating transfers with
/// the SIO0 for each port enabled.
int time_step(const int ticks_available);
void handle_ctrl_check();
/// Sets the SIO0 up for a reset.
void handle_sio0_reset();
void handle_port_trasnfer();
void transfer_data_tx();
void transfer_data_rx();
void transfer_set_next_port();
void transfer_stop(const bool raise_irq);
};

View file

@ -53,7 +53,7 @@ void CIopTimers::tick_timer(const ControllerEvent::Type ce_type)
{
auto& r = core->get_resources();
// Update the timers which are set to count based on the type of event recieved.
// Update the timers which are set to count based on the type of event received.
for (auto& unit : r.iop.timers.units)
{
auto _lock = unit->mode.scope_lock();

View file

@ -130,7 +130,7 @@ int CSpu2::transfer_data_adma_write(Spu2Core_Base& spu2_core)
{
// Set 'no data available' magic values for SPU2 registers (done on each try).
spu2_core.admas.set_adma_running(false);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::NEEDDATA, 1);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::DREQ, 1);
return 0;
}
@ -138,7 +138,7 @@ int CSpu2::transfer_data_adma_write(Spu2Core_Base& spu2_core)
uhword data;
spu2_core.dma_fifo_queue->read(reinterpret_cast<ubyte*>(&data), NUMBER_BYTES_IN_HWORD);
spu2_core.admas.set_adma_running(true);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::NEEDDATA, 0);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::DREQ, 0);
// Depending on the current transfer count, we are in the left or right sound channel data block (from SPU2-X/Dma.cpp).
// Data incoming is in a striped pattern with 0x100 hwords for the left channel, followed by 0x100 hwords for the right channel, repeated.
@ -186,14 +186,14 @@ int CSpu2::transfer_data_mdma_write(Spu2Core_Base& spu2_core)
if (!spu2_core.dma_fifo_queue->has_read_available(NUMBER_BYTES_IN_HWORD))
{
// Set 'no data available' magic values for SPU2 registers.
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::NEEDDATA, 1);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::DREQ, 1);
return 0;
}
// Read in data and set 'data available' magic values for SPU2 registers.
uhword data;
spu2_core.dma_fifo_queue->read(reinterpret_cast<ubyte*>(&data), NUMBER_BYTES_IN_HWORD);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::NEEDDATA, 0);
spu2_core.statx.insert_field(Spu2CoreRegister_Statx::DREQ, 0);
// Calculate write address. Make sure address is not outside 2MB limit (remember, we are addressing by hwords).
uhword tsal_addr_lo = spu2_core.tsal.read_uhword();

View file

@ -5,7 +5,7 @@
/// SPU2 system logic.
/// 2 steps involved:
/// 1. Check through the DMA channels, and send/recieve data as necessary.
/// 1. Check through the DMA channels, and send/receive data as necessary.
/// 2. Process audio and play samples at 44.1 or 48.0 kHz.
class CSpu2 : public CController
{

View file

@ -48,7 +48,7 @@ CoreOptions CoreOptions::make_default()
"",
"",
"",
200,
10,
4, //std::thread::hardware_concurrency() - 1,
1.0,

View file

@ -44,8 +44,8 @@ struct EeDmacConstants
"GIF",
"fromIPU",
"toIPU",
"SIF0",
"SIF1",
"SIF0 (from IOP)",
"SIF1 (to IOP)",
"SIF2",
"fromSPR",
"toSPR"};

View file

@ -15,8 +15,8 @@ struct IopDmacConstants
"OTClear",
"SPU2c1",
"DEV9",
"SIF0",
"SIF1",
"SIF0 (to EE)",
"SIF1 (from EE)",
"toSIO2",
"fromSIO2",
"Undefined"};

View file

@ -7,7 +7,7 @@
/// Responsible for communication with controllers and memory cards.
struct RSio0
{
SizedHwordRegister data;
Sio0Register_Data data; // Hybrid FIFO port - can read and write to this port simultaneously.
Sio0Register_Stat stat;
SizedHwordRegister mode;
Sio0Register_Ctrl ctrl;

View file

@ -10,4 +10,36 @@ void Sio0Register_Stat::byte_bus_write_uhword(const BusContext context, const us
{
auto _lock = scope_lock();
SizedHwordRegister::byte_bus_write_uhword(context, offset, value);
}
}
Sio0Register_Data::Sio0Register_Data() :
stat(nullptr)
{
}
void Sio0Register_Data::initialise()
{
command_queue.initialise();
response_queue.initialise();
}
ubyte Sio0Register_Data::read_ubyte()
{
ubyte data = response_queue.read_ubyte();
// Toggle the SIO0 FIFO empty bit.
auto _lock = stat->scope_lock();
stat->insert_field(Sio0Register_Stat::RX_NONEMPTY, response_queue.has_read_available(1) ? 1 : 0);
return data;
}
void Sio0Register_Data::write_ubyte(const ubyte data)
{
command_queue.write_ubyte(data);
// Toggle the SIO0 FIFO full bit. Also set unset the TX_RDY2 (ie: transfer not finished bit).
auto _lock = stat->scope_lock();
stat->insert_field(Sio0Register_Stat::TX_RDY1, command_queue.has_write_available(1) ? 1 : 0); // TODO: this is not right...
stat->insert_field(Sio0Register_Stat::TX_RDY2, 0);
}

View file

@ -1,40 +1,67 @@
#include "Common/Types/Bitfield.hpp"
#include "Common/Types/FifoQueue/DmaFifoQueue.hpp"
#include "Common/Types/Register/ByteRegister.hpp"
#include "Common/Types/Register/SizedHwordRegister.hpp"
#include "Common/Types/ScopeLock.hpp"
/// SIO0 ctrl register.
/// Needs to be accessed atomically by the SIO0 and SIO2 within emulator.
/// Assumed to be similar to the PSX SIO (pad/mc), consult no$psx docs.
class Sio0Register_Ctrl : public SizedHwordRegister, public ScopeLock
{
public:
static constexpr Bitfield TX_PERM = Bitfield(0, 1);
static constexpr Bitfield DTR = Bitfield(1, 1);
static constexpr Bitfield RX_PERM = Bitfield(2, 1);
static constexpr Bitfield BREAK = Bitfield(3, 1);
static constexpr Bitfield RESET_IRQ = Bitfield(4, 1); // This is like a "master controller has acknowledged IRQ" bit, don't be confused with SIO_RESET.
static constexpr Bitfield RTS = Bitfield(5, 1);
static constexpr Bitfield SIO_RESET = Bitfield(6, 1); // This is like a global SIO reset (see no$psx docs).
static constexpr Bitfield TXEN = Bitfield(0, 1); // Enable SIO0 transmit.
static constexpr Bitfield DTR = Bitfield(1, 1); // (Data terminal ready) SIO is ready to receive data from pad/mc selected by PORT.
static constexpr Bitfield RXEN = Bitfield(2, 1); // 0 means normal operation, RXEN is always enabled otherwise? (not sure what 1: "force" means)
static constexpr Bitfield RESET_IRQ = Bitfield(4, 1); // This is like a "master controller has acknowledged IRQ" bit, don't be confused with SIO_RESET.
static constexpr Bitfield SIO_RESET = Bitfield(6, 1); // This is like a global SIO reset (see no$psx docs).
static constexpr Bitfield RX_INT_MODE = Bitfield(8, 2); // Receive buffer interrupt mode, used with bit 11.
static constexpr Bitfield TX_INT_EN = Bitfield(10, 1); // Transmit interrupt enable.
static constexpr Bitfield RX_INT_EN = Bitfield(11, 1); // receive interrupt enable, interrupt when RX_INT_MODE bytes have been received.
static constexpr Bitfield ACK_INT_EN = Bitfield(12, 1); // Pad/mc acknowledge line interrupt enable (used with STAT.DSR).
static constexpr Bitfield PORT = Bitfield(13, 1); // Currently selected port (0/1).
/// Scope locked bus writes.
void byte_bus_write_uhword(const BusContext context, const usize offset, const uhword value) override;
};
/// SIO0 ctrl register.
/// Needs to be accessed atomically by the SIO2 within emulator.
/// SIO0 stat register.
/// Needs to be accessed atomically by the SIO0 and SIO2 within emulator.
/// Assumed to be similar to the PSX SIO (pad/mc), consult no$psx docs.
class Sio0Register_Stat : public SizedHwordRegister, public ScopeLock
{
public:
static constexpr Bitfield TX_RDY = Bitfield(0, 1);
static constexpr Bitfield RX_RDY = Bitfield(1, 1);
static constexpr Bitfield TX_EMPTY = Bitfield(2, 1);
static constexpr Bitfield TX_RDY1 = Bitfield(0, 1);
static constexpr Bitfield RX_NONEMPTY = Bitfield(1, 1); // RX FIFO non-empty.
static constexpr Bitfield TX_RDY2 = Bitfield(2, 1);
static constexpr Bitfield PARITY_ERR = Bitfield(3, 1);
static constexpr Bitfield RX_OVERRUN = Bitfield(4, 1);
static constexpr Bitfield FRAMING_ERR = Bitfield(5, 1);
static constexpr Bitfield SYNC_DETECT = Bitfield(6, 1);
static constexpr Bitfield DSR = Bitfield(7, 1);
static constexpr Bitfield CTS = Bitfield(8, 1);
static constexpr Bitfield IRQ = Bitfield(9, 1);
static constexpr Bitfield DSR = Bitfield(7, 1); // (Data set ready) Acknowledge input line level by pad/mc.
static constexpr Bitfield IRQ = Bitfield(9, 1); // IRQ line, setting this causes an interrupt to fire.
/// Scope locked bus writes.
void byte_bus_write_uhword(const BusContext context, const usize offset, const uhword value) override;
};
};
/// SIO0 data "register".
/// This is a hybrid FIFO port, where writing and reading access 2 different
/// FIFO queues. Tx direction means from SIO2 to SIO0, Rx direction means from
/// SIO0 to SIO2.
class Sio0Register_Data : public ByteRegister
{
public:
Sio0Register_Data();
void initialise() override;
ubyte read_ubyte() override;
void write_ubyte(const ubyte value) override;
/// FIFO queues.
/// These queues are meant to be accessed directly from the SIO0 only, for
/// all other components, this is meant to be accessed as a register.
DmaFifoQueue<> command_queue;
DmaFifoQueue<> response_queue;
/// Reference to the SIO0 stat register, needed to change status bits
/// depending on the different FIFO queue states (tx full/rx empty).
Sio0Register_Stat* stat;
};

View file

@ -1,6 +1,7 @@
#include "Resources/Iop/Sio2/RSio2.hpp"
RSio2::RSio2() :
recv1(0x1D100),
recv2(0xF, true)
{
}

View file

@ -1,7 +1,9 @@
#pragma once
#include "Common/Constants.hpp"
#include "Common/Types/FifoQueue/DmaFifoQueue.hpp"
#include "Common/Types/Register/SizedWordRegister.hpp"
#include "Resources/Iop/Sio2/Sio2Ports.hpp"
#include "Resources/Iop/Sio2/Sio2Registers.hpp"
/// SIO2 resources.
@ -12,37 +14,28 @@ struct RSio2
{
RSio2();
DmaFifoQueue<> data_fifo; // Fifo queue used for sending and receiving data (can change direction).
/// SIO2 ports (16 total).
Sio2Port_Full port_0;
Sio2Port_Full port_1;
Sio2Port_Full port_2;
Sio2Port_Full port_3;
Sio2Port_Slim port_4;
Sio2Port_Slim port_5;
Sio2Port_Slim port_6;
Sio2Port_Slim port_7;
Sio2Port_Slim port_8;
Sio2Port_Slim port_9;
Sio2Port_Slim port_a;
Sio2Port_Slim port_b;
Sio2Port_Slim port_c;
Sio2Port_Slim port_d;
Sio2Port_Slim port_e;
Sio2Port_Slim port_f;
Sio2Port ports[Constants::IOP::SIO2::NUMBER_PORTS];
SizedWordRegister port0_ctrl3; // TODO: figure out these names properly.
SizedWordRegister port1_ctrl3;
SizedWordRegister port2_ctrl3;
SizedWordRegister port3_ctrl3;
SizedWordRegister port4_ctrl3;
SizedWordRegister port5_ctrl3;
SizedWordRegister port6_ctrl3;
SizedWordRegister port7_ctrl3;
SizedWordRegister port8_ctrl3;
SizedWordRegister port9_ctrl3;
SizedWordRegister porta_ctrl3;
SizedWordRegister portb_ctrl3;
SizedWordRegister portc_ctrl3;
SizedWordRegister portd_ctrl3;
SizedWordRegister porte_ctrl3;
SizedWordRegister portf_ctrl3;
SizedWordRegister port0_ctrl1;
SizedWordRegister port0_ctrl2;
SizedWordRegister port1_ctrl1;
SizedWordRegister port1_ctrl2;
SizedWordRegister port2_ctrl1;
SizedWordRegister port2_ctrl2;
SizedWordRegister port3_ctrl1;
SizedWordRegister port3_ctrl2;
Sio2Register_Data data_in;
Sio2Register_Data data_out;
/// Common registers.
Sio2Register_Ctrl ctrl;
SizedWordRegister recv1;
SizedWordRegister recv1; // TODO: for now, returns device unplugged magic value (0x1D100).
SizedWordRegister recv2; // Constant 0xF value.
SizedWordRegister recv3;
SizedWordRegister register_8278;

View file

@ -1,35 +0,0 @@
#pragma once
/*
Describes a DMA argument type within a SIO2 packet.
For both in and out arguments.
TODO: document properly after I have figured it out.
*/
// struct SIO2DmaArgument_t
// {
// uword mAddress;
// size_t mSize;
// size_t mCount;
// };
/*
Describes a SIO2 packet.
See IopSio2.h and also the PS2SDK: https://github.com/ps2dev/ps2sdk/blob/master/iop/system/sio2log/include/sio2man.h.
TODO: document properly after I have figured it out.
*/
// struct SIO2Packet_t
// {
// uword mRecieveValue0;
// uword mSendValues0[4];
// uword mSendValues1[4];
// uword mRecieveValue1;
// uword mSendValues2[16];
// uword mRecieveValue2;
// size_t mSendSize;
// size_t mRecieveSize;
// uptr mSendAddress;
// uptr mRecieveAddress;
// SIO2DmaArgument_t mInDmaArgument;
// SIO2DmaArgument_t mOutDmaArgument;
// };

View file

@ -0,0 +1,21 @@
#include "Resources/Iop/Sio2/Sio2PortRegisters.hpp"
#include "Core.hpp"
Sio2PortRegister_Ctrl3::Sio2PortRegister_Ctrl3() :
write_latch(false),
port_transfer_started(false)
{
}
void Sio2PortRegister_Ctrl3::byte_bus_write_uword(const BusContext context, const usize offset, const uword value)
{
auto _lock = scope_lock();
if (write_latch)
BOOST_LOG(Core::get_logger()) << "SIO2 CTRL3 write latch was already set - please check (might be ok)!";
write_uword(value);
write_latch = true;
}

View file

@ -0,0 +1,36 @@
#include "Common/Types/Bitfield.hpp"
#include "Common/Types/Register/SizedWordRegister.hpp"
#include "Common/Types/ScopeLock.hpp"
class Sio2PortRegister_Ctrl1 : public SizedWordRegister
{
public:
};
class Sio2PortRegister_Ctrl2 : public SizedWordRegister
{
public:
};
// Infomation used from PCSX2: Sio.cpp and IopSio2.cpp.
class Sio2PortRegister_Ctrl3 : public SizedWordRegister, public ScopeLock
{
public:
static constexpr Bitfield PADPORT = Bitfield(0, 1); // Port 0/1 that the SIO should be configured for.
static constexpr Bitfield CMDLEN = Bitfield(8, 9); // Pad/mc command length (send to SIO0 length)
static constexpr Bitfield BUFSZ = Bitfield(18, 9); // Pad/mc response length (receive from SIO0 length) (TODO: assumed to always interrupt after).
// Note that PCSX2 appears to not use this properly, so maybe this isn't correct.
Sio2PortRegister_Ctrl3();
/// Write latch, used to signal when a port transfer should start.
/// Set upon bus writes.
bool write_latch;
/// Port transfer status, used to signify if a transfer has started on this
/// port. Changed by the SIO2 controller.
bool port_transfer_started;
/// Scope locked bus writes.
void byte_bus_write_uword(const BusContext context, const usize offset, const uword value) override;
};

View file

@ -0,0 +1,8 @@
#include "Resources/Iop/Sio2/Sio2Ports.hpp"
Sio2Port::Sio2Port() :
ctrl_1(nullptr),
ctrl_2(nullptr),
ctrl_3(nullptr)
{
}

View file

@ -0,0 +1,28 @@
#include "Resources/Iop/Sio2/Sio2PortRegisters.hpp"
/// Describes an abstract SIO2 port.
class Sio2Port
{
public:
Sio2Port();
Sio2PortRegister_Ctrl1* ctrl_1;
Sio2PortRegister_Ctrl2* ctrl_2;
Sio2PortRegister_Ctrl3* ctrl_3;
};
/// Port that contains all 3 CTRL registers.
class Sio2Port_Full
{
public:
Sio2PortRegister_Ctrl1 ctrl_1;
Sio2PortRegister_Ctrl2 ctrl_2;
Sio2PortRegister_Ctrl3 ctrl_3;
};
/// Port that contains only 1 CTRL register (ctrl_3).
class Sio2Port_Slim
{
public:
Sio2PortRegister_Ctrl3 ctrl_3;
};

View file

@ -1,12 +1,35 @@
#include <boost/format.hpp>
#include "Resources/Iop/Sio2/Sio2Registers.hpp"
#include "Core.hpp"
using Direction = Sio2Register_Ctrl::Direction;
Sio2Register_Ctrl::Sio2Register_Ctrl() :
transfer_started(false),
transfer_port(0),
transfer_port_count(0),
write_latch(false)
{
}
Direction Sio2Register_Ctrl::get_direction()
{
auto value = extract_field(DIRECTION);
switch (value & 0xF)
{
case 0x1:
case 0xD:
return Direction::RX;
case 0xC:
return Direction::TX;
default:
throw std::runtime_error(str(boost::format("Unknown SIO2 ctrl value: 0x%08X.") % value));
}
}
void Sio2Register_Ctrl::byte_bus_write_uword(const BusContext context, const usize offset, const uword value)
{
auto _lock = scope_lock();
@ -18,23 +41,3 @@ void Sio2Register_Ctrl::byte_bus_write_uword(const BusContext context, const usi
write_latch = true;
}
Sio2Register_Data::Sio2Register_Data() :
data_fifo(nullptr)
{
}
void Sio2Register_Data::initialise()
{
data_fifo->initialise();
}
ubyte Sio2Register_Data::read_ubyte()
{
return data_fifo->read_ubyte();
}
void Sio2Register_Data::write_ubyte(const ubyte value)
{
data_fifo->write_ubyte(value);
}

View file

@ -6,14 +6,23 @@
#include "Common/Types/ScopeLock.hpp"
/// SIO2 CTRL Register.
/// RESET_DIR is a reset SIO2 or SIO flag in TX/RX direction (?).
/// This seems like a bit of magic to me, no documentation really.
/// Looking into the bios, it changes between OR'ing 0xC or 0x1 with 0x3BC, which works with what PCSX2 says.
/// (ie: 0xC OR'd has no effect, 0x1 OR'd makes it 0x3BD.)
/// Bits 0, 2, 3 appear to be write only that toggle the send or receive mode.
/// Not sure about bit 1, but assuming it is also a write only bit (not used
/// elsewhere). If bits 2 & 3 are set (ie: byte 0xC written), this is TX
/// (to SIO0) mode. If bit 0 is set (ie: byte 0x1 or 0xD written), this is RX
/// (from SIO0) mode. 0xC, 0x1 and 0xD are the normal values written by the IOP.
class Sio2Register_Ctrl : public SizedWordRegister, public ScopeLock
{
public:
static constexpr Bitfield RESET_DIR = Bitfield(0, 1);
/// SIO2 transfer direction to/from SIO0.
enum class Direction
{
TX,
RX,
};
static constexpr Bitfield DIRECTION = Bitfield(0, 4); // Direction bits - not exactly sure what every bit means.
// One of the bits is probably the reset SIO0 bit.
Sio2Register_Ctrl();
@ -21,24 +30,15 @@ public:
/// Sets the write latch on bus write, throws error when it has already been set.
void byte_bus_write_uword(const BusContext context, const usize offset, const uword value) override;
/// Write latch.
/// Set to true on write, cleared by the system logic when the command has been processed.
/// Extracts the set direction from the register.
Direction get_direction();
/// Current transfer status to/from SIO0.
bool transfer_started;
size_t transfer_port;
size_t transfer_port_count;
Direction transfer_direction;
/// Write latch, set to true on bus write, cleared by the controller.
bool write_latch;
};
/// Data fifo port register.
/// Used as an interface by the SIO2 to transmit data.
class Sio2Register_Data : public ByteRegister
{
public:
Sio2Register_Data();
void initialise() override;
/// Reads and writes to the data fifo queue.
ubyte read_ubyte() override;
void write_ubyte(const ubyte value) override;
/// Reference to the data fifo queue.
DmaFifoQueue<>* data_fifo;
};

View file

@ -1642,32 +1642,32 @@ void initialise_iop(RResources* r)
r->iop.bus.map(0x1F80104C, &r->iop.sio0.ctrl);
// SIO2 Registers.
r->iop.bus.map(0x1F808200, &r->iop.sio2.port0_ctrl3);
r->iop.bus.map(0x1F808204, &r->iop.sio2.port1_ctrl3);
r->iop.bus.map(0x1F808208, &r->iop.sio2.port2_ctrl3);
r->iop.bus.map(0x1F80820C, &r->iop.sio2.port3_ctrl3);
r->iop.bus.map(0x1F808210, &r->iop.sio2.port4_ctrl3);
r->iop.bus.map(0x1F808214, &r->iop.sio2.port5_ctrl3);
r->iop.bus.map(0x1F808218, &r->iop.sio2.port6_ctrl3);
r->iop.bus.map(0x1F80821C, &r->iop.sio2.port7_ctrl3);
r->iop.bus.map(0x1F808220, &r->iop.sio2.port8_ctrl3);
r->iop.bus.map(0x1F808224, &r->iop.sio2.port9_ctrl3);
r->iop.bus.map(0x1F808228, &r->iop.sio2.porta_ctrl3);
r->iop.bus.map(0x1F80822C, &r->iop.sio2.portb_ctrl3);
r->iop.bus.map(0x1F808230, &r->iop.sio2.portc_ctrl3);
r->iop.bus.map(0x1F808234, &r->iop.sio2.portd_ctrl3);
r->iop.bus.map(0x1F808238, &r->iop.sio2.porte_ctrl3);
r->iop.bus.map(0x1F80823C, &r->iop.sio2.portf_ctrl3);
r->iop.bus.map(0x1F808240, &r->iop.sio2.port0_ctrl1);
r->iop.bus.map(0x1F808244, &r->iop.sio2.port0_ctrl2);
r->iop.bus.map(0x1F808248, &r->iop.sio2.port1_ctrl1);
r->iop.bus.map(0x1F80824C, &r->iop.sio2.port1_ctrl2);
r->iop.bus.map(0x1F808250, &r->iop.sio2.port2_ctrl1);
r->iop.bus.map(0x1F808254, &r->iop.sio2.port2_ctrl2);
r->iop.bus.map(0x1F808258, &r->iop.sio2.port3_ctrl1);
r->iop.bus.map(0x1F80825C, &r->iop.sio2.port3_ctrl2);
r->iop.bus.map(0x1F808260, &r->iop.sio2.data_in);
r->iop.bus.map(0x1F808264, &r->iop.sio2.data_out);
r->iop.bus.map(0x1F808200, &r->iop.sio2.port_0.ctrl_3);
r->iop.bus.map(0x1F808204, &r->iop.sio2.port_1.ctrl_3);
r->iop.bus.map(0x1F808208, &r->iop.sio2.port_2.ctrl_3);
r->iop.bus.map(0x1F80820C, &r->iop.sio2.port_3.ctrl_3);
r->iop.bus.map(0x1F808210, &r->iop.sio2.port_4.ctrl_3);
r->iop.bus.map(0x1F808214, &r->iop.sio2.port_5.ctrl_3);
r->iop.bus.map(0x1F808218, &r->iop.sio2.port_6.ctrl_3);
r->iop.bus.map(0x1F80821C, &r->iop.sio2.port_7.ctrl_3);
r->iop.bus.map(0x1F808220, &r->iop.sio2.port_8.ctrl_3);
r->iop.bus.map(0x1F808224, &r->iop.sio2.port_9.ctrl_3);
r->iop.bus.map(0x1F808228, &r->iop.sio2.port_a.ctrl_3);
r->iop.bus.map(0x1F80822C, &r->iop.sio2.port_b.ctrl_3);
r->iop.bus.map(0x1F808230, &r->iop.sio2.port_c.ctrl_3);
r->iop.bus.map(0x1F808234, &r->iop.sio2.port_d.ctrl_3);
r->iop.bus.map(0x1F808238, &r->iop.sio2.port_e.ctrl_3);
r->iop.bus.map(0x1F80823C, &r->iop.sio2.port_f.ctrl_3);
r->iop.bus.map(0x1F808240, &r->iop.sio2.port_0.ctrl_1);
r->iop.bus.map(0x1F808244, &r->iop.sio2.port_0.ctrl_2);
r->iop.bus.map(0x1F808248, &r->iop.sio2.port_1.ctrl_1);
r->iop.bus.map(0x1F80824C, &r->iop.sio2.port_1.ctrl_2);
r->iop.bus.map(0x1F808250, &r->iop.sio2.port_2.ctrl_1);
r->iop.bus.map(0x1F808254, &r->iop.sio2.port_2.ctrl_2);
r->iop.bus.map(0x1F808258, &r->iop.sio2.port_3.ctrl_1);
r->iop.bus.map(0x1F80825C, &r->iop.sio2.port_3.ctrl_2);
r->iop.bus.map(0x1F808260, &r->fifo_tosio2);
r->iop.bus.map(0x1F808264, &r->fifo_fromsio2);
r->iop.bus.map(0x1F808268, &r->iop.sio2.ctrl);
r->iop.bus.map(0x1F80826C, &r->iop.sio2.recv1);
r->iop.bus.map(0x1F808270, &r->iop.sio2.recv2);
@ -1725,8 +1725,39 @@ void initialise_iop_timers(RResources* r)
void initialise_iop_sio2(RResources* r)
{
r->iop.sio2.data_in.data_fifo = &r->iop.sio2.data_fifo;
r->iop.sio2.data_out.data_fifo = &r->iop.sio2.data_fifo;
r->iop.sio2.ports[0].ctrl_1 = &r->iop.sio2.port_0.ctrl_1;
r->iop.sio2.ports[0].ctrl_2 = &r->iop.sio2.port_0.ctrl_2;
r->iop.sio2.ports[0].ctrl_3 = &r->iop.sio2.port_0.ctrl_3;
r->iop.sio2.ports[1].ctrl_1 = &r->iop.sio2.port_1.ctrl_1;
r->iop.sio2.ports[1].ctrl_2 = &r->iop.sio2.port_1.ctrl_2;
r->iop.sio2.ports[1].ctrl_3 = &r->iop.sio2.port_1.ctrl_3;
r->iop.sio2.ports[2].ctrl_1 = &r->iop.sio2.port_2.ctrl_1;
r->iop.sio2.ports[2].ctrl_2 = &r->iop.sio2.port_2.ctrl_2;
r->iop.sio2.ports[2].ctrl_3 = &r->iop.sio2.port_2.ctrl_3;
r->iop.sio2.ports[3].ctrl_1 = &r->iop.sio2.port_3.ctrl_1;
r->iop.sio2.ports[3].ctrl_2 = &r->iop.sio2.port_3.ctrl_2;
r->iop.sio2.ports[3].ctrl_3 = &r->iop.sio2.port_3.ctrl_3;
r->iop.sio2.ports[4].ctrl_3 = &r->iop.sio2.port_4.ctrl_3;
r->iop.sio2.ports[5].ctrl_3 = &r->iop.sio2.port_5.ctrl_3;
r->iop.sio2.ports[6].ctrl_3 = &r->iop.sio2.port_6.ctrl_3;
r->iop.sio2.ports[7].ctrl_3 = &r->iop.sio2.port_7.ctrl_3;
r->iop.sio2.ports[8].ctrl_3 = &r->iop.sio2.port_8.ctrl_3;
r->iop.sio2.ports[9].ctrl_3 = &r->iop.sio2.port_9.ctrl_3;
r->iop.sio2.ports[10].ctrl_3 = &r->iop.sio2.port_a.ctrl_3;
r->iop.sio2.ports[11].ctrl_3 = &r->iop.sio2.port_b.ctrl_3;
r->iop.sio2.ports[12].ctrl_3 = &r->iop.sio2.port_c.ctrl_3;
r->iop.sio2.ports[13].ctrl_3 = &r->iop.sio2.port_d.ctrl_3;
r->iop.sio2.ports[14].ctrl_3 = &r->iop.sio2.port_e.ctrl_3;
r->iop.sio2.ports[15].ctrl_3 = &r->iop.sio2.port_f.ctrl_3;
}
void initialise_iop_sio0(RResources* r)
{
r->iop.sio0.data.stat = &r->iop.sio0.stat;
}
void initialise_ee_core(RResources* r)
@ -1785,6 +1816,7 @@ void initialise_resources(const std::unique_ptr<RResources>& r)
initialise_iop_dmac(r.get());
initialise_iop_timers(r.get());
initialise_iop_sio2(r.get());
initialise_iop_sio0(r.get());
initialise_cdvd(r.get());

View file

@ -96,11 +96,16 @@ public:
};
/// SPU2 Core STATX register.
/// TODO: find out more, not much to go on.
/// Lots of good info can be found by looking at where the IOP BIOS SPU2 debug
/// strings are used. BIOS will detect the SPU2 has timed out if many of these
/// bits are not set correctly, which it tries for 256 (0x100) / 3841 (0xF01)
/// times depending on the bit.
class Spu2CoreRegister_Statx : public SizedHwordRegister
{
public:
static constexpr Bitfield NEEDDATA = Bitfield(7, 1);
static constexpr Bitfield UNKNOWN4 = Bitfield(4, 2); // Unknown, referenced from BIOS.
static constexpr Bitfield DREQ = Bitfield(7, 1); // Request for more data (confirmed with BIOS).
static constexpr Bitfield WRDY_M = Bitfield(10, 1); // (write?) ready flag? BIOS waits for this to be 0, otherwise it says SPU2 has timed out.
};
/// SPU2 Core ADMAS register.