Merge pull request #1091 from kjhq/feat/libkernel-socket-events
feat(libkernel): implement read and write events for guest sockets
This commit is contained in:
commit
e8d6de4ae7
|
|
@ -736,6 +736,16 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_dup PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_socket_event_tests ${LIBS_INCLUDE_DIR}/tests/GuestSocketEvents.cpp)
|
||||
target_include_directories(guest_socket_event_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_socket_event_tests PRIVATE libkernel libc)
|
||||
configure_windows_unwind(guest_socket_event_tests)
|
||||
add_test(NAME guest_socket_events COMMAND guest_socket_event_tests)
|
||||
set_tests_properties(guest_socket_events PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_socket_events PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_elf_phdr_match_tests ${LIBS_INCLUDE_DIR}/tests/GuestElfPhdrMatch.cpp)
|
||||
target_include_directories(guest_elf_phdr_match_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_elf_phdr_match_tests PRIVATE libkernel libc)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "Equeue.hpp"
|
||||
#include "prx/libkernel/Time/include/Time.hpp"
|
||||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
|
@ -42,6 +44,7 @@ void KernelEqueuePrivate::Close() {
|
|||
}
|
||||
m_events.clear();
|
||||
m_cond.NotifyAll();
|
||||
WakePollers();
|
||||
}
|
||||
|
||||
void KernelEqueuePrivate::TriggerExpiredTimers(uint64_t nowNs) {
|
||||
|
|
@ -75,12 +78,48 @@ bool KernelEqueuePrivate::NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) con
|
|||
return true;
|
||||
}
|
||||
|
||||
bool KernelEqueuePrivate::PollEvents() {
|
||||
bool polled = false;
|
||||
for (auto& ev : m_events) {
|
||||
if (ev.filter.pollFunc != nullptr) {
|
||||
ev.triggered = ev.filter.pollFunc(&ev);
|
||||
polled = true;
|
||||
}
|
||||
}
|
||||
return polled;
|
||||
}
|
||||
|
||||
void KernelEqueuePrivate::WakePollers() {
|
||||
for (const auto waker : m_pollers) {
|
||||
GuestSockets::Wake(waker);
|
||||
}
|
||||
}
|
||||
|
||||
void KernelEqueuePrivate::WaitForDescriptors(std::unique_lock<std::mutex>& lock, std::uint64_t deadlineNanos) {
|
||||
std::vector<GuestSockets::Interest> interests;
|
||||
for (const auto& e : m_events) {
|
||||
if (e.filter.pollFunc != nullptr) {
|
||||
interests.push_back({static_cast<int>(e.event.ident), e.event.filter == EVFILT_WRITE});
|
||||
}
|
||||
}
|
||||
const auto waker = GuestSockets::CurrentWaker();
|
||||
m_pollers.push_back(waker);
|
||||
lock.unlock();
|
||||
const bool waited = GuestSockets::WaitAny(interests, deadlineNanos);
|
||||
lock.lock();
|
||||
m_pollers.erase(std::find(m_pollers.begin(), m_pollers.end(), waker));
|
||||
if (!waited) {
|
||||
m_cond.WaitUntil(lock, deadlineNanos);
|
||||
}
|
||||
}
|
||||
|
||||
int KernelEqueuePrivate::GetTriggeredEvents(KernelEvent* ev, int num) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
if (m_closed) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
TriggerExpiredTimers(MonotonicNs());
|
||||
PollEvents();
|
||||
int ret = 0;
|
||||
for (auto it = m_events.begin(); it != m_events.end();) {
|
||||
auto& e = *it;
|
||||
|
|
@ -123,6 +162,7 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
|
|||
const std::uint64_t deadline = TimedWait::DeadlineNanos(micros);
|
||||
for (;;) {
|
||||
TriggerExpiredTimers(MonotonicNs());
|
||||
const bool polling = PollEvents();
|
||||
int ret = 0;
|
||||
for (auto it = m_events.begin(); it != m_events.end() && ret < num;) {
|
||||
auto& e = *it;
|
||||
|
|
@ -159,8 +199,12 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
|
|||
}
|
||||
if (micros == 0) {
|
||||
uint32_t timerWait = 0;
|
||||
if (NextTimerWaitMicros(MonotonicNs(), &timerWait)) {
|
||||
m_cond.WaitUntil(lock, TimedWait::NowNanos() + static_cast<std::uint64_t>(timerWait) * 1000ULL);
|
||||
const bool hasTimer = NextTimerWaitMicros(MonotonicNs(), &timerWait);
|
||||
const std::uint64_t wake = hasTimer ? TimedWait::NowNanos() + static_cast<std::uint64_t>(timerWait) * 1000ULL : 0;
|
||||
if (polling) {
|
||||
WaitForDescriptors(lock, wake);
|
||||
} else if (hasTimer) {
|
||||
m_cond.WaitUntil(lock, wake);
|
||||
} else {
|
||||
m_cond.Wait(lock);
|
||||
}
|
||||
|
|
@ -172,7 +216,12 @@ int KernelEqueuePrivate::WaitForEvents(KernelEvent* ev, int num, uint32_t micros
|
|||
return 0;
|
||||
}
|
||||
const std::uint64_t timerDeadline = now + static_cast<std::uint64_t>(timerWait) * 1000ULL;
|
||||
m_cond.WaitUntil(lock, hasTimer ? std::min(deadline, timerDeadline) : deadline);
|
||||
const std::uint64_t wake = hasTimer ? std::min(deadline, timerDeadline) : deadline;
|
||||
if (polling) {
|
||||
WaitForDescriptors(lock, wake);
|
||||
} else {
|
||||
m_cond.WaitUntil(lock, wake);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -201,6 +250,7 @@ int KernelEqueuePrivate::AddEvent(const KernelEqueueEvent& event) {
|
|||
m_events.push_back(event);
|
||||
}
|
||||
m_cond.NotifyOne();
|
||||
WakePollers();
|
||||
return EQUEUE_OK;
|
||||
}
|
||||
|
||||
|
|
@ -223,6 +273,7 @@ int KernelEqueuePrivate::TriggerEvent(uintptr_t ident, int16_t filter, void* tri
|
|||
it->triggered = true;
|
||||
}
|
||||
m_cond.NotifyOne();
|
||||
WakePollers();
|
||||
return EQUEUE_OK;
|
||||
}
|
||||
|
||||
|
|
@ -244,9 +295,20 @@ int KernelEqueuePrivate::DeleteEvent(uintptr_t ident, int16_t filter) {
|
|||
it->filter.deleteEventFunc(m_handle, &*it);
|
||||
}
|
||||
m_events.erase(it);
|
||||
WakePollers();
|
||||
return EQUEUE_OK;
|
||||
}
|
||||
|
||||
void KernelEqueuePrivate::RemoveDescriptorEvents(uintptr_t ident) {
|
||||
std::unique_lock lock(m_mutex);
|
||||
const auto removed = std::erase_if(m_events, [ident](const auto& e) {
|
||||
return e.event.ident == ident && (e.event.filter == EVFILT_READ || e.event.filter == EVFILT_WRITE);
|
||||
});
|
||||
if (removed != 0) {
|
||||
WakePollers();
|
||||
}
|
||||
}
|
||||
|
||||
KernelEqueueRef EqueuePin_nid_postfix(KernelEqueue eq) {
|
||||
if (eq == 0) {
|
||||
return {};
|
||||
|
|
@ -481,4 +543,64 @@ int APS5_VABI sceKernelDeleteAmprSystemEvent(KernelEqueue eq, int id) {
|
|||
return sceKernelDeleteAmprEvent(eq, id);
|
||||
}
|
||||
|
||||
static int AddDescriptorEvent(KernelEqueue eq, int fd, std::size_t size, void* udata, int16_t filter) {
|
||||
if (fd < GuestSockets::FirstDescriptor) {
|
||||
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: non-socket descriptor" : "sceKernelAddWriteEvent: non-socket descriptor");
|
||||
}
|
||||
if (size > 1) {
|
||||
NotImplemented_nid_no_patch(filter == EVFILT_READ ? "sceKernelAddReadEvent: low watermark" : "sceKernelAddWriteEvent: low watermark");
|
||||
}
|
||||
if (!GuestSockets::IsOpen(fd)) {
|
||||
return SCE_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
KernelEqueueEvent event{};
|
||||
event.event.ident = static_cast<uintptr_t>(fd);
|
||||
event.event.filter = filter;
|
||||
event.event.flags = EV_ADD;
|
||||
event.event.udata = udata;
|
||||
event.filter.pollFunc = [](KernelEqueueEvent* e) {
|
||||
std::int64_t data = 0;
|
||||
bool eof = false;
|
||||
if (!GuestSockets::Ready(static_cast<int>(e->event.ident), e->event.filter == EVFILT_WRITE, &data, &eof)) {
|
||||
return false;
|
||||
}
|
||||
e->event.data = static_cast<intptr_t>(data);
|
||||
e->event.flags = static_cast<uint16_t>(eof ? (e->event.flags | EV_EOF) : (e->event.flags & ~EV_EOF));
|
||||
return true;
|
||||
};
|
||||
event.filter.resetFunc = [](KernelEqueueEvent* e) {
|
||||
e->triggered = false;
|
||||
};
|
||||
return EqueueAddEvent_nid_postfix(eq, event);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelAddReadEvent(KernelEqueue eq, int fd, std::size_t size, void* udata) {
|
||||
return AddDescriptorEvent(eq, fd, size, udata, EVFILT_READ);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelDeleteReadEvent(KernelEqueue eq, int fd) {
|
||||
return EqueueDeleteEvent_nid_postfix(eq, static_cast<uintptr_t>(fd), EVFILT_READ);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelAddWriteEvent(KernelEqueue eq, int fd, std::size_t size, void* udata) {
|
||||
return AddDescriptorEvent(eq, fd, size, udata, EVFILT_WRITE);
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelDeleteWriteEvent(KernelEqueue eq, int fd) {
|
||||
return EqueueDeleteEvent_nid_postfix(eq, static_cast<uintptr_t>(fd), EVFILT_WRITE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void EqueueDescriptorClosed(int descriptor) {
|
||||
std::vector<KernelEqueueRef> owners;
|
||||
{
|
||||
std::unique_lock lock(g_equeueMutex);
|
||||
for (const auto& [handle, owner] : g_equeues) {
|
||||
owners.push_back(owner);
|
||||
}
|
||||
}
|
||||
for (const auto& owner : owners) {
|
||||
owner->RemoveDescriptorEvents(static_cast<uintptr_t>(descriptor));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@
|
|||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "SceTypes.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/KernelErrors.hpp"
|
||||
#include "prx/libkernel/Time/include/TimedWait.hpp"
|
||||
|
||||
static constexpr int16_t EVFILT_READ = -1;
|
||||
static constexpr int16_t EVFILT_WRITE = -2;
|
||||
static constexpr int16_t EVFILT_TIMER = -7;
|
||||
static constexpr int16_t EVFILT_USER = -11;
|
||||
static constexpr int16_t EVFILT_VIDEO_OUT = -13;
|
||||
|
|
@ -24,6 +27,7 @@ static constexpr uint16_t EV_ADD = 0x0001;
|
|||
static constexpr uint16_t EV_ONESHOT = 0x0010;
|
||||
static constexpr uint16_t EV_CLEAR = 0x0020;
|
||||
static constexpr uint16_t EV_ERROR = 0x4000;
|
||||
static constexpr uint16_t EV_EOF = 0x8000;
|
||||
|
||||
static constexpr int EQUEUE_OK = 0;
|
||||
|
||||
|
|
@ -32,6 +36,7 @@ struct KernelEqueueEvent;
|
|||
using EqueueTriggerFunc = void (*)(KernelEqueueEvent* event, void* triggerData);
|
||||
using EqueueResetFunc = void (*)(KernelEqueueEvent* event);
|
||||
using EqueueDeleteFunc = void (*)(KernelEqueue eq, KernelEqueueEvent* event);
|
||||
using EqueuePollFunc = bool (*)(KernelEqueueEvent* event);
|
||||
|
||||
struct KernelFilter {
|
||||
void* data = nullptr;
|
||||
|
|
@ -39,6 +44,7 @@ struct KernelFilter {
|
|||
EqueueTriggerFunc triggerFunc = nullptr;
|
||||
EqueueResetFunc resetFunc = nullptr;
|
||||
EqueueDeleteFunc deleteEventFunc = nullptr;
|
||||
EqueuePollFunc pollFunc = nullptr;
|
||||
};
|
||||
|
||||
struct KernelEqueueEvent {
|
||||
|
|
@ -66,14 +72,19 @@ public:
|
|||
int DeleteEvent(uintptr_t ident, int16_t filter);
|
||||
int GetTriggeredEvents(KernelEvent* ev, int num);
|
||||
int WaitForEvents(KernelEvent* ev, int num, uint32_t micros);
|
||||
void RemoveDescriptorEvents(uintptr_t ident);
|
||||
void Close();
|
||||
static uint64_t MonotonicNs();
|
||||
|
||||
private:
|
||||
void TriggerExpiredTimers(uint64_t nowNs);
|
||||
bool NextTimerWaitMicros(uint64_t nowNs, uint32_t* out) const;
|
||||
bool PollEvents();
|
||||
void WakePollers();
|
||||
void WaitForDescriptors(std::unique_lock<std::mutex>& lock, std::uint64_t deadlineNanos);
|
||||
|
||||
std::list<KernelEqueueEvent> m_events;
|
||||
std::vector<std::uintptr_t> m_pollers;
|
||||
std::mutex m_mutex;
|
||||
TimedWait::Condition m_cond;
|
||||
std::string m_name;
|
||||
|
|
@ -82,6 +93,7 @@ private:
|
|||
};
|
||||
|
||||
using KernelEqueueRef = std::shared_ptr<KernelEqueuePrivate>;
|
||||
void EqueueDescriptorClosed(int descriptor);
|
||||
extern "C" {
|
||||
|
||||
KernelEqueueRef EqueuePin_nid_postfix(KernelEqueue eq);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
namespace GuestSockets {
|
||||
constexpr int FirstDescriptor = 0x10000000;
|
||||
int Duplicate(int descriptor);
|
||||
|
|
@ -10,6 +11,14 @@ bool IsOpen(int descriptor);
|
|||
int Family(int descriptor);
|
||||
std::int64_t Read(int descriptor, void* buffer, std::size_t length);
|
||||
std::int64_t Write(int descriptor, const void* buffer, std::size_t length);
|
||||
bool Ready(int descriptor, bool write, std::int64_t* data, bool* eof);
|
||||
struct Interest {
|
||||
int descriptor;
|
||||
bool write;
|
||||
};
|
||||
std::uintptr_t CurrentWaker();
|
||||
void Wake(std::uintptr_t waker);
|
||||
bool WaitAny(const std::vector<Interest>& interests, std::uint64_t deadlineNanos);
|
||||
}
|
||||
|
||||
extern "C" bool GuestSocketIsOpen_nid_no_patch(int descriptor);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
|
@ -13,6 +15,8 @@
|
|||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
|
||||
#include "prx/libkernel/Equeue/Equeue.hpp"
|
||||
#include "prx/libkernel/Time/include/TimedWait.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
|
|
@ -109,6 +113,43 @@ struct Socket {
|
|||
#endif
|
||||
}
|
||||
};
|
||||
struct Waker {
|
||||
NativeSocket value = Invalid;
|
||||
Waker() {
|
||||
value = ::socket(AF_INET, SOCK_DGRAM, 0);
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
socklen_t size = sizeof(address);
|
||||
#ifdef _WIN32
|
||||
u_long nonblocking = 1;
|
||||
const bool configured = value != Invalid && ioctlsocket(value, FIONBIO, &nonblocking) == 0;
|
||||
#else
|
||||
const bool configured = value != Invalid && ::fcntl(value, F_SETFL, ::fcntl(value, F_GETFL) | O_NONBLOCK) == 0;
|
||||
#endif
|
||||
if (!configured || bind(value, reinterpret_cast<sockaddr*>(&address), size) != 0 ||
|
||||
getsockname(value, reinterpret_cast<sockaddr*>(&address), &size) != 0 ||
|
||||
connect(value, reinterpret_cast<sockaddr*>(&address), size) != 0) {
|
||||
const int error = NativeError();
|
||||
Close();
|
||||
throw std::runtime_error("equeue waker: loopback socket setup failed, guest errno " + std::to_string(error));
|
||||
}
|
||||
}
|
||||
~Waker() { Close(); }
|
||||
void Close() {
|
||||
if (value == Invalid) return;
|
||||
#ifdef _WIN32
|
||||
closesocket(value);
|
||||
#else
|
||||
::close(value);
|
||||
#endif
|
||||
value = Invalid;
|
||||
}
|
||||
};
|
||||
Waker& ThreadWaker() {
|
||||
thread_local Waker waker;
|
||||
return waker;
|
||||
}
|
||||
std::mutex socketsMutex;
|
||||
std::map<int, std::shared_ptr<Socket>> sockets;
|
||||
int nextDescriptor = GuestSockets::FirstDescriptor;
|
||||
|
|
@ -173,8 +214,68 @@ void GuestAddress(const sockaddr_storage& native, void* output, std::uint32_t* l
|
|||
}
|
||||
|
||||
int GuestSockets::Close(int descriptor) {
|
||||
std::lock_guard lock(socketsMutex);
|
||||
return sockets.erase(descriptor) ? 0 : Fail(9);
|
||||
{
|
||||
std::lock_guard lock(socketsMutex);
|
||||
if (!sockets.erase(descriptor)) return Fail(9);
|
||||
}
|
||||
EqueueDescriptorClosed(descriptor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uintptr_t GuestSockets::CurrentWaker() {
|
||||
return static_cast<std::uintptr_t>(ThreadWaker().value);
|
||||
}
|
||||
|
||||
void GuestSockets::Wake(std::uintptr_t waker) {
|
||||
const char signal = 0;
|
||||
if (send(static_cast<NativeSocket>(waker), &signal, 1, 0) < 0 && NativeError() != 35)
|
||||
throw std::runtime_error("equeue waker: send failed, guest errno " + std::to_string(NativeError()));
|
||||
}
|
||||
|
||||
bool GuestSockets::WaitAny(const std::vector<Interest>& interests, std::uint64_t deadlineNanos) {
|
||||
const NativeSocket waker = ThreadWaker().value;
|
||||
std::vector<std::shared_ptr<Socket>> held;
|
||||
#ifdef _WIN32
|
||||
std::vector<WSAPOLLFD> entries;
|
||||
constexpr SHORT ReadMask = POLLRDNORM;
|
||||
constexpr SHORT WriteMask = POLLWRNORM;
|
||||
#else
|
||||
std::vector<pollfd> entries;
|
||||
constexpr short ReadMask = POLLIN;
|
||||
constexpr short WriteMask = POLLOUT;
|
||||
#endif
|
||||
{
|
||||
std::lock_guard lock(socketsMutex);
|
||||
for (const auto& interest : interests) {
|
||||
const auto found = sockets.find(interest.descriptor);
|
||||
if (found == sockets.end()) continue;
|
||||
held.push_back(found->second);
|
||||
entries.push_back({found->second->value, interest.write ? WriteMask : ReadMask, 0});
|
||||
}
|
||||
}
|
||||
entries.push_back({waker, ReadMask, 0});
|
||||
std::uint64_t remaining = 0;
|
||||
if (deadlineNanos != 0) {
|
||||
const std::uint64_t now = TimedWait::NowNanos();
|
||||
if (now >= deadlineNanos) return true;
|
||||
remaining = deadlineNanos - now;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
INT timeout = -1;
|
||||
if (deadlineNanos != 0) {
|
||||
if (remaining < 1000000) return false;
|
||||
timeout = static_cast<INT>(std::min<std::uint64_t>(remaining / 1000000, INT_MAX));
|
||||
}
|
||||
if (WSAPoll(entries.data(), static_cast<ULONG>(entries.size()), timeout) == SOCKET_ERROR)
|
||||
throw std::runtime_error("equeue wait: WSAPoll failed, guest errno " + std::to_string(NativeError()));
|
||||
#else
|
||||
timespec timeout{static_cast<time_t>(remaining / 1000000000), static_cast<long>(remaining % 1000000000)};
|
||||
if (::ppoll(entries.data(), entries.size(), deadlineNanos != 0 ? &timeout : nullptr, nullptr) < 0 && errno != EINTR)
|
||||
throw std::runtime_error("equeue wait: ppoll failed, guest errno " + std::to_string(NativeError()));
|
||||
#endif
|
||||
char drained[16];
|
||||
while (recv(waker, drained, sizeof(drained), 0) > 0) {}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GuestSockets::IsOpen(int descriptor) {
|
||||
|
|
@ -349,6 +450,40 @@ int UnnamedAddress(void* address, std::uint32_t* length) {
|
|||
}
|
||||
}
|
||||
|
||||
bool GuestSockets::Ready(int descriptor, bool write, std::int64_t* data, bool* eof) {
|
||||
std::shared_ptr<Socket> socket;
|
||||
{
|
||||
std::lock_guard lock(socketsMutex);
|
||||
const auto found = sockets.find(descriptor);
|
||||
if (found == sockets.end()) return false;
|
||||
socket = found->second;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
WSAPOLLFD entry{socket->value, static_cast<SHORT>(write ? POLLWRNORM : POLLRDNORM), 0};
|
||||
if (WSAPoll(&entry, 1, 0) <= 0) return false;
|
||||
#else
|
||||
pollfd entry{socket->value, static_cast<short>(write ? POLLOUT : (POLLIN | POLLRDHUP)), 0};
|
||||
if (::poll(&entry, 1, 0) <= 0) return false;
|
||||
if (!write && (entry.revents & POLLRDHUP)) entry.revents |= POLLHUP;
|
||||
#endif
|
||||
*eof = (entry.revents & (POLLHUP | POLLERR)) != 0;
|
||||
*data = 0;
|
||||
if (write) {
|
||||
int size = 0;
|
||||
socklen_t length = sizeof(size);
|
||||
if (getsockopt(socket->value, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&size), &length) == 0) *data = size;
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
unsigned long available = 0;
|
||||
if (ioctlsocket(socket->value, FIONREAD, &available) == 0) *data = static_cast<std::int64_t>(available);
|
||||
#else
|
||||
int available = 0;
|
||||
if (::ioctl(socket->value, FIONREAD, &available) == 0) *data = available;
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI fcntl_nid_postfix(int descriptor, int command, ...) {
|
||||
const auto socket = Lookup(descriptor);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
extern "C" {
|
||||
int APS5_VABI socket_nid_postfix(int, int, int);
|
||||
int APS5_VABI bind_nid_postfix(int, const void*, std::uint32_t);
|
||||
int APS5_VABI listen_nid_postfix(int, int);
|
||||
int APS5_VABI getsockname_nid_postfix(int, void*, std::uint32_t*);
|
||||
int APS5_VABI connect_nid_postfix(int, const void*, std::uint32_t);
|
||||
int APS5_VABI accept_nid_postfix(int, void*, std::uint32_t*);
|
||||
std::int64_t APS5_VABI send_nid_postfix(int, const void*, std::uint64_t, int);
|
||||
std::int64_t APS5_VABI recv_nid_postfix(int, void*, std::uint64_t, int);
|
||||
int APS5_VABI close_nid_postfix(int);
|
||||
int APS5_VABI sceKernelCreateEqueue(KernelEqueue* eq, const char* name);
|
||||
int APS5_VABI sceKernelDeleteEqueue(KernelEqueue eq);
|
||||
int APS5_VABI sceKernelWaitEqueue(KernelEqueue eq, KernelEvent* ev, int num, int* out, const KernelUseconds* timo);
|
||||
int APS5_VABI sceKernelAddReadEvent(KernelEqueue eq, int fd, std::size_t size, void* udata);
|
||||
int APS5_VABI sceKernelDeleteReadEvent(KernelEqueue eq, int fd);
|
||||
int APS5_VABI sceKernelAddWriteEvent(KernelEqueue eq, int fd, std::size_t size, void* udata);
|
||||
int APS5_VABI sceKernelDeleteWriteEvent(KernelEqueue eq, int fd);
|
||||
}
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int SCE_KERNEL_ERROR_ENOENT = static_cast<int>(0x80020002);
|
||||
static constexpr int SCE_KERNEL_ERROR_EBADF = static_cast<int>(0x80020009);
|
||||
static constexpr int SCE_KERNEL_ERROR_ETIMEDOUT = static_cast<int>(0x8002003c);
|
||||
static constexpr int EVFILT_READ = -1;
|
||||
static constexpr int EVFILT_WRITE = -2;
|
||||
static constexpr std::uint16_t EV_EOF = 0x8000;
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
static bool Rejects(int (APS5_VABI *add)(KernelEqueue, int, std::size_t, void*), KernelEqueue eq, int fd, std::size_t size) {
|
||||
try {
|
||||
add(eq, fd, size, nullptr);
|
||||
} catch (const std::exception&) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
const int listener = socket_nid_postfix(2, 1, 6);
|
||||
Require(listener >= 0);
|
||||
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
|
||||
Require(bind_nid_postfix(listener, address.data(), address.size()) == 0);
|
||||
Require(listen_nid_postfix(listener, 4) == 0);
|
||||
std::uint32_t addressSize = address.size();
|
||||
Require(getsockname_nid_postfix(listener, address.data(), &addressSize) == 0);
|
||||
|
||||
KernelEqueue eq = 0;
|
||||
Require(sceKernelCreateEqueue(&eq, "sockets") == SCE_OK);
|
||||
const KernelUseconds shortTimeout = 20000;
|
||||
const KernelUseconds timeout = 1000000;
|
||||
int marker = 0;
|
||||
int count = 0;
|
||||
std::array<KernelEvent, 4> events{};
|
||||
|
||||
Require(sceKernelAddReadEvent(eq, listener, 1, &marker) == SCE_OK);
|
||||
Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &shortTimeout) == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
Require(count == 0);
|
||||
|
||||
const int client = socket_nid_postfix(2, 1, 0);
|
||||
Require(client >= 0);
|
||||
Require(connect_nid_postfix(client, address.data(), address.size()) == 0);
|
||||
Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_OK);
|
||||
Require(count == 1);
|
||||
Require(events[0].ident == static_cast<std::uintptr_t>(listener));
|
||||
Require(events[0].filter == EVFILT_READ);
|
||||
Require(events[0].udata == &marker);
|
||||
const int accepted = accept_nid_postfix(listener, nullptr, nullptr);
|
||||
Require(accepted >= 0);
|
||||
Require(sceKernelDeleteReadEvent(eq, listener) == SCE_OK);
|
||||
Require(sceKernelDeleteReadEvent(eq, listener) == SCE_KERNEL_ERROR_ENOENT);
|
||||
|
||||
Require(sceKernelAddWriteEvent(eq, client, 0, nullptr) == SCE_OK);
|
||||
Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_OK);
|
||||
Require(count == 1 && events[0].filter == EVFILT_WRITE && events[0].data > 0);
|
||||
Require(sceKernelDeleteWriteEvent(eq, client) == SCE_OK);
|
||||
|
||||
Require(sceKernelAddReadEvent(eq, accepted, 1, nullptr) == SCE_OK);
|
||||
const char message[] = "ready";
|
||||
Require(send_nid_postfix(client, message, sizeof(message), 0) == sizeof(message));
|
||||
Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_OK);
|
||||
Require(count == 1 && events[0].ident == static_cast<std::uintptr_t>(accepted));
|
||||
Require(events[0].data == sizeof(message) && (events[0].flags & EV_EOF) == 0);
|
||||
char received[sizeof(message)]{};
|
||||
Require(recv_nid_postfix(accepted, received, sizeof(received), 0) == sizeof(received));
|
||||
Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &shortTimeout) == SCE_KERNEL_ERROR_ETIMEDOUT);
|
||||
|
||||
Require(close_nid_postfix(client) == 0);
|
||||
Require(sceKernelWaitEqueue(eq, events.data(), events.size(), &count, &timeout) == SCE_OK);
|
||||
Require(count == 1 && events[0].data == 0 && (events[0].flags & EV_EOF) != 0);
|
||||
|
||||
KernelEqueue blocking = 0;
|
||||
Require(sceKernelCreateEqueue(&blocking, "blocking") == SCE_OK);
|
||||
const int writer = socket_nid_postfix(2, 1, 0);
|
||||
Require(writer >= 0 && connect_nid_postfix(writer, address.data(), address.size()) == 0);
|
||||
const int reader = accept_nid_postfix(listener, nullptr, nullptr);
|
||||
Require(reader >= 0);
|
||||
Require(sceKernelAddReadEvent(blocking, reader, 1, nullptr) == SCE_OK);
|
||||
std::thread sender([&] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
Require(send_nid_postfix(writer, message, sizeof(message), 0) == sizeof(message));
|
||||
});
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
Require(sceKernelWaitEqueue(blocking, events.data(), events.size(), &count, nullptr) == SCE_OK);
|
||||
sender.join();
|
||||
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
|
||||
Require(count == 1 && events[0].ident == static_cast<std::uintptr_t>(reader) && events[0].data == sizeof(message));
|
||||
Require(recv_nid_postfix(reader, received, sizeof(received), 0) == sizeof(received));
|
||||
|
||||
std::thread waiter([&] {
|
||||
std::array<KernelEvent, 4> woken{};
|
||||
int wokenCount = 0;
|
||||
Require(sceKernelWaitEqueue(blocking, woken.data(), woken.size(), &wokenCount, nullptr) == SCE_OK);
|
||||
Require(wokenCount == 1 && woken[0].filter == EVFILT_WRITE && woken[0].ident == static_cast<std::uintptr_t>(writer));
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
Require(sceKernelAddWriteEvent(blocking, writer, 0, nullptr) == SCE_OK);
|
||||
waiter.join();
|
||||
Require(sceKernelDeleteWriteEvent(blocking, writer) == SCE_OK);
|
||||
|
||||
Require(close_nid_postfix(reader) == 0);
|
||||
Require(sceKernelDeleteReadEvent(blocking, reader) == SCE_KERNEL_ERROR_ENOENT);
|
||||
Require(close_nid_postfix(writer) == 0);
|
||||
Require(sceKernelDeleteEqueue(blocking) == SCE_OK);
|
||||
|
||||
Require(sceKernelAddReadEvent(eq, client, 1, nullptr) == SCE_KERNEL_ERROR_EBADF);
|
||||
Require(Rejects(sceKernelAddReadEvent, eq, 0, 1));
|
||||
Require(Rejects(sceKernelAddWriteEvent, eq, accepted, 64));
|
||||
|
||||
Require(close_nid_postfix(accepted) == 0);
|
||||
Require(close_nid_postfix(listener) == 0);
|
||||
Require(sceKernelDeleteEqueue(eq) == SCE_OK);
|
||||
}
|
||||
|
|
@ -332,6 +332,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [Pending storage-image results](../../core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp) (libSceAgcDriver) are stored when something reads their memory, not when the GPU work that wrote them completes. A title that waits for that work, frees the image and reuses its memory can have the new data overwritten by the late store (#1970). `APS5_STORE_AT_FLIP=1` stores every pending result at each flip; storing them at the title's GPU waits (labels, fences) is unimplemented.
|
||||
- Windows host-pointer imports of the guest mapping itself are compared instead of write-watched: a driver can mark imported pages dirty after the synchronous import window. Shared memory imported through a separate writable alias retains protection-based tracking on the guest views; driver access to the import alias does not disarm those views. [GuestMemory](../../core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp) excludes every overlapping 64 KiB tracker block while its backing is retained. A full block replaced by fresh private backing regains tracking and gets a new generation; protection changes, partial replacements and shared remappings do not restore it. This preserves CPU edits through byte comparisons but disables snapshot reuse and unit shadows for excluded blocks; restoring tracking on import retirement without replacing the backing and measuring the comparison cost remain unimplemented.
|
||||
- [shm_open](../../core/libs/prx/libkernel/File/src/SharedMemory.cpp) (libkernel) creates only anonymous objects (`SHM_ANON`), each backed by a host anonymous file: a `memfd` on Linux, a delete-on-close temporary file on Windows. The flag checks follow FreeBSD 11's `kern_shm_open`; the mode is ignored. Named objects throw. The descriptor cannot be mapped yet: `mmap` accepts only private anonymous mappings and fails a descriptor mapping with `EOPNOTSUPP`, so the known caller (the bundled Mesa in PPSA59322, which truncates the object and maps it `MAP_SHARED`) gets that failure
|
||||
- [sceKernelAddReadEvent and sceKernelAddWriteEvent](../../core/libs/prx/libkernel/Equeue/Equeue.cpp) (libkernel): a write event reports the socket's send buffer size (`SO_SNDBUF`) in `data`, not the free space left in it, as Windows has no query for that space. Only guest sockets are supported; other descriptors and a low watermark above 1 throw
|
||||
- [sceNetSendmsg and sceNetRecvmsg](../../core/libs/prx/libSceNet/Export.cpp) (libSceNet) copy the scatter/gather list through one host buffer and accept only the flags `sceNetSend`/`sceNetRecv` accept. Sending control data throws; a receive never returns control data (`msg_controllen` 0), since no socket option that queues it is supported
|
||||
- [Additional content](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) is not installed or mounted: `sceAppContentAddcontMount` answers `NOT_FOUND` for every entitlement label and `sceAppContentAddcontUnmount` for every mount point, as no add-content mount is ever handed out; `sceAppContentGetAddcontInfo` answers `DRM_NO_ENTITLEMENT` for every label and `sceAppContentGetAddcontInfoList` reports 0 entries
|
||||
- [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done
|
||||
|
|
|
|||
Loading…
Reference in New Issue