Merge pull request #3432 from stenzek/bba-tap-win

EXI: Refactor Windows BBA-TAP interface to a read thread, crash fixes, cleanups
This commit is contained in:
Pierre Bourdon 2016-02-26 12:46:20 +01:00
commit 1d07fee367
6 changed files with 163 additions and 155 deletions

View File

@ -3,12 +3,13 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <cctype> #include <cctype>
#include <cstdlib>
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
#include <random>
#include "Common/Network.h" #include "Common/Network.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Timer.h"
void GenerateMacAddress(const MACConsumer type, u8* mac) void GenerateMacAddress(const MACConsumer type, u8* mac)
{ {
@ -27,16 +28,12 @@ void GenerateMacAddress(const MACConsumer type, u8* mac)
break; break;
} }
srand((unsigned int)time(nullptr)); // Generate the 24-bit NIC-specific portion of the MAC address.
std::default_random_engine generator(Common::Timer::GetTimeMs());
u8 id[3] = std::uniform_int_distribution<int> distribution(0x00, 0xFF);
{ mac[3] = static_cast<u8>(distribution(generator));
(u8)rand(), mac[4] = static_cast<u8>(distribution(generator));
(u8)rand(), mac[5] = static_cast<u8>(distribution(generator));
(u8)rand()
};
memcpy(&mac[3], id, 3);
} }
std::string MacAddressToString(const u8* mac) std::string MacAddressToString(const u8* mac)

View File

