Entirely rewrite nvmap to separate global and per device state.

This will be required later for NVDEC/SMMU support and fixes many
significant issues in the previous implementation.
Based off of my 2.0.0/12.0.0 nvdrv REs.
This commit is contained in:
Billy Laws 2021-07-17 17:28:04 +01:00
parent d159605caf
commit 5123be6604
4 changed files with 399 additions and 208 deletions

View File

@ -0,0 +1,144 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "nvmap.h"
namespace skyline::service::nvdrv::core {
NvMap::Handle::Handle(u64 size, Id id) : size(size), alignedSize(size), origSize(size), id(id) {}
PosixResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) {
std::scoped_lock lock(mutex);
// Handles cannot be allocated twice
if (allocated) [[unlikely]]
return PosixResult::NotPermitted;
flags = pFlags;
kind = pKind;
align = pAlign < PAGE_SIZE ? PAGE_SIZE : pAlign;
// This flag is only applicable for handles with an address passed
if (pAddress)
flags.keepUncachedAfterFree = false;
else
throw exception("Mapping nvmap handles without a cpu side address is unimplemented!");
size = util::AlignUp(size, PAGE_SIZE);
alignedSize = util::AlignUp(size, align);
address = pAddress;
// TODO: pin init
allocated = true;
return PosixResult::Success;
}
PosixResult NvMap::Handle::Duplicate(bool internalSession) {
// Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
if (!allocated) [[unlikely]]
return PosixResult::InvalidArgument;
std::scoped_lock lock(mutex);
// If we internally use FromId the duplication tracking of handles won't work accurately due to us not implementing
// per-process handle refs.
if (internalSession)
internalDupes++;
else
dupes++;
return PosixResult::Success;
}
NvMap::NvMap(const DeviceState &state) : state(state) {}
void NvMap::AddHandle(std::shared_ptr<Handle> handle) {
std::scoped_lock lock(handlesLock);
handles.emplace(handle->id, std::move(handle));
}
bool NvMap::TryRemoveHandle(const std::shared_ptr<Handle> &h) {
// No dupes left, we can remove from handle map
if (h->dupes == 0 && h->internalDupes == 0) {
std::scoped_lock lock(handlesLock);
auto it{handles.find(h->id)};
if (it != handles.end())
handles.erase(it);
return true;
} else {
return false;
}
}
PosixResultValue<std::shared_ptr<NvMap::Handle>> NvMap::CreateHandle(u64 size) {
if (!size) [[unlikely]]
return PosixResult::InvalidArgument;
u32 id{nextHandleId.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
auto h{std::make_shared<Handle>(size, id)};
AddHandle(h);
return h;
}
std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
std::scoped_lock lock(handlesLock);
try {
return handles.at(handle);
} catch (std::out_of_range &e) {
return nullptr;
}
}
std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internalSession) {
std::weak_ptr<Handle> hWeak{GetHandle(handle)};
FreeInfo freeInfo;
// We use a weak ptr here so we can tell when the handle has been freed and report that back to guest
if (auto h = hWeak.lock()) {
if (!h) [[unlikely]]
return std::nullopt;
std::scoped_lock lock(h->mutex);
if (internalSession) {
if (--h->internalDupes < 0)
state.logger->Warn("Internal duplicate count inbalance detected!");
} else {
if (--h->dupes < 0) {
state.logger->Warn("User duplicate count inbalance detected!");
} else if (h->dupes == 0) {
// TODO: unpin
}
}
// Try to remove the shared ptr to the handle from the map, if nothing else is using the handle
// then it will now be freed when `h` goes out of scope
if (TryRemoveHandle(h))
state.logger->Debug("Removed nvmap handle: {}", handle);
else
state.logger->Debug("Tried to free nvmap handle: {} but didn't as it still has duplicates", handle);
freeInfo = {
.address = h->address,
.size = h->size,
.wasUncached = h->flags.mapUncached,
};
} else {
return std::nullopt;
}
// Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
if (!hWeak.expired()) {
state.logger->Debug("nvmap handle: {} wasn't freed as it is still in use", handle);
freeInfo.address = 0;
}
return freeInfo;
}
}

View File

