AMMediaboard: Emulate AMMBCommandSelect using poll/WSAPoll.
This commit is contained in:
1 parent
f25c467154
commit
5f6cd84ad7
1 file changed
+104
-92
@@ -38,6 +38,8 @@
|
|||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "Common/UnixUtil.h"
|
||||||
|
|
||||||
static constexpr auto* closesocket = close;
|
static constexpr auto* closesocket = close;
|
||||||
static auto ioctlsocket(auto... args)
|
static auto ioctlsocket(auto... args)
|
||||||
{
|
{
|
||||||
@@ -48,6 +50,7 @@ static constexpr int WSAEWOULDBLOCK = 10035;
|
|||||||
static constexpr int SOCKET_ERROR = -1;
|
static constexpr int SOCKET_ERROR = -1;
|
||||||
|
|
||||||
using SOCKET = int;
|
using SOCKET = int;
|
||||||
|
using WSAPOLLFD = pollfd;
|
||||||
|
|
||||||
static constexpr SOCKET INVALID_SOCKET = SOCKET(~0);
|
static constexpr SOCKET INVALID_SOCKET = SOCKET(~0);
|
||||||
|
|
||||||
@@ -471,96 +474,24 @@ u8* InitDIMM(u32 size)
|
|||||||
return s_dimm_disc.data();
|
return s_dimm_disc.data();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also updates the nfds value.
|
static int PlatformPoll(std::span<WSAPOLLFD> pfds, std::chrono::milliseconds timeout)
|
||||||
static fd_set GetHostFdSetFromGuestFdSet(SOCKET* nfds_ptr, int guest_nfds,
|
|
||||||
const void* guest_fds_ptr)
|
|
||||||
{
|
{
|
||||||
fd_set result_fds;
|
#if defined(_WIN32)
|
||||||
FD_ZERO(&result_fds);
|
return WSAPoll(pfds.data(), ULONG(pfds.size()), INT(timeout.count()));
|
||||||
|
#else
|
||||||
if (guest_fds_ptr == nullptr)
|
return UnixUtil::RetryOnEINTR(poll, pfds.data(), pfds.size(), timeout.count());
|
||||||
return result_fds;
|
#endif
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len)
|
static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, socklen_t* len)
|
||||||
{
|
{
|
||||||
GuestFdSet readfds;
|
WSAPOLLFD pfds[1]{{.fd = GetHostSocket(guest_socket), .events = POLLIN}};
|
||||||
readfds.SetFd(guest_socket);
|
|
||||||
|
|
||||||
timeval timeout{
|
constexpr auto timeout = std::chrono::milliseconds{10};
|
||||||
.tv_usec = 10000, // 10 milliseconds
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
const auto client_sock = accept_(GetHostSocket(guest_socket), addr, len);
|
||||||
if (client_sock == INVALID_GUEST_SOCKET)
|
if (client_sock == INVALID_GUEST_SOCKET)
|
||||||
@@ -581,7 +512,7 @@ static GuestSocket NetDIMMAccept(GuestSocket guest_socket, sockaddr* addr, sockl
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: select() failed in NetDIMMAccept ({})",
|
ERROR_LOG_FMT(AMMEDIABOARD, "GC-AM: poll() failed in NetDIMMAccept ({})",
|
||||||
Common::StrNetworkError());
|
Common::StrNetworkError());
|
||||||
s_last_error = SOCKET_ERROR;
|
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;
|
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)
|
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];
|
const u32 readfds_offset = s_media_buffer_32[parameter_offset + 1];
|
||||||
auto* const guest_readfds_ptr =
|
auto* const guest_readfds_ptr =
|
||||||
@@ -845,22 +824,55 @@ static void AMMBCommandSelect(u32 parameter_offset, u32 network_buffer_base)
|
|||||||
auto* const guest_timeout_ptr =
|
auto* const guest_timeout_ptr =
|
||||||
GetSafePtr(s_network_command_buffer, network_buffer_base, timeout_offset, sizeof(TimeVal));
|
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)
|
if (guest_timeout_ptr != nullptr)
|
||||||
{
|
{
|
||||||
TimeVal guest_timeout;
|
TimeVal guest_timeout;
|
||||||
std::memcpy(&guest_timeout, guest_timeout_ptr, sizeof(guest_timeout));
|
std::memcpy(&guest_timeout, guest_timeout_ptr, sizeof(guest_timeout));
|
||||||
|
|
||||||
timeout.tv_sec = guest_timeout.seconds;
|
timeout = duration_cast<std::chrono::milliseconds>(
|
||||||
timeout.tv_usec = guest_timeout.microseconds;
|
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} ) {}:{}",
|
if (timeout < std::chrono::milliseconds{})
|
||||||
guest_nfds, readfds_offset, writefds_offset, exceptfds_offset, timeout_offset,
|
{
|
||||||
guest_timeout_ptr ? timeout.tv_sec : 0, guest_timeout_ptr ? timeout.tv_usec : 0);
|
// 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,
|
NOTICE_LOG_FMT(
|
||||||
guest_exceptfds_ptr, (guest_timeout_ptr ? &timeout : nullptr));
|
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);
|
NOTICE_LOG_FMT(AMMEDIABOARD_NET, "GC-AM: select result: {}", ret);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user