@ -23,10 +23,8 @@ bool CEXIETHERNET::Activate()
return false; return false;
} }
readEnabled.store(false);
INFO_LOG(SP1, "BBA initialized."); INFO_LOG(SP1, "BBA initialized.");
return true; return RecvInit();
} }
void CEXIETHERNET::Deactivate() void CEXIETHERNET::Deactivate()
@ -34,7 +32,8 @@ void CEXIETHERNET::Deactivate()
close(fd); close(fd);
fd = -1; fd = -1;
readEnabled.store(false); readEnabled.Clear();
readThreadShutdown.Set();
if (readThread.joinable()) if (readThread.joinable())
readThread.join(); readThread.join();
} }
@ -64,11 +63,8 @@ bool CEXIETHERNET::SendFrame(u8* frame, u32 size)
static void ReadThreadHandler(CEXIETHERNET* self) static void ReadThreadHandler(CEXIETHERNET* self)
{ {
while (true) while (!self->readThreadShutdown.IsSet())
{ {
if (self->fd < 0)
return;
fd_set rfds; fd_set rfds;
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(self->fd, &rfds); FD_SET(self->fd, &rfds);
@ -79,14 +75,14 @@ static void ReadThreadHandler(CEXIETHERNET* self)
if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
continue; continue;
int readBytes = read(self->fd, self->mRecvBuffer, BBA_RECV_SIZE); int readBytes = read(self->fd, self->mRecvBuffer.get(), BBA_RECV_SIZE);
if (readBytes < 0) if (readBytes < 0)
{ {
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes); ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
} }
else if (self->readEnabled.load()) else if (self->readEnabled.IsSet())
{ {
INFO_LOG(SP1, "Read data: %s", ArrayToString(self->mRecvBuffer, readBytes, 0x10).c_str()); INFO_LOG(SP1, "Read data: %s", ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str());
self->mRecvBufferLength = readBytes; self->mRecvBufferLength = readBytes;
self->RecvHandlePacket(); self->RecvHandlePacket();
} }
@ -99,16 +95,12 @@ bool CEXIETHERNET::RecvInit()
return true; return true;
} }
bool CEXIETHERNET::RecvStart() void CEXIETHERNET::RecvStart()
{ {
if (!readThread.joinable()) readEnabled.Set();
RecvInit();
readEnabled.store(true);
return true;
} }
void CEXIETHERNET::RecvStop() void CEXIETHERNET::RecvStop()
{ {
readEnabled.store(false); readEnabled.Clear();
} }

View File

@ -68,10 +68,8 @@ bool CEXIETHERNET::Activate()
} }
ioctl(fd, TUNSETNOCSUM, 1); ioctl(fd, TUNSETNOCSUM, 1);
readEnabled.store(false);
INFO_LOG(SP1, "BBA initialized with associated tap %s", ifr.ifr_name); INFO_LOG(SP1, "BBA initialized with associated tap %s", ifr.ifr_name);
return true; return RecvInit();
#else #else
NOTIMPLEMENTED("Activate"); NOTIMPLEMENTED("Activate");
return false; return false;
@ -84,7 +82,8 @@ void CEXIETHERNET::Deactivate()
close(fd); close(fd);
fd = -1; fd = -1;
readEnabled.store(false); readEnabled.Clear();
readThreadShutdown.Set();
if (readThread.joinable()) if (readThread.joinable())
readThread.join(); readThread.join();
#else #else
@ -126,11 +125,8 @@ bool CEXIETHERNET::SendFrame(u8* frame, u32 size)
static void ReadThreadHandler(CEXIETHERNET* self) static void ReadThreadHandler(CEXIETHERNET* self)
{ {
while (true) while (!self->readThreadShutdown.IsSet())
{ {
if (self->fd < 0)
return;
fd_set rfds; fd_set rfds;
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(self->fd, &rfds); FD_SET(self->fd, &rfds);
@ -141,14 +137,14 @@ static void ReadThreadHandler(CEXIETHERNET* self)
if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0) if (select(self->fd + 1, &rfds, nullptr, nullptr, &timeout) <= 0)
continue; continue;
int readBytes = read(self->fd, self->mRecvBuffer, BBA_RECV_SIZE); int readBytes = read(self->fd, self->mRecvBuffer.get(), BBA_RECV_SIZE);
if (readBytes < 0) if (readBytes < 0)
{ {
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes); ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
} }
else if (self->readEnabled.load()) else if (self->readEnabled.IsSet())
{ {
INFO_LOG(SP1, "Read data: %s", ArrayToString(self->mRecvBuffer, readBytes, 0x10).c_str()); INFO_LOG(SP1, "Read data: %s", ArrayToString(self->mRecvBuffer.get(), readBytes, 0x10).c_str());
self->mRecvBufferLength = readBytes; self->mRecvBufferLength = readBytes;
self->RecvHandlePacket(); self->RecvHandlePacket();
} }
@ -166,24 +162,19 @@ bool CEXIETHERNET::RecvInit()
#endif #endif
} }
bool CEXIETHERNET::RecvStart() void CEXIETHERNET::RecvStart()
{ {
#ifdef __linux__ #ifdef __linux__
if (!readThread.joinable()) readEnabled.Set();
RecvInit();
readEnabled.store(true);
return true;
#else #else
NOTIMPLEMENTED("RecvStart"); NOTIMPLEMENTED("RecvStart");
return false;
#endif #endif
} }
void CEXIETHERNET::RecvStop() void CEXIETHERNET::RecvStop()
{ {
#ifdef __linux__ #ifdef __linux__
readEnabled.store(false); readEnabled.Clear();
#else #else
NOTIMPLEMENTED("RecvStop"); NOTIMPLEMENTED("RecvStop");
#endif #endif

View File

@ -2,6 +2,7 @@
// Licensed under GPLv2+ // Licensed under GPLv2+
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "Common/Assert.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
@ -222,7 +223,14 @@ bool CEXIETHERNET::Activate()
return false; return false;
} }
return true; /* initialize read/write events */
mReadOverlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
mWriteOverlapped.hEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
if (mReadOverlapped.hEvent == nullptr || mWriteOverlapped.hEvent == nullptr)
return false;
mWriteBuffer.reserve(1518);
return RecvInit();
} }
void CEXIETHERNET::Deactivate() void CEXIETHERNET::Deactivate()
@ -230,10 +238,24 @@ void CEXIETHERNET::Deactivate()
if (!IsActivated()) if (!IsActivated())
return; return;
RecvStop(); // Signal read thread to exit.
readEnabled.Clear();
readThreadShutdown.Set();
// Cancel any outstanding requests from both this thread (writes), and the read thread.
CancelIoEx(mHAdapter, nullptr);
// Wait for read thread to exit.
if (readThread.joinable())
readThread.join();
// Clean-up handles
CloseHandle(mReadOverlapped.hEvent);
CloseHandle(mWriteOverlapped.hEvent);
CloseHandle(mHAdapter); CloseHandle(mHAdapter);
mHAdapter = INVALID_HANDLE_VALUE; mHAdapter = INVALID_HANDLE_VALUE;
memset(&mReadOverlapped, 0, sizeof(mReadOverlapped));
memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
} }
bool CEXIETHERNET::IsActivated() bool CEXIETHERNET::IsActivated()
@ -241,101 +263,103 @@ bool CEXIETHERNET::IsActivated()
return mHAdapter != INVALID_HANDLE_VALUE; return mHAdapter != INVALID_HANDLE_VALUE;
} }
bool CEXIETHERNET::SendFrame(u8 *frame, u32 size) static void ReadThreadHandler(CEXIETHERNET* self)
{ {
DEBUG_LOG(SP1, "SendFrame %x\n%s", while (!self->readThreadShutdown.IsSet())
size, ArrayToString(frame, size, 0x10).c_str());
OVERLAPPED overlap;
ZeroMemory(&overlap, sizeof(overlap));
// WriteFile will always return false because the TAP handle is async
WriteFile(mHAdapter, frame, size, nullptr, &overlap);
DWORD res = GetLastError();
if (res != ERROR_IO_PENDING)
{ {
ERROR_LOG(SP1, "Failed to send packet with error 0x%X", res); DWORD transferred;
// Read from TAP into internal buffer.
if (ReadFile(self->mHAdapter, self->mRecvBuffer.get(), BBA_RECV_SIZE, &transferred, &self->mReadOverlapped))
{
// Returning immediately is not likely to happen, but if so, reset the event state manually.
ResetEvent(self->mReadOverlapped.hEvent);
}
else
{
// IO should be pending.
if (GetLastError() != ERROR_IO_PENDING)
{
ERROR_LOG(SP1, "ReadFile failed (err=0x%X)", GetLastError());
continue;
}
// Block until the read completes.
if (!GetOverlappedResult(self->mHAdapter, &self->mReadOverlapped, &transferred, TRUE))
{
// If CancelIO was called, we should exit (the flag will be set).
if (GetLastError() == ERROR_OPERATION_ABORTED)
continue;
// Something else went wrong.
ERROR_LOG(SP1, "GetOverlappedResult failed (err=0x%X)", GetLastError());
continue;
}
}
// Copy to BBA buffer, and fire interrupt if enabled.
DEBUG_LOG(SP1, "Received %u bytes\n: %s", transferred, ArrayToString(self->mRecvBuffer.get(), transferred, 0x10).c_str());
if (self->readEnabled.IsSet())
{
self->mRecvBufferLength = transferred;
self->RecvHandlePacket();
}
}
}
bool CEXIETHERNET::SendFrame(u8* frame, u32 size)
{
DEBUG_LOG(SP1, "SendFrame %u bytes:\n%s", size, ArrayToString(frame, size, 0x10).c_str());
// Check for a background write. We can't issue another one until this one has completed.
DWORD transferred;
if (mWritePending)
{
// Wait for previous write to complete.
if (!GetOverlappedResult(mHAdapter, &mWriteOverlapped, &transferred, TRUE))
ERROR_LOG(SP1, "GetOverlappedResult failed (err=0x%X)", GetLastError());
}
// Copy to write buffer.
mWriteBuffer.resize(size);
memcpy(mWriteBuffer.data(), frame, size);
mWritePending = true;
// Queue async write.
if (WriteFile(mHAdapter, mWriteBuffer.data(), size, &transferred, &mWriteOverlapped))
{
// Returning immediately is not likely to happen, but if so, reset the event state manually.
ResetEvent(mWriteOverlapped.hEvent);
}
else
{
// IO should be pending.
if (GetLastError() != ERROR_IO_PENDING)
{
ERROR_LOG(SP1, "WriteFile failed (err=0x%X)", GetLastError());
ResetEvent(mWriteOverlapped.hEvent);
mWritePending = false;
return false;
}
} }
// Always report the packet as being sent successfully, even though it might be a lie // Always report the packet as being sent successfully, even though it might be a lie
SendComplete(); SendComplete();
return true; return true;
} }
VOID CALLBACK CEXIETHERNET::ReadWaitCallback(PVOID lpParameter, BOOLEAN TimerFired)
{
CEXIETHERNET* self = (CEXIETHERNET*)lpParameter;
GetOverlappedResult(self->mHAdapter, &self->mReadOverlapped,
(LPDWORD)&self->mRecvBufferLength, false);
self->RecvHandlePacket();
}
bool CEXIETHERNET::RecvInit() bool CEXIETHERNET::RecvInit()
{ {
// Set up recv event readThread = std::thread(ReadThreadHandler, this);
if ((mHRecvEvent = CreateEvent(nullptr, false, false, nullptr)) == nullptr)
{
ERROR_LOG(SP1, "Failed to create recv event:%x", GetLastError());
return false;
}
ZeroMemory(&mReadOverlapped, sizeof(mReadOverlapped));
RegisterWaitForSingleObject(&mHReadWait, mHRecvEvent, ReadWaitCallback,
this, INFINITE, WT_EXECUTEDEFAULT);
mReadOverlapped.hEvent = mHRecvEvent;
return true; return true;
} }
bool CEXIETHERNET::RecvStart() void CEXIETHERNET::RecvStart()
{ {
if (!IsActivated()) readEnabled.Set();
return false;
if (mHRecvEvent == INVALID_HANDLE_VALUE)
RecvInit();
DWORD res = ReadFile(mHAdapter, mRecvBuffer, BBA_RECV_SIZE,
(LPDWORD)&mRecvBufferLength, &mReadOverlapped);
if (res)
{
// Since the read is synchronous here, complete immediately
RecvHandlePacket();
return true;
}
else
{
DWORD err = GetLastError();
if (err == ERROR_IO_PENDING)
{
return true;
}
// Unexpected error
ERROR_LOG(SP1, "Failed to recieve packet with error 0x%X", err);
return false;
}
} }
void CEXIETHERNET::RecvStop() void CEXIETHERNET::RecvStop()
{ {
if (!IsActivated()) readEnabled.Clear();
return;
UnregisterWaitEx(mHReadWait, INVALID_HANDLE_VALUE);
if (mHRecvEvent != INVALID_HANDLE_VALUE)
{
CloseHandle(mHRecvEvent);
mHRecvEvent = INVALID_HANDLE_VALUE;
}
} }

