AMMediaboard: Emulate AMMBCommandSelect using poll/WSAPoll.

This commit is contained in:
Jordan Woyak
2026-02-05 16:40:38 -06:00
parent f25c467154
commit 5f6cd84ad7

View File

@@ -38,6 +38,8 @@
#include <unistd.h>
#include "Common/UnixUtil.h"
static constexpr auto* closesocket = close;
static auto ioctlsocket(auto... args)
{
@@ -48,6 +50,7 @@ static constexpr int WSAEWOULDBLOCK = 10035;
static constexpr int SOCKET_ERROR = -1;
using SOCKET = int;
using WSAPOLLFD = pollfd;
static constexpr SOCKET INVALID_SOCKET = SOCKET(~0);
@@ -471,96 +474,24 @@ u8* InitDIMM(u32 size)
return s_dimm_disc.data();
}
// Also updates the nfds value.
static fd_set GetHostFdSetFromGuestFdSet(SOCKET* nfds_ptr, int guest_nfds,
const void* guest_fds_ptr)
static int PlatformPoll(std::span<WSAPOLLFD> pfds, std::chrono::milliseconds timeout)
{
fd_set result_fds;
FD_ZERO(&result_fds);
if (guest_fds_ptr == nullptr)
return result_fds;
GuestFdSet guest_fds;
std::memcpy(&guest_fds, guest_fds_ptr, sizeof(guest_fds));
auto nfds = *nfds_ptr;
for (int guest_fd = FIRST_VALID_FD; guest_fd < guest_nfds; ++guest_fd)
{
if (!guest_fds.IsFdSet(GuestSocket(guest_fd)))
continue;
const auto host_socket = GetHostSocket(GuestSocket(guest_fd));
if (host_socket == INVALID_SOCKET)
{
ERROR_LOG_FMT(AMMEDIABOARD, "GetHostFdSetFromGuestFdSet: INVALID_SOCKET");
continue;
}
if (host_socket >= FD_SETSIZE)
{
ERROR_LOG_FMT(AMMEDIABOARD, "GetHostFdSetFromGuestFdSet: host_socket >= FD_SETSIZE");
continue;
}
FD_SET(host_socket, &result_fds);
nfds = std::max(nfds, host_socket + 1);
}
*nfds_ptr = nfds;
return result_fds;
}
static void WriteGuestFdSetFromHostFdSet(void* guest_fds_ptr, int nfds, const fd_set* fds)
{
if (guest_fds_ptr == nullptr)
return;
GuestFdSet guest_fds;
for (int fd = 0; fd < nfds; ++fd)
{
if (!FD_ISSET(fd, fds))
continue;
guest_fds.SetFd(GetGuestSocket(fd));
}
std::memcpy(guest_fds_ptr, &guest_fds, sizeof(guest_fds));
}
static auto EmulateSelect(int guest_nfds, void* guest_readfds_ptr, void* guest_writefds_ptr,
void* guest_exceptfds_ptr, timeval* timeout)
{
// TODO: Emulate using poll or similar on Linux.
SOCKET nfds = 0;
fd_set readfds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_readfds_ptr);
fd_set writefds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_writefds_ptr);
fd_set exceptfds = GetHostFdSetFromGuestFdSet(&nfds, guest_nfds, guest_exceptfds_ptr);
const auto result = select(nfds, &readfds, &writefds, &exceptfds, timeout);
WriteGuestFdSetFromHostFdSet(guest_readfds_ptr, nfds, &readfds);
WriteGuestFdSetFromHostFdSet(guest_writefds_ptr, nfds, &writefds);
WriteGuestFdSetFromHostFdSet(guest_exceptfds_ptr, nfds, &exceptfds);
return result;
#if defined(_WIN32)
return WSAPoll(pfds.data(), ULONG(pfds.size()), INT(timeout.count()));
#else
return UnixUtil::RetryOnEINTR(poll, pfds.data(), pfds.size(), timeout.count());
#endif
}
static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len)
{
GuestFdSet readfds;
readfds.SetFd(guest_socket);
WSAPOLLFD pfds[1]{{.fd = GetHostSocket(guest_socket), .events = POLLIN}};
timeval timeout{
.tv_usec = 10000, // 10 milliseconds
};
constexpr auto timeout = std::chrono::milliseconds{10};
const int result = EmulateSelect(SOCKET(guest_socket) + 1, &readfds, nullptr, nullptr, &timeout);
const int result = PlatformPoll(pfds, timeout);
if (result > 0 && readfds.IsFdSet(guest_socket))
if (result > 0 && (pfds[0].revents & POLLIN) != 0)
{
const auto client_sock = accept_(GetHostSocket(guest_socket), addr, len);
if (client_sock == INVALID_GUEST_SOCKET)
@@ -581,7 +512,7 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl
}
else
{
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: select() failed in NetDIMMAccept ({})",
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMAccept ({})",
Common::StrNetworkError());
s_last_error = SOCKET_ERROR;
}
@@ -825,9 +756,57 @@ static void AMMBCommandConnect(u32 parameter_offset, u32 network_buffer_base)
s_media_buffer_32[1] = ret;
}
// Expects a pointer to a GuestFdSet or nullptr.
static void FillPollFdsFromGuestFdSet(std::span<WSAPOLLFD> pfds, const void* guest_fds_ptr,
short requested_events)
{
if (guest_fds_ptr == nullptr)
return;
GuestFdSet guest_fds;
std::memcpy(&guest_fds, guest_fds_ptr, sizeof(guest_fds));
u32 index = 0;
for (auto& pfd : pfds)
{
const auto guest_socket = GuestSocket(index);
if (guest_fds.IsFdSet(guest_socket))
{
pfd.fd = GetHostSocket(guest_socket);
pfd.events |= requested_events;
}
++index;
}
}
// Expects a pointer to a GuestFdSet or nullptr.
static void WriteGuestFdSetFromPollFds(void* guest_fds_ptr, std::span<const WSAPOLLFD> fds,
short returned_events)
{
if (guest_fds_ptr == nullptr)
return;
GuestFdSet guest_fds;
for (const auto& fd : fds)
{
if ((fd.revents & returned_events) == 0)
continue;
const auto guest_socket = GetGuestSocket(fd.fd);
if (guest_socket == INVALID_GUEST_SOCKET)
continue;
guest_fds.SetFd(guest_socket);
}
std::memcpy(guest_fds_ptr, &guest_fds, sizeof(guest_fds));
}
static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base)
{
const auto guest_nfds = int(s_media_buffer_32[parameter_offset]);
u32 nfds = int(s_media_buffer_32[parameter_offset]);
const u32 readfds_offset = s_media_buffer_32[parameter_offset + 1];
auto* const guest_readfds_ptr =
@@ -845,22 +824,55 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base)
auto* const guest_timeout_ptr =
GetSafePtr(s_network_command_buffer, network_buffer_base, timeout_offset, sizeof(TimeVal));
timeval timeout{};
if (nfds > std::size(s_sockets))
{
ERROR_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Unexpected nfds: {}", nfds);
nfds = std::size(s_sockets);
}
std::chrono::milliseconds timeout{-1};
if (guest_timeout_ptr != nullptr)
{
TimeVal guest_timeout;
std::memcpy(&guest_timeout, guest_timeout_ptr, sizeof(guest_timeout));
timeout.tv_sec = guest_timeout.seconds;
timeout.tv_usec = guest_timeout.microseconds;
timeout = duration_cast<std::chrono::milliseconds>(
std::chrono::seconds(guest_timeout.seconds) +
std::chrono::microseconds(guest_timeout.microseconds));
}
NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) {}:{}",
guest_nfds, readfds_offset, writefds_offset, exceptfds_offset, timeout_offset,
guest_timeout_ptr ? timeout.tv_sec : 0, guest_timeout_ptr ? timeout.tv_usec : 0);
if (timeout < std::chrono::milliseconds{})
{
// TODO: We should have a way to break out of any timeout on shutdown.
// e.g. include a "wakeup" socket in each `poll` call.
WARN_LOG_FMT(AMMEDIABOARD, "AMMBCommandSelect: Infinite timout!");
}
const int ret = EmulateSelect(guest_nfds, guest_readfds_ptr, guest_writefds_ptr,
guest_exceptfds_ptr, (guest_timeout_ptr ? &timeout : nullptr));
NOTICE_LOG_FMT(
AMMEDIABOARD_NET, "GC-AM: select( {}, 0x{:08x} 0x{:08x} 0x{:08x} 0x{:08x} ) timeout={}", nfds,
readfds_offset, writefds_offset, exceptfds_offset, timeout_offset, timeout.count());
// Fill with the host sockets for each guest socket less-than `nfds` in each GuestFdSet.
std::vector<WSAPOLLFD> pollfds(nfds, WSAPOLLFD{.fd = INVALID_SOCKET});
FillPollFdsFromGuestFdSet(pollfds, guest_readfds_ptr, POLLIN);
FillPollFdsFromGuestFdSet(pollfds, guest_writefds_ptr, POLLOUT);
FillPollFdsFromGuestFdSet(pollfds, guest_exceptfds_ptr, POLLPRI);
// Erase "INVALID" entries and also entries that weren't in any GuestFdSet.
std::erase_if(pollfds, [](const WSAPOLLFD& fd) { return fd.fd == INVALID_SOCKET; });
// TODO: There may be some edge cases where
// poll's (POLLIN,POLLOUT,POLLPRI) don't map 1:1 with select's (readfds,writefds,exceptfds).
const int ret = PlatformPoll(pollfds, timeout);
if (ret >= 0)
{
WriteGuestFdSetFromPollFds(guest_readfds_ptr, pollfds, POLLIN);
WriteGuestFdSetFromPollFds(guest_writefds_ptr, pollfds, POLLOUT);
WriteGuestFdSetFromPollFds(guest_exceptfds_ptr, pollfds, POLLPRI);
}
NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret);