Skip to content

Commit

Permalink
SI: Overhaul SI request/response
Browse files Browse the repository at this point in the history
This commit overhauls the SI request/response architecture to that each of
those parts of the protocol actually take some amount of time. Events are
used to trigger the next stage of the protcol, and it's only hooked up for
commands that go via SerialInterface::RunSIBuffer.
  • Loading branch information
booto committed Jul 13, 2019
1 parent 62a2611 commit 2ad0a31
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 35 deletions.
325 changes: 291 additions & 34 deletions Source/Core/Core/HW/SI/SI.cpp
Expand Up @@ -210,6 +210,9 @@ union USIEXIClockCount
static CoreTiming::EventType* s_change_device_event;
static CoreTiming::EventType* s_tranfer_pending_event;

static CoreTiming::EventType* s_get_response_event[4];
static CoreTiming::EventType* s_complete_transfer_event[4];

// User-configured device type. possibly overridden by TAS/Netplay
static std::array<std::atomic<SIDevices>, MAX_SI_CHANNELS> s_desired_device_types;

Expand Down Expand Up @@ -242,12 +245,6 @@ static void SetNoResponse(u32 channel)
s_com_csr.COMERR = 1;
}

static void ChangeDeviceCallback(u64 user_data, s64 cycles_late)
{
// The purpose of this callback is to simply re-enable device changes.
s_channel[user_data].has_recent_device_change = false;
}

static void UpdateInterrupts()
{
// check if we have to update the RDSTINT flag
Expand Down Expand Up @@ -290,6 +287,274 @@ constexpr u32 ConvertSILengthField(u32 field)
return ((field - 1) & SI_XFER_LENGTH_MASK) + 1;
}

constexpr u32 SI_MICROSECONDS_PER_BIT = 4;
s32 EstimateTicksForXfer(size_t bit_count)
{
return static_cast<s32>(bit_count * SI_MICROSECONDS_PER_BIT * SystemTimers::GetTicksPerSecond() / 1000000);
}

constexpr u64 SI_TC_FLAG_CHAN_MASK = 0x0000000000000003ull;
constexpr u64 SI_TC_FLAG_CHAN0 = 0x0000000000000000ull;
constexpr u64 SI_TC_FLAG_CHAN1 = 0x0000000000000001ull;
constexpr u64 SI_TC_FLAG_CHAN2 = 0x0000000000000002ull;
constexpr u64 SI_TC_FLAG_CHAN3 = 0x0000000000000003ull;
constexpr u64 SI_TC_FLAG_TCINT = 0x0000000000000004ull;
constexpr u64 SI_TC_FLAG_RDST = 0x0000000000000008ull;
constexpr u64 SI_TC_FLAG_NOREP = 0x0000000000000010ull;
constexpr u64 SI_TC_FLAG_COLL = 0x0000000000000020ull;
constexpr u64 SI_TC_FLAG_OVRUN = 0x0000000000000040ull;
constexpr u64 SI_TC_FLAG_UNRUN = 0x0000000000000080ull;
constexpr u64 SI_TC_FLAG_POLL = 0x0000000000000100ull;