View File

@ -20,10 +20,10 @@
CEXIETHERNET::CEXIETHERNET() CEXIETHERNET::CEXIETHERNET()
{ {
tx_fifo = new u8[1518]; tx_fifo = std::make_unique<u8[]>(BBA_TXFIFO_SIZE);
mBbaMem = new u8[BBA_MEM_SIZE]; mBbaMem = std::make_unique<u8[]>(BBA_MEM_SIZE);
mRecvBuffer = new u8[BBA_RECV_SIZE]; mRecvBuffer = std::make_unique<u8[]>(BBA_RECV_SIZE);
mRecvBufferLength = 0; mRecvBufferLength = 0;
MXHardReset(); MXHardReset();
@ -47,8 +47,9 @@ CEXIETHERNET::CEXIETHERNET()
#if defined(_WIN32) #if defined(_WIN32)
mHAdapter = INVALID_HANDLE_VALUE; mHAdapter = INVALID_HANDLE_VALUE;
mHRecvEvent = INVALID_HANDLE_VALUE; memset(&mReadOverlapped, 0, sizeof(mReadOverlapped));
mHReadWait = INVALID_HANDLE_VALUE; memset(&mWriteOverlapped, 0, sizeof(mWriteOverlapped));
mWritePending = false;
#elif defined(__linux__) || defined(__APPLE__) #elif defined(__linux__) || defined(__APPLE__)
fd = -1; fd = -1;
#endif #endif
@ -57,10 +58,6 @@ CEXIETHERNET::CEXIETHERNET()
CEXIETHERNET::~CEXIETHERNET() CEXIETHERNET::~CEXIETHERNET()
{ {
Deactivate(); Deactivate();
delete[] tx_fifo;
delete[] mBbaMem;
delete[] mRecvBuffer;
} }
void CEXIETHERNET::SetCS(int cs) void CEXIETHERNET::SetCS(int cs)
@ -205,9 +202,8 @@ void CEXIETHERNET::DMARead(u32 addr, u32 size)
void CEXIETHERNET::DoState(PointerWrap &p) void CEXIETHERNET::DoState(PointerWrap &p)
{ {
p.Do(mBbaMem); p.DoArray(tx_fifo.get(), BBA_TXFIFO_SIZE);
// TODO ... the rest... p.DoArray(mBbaMem.get(), BBA_MEM_SIZE);
ERROR_LOG(SP1, "CEXIETHERNET::DoState not implemented!");
} }
bool CEXIETHERNET::IsMXCommand(u32 const data) bool CEXIETHERNET::IsMXCommand(u32 const data)
@ -298,7 +294,7 @@ const char* CEXIETHERNET::GetRegisterName() const
void CEXIETHERNET::MXHardReset() void CEXIETHERNET::MXHardReset()
{ {
memset(mBbaMem, 0, BBA_MEM_SIZE); memset(mBbaMem.get(), 0, BBA_MEM_SIZE);
mBbaMem[BBA_NCRB] = NCRB_PR; mBbaMem[BBA_NCRB] = NCRB_PR;
mBbaMem[BBA_NWAYC] = NWAYC_LTE | NWAYC_ANE; mBbaMem[BBA_NWAYC] = NWAYC_LTE | NWAYC_ANE;
@ -384,7 +380,7 @@ void CEXIETHERNET::DirectFIFOWrite(u8 *data, u32 size)
u16 *tx_fifo_count = (u16 *)&mBbaMem[BBA_TXFIFOCNT]; u16 *tx_fifo_count = (u16 *)&mBbaMem[BBA_TXFIFOCNT];
memcpy(tx_fifo + *tx_fifo_count, data, size); memcpy(tx_fifo.get() + *tx_fifo_count, data, size);
*tx_fifo_count += size; *tx_fifo_count += size;
// TODO: not sure this mask is correct. // TODO: not sure this mask is correct.
@ -395,7 +391,7 @@ void CEXIETHERNET::DirectFIFOWrite(u8 *data, u32 size)
void CEXIETHERNET::SendFromDirectFIFO() void CEXIETHERNET::SendFromDirectFIFO()
{ {
SendFrame(tx_fifo, *(u16 *)&mBbaMem[BBA_TXFIFOCNT]); SendFrame(tx_fifo.get(), *(u16 *)&mBbaMem[BBA_TXFIFOCNT]);
} }
void CEXIETHERNET::SendFromPacketBuffer() void CEXIETHERNET::SendFromPacketBuffer()
@ -452,9 +448,9 @@ inline bool CEXIETHERNET::RecvMACFilter()
// Unicast? // Unicast?
if ((mRecvBuffer[0] & 0x01) == 0) if ((mRecvBuffer[0] & 0x01) == 0)
{ {
return memcmp(mRecvBuffer, &mBbaMem[BBA_NAFR_PAR0], 6) == 0; return memcmp(mRecvBuffer.get(), &mBbaMem[BBA_NAFR_PAR0], 6) == 0;
} }
else if (memcmp(mRecvBuffer, broadcast, 6) == 0) else if (memcmp(mRecvBuffer.get(), broadcast, 6) == 0)
{ {
// Accept broadcast? // Accept broadcast?
return !!(mBbaMem[BBA_NCRB] & NCRB_AB); return !!(mBbaMem[BBA_NCRB] & NCRB_AB);
@ -467,7 +463,7 @@ inline bool CEXIETHERNET::RecvMACFilter()
else else
{ {
// Lookup the dest eth address in the hashmap // Lookup the dest eth address in the hashmap
u16 index = HashIndex(mRecvBuffer); u16 index = HashIndex(mRecvBuffer.get());
return !!(mBbaMem[BBA_NAFR_MAR0 + index / 8] & (1 << (index % 8))); return !!(mBbaMem[BBA_NAFR_MAR0 + index / 8] & (1 << (index % 8)));
} }
} }

View File

@ -6,11 +6,13 @@
#include <atomic> #include <atomic>
#include <thread> #include <thread>
#include <vector>
#ifdef _WIN32 #ifdef _WIN32
#include <Windows.h> #include <Windows.h>
#endif #endif
#include "Common/Flag.h"
#include "Core/HW/EXI_Device.h" #include "Core/HW/EXI_Device.h"
class PointerWrap; class PointerWrap;
@ -157,7 +159,8 @@ enum
{ {
BBA_NUM_PAGES = 0x10, BBA_NUM_PAGES = 0x10,
BBA_PAGE_SIZE = 0x100, BBA_PAGE_SIZE = 0x100,
BBA_MEM_SIZE = BBA_NUM_PAGES * BBA_PAGE_SIZE BBA_MEM_SIZE = BBA_NUM_PAGES * BBA_PAGE_SIZE,
BBA_TXFIFO_SIZE = 1518
}; };
enum enum
@ -307,8 +310,8 @@ public:
void inc_rwp(); void inc_rwp();
bool RecvHandlePacket(); bool RecvHandlePacket();
u8 *tx_fifo; std::unique_ptr<u8[]> mBbaMem;
u8 *mBbaMem; std::unique_ptr<u8[]> tx_fifo;
// TAP interface // TAP interface
bool Activate(); bool Activate();
@ -316,21 +319,26 @@ public:
bool IsActivated(); bool IsActivated();
bool SendFrame(u8 *frame, u32 size); bool SendFrame(u8 *frame, u32 size);
bool RecvInit(); bool RecvInit();
bool RecvStart(); void RecvStart();
void RecvStop(); void RecvStop();
u8 *mRecvBuffer; std::unique_ptr<u8[]> mRecvBuffer;
u32 mRecvBufferLength; u32 mRecvBufferLength;
#if defined(_WIN32) #if defined(_WIN32)
HANDLE mHAdapter, mHRecvEvent, mHReadWait; HANDLE mHAdapter;
DWORD mMtu;
OVERLAPPED mReadOverlapped; OVERLAPPED mReadOverlapped;
static VOID CALLBACK ReadWaitCallback(PVOID lpParameter, BOOLEAN TimerFired); OVERLAPPED mWriteOverlapped;
std::vector<u8> mWriteBuffer;
bool mWritePending;
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
int fd; int fd;
#endif
#if defined(WIN32) || defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
std::thread readThread; std::thread readThread;
std::atomic<bool> readEnabled; Common::Flag readEnabled;
Common::Flag readThreadShutdown;
#endif #endif
}; };