mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-10 14:39:01 +01:00
Preliminary BBA support for Linux. Assumes that a TAP interface named 'Dolphin' exists and is preconfigured. Contains some dirty hacks.
This commit is contained in:
parent
3cb6e4a864
commit
9a3dd778cb
@ -15,305 +15,165 @@
|
|||||||
// Official SVN repository and contact information can be found at
|
// Official SVN repository and contact information can be found at
|
||||||
// http://code.google.com/p/dolphin-emu/
|
// http://code.google.com/p/dolphin-emu/
|
||||||
|
|
||||||
#include "../Memmap.h"
|
#include "StringUtil.h"
|
||||||
#include "../EXI_Device.h"
|
#include "../EXI_Device.h"
|
||||||
#include "../EXI_DeviceEthernet.h"
|
#include "../EXI_DeviceEthernet.h"
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netinet/in.h>
|
#ifdef __linux__
|
||||||
#include <stdio.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <net/if.h>
|
|
||||||
#ifdef __linux__
|
|
||||||
#include <linux/if_tun.h>
|
#include <linux/if_tun.h>
|
||||||
#else
|
#include <net/if.h>
|
||||||
#include <net/if_tun.h>
|
#include <netinet/in.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#endif
|
#endif
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
int fd = -1;
|
|
||||||
std::thread cpuThread;
|
|
||||||
|
|
||||||
bool CEXIETHERNET::deactivate()
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
fd = -1;
|
|
||||||
cpuThread.join();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::isActivated()
|
#define NOTIMPLEMENTED(Name) \
|
||||||
{
|
NOTICE_LOG(SP1, "CEXIETHERNET::%s not implemented for your UNIX", Name);
|
||||||
return fd != -1 ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::activate()
|
bool CEXIETHERNET::Activate()
|
||||||
{
|
{
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
if(isActivated())
|
if (IsActivated())
|
||||||
return true;
|
return true;
|
||||||
if( (fd = open("/dev/net/tun", O_RDWR)) < 0)
|
|
||||||
|
// Assumes that there is a TAP device named "Dolphin" preconfigured for
|
||||||
|
// bridge/NAT/whatever the user wants it configured.
|
||||||
|
|
||||||
|
if ((fd = open("/dev/net/tun", O_RDWR)) < 0)
|
||||||
{
|
{
|
||||||
INFO_LOG(SP1, "Couldn't Open device\n");
|
ERROR_LOG(SP1, "Couldn't open /dev/net/tun, unable to init BBA");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
struct ifreq ifr;
|
|
||||||
|
|
||||||
int err;
|
struct ifreq ifr;
|
||||||
memset(&ifr, 0, sizeof(ifr));
|
memset(&ifr, 0, sizeof(ifr));
|
||||||
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE;
|
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE;
|
||||||
|
|
||||||
strncpy(ifr.ifr_name, "Dolphin", IFNAMSIZ);
|
strncpy(ifr.ifr_name, "Dolphin", IFNAMSIZ);
|
||||||
|
|
||||||
if( (err = ioctl(fd, TUNSETIFF, (void*) &ifr)) < 0)
|
int err;
|
||||||
|
if ((err = ioctl(fd, TUNSETIFF, (void*)&ifr)) < 0)
|
||||||
{
|
{
|
||||||
close(fd);
|
close(fd);
|
||||||
fd = -1;
|
fd = -1;
|
||||||
INFO_LOG(SP1, " Error with IOCTL: 0x%X\n", err);
|
ERROR_LOG(SP1, "TUNSETIFF failed: err=%d", err);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ioctl( fd, TUNSETNOCSUM, 1 );
|
ioctl(fd, TUNSETNOCSUM, 1);
|
||||||
/*int flags;
|
|
||||||
if ((flags = fcntl( fd, F_GETFL)) < 0)
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "getflags on tun device: %s", strerror (errno));
|
|
||||||
}
|
|
||||||
flags |= O_NONBLOCK;
|
|
||||||
if (fcntl( fd, F_SETFL, flags ) < 0)
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "set tun device flags: %s", strerror (errno));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
INFO_LOG(SP1, "Returned Socket name is: %s\n", ifr.ifr_name);
|
readEnabled = false;
|
||||||
system("brctl addif pan0 Dolphin");
|
|
||||||
system("ifconfig Dolphin 0.0.0.0 promisc up");
|
INFO_LOG(SP1, "BBA initialized with associated tap %s", ifr.ifr_name);
|
||||||
resume();
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("Activate");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIETHERNET::Deactivate()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
close(fd);
|
||||||
|
fd = -1;
|
||||||
|
|
||||||
|
// TODO: find a way to interrupt the read(2) in the readThread. Kill the
|
||||||
|
// thread maybe?
|
||||||
|
readEnabled = false;
|
||||||
|
if (readThread.joinable())
|
||||||
|
readThread.join();
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("Deactivate");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIETHERNET::IsActivated()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
return fd != -1 ? true : false;
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::CheckRecieved()
|
bool CEXIETHERNET::SendFrame(u8* frame, u32 size)
|
||||||
{
|
{
|
||||||
if(!isActivated())
|
#ifdef __linux__
|
||||||
|
WARN_LOG(SP1, "SendFrame %x\n%s", size, ArrayToString(frame, size, 0x10).c_str());
|
||||||
|
|
||||||
|
int writtenBytes = write(fd, frame, size);
|
||||||
|
if ((u32)writtenBytes != size)
|
||||||
|
{
|
||||||
|
ERROR_LOG(SP1, "SendFrame(): expected to write %d bytes, instead wrote %d",
|
||||||
|
size, writtenBytes);
|
||||||
return false;
|
return false;
|
||||||
int maxfd;
|
|
||||||
int retval;
|
|
||||||
struct timeval tv;
|
|
||||||
int timeout = 9999; // 3 seconds will kill him
|
|
||||||
fd_set mask;
|
|
||||||
|
|
||||||
/* Find the largest file descriptor */
|
|
||||||
maxfd = fd;
|
|
||||||
|
|
||||||
/* Check the file descriptors for available data */
|
|
||||||
errno = 0;
|
|
||||||
|
|
||||||
/* Set up the mask of file descriptors */
|
|
||||||
FD_ZERO(&mask);
|
|
||||||
|
|
||||||
FD_SET(fd, &mask);
|
|
||||||
|
|
||||||
/* Set up the timeout */
|
|
||||||
tv.tv_sec = timeout/1000;
|
|
||||||
tv.tv_usec = (timeout%1000)*1000;
|
|
||||||
|
|
||||||
/* Look! */
|
|
||||||
retval = select(maxfd+1, &mask, NULL, NULL, &tv);
|
|
||||||
|
|
||||||
/* Mark all file descriptors ready that have data available */
|
|
||||||
if ( retval > 0 ) {
|
|
||||||
if ( FD_ISSET(fd, &mask) )
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "\t\t\t\tWe have data!\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendComplete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("SendFrame");
|
||||||
return false;
|
return false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEXIETHERNET::resume()
|
void ReadThreadHandler(CEXIETHERNET* self)
|
||||||
{
|
{
|
||||||
if(!isActivated())
|
while (true)
|
||||||
return true;
|
|
||||||
INFO_LOG(SP1, "BBA resume\n");
|
|
||||||
if(mBbaMem[BBA_NCRA] & NCRA_SR) {
|
|
||||||
startRecv();
|
|
||||||
}
|
|
||||||
INFO_LOG(SP1, "BBA resume complete\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CpuThread(CEXIETHERNET *self)
|
|
||||||
{
|
|
||||||
while(1)
|
|
||||||
{
|
{
|
||||||
if(self->CheckRecieved())
|
if (self->fd < 0)
|
||||||
{
|
|
||||||
u8 B[1514];
|
|
||||||
self->mRecvBufferLength = read(fd, B, 1500);
|
|
||||||
//INFO_LOG(SP1, "read return of 0x%x\n", self->mRecvBufferLength);
|
|
||||||
if (self->mRecvBufferLength == 0xffffffff)
|
|
||||||
{
|
|
||||||
//Fail Boat
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if(self->mRecvBufferLength > 0)
|
|
||||||
{
|
|
||||||
//mRecvBuffer.write(B, BytesRead);
|
|
||||||
//strncat(mRecvBuffer.p(), B, BytesRead);
|
|
||||||
memcpy(self->mRecvBuffer, B, self->mRecvBufferLength);
|
|
||||||
}
|
|
||||||
else if(self->mRecvBufferLength == -1U)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "Unknown read return of 0x%x\n", self->mRecvBufferLength);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
INFO_LOG(SP1, "Received %d bytes of data\n", self->mRecvBufferLength);
|
|
||||||
self->mWaiting = false;
|
|
||||||
self->handleRecvdPacket();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
//sleep(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::startRecv()
|
int readBytes = read(self->fd, self->mRecvBuffer, BBA_RECV_SIZE);
|
||||||
{
|
if (readBytes < 0)
|
||||||
INFO_LOG(SP1, "Start Receive!\n");
|
|
||||||
//exit(0);
|
|
||||||
INFO_LOG(SP1, "startRecv... ");
|
|
||||||
if(mWaiting) {
|
|
||||||
INFO_LOG(SP1, "already waiting\n");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
cpuThread = std::thread(CpuThread, this);
|
|
||||||
mWaiting = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::sendPacket(u8 *etherpckt, int size)
|
|
||||||
{
|
|
||||||
if(!isActivated())
|
|
||||||
return false;
|
|
||||||
INFO_LOG(SP1, "Packet: 0x");
|
|
||||||
for(int a = 0; a < size; ++a)
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "%02X ", etherpckt[a]);
|
|
||||||
}
|
|
||||||
INFO_LOG(SP1, " : Size: %d\n", size);
|
|
||||||
int numBytesWrit = write(fd, etherpckt, size);
|
|
||||||
if(numBytesWrit != size)
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "BBA sendPacket %i only got %i bytes sent!errno: %d\n", size, numBytesWrit, errno);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
recordSendComplete();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CEXIETHERNET::handleRecvdPacket()
|
|
||||||
{
|
|
||||||
int rbwpp = mCbw.p_write() + CB_OFFSET; //read buffer write page pointer
|
|
||||||
u32 available_bytes_in_cb;
|
|
||||||
if(rbwpp < mRBRPP)
|
|
||||||
available_bytes_in_cb = mRBRPP - rbwpp;
|
|
||||||
else if(rbwpp == mRBRPP)
|
|
||||||
available_bytes_in_cb = mRBEmpty ? CB_SIZE : 0;
|
|
||||||
else //rbwpp > mRBRPP
|
|
||||||
available_bytes_in_cb = CB_SIZE - rbwpp + (mRBRPP - CB_OFFSET);
|
|
||||||
|
|
||||||
//DUMPWORD(rbwpp);
|
|
||||||
//DUMPWORD(mRBRPP);
|
|
||||||
//DUMPWORD(available_bytes_in_cb);
|
|
||||||
|
|
||||||
assert(available_bytes_in_cb <= CB_SIZE);
|
|
||||||
if(available_bytes_in_cb != CB_SIZE)//< mRecvBufferLength + SIZEOF_RECV_DESCRIPTOR)
|
|
||||||
return true;
|
|
||||||
cbwriteDescriptor(mRecvBufferLength);
|
|
||||||
mCbw.write(mRecvBuffer, mRecvBufferLength);
|
|
||||||
mCbw.align();
|
|
||||||
rbwpp = mCbw.p_write() + CB_OFFSET;
|
|
||||||
//DUMPWORD(rbwpp);
|
|
||||||
|
|
||||||
//mPacketsRcvd++;
|
|
||||||
mRecvBufferLength = 0;
|
|
||||||
|
|
||||||
if(mBbaMem[BBA_IMR] & INT_R)
|
|
||||||
{
|
|
||||||
if(!(mBbaMem[BBA_IR] & INT_R))
|
|
||||||
{
|
{
|
||||||
mBbaMem[BBA_IR] |= INT_R;
|
ERROR_LOG(SP1, "Failed to read from BBA, err=%d", readBytes);
|
||||||
INFO_LOG(SP1, "BBA Recv interrupt raised\n");
|
}
|
||||||
m_bInterruptSet = true;
|
else if (self->readEnabled)
|
||||||
|
{
|
||||||
|
// HACK: This usleep is there to avoid BBA buffer overflow. Has to
|
||||||
|
// be replaced by a better throttling support at some point.
|
||||||
|
usleep(1000);
|
||||||
|
WARN_LOG(SP1, "Read data: %s", ArrayToString(self->mRecvBuffer, readBytes, 0x10).c_str());
|
||||||
|
self->mRecvBufferLength = readBytes;
|
||||||
|
self->RecvHandlePacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mBbaMem[BBA_NCRA] & NCRA_SR)
|
|
||||||
{
|
|
||||||
startRecv();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
union bba_descr {
|
bool CEXIETHERNET::RecvInit()
|
||||||
struct { u32 next_packet_ptr:12, packet_len:12, status:8; };
|
|
||||||
u32 word;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool CEXIETHERNET::cbwriteDescriptor(u32 size)
|
|
||||||
{
|
{
|
||||||
if(size < SIZEOF_ETH_HEADER)
|
#ifdef __linux__
|
||||||
{
|
readThread = std::thread(ReadThreadHandler, this);
|
||||||
INFO_LOG(SP1, "Packet too small: %i bytes\n", size);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
size += SIZEOF_RECV_DESCRIPTOR; //The descriptor supposed to include the size of itself
|
|
||||||
|
|
||||||
//We should probably not implement wraparound here,
|
|
||||||
//since neither tmbinc, riptool.dol, or libogc does...
|
|
||||||
if(mCbw.p_write() + SIZEOF_RECV_DESCRIPTOR >= CB_SIZE)
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "The descriptor won't fit\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(size >= CB_SIZE)
|
|
||||||
{
|
|
||||||
INFO_LOG(SP1, "Packet too big: %i bytes\n", size);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bba_descr descr;
|
|
||||||
descr.word = 0;
|
|
||||||
descr.packet_len = size;
|
|
||||||
descr.status = 0;
|
|
||||||
u32 npp;
|
|
||||||
if(mCbw.p_write() + size < CB_SIZE)
|
|
||||||
{
|
|
||||||
npp = mCbw.p_write() + size + CB_OFFSET;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
npp = mCbw.p_write() + size + CB_OFFSET - CB_SIZE;
|
|
||||||
}
|
|
||||||
npp = (npp + 0xff) & ~0xff;
|
|
||||||
if(npp >= CB_SIZE + CB_OFFSET)
|
|
||||||
npp -= CB_SIZE;
|
|
||||||
descr.next_packet_ptr = npp >> 8;
|
|
||||||
//DWORD swapped = swapw(descr.word);
|
|
||||||
//next_packet_ptr:12, packet_len:12, status:8;
|
|
||||||
INFO_LOG(SP1, "Writing descriptor 0x%08X @ 0x%04lX: next 0x%03X len 0x%03X status 0x%02X\n",
|
|
||||||
descr.word, (unsigned long)mCbw.p_write() + CB_OFFSET, descr.next_packet_ptr,
|
|
||||||
descr.packet_len, descr.status);
|
|
||||||
mCbw.write(&descr.word, SIZEOF_RECV_DESCRIPTOR);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("RecvInit");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CEXIETHERNET::RecvStart()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
if (!readThread.joinable())
|
||||||
|
RecvInit();
|
||||||
|
|
||||||
|
readEnabled = true;
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("RecvStart");
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIETHERNET::RecvStop()
|
||||||
|
{
|
||||||
|
#ifdef __linux__
|
||||||
|
readEnabled = false;
|
||||||
|
#else
|
||||||
|
NOTIMPLEMENTED("RecvStop");
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,12 @@ CEXIETHERNET::CEXIETHERNET(const std::string& mac_addr)
|
|||||||
// hax .. fully established 100BASE-T link
|
// hax .. fully established 100BASE-T link
|
||||||
mBbaMem[BBA_NWAYS] = NWAYS_LS100 | NWAYS_LPNWAY | NWAYS_100TXF | NWAYS_ANCLPT;
|
mBbaMem[BBA_NWAYS] = NWAYS_LS100 | NWAYS_LPNWAY | NWAYS_100TXF | NWAYS_ANCLPT;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if defined(_WIN32)
|
||||||
mHAdapter = INVALID_HANDLE_VALUE;
|
mHAdapter = INVALID_HANDLE_VALUE;
|
||||||
mHRecvEvent = INVALID_HANDLE_VALUE;
|
mHRecvEvent = INVALID_HANDLE_VALUE;
|
||||||
mHReadWait = INVALID_HANDLE_VALUE;
|
mHReadWait = INVALID_HANDLE_VALUE;
|
||||||
|
#elif defined(__linux__)
|
||||||
|
fd = -1;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mRecvBufferLength = 0;
|
mRecvBufferLength = 0;
|
||||||
@ -484,6 +486,11 @@ void CEXIETHERNET::inc_rwp()
|
|||||||
|
|
||||||
bool CEXIETHERNET::RecvHandlePacket()
|
bool CEXIETHERNET::RecvHandlePacket()
|
||||||
{
|
{
|
||||||
|
u8 *write_ptr;
|
||||||
|
u8 *end_ptr;
|
||||||
|
u8 *read_ptr;
|
||||||
|
Descriptor *descriptor;
|
||||||
|
|
||||||
if (!RecvMACFilter())
|
if (!RecvMACFilter())
|
||||||
goto wait_for_next;
|
goto wait_for_next;
|
||||||
|
|
||||||
@ -498,11 +505,11 @@ bool CEXIETHERNET::RecvHandlePacket()
|
|||||||
PAGE_PTR(BBA_RWP),
|
PAGE_PTR(BBA_RWP),
|
||||||
PAGE_PTR(BBA_RHBP));
|
PAGE_PTR(BBA_RHBP));
|
||||||
|
|
||||||
u8 *write_ptr = PTR_FROM_PAGE_PTR(BBA_RWP);
|
write_ptr = PTR_FROM_PAGE_PTR(BBA_RWP);
|
||||||
u8 *end_ptr = PTR_FROM_PAGE_PTR(BBA_RHBP);
|
end_ptr = PTR_FROM_PAGE_PTR(BBA_RHBP);
|
||||||
u8 *read_ptr = PTR_FROM_PAGE_PTR(BBA_RRP);
|
read_ptr = PTR_FROM_PAGE_PTR(BBA_RRP);
|
||||||
|
|
||||||
Descriptor *descriptor = (Descriptor *)write_ptr;
|
descriptor = (Descriptor *)write_ptr;
|
||||||
//u8 *descriptor = write_ptr;
|
//u8 *descriptor = write_ptr;
|
||||||
write_ptr += 4;
|
write_ptr += 4;
|
||||||
|
|
||||||
|
@ -303,15 +303,18 @@ public:
|
|||||||
|
|
||||||
u8 *mRecvBuffer;
|
u8 *mRecvBuffer;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if defined(_WIN32)
|
||||||
HANDLE mHAdapter, mHRecvEvent, mHReadWait;
|
HANDLE mHAdapter, mHRecvEvent, mHReadWait;
|
||||||
DWORD mMtu;
|
DWORD mMtu;
|
||||||
OVERLAPPED mReadOverlapped;
|
OVERLAPPED mReadOverlapped;
|
||||||
DWORD mRecvBufferLength;
|
|
||||||
static VOID CALLBACK ReadWaitCallback(PVOID lpParameter, BOOLEAN TimerFired);
|
static VOID CALLBACK ReadWaitCallback(PVOID lpParameter, BOOLEAN TimerFired);
|
||||||
#else
|
#elif defined(__linux__)
|
||||||
u32 mRecvBufferLength;
|
int fd;
|
||||||
|
std::thread readThread;
|
||||||
|
volatile bool readEnabled;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
u32 mRecvBufferLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user