static void CompleteTransferHandler(u64 user_data, s64 cycles_late)
{
u32 channel = user_data & SI_TC_FLAG_CHAN_MASK;
u32 register_channel = s_com_csr.CHANNEL;
bool is_poll = (user_data & SI_TC_FLAG_POLL) == SI_TC_FLAG_POLL;
bool is_tcint = (user_data & SI_TC_FLAG_TCINT) == SI_TC_FLAG_TCINT;
bool is_rdst = (user_data & SI_TC_FLAG_RDST) == SI_TC_FLAG_RDST;

if (!is_poll & !s_com_csr.TSTART)
return;

if (!is_poll && (channel != register_channel))
{
ERROR_LOG(SERIALINTERFACE,
"SI::CompleteTransferHandler: is_poll: %u channel: %u register_channel: %u", is_poll,
channel, register_channel);
}

if (!is_poll && is_rdst)
{
ERROR_LOG(SERIALINTERFACE, "SI::CompleteTransferHandler: is_poll: %u is_rdst: %u", is_poll,
is_rdst);
}

// poll has new data
if (is_rdst)
{
switch (channel)
{
case SI_TC_FLAG_CHAN0:
s_status_reg.RDST0 = 1;
break;
case SI_TC_FLAG_CHAN1:
s_status_reg.RDST1 = 1;
break;
case SI_TC_FLAG_CHAN2:
s_status_reg.RDST2 = 1;
break;
case SI_TC_FLAG_CHAN3:
s_status_reg.RDST3 = 1;
break;
}
}

bool set_communication_error = false;
// no response
if (user_data & SI_TC_FLAG_NOREP)
{
switch (channel)
{
case SI_TC_FLAG_CHAN0:
s_status_reg.NOREP0 = 1;
break;
case SI_TC_FLAG_CHAN1:
s_status_reg.NOREP1 = 1;
break;
case SI_TC_FLAG_CHAN2:
s_status_reg.NOREP2 = 1;
break;
case SI_TC_FLAG_CHAN3:
s_status_reg.NOREP3 = 1;
break;
}
set_communication_error = true;
}

// collision
if (user_data & SI_TC_FLAG_COLL)
{
switch (channel)
{
case SI_TC_FLAG_CHAN0:
s_status_reg.COLL0 = 1;
break;
case SI_TC_FLAG_CHAN1:
s_status_reg.COLL1 = 1;
break;
case SI_TC_FLAG_CHAN2:
s_status_reg.COLL2 = 1;
break;
case SI_TC_FLAG_CHAN3:
s_status_reg.COLL3 = 1;
break;
}
set_communication_error = true;
}

// response overrun
if (user_data & SI_TC_FLAG_OVRUN)
{
switch (channel)
{
case SI_TC_FLAG_CHAN0:
s_status_reg.OVRUN0 = 1;
break;
case SI_TC_FLAG_CHAN1:
s_status_reg.OVRUN1 = 1;
break;
case SI_TC_FLAG_CHAN2:
s_status_reg.OVRUN2 = 1;
break;
case SI_TC_FLAG_CHAN3:
s_status_reg.OVRUN3 = 1;
break;
}
set_communication_error = true;
}

// response underrun
if (user_data & SI_TC_FLAG_UNRUN)
{
switch (channel)
{
case SI_TC_FLAG_CHAN0:
s_status_reg.UNRUN0 = 1;
break;
case SI_TC_FLAG_CHAN1:
s_status_reg.UNRUN1 = 1;
break;
case SI_TC_FLAG_CHAN2:
s_status_reg.UNRUN2 = 1;
break;
case SI_TC_FLAG_CHAN3:
s_status_reg.UNRUN3 = 1;
break;
}
set_communication_error = true;
}

if (is_poll) {
if (set_communication_error)
{
s_channel[channel].in_hi.ERRSTAT = 1;
s_channel[channel].in_hi.ERRLATCH = 1;
}
else
{
s_channel[channel].in_hi.ERRSTAT = 0;
}
}
else
{
s_com_csr.COMERR = 1;
}

if (is_tcint)
{
// This will update RDST interrupt as well
GenerateSIInterrupt(INT_TCINT);
}
s_com_csr.TSTART = 0;
}

static void GetResponseHandler(u64 user_data, s64 cycles_late)
{
u32 channel = user_data & SI_TC_FLAG_CHAN_MASK;
bool is_poll = (user_data & SI_TC_FLAG_POLL) == SI_TC_FLAG_POLL;

if (!is_poll & !s_com_csr.TSTART)
return;

u32 expected_response_length = 8;
if (!is_poll)
{
expected_response_length = ConvertSILengthField(s_com_csr.INLNGTH);
}
std::unique_ptr<ISIDevice>& device = s_channel[channel].device;

std::array<u8, 128> rcv_buffer;
size_t actual_response_length = device->RecvResponse(rcv_buffer.data(), rcv_buffer.size());

if (actual_response_length == 0)
{
user_data |= SI_TC_FLAG_NOREP;
// TODO:
// Investigate the timeout period for NOREP
CoreTiming::ScheduleEvent(EstimateTicksForXfer(1),
s_complete_transfer_event[s_com_csr.CHANNEL], user_data);
return;
}
if (is_poll && (actual_response_length != 0))
{
// copy up to 8 bytes, but don't overwrite existing data if the response is short
do
{
s_channel[channel].in_hi.INPUT0 = rcv_buffer[0] & 0x3f;
if (actual_response_length == 1)
break;
s_channel[channel].in_hi.INPUT1 = rcv_buffer[1];
if (actual_response_length == 2)
break;
s_channel[channel].in_hi.INPUT2 = rcv_buffer[2];
if (actual_response_length == 3)
break;
s_channel[channel].in_hi.INPUT3 = rcv_buffer[3];
if (actual_response_length == 4)
break;
s_channel[channel].in_lo.INPUT4 = rcv_buffer[4];
if (actual_response_length == 5)
break;
s_channel[channel].in_lo.INPUT5 = rcv_buffer[5];
if (actual_response_length == 6)
break;
s_channel[channel].in_lo.INPUT6 = rcv_buffer[6];
if (actual_response_length == 7)
break;
s_channel[channel].in_lo.INPUT7 = rcv_buffer[7];
} while (0);
user_data |= SI_TC_FLAG_RDST;
}
else
{
// copy up to s_si_buffer.size() bytes
std::copy(rcv_buffer.data(),
rcv_buffer.data() + std::min(actual_response_length, s_si_buffer.size()),
s_si_buffer.data());
}

if (expected_response_length != actual_response_length)
{
std::stringstream ss;
for (u8 b : device->GetRequestBuffer())
{
ss << std::hex << std::setw(2) << std::setfill('0') << (int)b << ' ';
}

ERROR_LOG(
SERIALINTERFACE,
"SI::GetResponseHandler: expected_response_length(%u) != actual_response_length(%u): request: %s",
expected_response_length, actual_response_length, ss.str().c_str());
if (expected_response_length < actual_response_length)
{
user_data |= SI_TC_FLAG_OVRUN;
}
else
{
user_data |= SI_TC_FLAG_UNRUN;
}
}
CoreTiming::ScheduleEvent(EstimateTicksForXfer(actual_response_length * 8 + 1),
s_complete_transfer_event[s_com_csr.CHANNEL], user_data | SI_TC_FLAG_TCINT);
}

