diff --git a/core/libs/prx/libSceNet/CMakeLists.txt b/core/libs/prx/libSceNet/CMakeLists.txt index 0e2e8e419..2516da1e1 100644 --- a/core/libs/prx/libSceNet/CMakeLists.txt +++ b/core/libs/prx/libSceNet/CMakeLists.txt @@ -5,7 +5,7 @@ configure_windows_unwind(libSceNet) if(BUILD_TESTING) add_test_executable(guest_sce_net_tests ${LIBS_INCLUDE_DIR}/tests/GuestSceNet.cpp) target_include_directories(guest_sce_net_tests PRIVATE ${LIBS_INCLUDE_DIR}) - target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libc) + target_link_libraries(guest_sce_net_tests PRIVATE libSceNet libSceNetCtl libkernel libc) configure_windows_unwind(guest_sce_net_tests) add_test(NAME guest_sce_net COMMAND guest_sce_net_tests) set_tests_properties(guest_sce_net PROPERTIES TIMEOUT 20) diff --git a/core/libs/prx/libSceNet/Export.cpp b/core/libs/prx/libSceNet/Export.cpp index a67af3b25..8d3989baa 100644 --- a/core/libs/prx/libSceNet/Export.cpp +++ b/core/libs/prx/libSceNet/Export.cpp @@ -31,6 +31,7 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" // Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets. @@ -293,6 +294,65 @@ int* APS5_VABI sceNetErrnoLoc(void) { return errno_slot(); } +int PollSockets(KernelSocketPoll::Entry* entries, int count, int timeoutMilliseconds) { + std::vector> natives(static_cast(count)); + { + std::lock_guard lk(g_mutex); + for (int index = 0; index < count; ++index) { + const auto socket = g_socks.find(entries[index].descriptor); + if (socket != g_socks.end()) natives[static_cast(index)] = socket->second.native; + } + } +#ifdef _WIN32 + std::vector descriptors; + constexpr short NativeReadable = POLLRDNORM; + constexpr short NativeUrgent = POLLRDBAND; + constexpr short NativeWritable = POLLWRNORM; +#else + std::vector descriptors; + constexpr short NativeReadable = POLLIN; + constexpr short NativeUrgent = POLLPRI; + constexpr short NativeWritable = POLLOUT; +#endif + std::vector owners; + for (int index = 0; index < count; ++index) { + auto& entry = entries[index]; + if (!natives[static_cast(index)]) { + entry.revents = KernelSocketPoll::Unknown; + continue; + } + decltype(descriptors)::value_type descriptor{}; + descriptor.fd = natives[static_cast(index)]->value; + if (entry.events & KernelSocketPoll::Readable) descriptor.events |= NativeReadable; + if (entry.events & KernelSocketPoll::Urgent) descriptor.events |= NativeUrgent; + if (entry.events & KernelSocketPoll::Writable) descriptor.events |= NativeWritable; + descriptors.push_back(descriptor); + owners.push_back(index); + } + if (descriptors.empty()) return 0; +#ifdef _WIN32 + const int result = WSAPoll(descriptors.data(), static_cast(descriptors.size()), timeoutMilliseconds); +#else + const int result = ::poll(descriptors.data(), descriptors.size(), timeoutMilliseconds); +#endif + if (result < 0) return -native_error(); + int ready = 0; + for (std::size_t index = 0; index < descriptors.size(); ++index) { + const short native = descriptors[index].revents; + short revents = 0; + if (native & NativeReadable) revents |= KernelSocketPoll::Readable; + if (native & NativeUrgent) revents |= KernelSocketPoll::Urgent; + if (native & NativeWritable) revents |= KernelSocketPoll::Writable; + if (native & (POLLERR | POLLNVAL)) revents |= KernelSocketPoll::Error; + if (native & POLLHUP) revents |= KernelSocketPoll::HangUp; + entries[owners[index]].revents = revents; + if (revents != 0) ++ready; + } + return ready; +} + +const bool g_socketPollerRegistered = (KernelSetSocketPoller_nid_no_patch(&PollSockets), true); + int APS5_VABI sceNetInit_nid_postfix(void) { std::lock_guard lk(g_mutex); g_net_inited = true; diff --git a/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp new file mode 100644 index 000000000..71d5f62e7 --- /dev/null +++ b/core/libs/prx/libkernel/Socket/include/SocketPoll.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +namespace KernelSocketPoll { +constexpr short Readable = 0x1; +constexpr short Urgent = 0x2; +constexpr short Writable = 0x4; +constexpr short Error = 0x8; +constexpr short HangUp = 0x10; +constexpr short Unknown = 0x20; + +struct Entry { + int descriptor; + short events; + short revents; +}; + +using Poller = int (*)(Entry* entries, int count, int timeoutMilliseconds); +} + +extern "C" void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller); diff --git a/core/libs/prx/libkernel/Socket/src/Socket.cpp b/core/libs/prx/libkernel/Socket/src/Socket.cpp index 4e3025326..d2f176af1 100644 --- a/core/libs/prx/libkernel/Socket/src/Socket.cpp +++ b/core/libs/prx/libkernel/Socket/src/Socket.cpp @@ -2,6 +2,35 @@ #include #include "SceTypes.hpp" #include "prx/libc/include/General.hpp" +#include "prx/libkernel/Socket/include/SocketPoll.hpp" + +#include +#include +#include +#include +#include + +namespace { + +constexpr int GuestEinval = 22; +constexpr int SelectDescriptorLimit = 1024; + +struct GuestTimeval { + std::int64_t seconds; + std::int64_t microseconds; +}; + +std::atomic g_socketPoller{nullptr}; + +bool DescriptorSet(const void* set, int descriptor) { + return set != nullptr && ((static_cast(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0; +} + +void ClearDescriptor(void* set, int descriptor) { + if (set != nullptr) static_cast(set)[descriptor / 64] &= ~(std::uint64_t{1} << (descriptor % 64)); +} + +} extern "C" { @@ -16,14 +45,72 @@ int APS5_VABI inet_pton_nid_postfix(int af, const char* src, void* dst) { return __inet_pton_nid_postfix(af, src, dst); } +int* APS5_VABI __error_nid_postfix(); + +void KernelSetSocketPoller_nid_no_patch(KernelSocketPoll::Poller poller) { + g_socketPoller.store(poller); +} + int APS5_VABI select_nid_postfix(int nfds, void* readfds, void* writefds, void* exceptfds, const void* timeout) { - (void)nfds; - (void)readfds; - (void)writefds; - (void)exceptfds; - (void)timeout; - NotImplemented_nid_no_patch(__func__); - return 0; + const auto* limit = static_cast(timeout); + if (nfds < 0 || nfds > SelectDescriptorLimit || (limit != nullptr && (limit->seconds < 0 || limit->microseconds < 0 || limit->microseconds >= 1000000))) { + *__error_nid_postfix() = GuestEinval; + return -1; + } + std::vector entries; + for (int descriptor = 0; descriptor < nfds; ++descriptor) { + short events = 0; + if (DescriptorSet(readfds, descriptor)) events |= KernelSocketPoll::Readable; + if (DescriptorSet(writefds, descriptor)) events |= KernelSocketPoll::Writable; + if (DescriptorSet(exceptfds, descriptor)) events |= KernelSocketPoll::Urgent; + if (events != 0) entries.push_back({descriptor, events, 0}); + } + const auto start = std::chrono::steady_clock::now(); + const auto deadline = limit == nullptr ? std::chrono::steady_clock::time_point::max() + : start + std::chrono::seconds(limit->seconds) + std::chrono::microseconds(limit->microseconds); + for (;;) { + int waitMilliseconds = 100; + if (limit != nullptr) { + const auto remaining = std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()).count(); + waitMilliseconds = static_cast(std::clamp((remaining + 999) / 1000, 0, 100)); + } + if (!entries.empty()) { + const auto poller = g_socketPoller.load(); + if (poller == nullptr) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); + for (auto& entry : entries) entry.revents = 0; + const int result = poller(entries.data(), static_cast(entries.size()), waitMilliseconds); + if (result < 0) { + *__error_nid_postfix() = -result; + return -1; + } + int ready = 0; + for (const auto& entry : entries) { + if ((entry.revents & KernelSocketPoll::Unknown) != 0) NotImplemented_nid_no_patch("select on descriptors that are not libSceNet sockets"); + const bool readable = (entry.events & KernelSocketPoll::Readable) != 0 && (entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) != 0; + const bool writable = (entry.events & KernelSocketPoll::Writable) != 0 && (entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) != 0; + const bool urgent = (entry.events & KernelSocketPoll::Urgent) != 0 && (entry.revents & KernelSocketPoll::Urgent) != 0; + ready += static_cast(readable) + static_cast(writable) + static_cast(urgent); + } + if (ready > 0) { + for (const auto& entry : entries) { + if ((entry.revents & (KernelSocketPoll::Readable | KernelSocketPoll::HangUp | KernelSocketPoll::Error)) == 0) ClearDescriptor(readfds, entry.descriptor); + if ((entry.revents & (KernelSocketPoll::Writable | KernelSocketPoll::Error)) == 0) ClearDescriptor(writefds, entry.descriptor); + if ((entry.revents & KernelSocketPoll::Urgent) == 0) ClearDescriptor(exceptfds, entry.descriptor); + } + return ready; + } + } else if (waitMilliseconds > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(waitMilliseconds)); + } + if (limit != nullptr && std::chrono::steady_clock::now() >= deadline) { + for (const auto& entry : entries) { + ClearDescriptor(readfds, entry.descriptor); + ClearDescriptor(writefds, entry.descriptor); + ClearDescriptor(exceptfds, entry.descriptor); + } + return 0; + } + } } } diff --git a/core/libs/tests/GuestSceNet.cpp b/core/libs/tests/GuestSceNet.cpp index f20e1c315..e2176f652 100644 --- a/core/libs/tests/GuestSceNet.cpp +++ b/core/libs/tests/GuestSceNet.cpp @@ -35,6 +35,8 @@ int APS5_VABI sceNetResolverStartNtoa(int, const char*, void*, int, int, int); int APS5_VABI sceNetResolverDestroy(int); int APS5_VABI sceNetResolverGetError(int, int*); int APS5_VABI sceNetCtlGetState(int*); +int APS5_VABI select_nid_postfix(int, void*, void*, void*, const void*); +int* APS5_VABI __error_nid_postfix(); int APS5_VABI sceNetInetPton(int, const char*, void*); const char* APS5_VABI sceNetInetNtop(int, const void*, char*, std::uint32_t); int* APS5_VABI sceNetErrnoLoc(void); @@ -264,6 +266,20 @@ int main() { Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0, source.data(), &address_size) == sizeof(datagram_result)); Require(std::strcmp(datagram, datagram_result) == 0 && source[1] == 2); + std::uint64_t readable[16]{}; + readable[udp_receiver / 64] |= std::uint64_t{1} << (udp_receiver % 64); + const std::int64_t poll_now[2]{0, 0}; + Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, poll_now) == 0); + Require(readable[udp_receiver / 64] == 0); + Require(sceNetSendto(udp_sender, datagram, sizeof(datagram), 0, address.data(), address.size()) == sizeof(datagram)); + readable[udp_receiver / 64] |= std::uint64_t{1} << (udp_receiver % 64); + const std::int64_t wait_second[2]{1, 0}; + Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, wait_second) == 1); + Require(((readable[udp_receiver / 64] >> (udp_receiver % 64)) & 1u) != 0); + Require(sceNetRecvfrom(udp_receiver, datagram_result, sizeof(datagram_result), 0, nullptr, nullptr) == sizeof(datagram_result)); + const std::int64_t bad_timeout[2]{0, 1000000}; + Require(select_nid_postfix(udp_receiver + 1, readable, nullptr, nullptr, bad_timeout) == -1 && *__error_nid_postfix() == 22); + Require(select_nid_postfix(1025, nullptr, nullptr, nullptr, poll_now) == -1 && *__error_nid_postfix() == 22); CheckMessages(udp_receiver, udp_sender, address); Require(sceNetSocketClose(udp_sender) == 0); Require(sceNetSocketClose(udp_receiver) == 0);