Make SO_POLL complete asynchronously

This commit is contained in:
nbouteme 2020-05-17 01:00:35 +02:00
parent a1a107dc08
commit b68ab560dc
5 changed files with 214 additions and 66 deletions

View File

@ -50,13 +50,6 @@
#include <unistd.h>
#endif
// WSAPoll doesn't support POLLPRI and POLLWRBAND flags
#ifdef _WIN32
#define UNSUPPORTED_WSAPOLL POLLPRI | POLLWRBAND
#else
#define UNSUPPORTED_WSAPOLL 0
#endif
namespace IOS::HLE::Device
{
enum SOResultCode : s32
@ -80,6 +73,12 @@ NetIPTop::~NetIPTop()
#endif
}
void NetIPTop::DoState(PointerWrap& p)
{
DoStateShared(p);
WiiSockMan::GetInstance().DoState(p);
}
static constexpr u32 inet_addr(u8 a, u8 b, u8 c, u8 d)
{
return (static_cast<u32>(a) << 24) | (static_cast<u32>(b) << 16) | (static_cast<u32>(c) << 8) | d;
@ -599,77 +598,45 @@ IPCCommandResult NetIPTop::HandleInetNToPRequest(const IOCtlRequest& request)
IPCCommandResult NetIPTop::HandlePollRequest(const IOCtlRequest& request)
{
// Map Wii/native poll events types
struct
WiiSockMan& sm = WiiSockMan::GetInstance();
if (!request.buffer_in || !request.buffer_out)
return GetDefaultReply(-SO_EINVAL);
// Negative timeout indicates wait forever
const s64 timeout = static_cast<s64>(Memory::Read_U64(request.buffer_in));
const u32 nfds = request.buffer_out_size / 0xc;
if (nfds == 0 || nfds > WII_SOCKET_FD_MAX)
{
int native;
int wii;
} mapping[] = {
{POLLRDNORM, 0x0001}, {POLLRDBAND, 0x0002}, {POLLPRI, 0x0004}, {POLLWRNORM, 0x0008},
{POLLWRBAND, 0x0010}, {POLLERR, 0x0020}, {POLLHUP, 0x0040}, {POLLNVAL, 0x0080},
};
u32 unknown = Memory::Read_U32(request.buffer_in);
u32 timeout = Memory::Read_U32(request.buffer_in + 4);
int nfds = request.buffer_out_size / 0xc;
if (nfds == 0)
ERROR_LOG(IOS_NET, "Hidden POLL");
ERROR_LOG(IOS_NET, "IOCTL_SO_POLL failed: Invalid array size %d, ret=%d", nfds, -SO_EINVAL);
return GetDefaultReply(-SO_EINVAL);
}
std::vector<pollfd_t> ufds(nfds);
for (int i = 0; i < nfds; ++i)
for (u32 i = 0; i < nfds; ++i)
{
s32 wii_fd = Memory::Read_U32(request.buffer_out + 0xc * i);
ufds[i].fd = WiiSockMan::GetInstance().GetHostSocket(wii_fd); // fd
int events = Memory::Read_U32(request.buffer_out + 0xc * i + 4); // events
ufds[i].revents = Memory::Read_U32(request.buffer_out + 0xc * i + 8); // revents
const s32 wii_fd = Memory::Read_U32(request.buffer_out + 0xc * i);
ufds[i].fd = sm.GetHostSocket(wii_fd); // fd
const int events = Memory::Read_U32(request.buffer_out + 0xc * i + 4); // events
ufds[i].revents = 0;
// Translate Wii to native events
int unhandled_events = events;
ufds[i].events = 0;
for (auto& map : mapping)
{
if (events & map.wii)
ufds[i].events |= map.native;
unhandled_events &= ~map.wii;
}
ufds[i].events = WiiSockMan::ConvertEvents(events, WiiSockMan::ConvertDirection::WiiToNative);
DEBUG_LOG(IOS_NET,
"IOCTL_SO_POLL(%d) "
"Sock: %08x, Unknown: %08x, Events: %08x, "
"Sock: %08x, Events: %08x, "
"NativeEvents: %08x",
i, wii_fd, unknown, events, ufds[i].events);
i, wii_fd, events, ufds[i].events);
// Do not pass return-only events to the native poll
ufds[i].events &= ~(POLLERR | POLLHUP | POLLNVAL | UNSUPPORTED_WSAPOLL);
if (unhandled_events)
ERROR_LOG(IOS_NET, "SO_POLL: unhandled Wii event types: %04x", unhandled_events);
}
int ret = poll(ufds.data(), nfds, timeout);
ret = WiiSockMan::GetNetErrorCode(ret, "SO_POLL", false);
for (int i = 0; i < nfds; ++i)
{
// Translate native to Wii events
int revents = 0;
for (auto& map : mapping)
{
if (ufds[i].revents & map.native)
revents |= map.wii;
}
// No need to change fd or events as they are input only.
// Memory::Write_U32(ufds[i].fd, request.buffer_out + 0xc*i); //fd
// Memory::Write_U32(events, request.buffer_out + 0xc*i + 4); //events
Memory::Write_U32(revents, request.buffer_out + 0xc * i + 8); // revents
DEBUG_LOG(IOS_NET, "IOCTL_SO_POLL socket %d wevents %08X events %08X revents %08X", i, revents,
ufds[i].events, ufds[i].revents);
}
return GetDefaultReply(ret);
// Prevents blocking emulation on a blocking poll
sm.AddPollCommand({request.address, request.buffer_out, std::move(ufds), timeout});
return GetNoReply();
}
IPCCommandResult NetIPTop::HandleGetHostByNameRequest(const IOCtlRequest& request)