static void ChangeDeviceCallback(u64 user_data, s64 cycles_late)
{
// The purpose of this callback is to simply re-enable device changes.
s_channel[user_data].has_recent_device_change = false;
}

static void RunSIBuffer(u64 user_data, s64 cycles_late)
{
if (s_com_csr.TSTART)
Expand All @@ -299,42 +564,21 @@ static void RunSIBuffer(u64 user_data, s64 cycles_late)
std::vector<u8> request_copy(s_si_buffer.data(), s_si_buffer.data() + request_length);

std::unique_ptr<ISIDevice>& device = s_channel[s_com_csr.CHANNEL].device;
u32 actual_response_length = device->RunBuffer(s_si_buffer.data(), request_length);
device->SendRequest(s_si_buffer.data(), request_length);

DEBUG_LOG(SERIALINTERFACE,
"RunSIBuffer chan: %d request_length: %u expected_response_length: %u "
"actual_response_length: %u",
s_com_csr.CHANNEL, request_length, expected_response_length, actual_response_length);
if (expected_response_length != actual_response_length)
{
std::stringstream ss;
for (u8 b : request_copy)
{
ss << std::hex << std::setw(2) << std::setfill('0') << (int)b << ' ';
}
ERROR_LOG(
SERIALINTERFACE,
"RunSIBuffer: expected_response_length(%u) != actual_response_length(%u): request: %s",
expected_response_length, actual_response_length, ss.str().c_str());
}
CoreTiming::ScheduleEvent(EstimateTicksForXfer(request_length * 8 + 1),
s_get_response_event[s_com_csr.CHANNEL], s_com_csr.CHANNEL);

DEBUG_LOG(SERIALINTERFACE,
"RunSIBuffer chan: %d request_length: %u expected_response_length: %u",
s_com_csr.CHANNEL, request_length, expected_response_length);
// TODO:
// 1) Wait a reasonable amount of time for the result to be available:
// Wait a reasonable amount of time for the result to be available:
// request is N bytes, ends with a stop bit
// response in M bytes, ends with a stop bit
// processing controller-side takes K us (investigate?)
// each bit takes 4us ([3us low/1us high] for a 0, [1us low/3us high] for a 1)
// time until response is available is at least: K + ((N*8 + 1) + (M*8 + 1)) * 4 us
// 2) Investigate the timeout period for NOREP0
if (actual_response_length != 0)
{
s_com_csr.TSTART = 0;
GenerateSIInterrupt(INT_TCINT);
}
else
{
CoreTiming::ScheduleEvent(device->TransferInterval() - cycles_late, s_tranfer_pending_event);
}
}
}

Expand Down Expand Up @@ -415,6 +659,19 @@ void Init()

s_change_device_event = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback);
s_tranfer_pending_event = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer);

s_get_response_event[0] = CoreTiming::RegisterEvent("SI_GetResponse0", GetResponseHandler);
s_complete_transfer_event[0] =
CoreTiming::RegisterEvent("SI_CompleteTransfer0", CompleteTransferHandler);
s_get_response_event[1] = CoreTiming::RegisterEvent("SI_GetResponse1", GetResponseHandler);
s_complete_transfer_event[1] =
CoreTiming::RegisterEvent("SI_CompleteTransfer1", CompleteTransferHandler);
s_get_response_event[2] = CoreTiming::RegisterEvent("SI_GetResponse2", GetResponseHandler);
s_complete_transfer_event[2] =
CoreTiming::RegisterEvent("SI_CompleteTransfer2", CompleteTransferHandler);
s_get_response_event[3] = CoreTiming::RegisterEvent("SI_GetResponse3", GetResponseHandler);
s_complete_transfer_event[3] =
CoreTiming::RegisterEvent("SI_CompleteTransfer3", CompleteTransferHandler);
}

void Shutdown()
Expand Down

0 comments on commit 2ad0a31

Please sign in to comment.