@ -0,0 +1,116 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
#include <services/common/result.h>
namespace skyline::service::nvdrv::core {
/**
* @brief The nvmap core class holds the global state for nvmap and provides methods to manage handles
*/
class NvMap {
public:
/**
* @brief A handle to a contiguous block of memory in an application's address space
*/
struct Handle {
std::mutex mutex;
u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
u64 size; //!< Page-aligned size of the memory the handle refers to
u64 alignedSize; //!< `align`-aligned size of the memory the handle refers to
u64 origSize; //!< Original unaligned size of the memory this handle refers to
i32 dupes{1}; //!< How many guest references there are to this handle
i32 internalDupes{0}; //!< How many emulator-internal references there are to this handle
using Id = u32;
Id id; //!< A globally unique identifier for this handle
struct Flags {
bool mapUncached : 1; //!< If the handle should be mapped as uncached
bool _pad0_ : 1;
bool keepUncachedAfterFree : 1; //!< Only applicable when the handle was allocated with a fixed address
bool _pad1_ : 1;
bool _unk0_ : 1; //!< Passed to IOVMM for pins
u32 _pad2_ : 27;
} flags{};
static_assert(sizeof(Flags) == sizeof(u32));
u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to, this can also be in the nvdrv tmem
bool isSharedMemMapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC call
u8 kind{}; //!< Used for memory compression
bool allocated{}; //!< If the handle has been allocated with `Alloc`
Handle(u64 size, Id id);
/**
* @brief Sets up the handle with the given memory config, can allocate memory from the tmem if a 0 address is passed
*/
[[nodiscard]] PosixResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress);
/**
* @brief Increases the dupe counter of the handle for the given session
*/
[[nodiscard]] PosixResult Duplicate(bool internalSession);
/**
* @brief Obtains a pointer to the handle's memory and marks the handle it as having been mapped
*/
u8 *GetPointer() {
if (!address)
throw exception("Cannot get a pointer to the memory of an unallocated handle!");
isSharedMemMapped = true;
return reinterpret_cast<u8 *>(address);
}
};
private:
const DeviceState &state;
std::unordered_map<Handle::Id, std::shared_ptr<Handle>> handles; //!< Main owning map of handles
std::mutex handlesLock; //!< Protects access to `handles`
static constexpr u32 HandleIdIncrement{4}; //!< Each new handle ID is an increment of 4 from the previous
std::atomic<u32> nextHandleId{HandleIdIncrement};
void AddHandle(std::shared_ptr<Handle> handle);
/**
* @brief Removes a handle from the map taking its dupes into account
* @note h->mutex MUST be locked when calling this
* @return If the handle was removed from the map
*/
bool TryRemoveHandle(const std::shared_ptr<Handle> &h);
public:
NvMap(const DeviceState &state);
/**
* @brief Encapsulates the result of a FreeHandle operation
*/
struct FreeInfo {
u64 address; //!< Address the handle referred to before deletion
u64 size; //!< Page-aligned handle size
bool wasUncached; //!< If the handle was allocated as uncached
};
/**
* @brief Creates an unallocated handle of the given size
*/
[[nodiscard]] PosixResultValue<std::shared_ptr<Handle>> CreateHandle(u64 size);
std::shared_ptr<Handle> GetHandle(Handle::Id handle);
/**
* @brief Tries to free a handle and remove a single dupe
* If a handle has no dupes left and has no other users a FreeInfo struct will be returned describing the prior state of the handle.
*/
std::optional<FreeInfo> FreeHandle(Handle::Id handle, bool internalSession);
};
}

View File

