Merge pull request #5211 from Sonicadvance1/46

Linux: Moves DRM LRU FD cache to be per-thread
This commit is contained in:
Ryan Houdek authored and GitHub committed 2026-01-26 13:11:24 -08:00
commit 1b18f85e42
4 files changed
+173 -140

No files matched your search

@@ -11,6 +11,7 @@ $end_info$
#include "Common/SHMStats.h"
#include "LinuxSyscalls/Types.h"
#include "LinuxSyscalls/x32/IoctlEmulation.h"
#include <FEXCore/Config/Config.h>
#include <FEXCore/Core/Context.h>
@@ -125,6 +126,9 @@ struct ThreadStateObject : public FEXCore::Allocator::FEXAllocOperators {
FEXCore::Core::CPUState::gdt_segment gdt[32] {};
size_t ldt_entry_count {};
FEXCore::Core::CPUState::gdt_segment* ldt_entries {};
// 32-bit FD cache for DRM handlers.
fextl::unique_ptr<x32::DRMLRUCacheFDCache> DRMLRUCache {};
};
class ThreadManager final {
@@ -137,7 +137,7 @@ auto fcntlHandler = [](FEXCore::Core::CpuStateFrame* Frame, int fd, int cmd, uin
break;
} break;
case F_DUPFD:
case F_DUPFD_CLOEXEC: FEX::HLE::x32::CheckAndAddFDDuplication(fd, Result); break;
case F_DUPFD_CLOEXEC: FEX::HLE::x32::CheckAndAddFDDuplication(Frame, fd, Result); break;
case F_GETFL: {
Result = FEX::HLE::RemapToX86Flags(Result);
break;
@@ -509,7 +509,7 @@ void RegisterFD(FEX::HLE::SyscallHandler* Handler) {
REGISTER_SYSCALL_IMPL_X32(dup, [](FEXCore::Core::CpuStateFrame* Frame, int oldfd) -> uint64_t {
uint64_t Result = ::dup(oldfd);
if (Result != -1) {
CheckAndAddFDDuplication(oldfd, Result);
CheckAndAddFDDuplication(Frame, oldfd, Result);
}
SYSCALL_ERRNO();
});
@@ -517,7 +517,7 @@ void RegisterFD(FEX::HLE::SyscallHandler* Handler) {
REGISTER_SYSCALL_IMPL_X32(dup2, [](FEXCore::Core::CpuStateFrame* Frame, int oldfd, int newfd) -> uint64_t {
uint64_t Result = ::dup2(oldfd, newfd);
if (Result != -1) {
CheckAndAddFDDuplication(oldfd, newfd);
CheckAndAddFDDuplication(Frame, oldfd, newfd);
}
SYSCALL_ERRNO();
});
@@ -37,14 +37,14 @@ static void UnhandledIoctl(const char* Type, int fd, uint32_t cmd, uint32_t args
}
namespace BasicHandler {
uint32_t BasicHandler(int fd, uint32_t cmd, uint32_t args) {
uint32_t BasicHandler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
uint64_t Result = ::ioctl(fd, cmd, args);
SYSCALL_ERRNO();
}
} // namespace BasicHandler
namespace V4l2 {
uint32_t V4l2Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t V4l2Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
case _IOC_NR(FEX_VIDIOC_G_FMT): {
fex_v4l2_format* format = reinterpret_cast<fex_v4l2_format*>(args);
@@ -277,114 +277,35 @@ namespace V4l2 {
} // namespace V4l2
namespace DRM {
uint32_t AddAndRunHandler(int fd, uint32_t cmd, uint32_t args);
void AssignDeviceTypeToFD(int fd, const drm_version& Version);
uint32_t AddAndRunHandler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args);
void AssignDeviceTypeToFD(FEXCore::Core::CpuStateFrame* Frame, int fd, const drm_version& Version);
template<size_t LRUSize>
class LRUCacheFDCache {
public:
LRUCacheFDCache() {
// Set the last element to our handler
// This element will always be the last one
LRUCache[LRUSize] = LRUObject {-1, AddAndRunHandler};
DRMLRUCacheFDCache::HandlerType FindHandler(FEXCore::Core::CpuStateFrame* Frame, int32_t FD) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (!Thread->DRMLRUCache) {
Thread->DRMLRUCache = fextl::make_unique<DRMLRUCacheFDCache>();
}
using HandlerType = uint32_t (*)(int fd, uint32_t cmd, uint32_t args);
void SetFDHandler(uint32_t FD, HandlerType Handler) {
FDToHandler[FD] = Handler;
}
void DuplicateFD(int fd, int NewFD) {
auto it = FDToHandler.find(fd);
if (it != FDToHandler.end()) {
FDToHandler[NewFD] = it->second;
}
}
HandlerType FindHandler(int32_t FD) {
HandlerType Handler {};
for (size_t i = 0; i < LRUSize; ++i) {
auto& it = LRUCache[i];
if (it.FD == FD) {
if (i == 0) {
// If we are the first in the queue then just return it
return it.Handler;
}
Handler = it.Handler;
break;
}
}
if (Handler) {
AddToFront(FD, Handler);
return Handler;
}
return LRUCache[LRUSize].Handler;
}
uint32_t AddAndRunMapHandler(int fd, uint32_t cmd, uint32_t args) {
// Couldn't find in cache, check map
{
auto it = FDToHandler.find(fd);
if (it != FDToHandler.end()) {
// Found, add to the cache
AddToFront(fd, it->second);
return it->second(fd, cmd, args);
}
}
// Wasn't found in map, query it
drm_version Host_Version {};
Host_Version.name = reinterpret_cast<char*>(alloca(128));
Host_Version.name_len = 128;
uint64_t Result = ioctl(fd, DRM_IOCTL_VERSION, &Host_Version);
// Add it to the map and double check that it was added
// Next time around when the ioctl is used then it will be added to cache
if (Result != -1) {
AssignDeviceTypeToFD(fd, Host_Version);
}
auto it = FDToHandler.find(fd);
if (it == FDToHandler.end()) {
// We don't understand this DRM ioctl
return -EPERM;
}
Result = it->second(fd, cmd, args);
SYSCALL_ERRNO();
}
private:
void AddToFront(int32_t FD, HandlerType Handler) {
// Push the element to the front if we found one
// First copy all the other elements back one
// Ensuring the final element isn't written over
memmove(&LRUCache[1], &LRUCache[0], (LRUSize - 1) * sizeof(LRUCache[0]));
// Now set the first element to the one we just found
LRUCache[0] = LRUObject {FD, Handler};
}
struct LRUObject {
int32_t FD;
HandlerType Handler;
};
// With four elements total (3 + 1) then this is a single cacheline in size
LRUObject LRUCache[LRUSize + 1];
fextl::map<int32_t, HandlerType> FDToHandler;
};
static LRUCacheFDCache<3> FDToHandler;
uint32_t AddAndRunHandler(int fd, uint32_t cmd, uint32_t args) {
return FDToHandler.AddAndRunMapHandler(fd, cmd, args);
return Thread->DRMLRUCache->FindHandler(FD);
}
void CheckAndAddFDDuplication(int fd, int NewFD) {
FDToHandler.DuplicateFD(fd, NewFD);
uint32_t AddAndRunHandler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (!Thread->DRMLRUCache) {
Thread->DRMLRUCache = fextl::make_unique<DRMLRUCacheFDCache>();
}
return Thread->DRMLRUCache->AddAndRunMapHandler(Frame, fd, cmd, args);
}
uint32_t AMDGPU_Handler(int fd, uint32_t cmd, uint32_t args) {
void CheckAndAddFDDuplication(FEXCore::Core::CpuStateFrame* Frame, int fd, int NewFD) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (!Thread->DRMLRUCache) {
Thread->DRMLRUCache = fextl::make_unique<DRMLRUCacheFDCache>();
}
Thread->DRMLRUCache->DuplicateFD(fd, NewFD);
}
uint32_t AMDGPU_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
case _IOC_NR(FEX_DRM_IOCTL_AMDGPU_GEM_METADATA): {
AMDGPU::fex_drm_amdgpu_gem_metadata* val = reinterpret_cast<AMDGPU::fex_drm_amdgpu_gem_metadata*>(args);
@@ -419,7 +340,7 @@ namespace DRM {
return -EPERM;
}
uint32_t RADEON_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t RADEON_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
case _IOC_NR(FEX_DRM_IOCTL_RADEON_CP_INIT): {
RADEON::fex_drm_radeon_init_t* val = reinterpret_cast<RADEON::fex_drm_radeon_init_t*>(args);
@@ -554,7 +475,7 @@ namespace DRM {
return -EPERM;
}
uint32_t MSM_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t MSM_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
case _IOC_NR(FEX_DRM_IOCTL_MSM_WAIT_FENCE): {
MSM::fex_drm_msm_wait_fence* val = reinterpret_cast<MSM::fex_drm_msm_wait_fence*>(args);
@@ -590,7 +511,7 @@ namespace DRM {
return -EPERM;
}
uint32_t Nouveau_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t Nouveau_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
#define _BASIC_META(x) case _IOC_NR(x):
#define _BASIC_META_VAR(x, args...) case _IOC_NR(x):
@@ -616,7 +537,7 @@ namespace DRM {
return -EPERM;
}
uint32_t I915_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t I915_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
#define SIMPLE(enum, type) \
case _IOC_NR(FEX_##enum): { \
I915::fex_##type* guest = reinterpret_cast<I915::fex_##type*>(args); \
@@ -661,7 +582,7 @@ namespace DRM {
return -EPERM;
}
uint32_t Panfrost_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t Panfrost_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
#define _BASIC_META(x) case _IOC_NR(x):
#define _BASIC_META_VAR(x, args...) case _IOC_NR(x):
@@ -686,7 +607,7 @@ namespace DRM {
return -EPERM;
}
uint32_t Lima_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t Lima_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
#define _BASIC_META(x) case _IOC_NR(x):
#define _BASIC_META_VAR(x, args...) case _IOC_NR(x):
@@ -711,7 +632,7 @@ namespace DRM {
return -EPERM;
}
uint32_t VC4_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t VC4_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
case _IOC_NR(FEX_DRM_IOCTL_VC4_PERFMON_GET_VALUES): {
FEX::HLE::x32::VC4::fex_drm_vc4_perfmon_get_values* val = reinterpret_cast<FEX::HLE::x32::VC4::fex_drm_vc4_perfmon_get_values*>(args);
@@ -747,7 +668,7 @@ namespace DRM {
return -EPERM;
}
uint32_t V3D_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t V3D_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
case _IOC_NR(FEX_DRM_IOCTL_V3D_SUBMIT_CSD): {
FEX::HLE::x32::V3D::fex_drm_v3d_submit_csd* val = reinterpret_cast<FEX::HLE::x32::V3D::fex_drm_v3d_submit_csd*>(args);
@@ -783,7 +704,7 @@ namespace DRM {
return -EPERM;
}
uint32_t Virtio_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t Virtio_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
switch (_IOC_NR(cmd)) {
#define _BASIC_META(x) case _IOC_NR(x):
#define _BASIC_META_VAR(x, args...) case _IOC_NR(x):
@@ -808,46 +729,51 @@ namespace DRM {
return -EPERM;
}
uint32_t Default_Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t Default_Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
// Default handler assumes everything is correct and doesn't need to do any work.
uint64_t Result = ::ioctl(fd, cmd, args);
SYSCALL_ERRNO();
}
void AssignDeviceTypeToFD(int fd, const drm_version& Version) {
void AssignDeviceTypeToFD(FEXCore::Core::CpuStateFrame* Frame, int fd, const drm_version& Version) {
if (Version.name) {
auto Thread = FEX::HLE::ThreadManager::GetStateObjectFromCPUState(Frame);
if (!Thread->DRMLRUCache) {
Thread->DRMLRUCache = fextl::make_unique<DRMLRUCacheFDCache>();
}
const std::string_view Name(Version.name, Version.name_len);
if (Name == "amdgpu") {
FDToHandler.SetFDHandler(fd, AMDGPU_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, AMDGPU_Handler);
} else if (Name == "radeon") {
FDToHandler.SetFDHandler(fd, RADEON_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, RADEON_Handler);
} else if (Name == "msm") {
FDToHandler.SetFDHandler(fd, MSM_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, MSM_Handler);
} else if (Name == "nouveau") {
FDToHandler.SetFDHandler(fd, Nouveau_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, Nouveau_Handler);
} else if (Name == "i915") {
FDToHandler.SetFDHandler(fd, I915_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, I915_Handler);
} else if (Name == "panfrost") {
FDToHandler.SetFDHandler(fd, Panfrost_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, Panfrost_Handler);
} else if (Name == "lima") {
FDToHandler.SetFDHandler(fd, Lima_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, Lima_Handler);
} else if (Name == "vc4") {
FDToHandler.SetFDHandler(fd, VC4_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, VC4_Handler);
} else if (Name == "v3d") {
FDToHandler.SetFDHandler(fd, V3D_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, V3D_Handler);
} else if (Name == "virtio_gpu") {
FDToHandler.SetFDHandler(fd, Virtio_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, Virtio_Handler);
} else {
// Known safe drm drivers.
if (!(Name == "asahi" || Name == "panthor" || Name == "xe")) {
LogMan::Msg::IFmt("Unknown DRM device: '{}'. Using default passthrough", Version.name);
}
FDToHandler.SetFDHandler(fd, Default_Handler);
Thread->DRMLRUCache->SetFDHandler(fd, Default_Handler);
}
}
}
uint32_t Handler(int fd, uint32_t cmd, uint32_t args) {
uint32_t Handler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
#define SIMPLE(enum, type) \
case _IOC_NR(FEX_##enum): { \
DRM::fex_##type* guest = reinterpret_cast<DRM::fex_##type*>(args); \
@@ -867,7 +793,7 @@ namespace DRM {
uint64_t Result = ::ioctl(fd, DRM_IOCTL_VERSION, &Host_Version);
if (Result != -1) {
*version = Host_Version;
AssignDeviceTypeToFD(fd, Host_Version);
AssignDeviceTypeToFD(Frame, fd, Host_Version);
}
SYSCALL_ERRNO();
break;
@@ -925,8 +851,8 @@ namespace DRM {
case DRM_COMMAND_BASE ...(DRM_COMMAND_END - 1): {
// This is the space of the DRM device commands
auto it = FDToHandler.FindHandler(fd);
return it(fd, cmd, args);
auto it = FindHandler(Frame, fd);
return it(Frame, fd, cmd, args);
break;
}
default:
@@ -944,13 +870,91 @@ namespace DRM {
}
} // namespace DRM
using HandlerType = uint32_t (*)(int fd, uint32_t cmd, uint32_t args);
DRMLRUCacheFDCache::DRMLRUCacheFDCache() {
// Set the last element to our handler
// This element will always be the last one
LRUCache[LRUSize] = LRUObject {-1, DRM::AddAndRunHandler};
}
std::array<HandlerType, 1U << _IOC_TYPEBITS> Handlers = []() consteval {
void DRMLRUCacheFDCache::SetFDHandler(uint32_t FD, HandlerType Handler) {
FDToHandler[FD] = Handler;
}
void DRMLRUCacheFDCache::DuplicateFD(int fd, int NewFD) {
auto it = FDToHandler.find(fd);
if (it != FDToHandler.end()) {
FDToHandler[NewFD] = it->second;
}
}
DRMLRUCacheFDCache::HandlerType DRMLRUCacheFDCache::FindHandler(int32_t FD) {
HandlerType Handler {};
for (size_t i = 0; i < LRUSize; ++i) {
auto& it = LRUCache[i];
if (it.FD == FD) {
if (i == 0) {
// If we are the first in the queue then just return it
return it.Handler;
}
Handler = it.Handler;
break;
}
}
if (Handler) {
AddToFront(FD, Handler);
return Handler;
}
return LRUCache[LRUSize].Handler;
}
void DRMLRUCacheFDCache::AddToFront(int32_t FD, HandlerType Handler) {
// Push the element to the front if we found one
// First copy all the other elements back one
// Ensuring the final element isn't written over
memmove(&LRUCache[1], &LRUCache[0], (LRUSize - 1) * sizeof(LRUCache[0]));
// Now set the first element to the one we just found
LRUCache[0] = LRUObject {FD, Handler};
}
uint32_t DRMLRUCacheFDCache::AddAndRunMapHandler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args) {
// Couldn't find in cache, check map
{
auto it = FDToHandler.find(fd);
if (it != FDToHandler.end()) {
// Found, add to the cache
AddToFront(fd, it->second);
return it->second(Frame, fd, cmd, args);
}
}
// Wasn't found in map, query it
drm_version Host_Version {};
Host_Version.name = reinterpret_cast<char*>(alloca(128));
Host_Version.name_len = 128;
uint64_t Result = ioctl(fd, DRM_IOCTL_VERSION, &Host_Version);
// Add it to the map and double check that it was added
// Next time around when the ioctl is used then it will be added to cache
if (Result != -1) {
DRM::AssignDeviceTypeToFD(Frame, fd, Host_Version);
}
auto it = FDToHandler.find(fd);
if (it == FDToHandler.end()) {
// We don't understand this DRM ioctl
return -EPERM;
}
Result = it->second(Frame, fd, cmd, args);
SYSCALL_ERRNO();
}
std::array<DRMLRUCacheFDCache::HandlerType, 1U << _IOC_TYPEBITS> Handlers = []() consteval {
using namespace DRM;
using namespace sockios;
using namespace V4l2;
std::array<HandlerType, 1U << _IOC_TYPEBITS> Handlers {};
std::array<DRMLRUCacheFDCache::HandlerType, 1U << _IOC_TYPEBITS> Handlers {};
///< Default fill handlers with BasicHandler.
for (auto& Handler : Handlers) {
@@ -999,10 +1003,10 @@ std::array<HandlerType, 1U << _IOC_TYPEBITS> Handlers = []() consteval {
}();
uint32_t ioctl32(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t request, uint32_t args) {
return Handlers[_IOC_TYPE(request)](fd, request, args);
return Handlers[_IOC_TYPE(request)](Frame, fd, request, args);
}
void CheckAndAddFDDuplication(int fd, int NewFD) {
DRM::CheckAndAddFDDuplication(fd, NewFD);
void CheckAndAddFDDuplication(FEXCore::Core::CpuStateFrame* Frame, int fd, int NewFD) {
DRM::CheckAndAddFDDuplication(Frame, fd, NewFD);
}
} // namespace FEX::HLE::x32
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: MIT
#pragma once
#include <FEXCore/fextl/map.h>
#include <cstdint>
namespace FEXCore::Core {
@@ -7,6 +9,29 @@ struct CpuStateFrame;
}
namespace FEX::HLE::x32 {
class DRMLRUCacheFDCache final {
public:
using HandlerType = uint32_t (*)(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args);
DRMLRUCacheFDCache();
void SetFDHandler(uint32_t FD, HandlerType Handler);
void DuplicateFD(int fd, int NewFD);
HandlerType FindHandler(int32_t FD);
uint32_t AddAndRunMapHandler(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t cmd, uint32_t args);
protected:
constexpr static size_t LRUSize = 3;
void AddToFront(int32_t FD, HandlerType Handler);
struct LRUObject {
int32_t FD;
HandlerType Handler;
};
// With four elements total (3 + 1) then this is a single cacheline in size
LRUObject LRUCache[LRUSize + 1];
fextl::map<int32_t, HandlerType> FDToHandler;
};
uint32_t ioctl32(FEXCore::Core::CpuStateFrame* Frame, int fd, uint32_t request, uint32_t args);
void CheckAndAddFDDuplication(int fd, int NewFD);
void CheckAndAddFDDuplication(FEXCore::Core::CpuStateFrame* Frame, int fd, int NewFD);
} // namespace FEX::HLE::x32