View File

@ -13,6 +13,13 @@
#include <ws2tcpip.h>
#endif
// WSAPoll doesn't support POLLPRI and POLLWRBAND flags
#ifdef _WIN32
constexpr int UNSUPPORTED_WSAPOLL = POLLPRI | POLLWRBAND;
#else
constexpr int UNSUPPORTED_WSAPOLL = 0;
#endif
namespace IOS::HLE
{
enum NET_IOCTL
@ -62,6 +69,7 @@ public:
NetIPTop(Kernel& ios, const std::string& device_name);
virtual ~NetIPTop();
void DoState(PointerWrap& p) override;
IPCCommandResult IOCtl(const IOCtlRequest& request) override;
IPCCommandResult IOCtlV(const IOCtlVRequest& request) override;

View File

@ -6,6 +6,8 @@
#include "Core/IOS/Network/Socket.h"
#include <algorithm>
#include <numeric>
#include <mbedtls/error.h>
#ifndef _WIN32
#include <arpa/inet.h>
@ -697,7 +699,7 @@ void WiiSockMan::Update()
while (socket_iter != end_socks)
{
WiiSocket& sock = socket_iter->second;
const WiiSocket& sock = socket_iter->second;
if (sock.IsValid())
{
FD_SET(sock.fd, &read_fds);
@ -712,7 +714,8 @@ void WiiSockMan::Update()
socket_iter = WiiSockets.erase(socket_iter);
}
}
s32 ret = select(nfds, &read_fds, &write_fds, &except_fds, &t);
const s32 ret = select(nfds, &read_fds, &write_fds, &except_fds, &t);
if (ret >= 0)
{
@ -730,6 +733,87 @@ void WiiSockMan::Update()
elem.second.Update(false, false, false);
}
}
UpdatePollCommands();
}
void WiiSockMan::UpdatePollCommands()
{
static constexpr int error_event = (POLLHUP | POLLERR);
if (pending_polls.empty())
return;
const auto now = std::chrono::high_resolution_clock::now();
const auto elapsed_d = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_time);
const auto elapsed = elapsed_d.count();
last_time = now;
for (auto& pcmd : pending_polls)
{
// Don't touch negative timeouts
if (pcmd.timeout > 0)
pcmd.timeout = std::max<s64>(0, pcmd.timeout - elapsed);
}
pending_polls.erase(
std::remove_if(
pending_polls.begin(), pending_polls.end(),
[this](auto& pcmd) {
const auto request = Request(pcmd.request_addr);
auto& pfds = pcmd.wii_fds;
int ret = 0;
// Happens only on savestate load
if (pfds[0].revents & error_event)
{
ret = static_cast<int>(pfds.size());
}
else
{
// Make the behavior of poll consistent across platforms by not passing:
// - Set with invalid fds, revents is set to 0 (Linux) or POLLNVAL (Windows)
// - Set without a valid socket, raises an error on Windows
std::vector<int> original_order(pfds.size());
std::iota(original_order.begin(), original_order.end(), 0);
// Select indices with valid fds
auto mid = std::partition(original_order.begin(), original_order.end(), [&](auto i) {
return GetHostSocket(Memory::Read_U32(pcmd.buffer_out + 0xc * i)) >= 0;
});
const auto n_valid = std::distance(original_order.begin(), mid);
// Move all the valid pollfds to the front of the vector
for (auto i = 0; i < n_valid; ++i)
std::swap(pfds[i], pfds[original_order[i]]);
if (n_valid > 0)
ret = poll(pfds.data(), n_valid, 0);
if (ret < 0)
ret = GetNetErrorCode(ret, "UpdatePollCommands", false);
// Move everything back to where they were
for (auto i = 0; i < n_valid; ++i)
std::swap(pfds[i], pfds[original_order[i]]);
}
if (ret == 0 && pcmd.timeout)
return false;
// Translate native to Wii events,
for (u32 i = 0; i < pfds.size(); ++i)
{
const int revents = ConvertEvents(pfds[i].revents, ConvertDirection::NativeToWii);
// No need to change fd or events as they are input only.
// Memory::Write_U32(ufds[i].fd, request.buffer_out + 0xc*i); //fd
// Memory::Write_U32(events, request.buffer_out + 0xc*i + 4); //events
Memory::Write_U32(revents, pcmd.buffer_out + 0xc * i + 8); // revents
DEBUG_LOG(IOS_NET, "IOCTL_SO_POLL socket %d wevents %08X events %08X revents %08X", i,
revents, pfds[i].events, pfds[i].revents);
}
GetIOS()->EnqueueIPCReply(request, ret);
return true;
}),
pending_polls.end());
}
void WiiSockMan::Convert(WiiSockAddrIn const& from, sockaddr_in& to)
@ -739,6 +823,43 @@ void WiiSockMan::Convert(WiiSockAddrIn const& from, sockaddr_in& to)
to.sin_port = from.port;
}
s32 WiiSockMan::ConvertEvents(s32 events, ConvertDirection dir)
{
constexpr struct
{
int native;
int wii;
} mapping[] = {
{POLLRDNORM, 0x0001}, {POLLRDBAND, 0x0002}, {POLLPRI, 0x0004}, {POLLWRNORM, 0x0008},
{POLLWRBAND, 0x0010}, {POLLERR, 0x0020}, {POLLHUP, 0x0040}, {POLLNVAL, 0x0080},
};
s32 converted_events = 0;
s32 unhandled_events = 0;
if (dir == ConvertDirection::NativeToWii)
{
for (const auto& map : mapping)
{
if (events & map.native)
converted_events |= map.wii;
}
}
else
{
unhandled_events = events;
for (const auto& map : mapping)
{
if (events & map.wii)
converted_events |= map.native;
unhandled_events &= ~map.wii;
}
}
if (unhandled_events)
ERROR_LOG(IOS_NET, "SO_POLL: unhandled Wii event types: %04x", unhandled_events);
return converted_events;
}
void WiiSockMan::Convert(sockaddr_in const& from, WiiSockAddrIn& to, s32 addrlen)
{
to.addr.addr = from.sin_addr.s_addr;
@ -750,6 +871,35 @@ void WiiSockMan::Convert(sockaddr_in const& from, WiiSockAddrIn& to, s32 addrlen
to.len = addrlen;
}
void WiiSockMan::DoState(PointerWrap& p)
{
bool saving =
p.mode == PointerWrap::Mode::MODE_WRITE || p.mode == PointerWrap::Mode::MODE_MEASURE;
auto size = pending_polls.size();
p.Do(size);
if (!saving)
pending_polls.resize(size);
for (auto& pcmd : pending_polls)
{
p.Do(pcmd.request_addr);
p.Do(pcmd.buffer_out);
p.Do(pcmd.wii_fds);
}
if (saving)
return;
for (auto& pcmd : pending_polls)
{
for (auto& wfd : pcmd.wii_fds)
wfd.revents = (POLLHUP | POLLERR);
}
}
void WiiSockMan::AddPollCommand(const PollCommand& cmd)
{
pending_polls.push_back(cmd);
}
void WiiSockMan::UpdateWantDeterminism(bool want)
{
// If we switched into movie recording, kill existing sockets.

View File

@ -212,6 +212,20 @@ private:
class WiiSockMan
{
public:
enum class ConvertDirection
{
WiiToNative,
NativeToWii
};
struct PollCommand
{
u32 request_addr;
u32 buffer_out;
std::vector<pollfd_t> wii_fds;
s64 timeout;
};
static s32 GetNetErrorCode(s32 ret, const char* caller, bool isRW);
static char* DecodeError(s32 ErrorCode);
@ -223,6 +237,10 @@ public:
void Update();
static void Convert(WiiSockAddrIn const& from, sockaddr_in& to);
static void Convert(sockaddr_in const& from, WiiSockAddrIn& to, s32 addrlen = -1);
static s32 ConvertEvents(s32 events, ConvertDirection dir);
void DoState(PointerWrap& p);
void AddPollCommand(const PollCommand& cmd);
// NON-BLOCKING FUNCTIONS
s32 NewSocket(s32 af, s32 type, s32 protocol);
s32 AddSocket(s32 fd, bool is_rw);
@ -256,7 +274,12 @@ private:
WiiSockMan(WiiSockMan&&) = delete;
WiiSockMan& operator=(WiiSockMan&&) = delete;
void UpdatePollCommands();
std::unordered_map<s32, WiiSocket> WiiSockets;
s32 errno_last;
std::vector<PollCommand> pending_polls;
std::chrono::time_point<std::chrono::high_resolution_clock> last_time =
std::chrono::high_resolution_clock::now();
};
} // namespace IOS::HLE

View File

@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 117; // Last changed in PR 7396
constexpr u32 STATE_VERSION = 118; // Last changed in PR 8813
// Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list,