@ -1,172 +1,134 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
#include <services/nvdrv/core/nvmap.h>
#include "nvmap.h"
namespace skyline::service::nvdrv::device {
NvMap::NvMapObject::NvMapObject(u32 id, u32 size) : id(id), size(size) {}
NvMap::NvMap(const DeviceState &state, Core &core, const SessionContext &ctx) : NvDevice(state, core, ctx) {}
NvMap::NvMap(const DeviceState &state) : NvDevice(state) {}
NvStatus NvMap::Create(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 size; // In
u32 handle; // Out
} &data = buffer.as<Data>();
std::unique_lock lock(mapMutex);
maps.push_back(std::make_shared<NvMapObject>(idIndex++, data.size));
data.handle = maps.size();
state.logger->Debug("Size: 0x{:X} -> Handle: 0x{:X}", data.size, data.handle);
return NvStatus::Success;
}
NvStatus NvMap::FromId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 id; // In
u32 handle; // Out
} &data = buffer.as<Data>();
std::shared_lock lock(mapMutex);
for (auto it{maps.begin()}; it < maps.end(); it++) {
if ((*it)->id == data.id) {
data.handle = (it - maps.begin()) + 1;
state.logger->Debug("ID: 0x{:X} -> Handle: 0x{:X}", data.id, data.handle);
return NvStatus::Success;
}
PosixResult NvMap::Create(In<u32> size, Out<NvMapCore::Handle::Id> handle) {
auto h{core.nvMap.CreateHandle(util::AlignUp(size, PAGE_SIZE))};
if (h) {
(*h)->origSize = size; // Orig size is the unaligned size
handle = (*h)->id;
}
state.logger->Warn("Handle not found for ID: 0x{:X}", data.id);
return NvStatus::BadValue;
return h;
}
NvStatus NvMap::Alloc(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 handle; // In
u32 heapMask; // In
u32 flags; // In
u32 align; // In
u8 kind; // In
u8 _pad0_[7];
u8 *ptr; // InOut
} &data = buffer.as<Data>();
PosixResult NvMap::FromId(In<NvMapCore::Handle::Id> id, Out<NvMapCore::Handle::Id> handle) {
// Handles and IDs are always the same value in nvmap however IDs can be used globally given the right permissions.
// Since we don't plan on ever supporting multiprocess we can skip implementing handle refs and so this function
// just does simple validation and passes through the handle id.
if (!id) [[unlikely]]
return PosixResult::InvalidArgument;
try {
auto object{GetObject(data.handle)};
object->heapMask = data.heapMask;
object->flags = data.flags;
object->align = data.align;
object->kind = data.kind;
object->ptr = data.ptr;
object->status = NvMapObject::Status::Allocated;
auto h{core.nvMap.GetHandle(id)};
if (!h) [[unlikely]]
return PosixResult::InvalidArgument;
state.logger->Debug("Handle: 0x{:X}, HeapMask: 0x{:X}, Flags: {}, Align: 0x{:X}, Kind: {}, Pointer: 0x{:X}", data.handle, data.heapMask, data.flags, data.align, data.kind, data.ptr);
return NvStatus::Success;
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
return NvStatus::BadParameter;
return h->Duplicate(ctx.internalSession);
}
PosixResult NvMap::Alloc(In<NvMapCore::Handle::Id> handle, In<u32> heapMask, In<NvMapCore::Handle::Flags> flags, InOut<u32> align, In<u8> kind, In<u64> address) {
if (!handle) [[unlikely]]
return PosixResult::InvalidArgument;
if (!std::ispow2(align)) [[unlikely]]
return PosixResult::InvalidArgument;
// Force page size alignment at a minimum
if (align < PAGE_SIZE) [[unlikely]]
align = PAGE_SIZE;
auto h{core.nvMap.GetHandle(handle)};
if (!h) [[unlikely]]
return PosixResult::InvalidArgument;
return h->Alloc(flags, align, kind, address);
}
PosixResult NvMap::Free(In<NvMapCore::Handle::Id> handle, Out<u64> address, Out<u32> size, Out<NvMapCore::Handle::Flags> flags) {
if (!handle) [[unlikely]]
return PosixResult::Success;
if (auto freeInfo{core.nvMap.FreeHandle(handle, ctx.internalSession)}) {
address = freeInfo->address;
size = static_cast<u32>(freeInfo->size);
flags = NvMapCore::Handle::Flags{ .mapUncached = freeInfo->wasUncached };
}
return PosixResult::Success;
}
PosixResult NvMap::Param(In<NvMapCore::Handle::Id> handle, In<HandleParameterType> param, Out<u32> result) {
if (!handle)
return PosixResult::InvalidArgument;
auto h{core.nvMap.GetHandle(handle)};
if (!h) [[unlikely]]
return PosixResult::InvalidArgument;
switch (param) {
case HandleParameterType::Size:
result = h->origSize;
return PosixResult::Success;
case HandleParameterType::Alignment:
result = h->align;
return PosixResult::Success;
case HandleParameterType::Base:
result = -static_cast<i32>(PosixResult::InvalidArgument);
return PosixResult::Success;
case HandleParameterType::Heap:
if (h->allocated)
result = 0x40000000;
else
result = 0;
return PosixResult::Success;
case HandleParameterType::Kind:
result = h->kind;
return PosixResult::Success;
case HandleParameterType::IsSharedMemMapped:
result = h->isSharedMemMapped;
return PosixResult::Success;
default:
return PosixResult::InvalidArgument;
}
}
NvStatus NvMap::Free(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 handle; // In
u32 _pad0_;
u8 *ptr; // Out
u32 size; // Out
u32 flags; // Out
} &data = buffer.as<Data>();
PosixResult NvMap::GetId(Out<NvMapCore::Handle::Id> id, In<NvMapCore::Handle::Id> handle) {
// See the comment in FromId for extra info on this function
if (!handle) [[unlikely]]
return PosixResult::InvalidArgument;
std::unique_lock lock(mapMutex);
try {
auto &object{maps.at(data.handle - 1)};
if (object.use_count() > 1) {
data.ptr = object->ptr;
data.flags = 0x0;
} else {
data.ptr = nullptr;
data.flags = 0x1; // Not free yet
}
auto h{core.nvMap.GetHandle(handle)};
if (!h) [[unlikely]]
return PosixResult::NotPermitted; // This will always return EPERM irrespective of if the handle exists or not
data.size = object->size;
object = nullptr;
state.logger->Debug("Handle: 0x{:X} -> Pointer: 0x{:X}, Size: 0x{:X}, Flags: 0x{:X}", data.handle, data.ptr, data.size, data.flags);
return NvStatus::Success;
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
return NvStatus::BadParameter;
}
id = h->id;
return PosixResult::Success;
}
NvStatus NvMap::Param(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
// https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102
enum class Parameter : u32 {
Size = 1,
Alignment = 2,
Base = 3,
HeapMask = 4,
Kind = 5,
Compr = 6,
};
#include "deserialisation/macro_def.h"
static constexpr u32 NvMapMagic{1};
struct Data {
u32 handle; // In
Parameter parameter; // In
u32 result; // Out
} &data = buffer.as<Data>();
try {
auto object{GetObject(data.handle)};
switch (data.parameter) {
case Parameter::Size:
data.result = object->size;
break;
case Parameter::Alignment:
data.result = object->align;
break;
case Parameter::HeapMask:
data.result = object->heapMask;
break;
case Parameter::Kind:
data.result = object->kind;
break;
case Parameter::Compr:
data.result = 0;
break;
default:
state.logger->Warn("Parameter not implemented: 0x{:X}", data.parameter);
return NvStatus::NotImplemented;
}
state.logger->Debug("Handle: 0x{:X}, Parameter: {} -> Result: 0x{:X}", data.handle, data.parameter, data.result);
return NvStatus::Success;
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
return NvStatus::BadParameter;
}
}
NvStatus NvMap::GetId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 id; // Out
u32 handle; // In
} &data = buffer.as<Data>();
try {
data.id = GetObject(data.handle)->id;
state.logger->Debug("Handle: 0x{:X} -> ID: 0x{:X}", data.handle, data.id);
return NvStatus::Success;
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
return NvStatus::BadParameter;
}
}
IOCTL_HANDLER_FUNC(NvMap, ({
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x1),
Create, ARGS(In<u32>, Out<NvMapCore::Handle::Id>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x3),
FromId, ARGS(In<NvMapCore::Handle::Id>, Out<NvMapCore::Handle::Id>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x20), MAGIC(NvMapMagic), FUNC(0x4),
Alloc, ARGS(In<NvMapCore::Handle::Id>, In<u32>, In<NvMapCore::Handle::Flags>, InOut<u32>, In<u8>, Pad<u8, 0x7>, In<u64>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(NvMapMagic), FUNC(0x5),
Free, ARGS(In<NvMapCore::Handle::Id>, Pad<u32>, Out<u64>, Out<u32>, Out<NvMapCore::Handle::Flags>))
IOCTL_CASE_ARGS(INOUT, SIZE(0xC), MAGIC(NvMapMagic), FUNC(0x9),
Param, ARGS(In<NvMapCore::Handle::Id>, In<HandleParameterType>, Out<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0xE),
GetId, ARGS(Out<NvMapCore::Handle::Id>, In<NvMapCore::Handle::Id>))
}))
#include "deserialisation/macro_undef.h"
}

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
@ -7,91 +7,60 @@
namespace skyline::service::nvdrv::device {
/**
* @brief NvMap (/dev/nvmap) is used to map certain CPU memory as GPU memory (https://switchbrew.org/wiki/NV_services)
* @brief NvMap (/dev/nvmap) is used to keep track of buffers and map them onto the SMMU (https://switchbrew.org/wiki/NV_services)
* @url https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h
*/
class NvMap : public NvDevice {
public:
/**
* @brief NvMapObject is used to hold the state of held objects
*/
struct NvMapObject {
u32 id;
u32 size;
u8 *ptr{};
u32 flags{}; //!< The flag of the memory (0 = Read Only, 1 = Read-Write)
u32 align{};
u32 heapMask{}; //!< This is set during Alloc and returned during Param
u8 kind{}; //!< This is same as heapMask
using NvMapCore = core::NvMap;
enum class Status {
Created, //!< The object has been created but memory has not been allocated
Allocated //!< The object has been allocated
} status{Status::Created}; //!< This holds the status of the object
NvMapObject(u32 id, u32 size);
enum class HandleParameterType : u32 {
Size = 1,
Alignment = 2,
Base = 3,
Heap = 4,
Kind = 5,
IsSharedMemMapped = 6
};
std::shared_mutex mapMutex; //!< Synchronizes mutations and accesses of the mappings
std::vector<std::shared_ptr<NvMapObject>> maps;
u32 idIndex{1}; //!< This is used to keep track of the next ID to allocate
NvMap(const DeviceState &state);
std::shared_ptr<NvMapObject> GetObject(u32 handle) {
if (handle-- == 0)
throw std::out_of_range("0 is an invalid nvmap handle");
std::shared_lock lock(mapMutex);
auto &object{maps.at(handle)};
if (!object)
throw std::out_of_range("A freed nvmap handle was requested");
return object;
}
NvMap(const DeviceState &state, Core &core, const SessionContext &ctx);
/**
* @brief Creates an NvMapObject and returns an handle to it
* @brief Creates an nvmap handle for the given size
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_CREATE
*/
NvStatus Create(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
PosixResult Create(In<u32> size, Out<NvMapCore::Handle::Id> handle);
/**
* @brief Returns the handle of an NvMapObject from its ID
* @brief Creates a new ref to the handle of the given ID
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID
*/
NvStatus FromId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
PosixResult FromId(In<NvMapCore::Handle::Id> id, Out<NvMapCore::Handle::Id> handle);
/**
* @brief Allocates memory for an NvMapObject
* @brief Adds the given backing memory to the nvmap handle
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_ALLOC
*/
NvStatus Alloc(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
PosixResult Alloc(In<NvMapCore::Handle::Id> handle, In<u32> heapMask, In<NvMapCore::Handle::Flags> flags, InOut<u32> align, In<u8> kind, In<u64> address);
/**
* @brief Frees previously allocated memory
* @brief Attempts to free a handle and unpin it from SMMU memory
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FREE
*/
NvStatus Free(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
PosixResult Free(In<NvMapCore::Handle::Id> handle, Out<u64> address, Out<u32> size, Out<NvMapCore::Handle::Flags> flags);
/**
* @brief Returns a particular parameter from an NvMapObject
* @brief Returns info about a property of the nvmap handle
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_PARAM
*/
NvStatus Param(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
PosixResult Param(In<NvMapCore::Handle::Id> handle, In<HandleParameterType> param, Out<u32> result);
/**
* @brief Returns the ID of an NvMapObject from its handle
* @brief Returns a global ID for the given nvmap handle
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID
*/
NvStatus GetId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
PosixResult GetId(Out<NvMapCore::Handle::Id> id, In<NvMapCore::Handle::Id> handle);
NVDEVICE_DECL(
NVFUNC(0x0101, NvMap, Create),
NVFUNC(0x0103, NvMap, FromId),
NVFUNC(0x0104, NvMap, Alloc),
NVFUNC(0x0105, NvMap, Free),
NVFUNC(0x0109, NvMap, Param),
NVFUNC(0x010E, NvMap, GetId)
)
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
};
}