Merge pull request #1373 from gjonn/feat/kernel-select-net-sockets

feat(libkernel): implement select over libSceNet sockets
This commit is contained in:
Dean 2026-10-08 22:36:09 +13:00 committed by GitHub
commit f100d1d67a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 192 additions and 8 deletions

View File

@ -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)

View File

@ -31,6 +31,7 @@
#include <string>
#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<std::shared_ptr<NativeSocketHandle>> natives(static_cast<std::size_t>(count));
{
std::lock_guard<std::mutex> 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<std::size_t>(index)] = socket->second.native;
}
}
#ifdef _WIN32
std::vector<WSAPOLLFD> descriptors;
constexpr short NativeReadable = POLLRDNORM;
constexpr short NativeUrgent = POLLRDBAND;
constexpr short NativeWritable = POLLWRNORM;
#else
std::vector<pollfd> descriptors;
constexpr short NativeReadable = POLLIN;
constexpr short NativeUrgent = POLLPRI;
constexpr short NativeWritable = POLLOUT;
#endif
std::vector<int> owners;
for (int index = 0; index < count; ++index) {
auto& entry = entries[index];
if (!natives[static_cast<std::size_t>(index)]) {
entry.revents = KernelSocketPoll::Unknown;
continue;
}
decltype(descriptors)::value_type descriptor{};
descriptor.fd = natives[static_cast<std::size_t>(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<ULONG>(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<std::mutex> lk(g_mutex);
g_net_inited = true;

View File

@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
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);

View File

@ -2,6 +2,35 @@
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <thread>
#include <vector>
namespace {
constexpr int GuestEinval = 22;
constexpr int SelectDescriptorLimit = 1024;
struct GuestTimeval {
std::int64_t seconds;
std::int64_t microseconds;
};
std::atomic<KernelSocketPoll::Poller> g_socketPoller{nullptr};
bool DescriptorSet(const void* set, int descriptor) {
return set != nullptr && ((static_cast<const std::uint64_t*>(set)[descriptor / 64] >> (descriptor % 64)) & 1u) != 0;
}
void ClearDescriptor(void* set, int descriptor) {
if (set != nullptr) static_cast<std::uint64_t*>(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<const GuestTimeval*>(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<KernelSocketPoll::Entry> 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<std::chrono::microseconds>(deadline - std::chrono::steady_clock::now()).count();
waitMilliseconds = static_cast<int>(std::clamp<std::int64_t>((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<int>(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<int>(readable) + static_cast<int>(writable) + static_cast<int>(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;
}
}
}
}

View File

@ -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);