diff --git a/Source/Core/Core/IOS/Network/IP/Top.cpp b/Source/Core/Core/IOS/Network/IP/Top.cpp index 7fb436dbb8..5f8ee3282a 100644 --- a/Source/Core/Core/IOS/Network/IP/Top.cpp +++ b/Source/Core/Core/IOS/Network/IP/Top.cpp @@ -50,13 +50,6 @@ #include #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(a) << 24) | (static_cast(b) << 16) | (static_cast(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(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 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) diff --git a/Source/Core/Core/IOS/Network/IP/Top.h b/Source/Core/Core/IOS/Network/IP/Top.h index 06fe19234f..9a36021e6d 100644 --- a/Source/Core/Core/IOS/Network/IP/Top.h +++ b/Source/Core/Core/IOS/Network/IP/Top.h @@ -13,6 +13,13 @@ #include #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; diff --git a/Source/Core/Core/IOS/Network/Socket.cpp b/Source/Core/Core/IOS/Network/Socket.cpp index b2ff4c6795..313895a43f 100644 --- a/Source/Core/Core/IOS/Network/Socket.cpp +++ b/Source/Core/Core/IOS/Network/Socket.cpp @@ -6,6 +6,8 @@ #include "Core/IOS/Network/Socket.h" #include +#include + #include #ifndef _WIN32 #include @@ -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(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(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(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 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. diff --git a/Source/Core/Core/IOS/Network/Socket.h b/Source/Core/Core/IOS/Network/Socket.h index 615560f305..99171a4e78 100644 --- a/Source/Core/Core/IOS/Network/Socket.h +++ b/Source/Core/Core/IOS/Network/Socket.h @@ -212,6 +212,20 @@ private: class WiiSockMan { public: + enum class ConvertDirection + { + WiiToNative, + NativeToWii + }; + + struct PollCommand + { + u32 request_addr; + u32 buffer_out; + std::vector 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 WiiSockets; s32 errno_last; + std::vector pending_polls; + std::chrono::time_point last_time = + std::chrono::high_resolution_clock::now(); }; } // namespace IOS::HLE diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 1a2b74c00b..9c01be2d72 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -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,