Rework the nvdrv core for accuracy and support new deserialisation

Devices will need to be moved over in order to support this new
interface, IOCTL2/3 support will be added in a later commit.
This commit is contained in:
Billy Laws 2021-07-17 17:13:15 +01:00
parent 01d58dee27
commit f6a7ccf7eb
9 changed files with 300 additions and 300 deletions

View File

@ -1,4 +1,4 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h> #include <kernel/types/KProcess.h>
@ -6,72 +6,72 @@
#include "driver.h" #include "driver.h"
#include "devices/nvdevice.h" #include "devices/nvdevice.h"
#define NVRESULT(x) [&response](NvResult err) { response.Push<NvResult>(err); return Result{}; }(x)
namespace skyline::service::nvdrv { namespace skyline::service::nvdrv {
INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager) : driver(nvdrv::driver.expired() ? std::make_shared<Driver>(state) : nvdrv::driver.lock()), BaseService(state, manager) { INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms) : BaseService(state, manager), driver(driver), ctx(SessionContext{.perms = perms}) {}
if (nvdrv::driver.expired())
nvdrv::driver = driver;
}
Result INvDrvServices::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto path{request.inputBuf.at(0).as_string()}; constexpr FileDescriptor SessionFdLimit{sizeof(u64) * 2 * 8}; //!< Nvdrv uses two 64 bit variables to store a bitset
response.Push<u32>(driver->OpenDevice(path)); auto path{request.inputBuf.at(0).as_string(true)};
response.Push(device::NvStatus::Success); if (path.empty() || nextFdIndex == SessionFdLimit) {
response.Push<FileDescriptor>(InvalidFileDescriptor);
return NVRESULT(NvResult::FileOperationFailed);
}
return {}; if (auto err{driver.OpenDevice(path, nextFdIndex, ctx)}; err != NvResult::Success) {
response.Push<FileDescriptor>(InvalidFileDescriptor);
return NVRESULT(err);
}
response.Push(nextFdIndex++);
return NVRESULT(NvResult::Success);
} }
Result INvDrvServices::Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()}; auto fd{request.Pop<FileDescriptor>()};
auto cmd{request.Pop<u32>()}; auto ioctl{request.Pop<IoctlDescriptor>()};
auto device{driver->GetDevice(fd)}; auto inBuf{request.inputBuf.at(0)};
auto outBuf{request.outputBuf.at(0)};
// Strip the permissions from the command leaving only the ID if (ioctl.in && inBuf.size() < ioctl.size)
cmd &= 0xFFFF; return NVRESULT(NvResult::InvalidSize);
span<u8> buffer{}; if (ioctl.out && outBuf.size() < ioctl.size)
if (request.inputBuf.empty() || request.outputBuf.empty()) { return NVRESULT(NvResult::InvalidSize);
if (!request.inputBuf.empty())
buffer = request.inputBuf.at(0); if (ioctl.in && ioctl.out) {
else if (!request.outputBuf.empty()) if (outBuf.size() < inBuf.size())
buffer = request.outputBuf.at(0); return NVRESULT(NvResult::InvalidSize);
else
throw exception("No IOCTL Buffers"); // Copy in buf to out buf for inout ioctls to avoid needing to pass around two buffers everywhere
} else if (request.inputBuf[0].data() == request.outputBuf[0].data()) { if (outBuf.data() != inBuf.data())
if (request.inputBuf[0].size() >= request.outputBuf[0].size()) outBuf.copy_from(inBuf, ioctl.size);
buffer = request.inputBuf[0];
else
buffer = request.outputBuf[0];
} else {
throw exception("IOCTL Input Buffer (0x{:X}) != Output Buffer (0x{:X})", request.inputBuf[0].data(), request.outputBuf[0].data());
} }
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl, buffer, {})); return NVRESULT(driver.Ioctl(fd, ioctl, ioctl.out ? outBuf : inBuf));
return {};
} }
Result INvDrvServices::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()}; auto fd{request.Pop<u32>()};
state.logger->Debug("Closing NVDRV device ({})", fd); state.logger->Debug("Closing NVDRV device ({})", fd);
driver->CloseDevice(fd); driver.CloseDevice(fd);
response.Push(device::NvStatus::Success); return NVRESULT(NvResult::Success);
return {};
} }
Result INvDrvServices::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(device::NvStatus::Success); return NVRESULT(NvResult::Success);
return {};
} }
Result INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()}; auto fd{request.Pop<u32>()};
auto eventId{request.Pop<u32>()}; auto eventId{request.Pop<u32>()};
auto device{driver->GetDevice(fd)}; auto event{driver.QueryEvent(fd, eventId)};
auto event{device->QueryEvent(eventId)};
if (event != nullptr) { if (event != nullptr) {
auto handle{state.process->InsertItem<type::KEvent>(event)}; auto handle{state.process->InsertItem<type::KEvent>(event)};
@ -79,70 +79,16 @@ namespace skyline::service::nvdrv {
state.logger->Debug("FD: {}, Event ID: {}, Handle: 0x{:X}", fd, eventId, handle); state.logger->Debug("FD: {}, Event ID: {}, Handle: 0x{:X}", fd, eventId, handle);
response.copyHandles.push_back(handle); response.copyHandles.push_back(handle);
response.Push(device::NvStatus::Success); return NVRESULT(NvResult::Success);
} else { } else {
response.Push(device::NvStatus::BadValue); return NVRESULT(NvResult::BadValue);
} }
return {};
} }
Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(device::NvStatus::Success); return NVRESULT(NvResult::Success);
return {};
} }
Result INvDrvServices::Ioctl2(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()};
auto cmd{request.Pop<u32>()};
auto device{driver->GetDevice(fd)};
// Strip the permissions from the command leaving only the ID
cmd &= 0xFFFF;
if (request.inputBuf.size() < 2 || request.outputBuf.empty())
throw exception("Inadequate amount of buffers for IOCTL2: I - {}, O - {}", request.inputBuf.size(), request.outputBuf.size());
else if (request.inputBuf[0].data() != request.outputBuf[0].data())
throw exception("IOCTL2 Input Buffer (0x{:X}) != Output Buffer (0x{:X}) [Input Buffer #2: 0x{:X}]", request.inputBuf[0].data(), request.outputBuf[0].data(), request.inputBuf[1].data());
span<u8> buffer{};
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
buffer = request.inputBuf[0];
else
buffer = request.outputBuf[0];
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl2, buffer, request.inputBuf[1]));
return {};
}
Result INvDrvServices::Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()};
auto cmd{request.Pop<u32>()};
auto device{driver->GetDevice(fd)};
// Strip the permissions from the command leaving only the ID
cmd &= 0xFFFF;
if (request.inputBuf.empty() || request.outputBuf.empty()) {
throw exception("Inadequate amount of buffers for IOCTL3: I - {}, O - {}", request.inputBuf.size(), request.outputBuf.size());
} else if (request.inputBuf[0].data() != request.outputBuf[0].data()) {
if (request.outputBuf.size() >= 2)
throw exception("IOCTL3 Input Buffer (0x{:X}) != Output Buffer (0x{:X}) [Output Buffer #2: 0x{:X}]", request.inputBuf[0].data(), request.outputBuf[0].data(), request.outputBuf[1].data());
else
throw exception("IOCTL3 Input Buffer (0x{:X}) != Output Buffer (0x{:X})", request.inputBuf[0].data(), request.outputBuf[0].data());
}
span<u8> buffer{};
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
buffer = request.inputBuf[0];
else
buffer = request.outputBuf[0];
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl3, buffer, request.outputBuf.size() >= 2 ? request.outputBuf[1] : span<u8>()));
return {};
}
Result INvDrvServices::SetGraphicsFirmwareMemoryMarginEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { Result INvDrvServices::SetGraphicsFirmwareMemoryMarginEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return {}; return {};

View File

@ -1,9 +1,10 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once #pragma once
#include <services/serviceman.h> #include <services/serviceman.h>
#include "types.h"
namespace skyline::service::nvdrv { namespace skyline::service::nvdrv {
class Driver; class Driver;
@ -14,10 +15,13 @@ namespace skyline::service::nvdrv {
*/ */
class INvDrvServices : public BaseService { class INvDrvServices : public BaseService {
private: private:
std::shared_ptr<Driver> driver; Driver &driver; //!< The global nvdrv driver this session accesses
SessionContext ctx; //!< Session specific context
FileDescriptor nextFdIndex{1}; //!< The index for the next allocated file descriptor
public: public:
INvDrvServices(const DeviceState &state, ServiceManager &manager); INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms);
/** /**
* @brief Open a specific device and return a FD * @brief Open a specific device and return a FD
@ -59,13 +63,13 @@ namespace skyline::service::nvdrv {
* @brief Perform an IOCTL on the specified FD with an extra input buffer * @brief Perform an IOCTL on the specified FD with an extra input buffer
* @url https://switchbrew.org/wiki/NV_services#Ioctl2 * @url https://switchbrew.org/wiki/NV_services#Ioctl2
*/ */
Result Ioctl2(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); // Result Ioctl2(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/** /**
* @brief Perform an IOCTL on the specified FD with an extra output buffer * @brief Perform an IOCTL on the specified FD with an extra output buffer
* @url https://switchbrew.org/wiki/NV_services#Ioctl3 * @url https://switchbrew.org/wiki/NV_services#Ioctl3
*/ */
Result Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); // Result Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/** /**
* @brief Enables the graphics firmware memory margin * @brief Enables the graphics firmware memory margin
@ -80,8 +84,7 @@ namespace skyline::service::nvdrv {
SFUNC(0x3, INvDrvServices, Initialize), SFUNC(0x3, INvDrvServices, Initialize),
SFUNC(0x4, INvDrvServices, QueryEvent), SFUNC(0x4, INvDrvServices, QueryEvent),
SFUNC(0x8, INvDrvServices, SetAruid), SFUNC(0x8, INvDrvServices, SetAruid),
SFUNC(0xB, INvDrvServices, Ioctl2),
SFUNC(0xC, INvDrvServices, Ioctl3),
SFUNC(0xD, INvDrvServices, SetGraphicsFirmwareMemoryMarginEnabled) SFUNC(0xD, INvDrvServices, SetGraphicsFirmwareMemoryMarginEnabled)
) )
}; };

View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "nvmap.h"
#include "syncpoint_manager.h"
namespace skyline::service::nvdrv {
/**
* @brief Holds the global state of nvdrv
*/
struct Core {
core::NvMap nvMap;
core::SyncpointManager syncpointManager;
Core(const DeviceState &state) : nvMap(state), syncpointManager(state) {}
};
}

View File

@ -1,11 +1,12 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <cxxabi.h> #include <cxxabi.h>
#include <common/trace.h>
#include "nvdevice.h" #include "nvdevice.h"
namespace skyline::service::nvdrv::device { namespace skyline::service::nvdrv::device {
NvDevice::NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx) : state(state), core(core), ctx(ctx) {}
const std::string &NvDevice::GetName() { const std::string &NvDevice::GetName() {
if (name.empty()) { if (name.empty()) {
auto mangledName{typeid(*this).name()}; auto mangledName{typeid(*this).name()};
@ -18,32 +19,4 @@ namespace skyline::service::nvdrv::device {
} }
return name; return name;
} }
NvStatus NvDevice::HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
std::string_view typeString{[type] {
switch (type) {
case IoctlType::Ioctl:
return "IOCTL";
case IoctlType::Ioctl2:
return "IOCTL2";
case IoctlType::Ioctl3:
return "IOCTL3";
}
}()};
NvDeviceFunctionDescriptor function;
try {
function = GetIoctlFunction(cmd);
state.logger->DebugNoPrefix("{}: {}", typeString, function.name);
} catch (std::out_of_range &) {
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", GetName(), cmd);
return NvStatus::NotImplemented;
}
TRACE_EVENT("service", perfetto::StaticString{function.name});
try {
return function(type, buffer, inlineBuffer);
} catch (const std::exception &e) {
throw exception("{} ({}: {})", e.what(), typeString, function.name);
}
}
} }

View File

@ -1,66 +1,19 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once #pragma once
#include <kernel/ipc.h> #include <kernel/ipc.h>
#include <kernel/types/KEvent.h> #include <kernel/types/KEvent.h>
#include <services/common/result.h>
#include <services/nvdrv/types.h>
#include <services/nvdrv/core/core.h>
#define NV_STRINGIFY(string) #string #include "deserialisation/types.h"
#define NVFUNC(id, Class, Function) std::pair<u32, std::pair<NvStatus(Class::*)(IoctlType, span<u8>, span<u8>), const char*>>{id, {&Class::Function, NV_STRINGIFY(Class::Function)}}
#define NVDEVICE_DECL_AUTO(name, value) decltype(value) name = value
#define NVDEVICE_DECL(...) \
NVDEVICE_DECL_AUTO(functions, frz::make_unordered_map({__VA_ARGS__})); \
NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) override { \
auto& function{functions.at(id)}; \
return NvDeviceFunctionDescriptor{ \
reinterpret_cast<DerivedDevice*>(this), \
reinterpret_cast<decltype(NvDeviceFunctionDescriptor::function)>(function.first), \
function.second \
}; \
}
namespace skyline::service::nvdrv::device { namespace skyline::service::nvdrv::device {
using namespace kernel; using namespace kernel;
using namespace deserialisation;
/**
* @brief All the possible error codes returned by the Nvidia driver
* @url https://switchbrew.org/wiki/NV_services#Errors
*/
enum class NvStatus : u32 {
Success = 0x0, //!< The operation has succeeded
NotImplemented = 0x1, //!< The operation is not implemented
NotSupported = 0x2, //!< The operation is not supported
NotInitialized = 0x3, //!< The operation uses an uninitialized object
BadParameter = 0x4, //!< The operation was provided a bad parameter
Timeout = 0x5, //!< The operation has timed out
InsufficientMemory = 0x6, //!< The device ran out of memory during the operation
ReadOnlyAttribute = 0x7, //!< The mutating operation was performed on a read only section
InvalidState = 0x8, //!< The state of the device was invalid
InvalidAddress = 0x9, //!< The provided address is invalid
InvalidSize = 0xA, //!< The provided size is invalid
BadValue = 0xB, //!< The operation was provided a bad value
AlreadyAllocated = 0xD, //!< An object was tried to be reallocated
Busy = 0xE, //!< The device is busy
ResourceError = 0xF, //!< There was an error accessing the resource
CountMismatch = 0x10, //!< ?
SharedMemoryTooSmall = 0x1000, //!< The shared memory segment is too small
FileOperationFailed = 0x30003, //!< The file operation has failed
DirOperationFailed = 0x30004, //!< The directory operation has failed
IoctlFailed = 0x3000F, //!< The IOCTL operation has failed
AccessDenied = 0x30010, //!< The access to a resource was denied
FileNotFound = 0x30013, //!< A file was not found
ModuleNotPresent = 0xA000E, //!< A module was not present
};
/**
* @brief The IOCTL call variants, they have different buffer configurations
*/
enum class IoctlType : u8 {
Ioctl, //!< 1 input/output buffer
Ioctl2, //!< 1 input/output buffer + 1 input buffer
Ioctl3, //!< 1 input/output buffer + 1 output buffer
};
/** /**
* @brief NvDevice is the base class that all /dev/nv* devices inherit from * @brief NvDevice is the base class that all /dev/nv* devices inherit from
@ -71,40 +24,25 @@ namespace skyline::service::nvdrv::device {
protected: protected:
const DeviceState &state; const DeviceState &state;
Core &core;
class DerivedDevice; //!< A placeholder derived class which is used for class function semantics SessionContext ctx;
/**
* @brief A per-function descriptor for NvDevice functions
*/
struct NvDeviceFunctionDescriptor {
DerivedDevice *clazz; //!< A pointer to the class that this was derived from, it's used as the 'this' pointer for the function
NvStatus (DerivedDevice::*function)(IoctlType, span<u8>, span<u8>); //!< A function pointer to the implementation of the function
const char *name; //!< A pointer to a static string in the format "Class::Function" for the specific device class/function
constexpr NvStatus operator()(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return (clazz->*function)(type, buffer, inlineBuffer);
}
};
public: public:
NvDevice(const DeviceState &state) : state(state) {} NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx);
virtual ~NvDevice() = default; virtual ~NvDevice() = default;
virtual NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) = 0;
/** /**
* @return The name of the class * @return The name of the class
* @note The lifetime of the returned string is tied to that of the class * @note The lifetime of the returned string is tied to that of the class
*/ */
const std::string &GetName(); const std::string &GetName();
/** virtual PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) = 0;
* @brief Handles IOCTL calls for devices
* @param cmd The IOCTL command that was called virtual PosixResult Ioctl2(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineOutput) { return PosixResult::NotSupported; }
*/
NvStatus HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer); virtual PosixResult Ioctl3(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineInput) { return PosixResult::NotSupported; }
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) { virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
return nullptr; return nullptr;

View File

@ -1,61 +1,121 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "driver.h" #include "driver.h"
#include "devices/nvhost_ctrl.h"
#include "devices/nvhost_ctrl_gpu.h"
#include "devices/nvmap.h" #include "devices/nvmap.h"
#include "devices/nvhost_channel.h" #include "devices/nvhost/ctrl.h"
#include "devices/nvhost_as_gpu.h" #include "devices/nvhost/ctrl_gpu.h"
#include "devices/nvhost/gpu_channel.h"
#include "devices/nvhost/as_gpu.h"
namespace skyline::service::nvdrv { namespace skyline::service::nvdrv {
Driver::Driver(const DeviceState &state) : state(state), hostSyncpoint(state) {} Driver::Driver(const DeviceState &state) : state(state), core(state) {}
u32 Driver::OpenDevice(std::string_view path) { NvResult Driver::OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx) {
state.logger->Debug("Opening NVDRV device ({}): {}", fdIndex, path); state.logger->Debug("Opening NvDrv device ({}): {}", fd, path);
auto pathHash{util::Hash(path)};
switch (util::Hash(path)) { #define DEVICE_SWITCH(cases) \
#define NVDEVICE(type, name, devicePath) \ switch (pathHash) { \
case util::Hash(devicePath): { \ cases; \
std::shared_ptr<device::type> device{}; \ default: \
if (name.expired()) { \
device = std::make_shared<device::type>(state); \
name = device; \
} else { \
device = name.lock(); \
} \
devices.push_back(device); \
break; \ break; \
} }
NVDEVICE_LIST
#undef NVDEVICE
#define DEVICE_CASE(path, object) \
case util::Hash(path): \
devices.emplace(fd, std::make_unique<device::object>(state, core, ctx)); \
return NvResult::Success;
DEVICE_SWITCH(
DEVICE_CASE("/dev/nvmap", NvMap)
DEVICE_CASE("/dev/nvhost-ctrl", nvhost::Ctrl)
);
if (ctx.perms.AccessGpu) {
DEVICE_SWITCH(
DEVICE_CASE("/dev/nvhost-as-gpu", nvhost::AsGpu)
DEVICE_CASE("/dev/nvhost-ctrl-gpu", nvhost::CtrlGpu)
DEVICE_CASE("/dev/nvhost-gpu", nvhost::GpuChannel)
);
}/*
if (ctx.perms.AccessVic) {
switch (pathHash) {
ENTRY("/dev/nvhost-vic", nvhost::Vic)
default: default:
throw exception("Cannot find NVDRV device"); break;
}
} }
return fdIndex++; if (ctx.perms.AccessVideoDecoder) {
switch (pathHash) {
ENTRY("/dev/nvhost-nvdec", nvhost::NvDec)
default:
break;
}
} }
std::shared_ptr<device::NvDevice> Driver::GetDevice(u32 fd) { if (ctx.perms.AccessJpeg) {
switch (pathHash) {
ENTRY("/dev/nvhost-nvjpg", nvhost::NvJpg)
default:
break;
}
}*/
#undef DEVICE_CASE
#undef DEVICE_SWITCH
// Device doesn't exist/no permissions
return NvResult::FileOperationFailed;
}
static NvResult ConvertResult(PosixResult result) {
switch (result) {
case PosixResult::Success:
return NvResult::Success;
case PosixResult::NotPermitted:
return NvResult::AccessDenied;
case PosixResult::TryAgain:
return NvResult::Timeout;
case PosixResult::Busy:
return NvResult::Busy;
case PosixResult::InvalidArgument:
return NvResult::BadValue;
case PosixResult::InappropriateIoctlForDevice:
return NvResult::IoctlFailed;
case PosixResult::NotSupported:
return NvResult::NotSupported;
case PosixResult::TimedOut:
return NvResult::Timeout;
default:
throw exception("Unhandled POSIX result: {}!", static_cast<i32>(result));
}
}
NvResult Driver::Ioctl(u32 fd, IoctlDescriptor cmd, span<u8> buffer) {
try { try {
auto item{devices.at(fd)}; return ConvertResult(devices.at(fd)->Ioctl(cmd, buffer));
if (!item) } catch (const std::out_of_range &) {
throw exception("GetDevice was called with a closed file descriptor: 0x{:X}", fd);
return item;
} catch (std::out_of_range) {
throw exception("GetDevice was called with invalid file descriptor: 0x{:X}", fd); throw exception("GetDevice was called with invalid file descriptor: 0x{:X}", fd);
} }
} }
void Driver::CloseDevice(u32 fd) { void Driver::CloseDevice(u32 fd) {
try { try {
auto &device{devices.at(fd)}; devices.at(fd).reset();
device.reset();
} catch (const std::out_of_range &) { } catch (const std::out_of_range &) {
state.logger->Warn("Trying to close non-existent FD"); state.logger->Warn("Trying to close non-existent FD");
} }
} }
std::weak_ptr<Driver> driver{}; std::shared_ptr<kernel::type::KEvent> Driver::QueryEvent(u32 fd, u32 eventId) {
try {
return devices.at(fd)->QueryEvent(eventId);
} catch (const std::exception &) {
throw exception("QueryEvent was called with invalid file descriptor: 0x{:X}", fd);
}
}
} }

View File

@ -1,76 +1,45 @@
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/) // Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once #pragma once
#include "devices/nvhost_syncpoint.h" #include <common.h>
#include "types.h"
#define NVDEVICE_LIST \ #include "devices/nvdevice.h"
NVDEVICE(NvHostCtrl, nvHostCtrl, "/dev/nvhost-ctrl") \ #include "core/core.h"
NVDEVICE(NvHostChannel, nvHostGpu, "/dev/nvhost-gpu") \
NVDEVICE(NvHostChannel, nvHostNvdec, "/dev/nvhost-nvdec") \
NVDEVICE(NvHostChannel, nvHostVic, "/dev/nvhost-vic") \
NVDEVICE(NvMap, nvMap, "/dev/nvmap") \
NVDEVICE(NvHostAsGpu, nvHostAsGpu, "/dev/nvhost-as-gpu") \
NVDEVICE(NvHostCtrlGpu, nvHostCtrlGpu, "/dev/nvhost-ctrl-gpu")
namespace skyline::service::nvdrv { namespace skyline::service::nvdrv {
namespace device {
class NvDevice;
#define NVDEVICE(type, name, path) class type;
NVDEVICE_LIST
#undef NVDEVICE
}
/**
* @brief nvnflinger:dispdrv or nns::hosbinder::IHOSBinderDriver is responsible for writing buffers to the display
*/
class Driver { class Driver {
private: private:
const DeviceState &state; const DeviceState &state;
std::vector<std::shared_ptr<device::NvDevice>> devices; //!< A vector of shared pointers to NvDevice object that correspond to FDs std::unordered_map<FileDescriptor, std::unique_ptr<device::NvDevice>> devices;
u32 fdIndex{}; //!< The next file descriptor to assign
public: public:
NvHostSyncpoint hostSyncpoint; Core core; //!< The core global state object of nvdrv that is accessed by devices
#define NVDEVICE(type, name, path) std::weak_ptr<device::type> name;
NVDEVICE_LIST
#undef NVDEVICE
Driver(const DeviceState &state); Driver(const DeviceState &state);
/** /**
* @brief Open a specific device and return a FD * @brief Creates a new device as specified by path
* @param path The path of the device to open an FD to * @param path The /dev path that corresponds to the device
* @return The file descriptor to the device * @param fd The fd that will be used to refer to the device
* @param ctx The context to be attached to the device
*/ */
u32 OpenDevice(std::string_view path); NvResult OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx);
/** /**
* @brief Returns a particular device with a specific FD * @brief Calls an IOCTL on the device specified by `fd`
* @param fd The file descriptor to retrieve
* @return A shared pointer to the device
*/ */
std::shared_ptr<device::NvDevice> GetDevice(u32 fd); NvResult Ioctl(u32 fd, IoctlDescriptor cmd, span<u8> buffer);
/** /**
* @brief Returns a particular device with a specific FD * @brief Queries a KEvent for the given `eventId` for the device specified by `fd`
* @tparam objectClass The class of the device to return
* @param fd The file descriptor to retrieve
* @return A shared pointer to the device
*/ */
template<typename objectClass> std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 fd, u32 eventId);
std::shared_ptr<objectClass> GetDevice(u32 fd) {
return std::static_pointer_cast<objectClass>(GetDevice(fd));
}
/** /**
* @brief Closes the specified device with its file descriptor * @brief Closes the device specified by `fd`
*/ */
void CloseDevice(u32 fd); void CloseDevice(u32 fd);
}; };
extern std::weak_ptr<Driver> driver; //!< A globally shared instance of the Driver
} }

View File

@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
namespace skyline::service::nvdrv {
using FileDescriptor = i32;
constexpr FileDescriptor InvalidFileDescriptor{-1};
/**
* @brief Holds the permissions for an nvdrv instance
*/
struct SessionPermissions {
bool AccessGpu;
bool AccessGpuDebug;
bool AccessGpuSchedule;
bool AccessVic;
bool AccessVideoEncoder;
bool AccessVideoDecoder;
bool AccessTsec;
bool AccessJpeg;
bool AccessDisplay;
bool AccessImportMemory;
bool NoCheckedAruid;
bool ModifyGraphicsMargin;
bool DuplicateNvMapHandles;
bool ExportNvMapHandles;
};
/**
* @brief Holds the per-session context for nvdrv
*/
struct SessionContext {
SessionPermissions perms;
bool internalSession;
};
/**
* @brief Permissions that applications have when using the 'nvdrv' service
*/
static constexpr SessionPermissions ApplicationSessionPermissions {
.AccessGpu = true,
.AccessGpuDebug = true,
.AccessVic = true,
.AccessVideoDecoder = true,
.ModifyGraphicsMargin = true
};
/**
* @brief An bitfield struct that unpacks an ioctl number, used as an alternative to Linux's macros
*/
union IoctlDescriptor {
struct {
u8 function; //!< The function number corresponding to a specific call in the driver
i8 magic; //!< Unique to each driver
u16 size : 14; //!< Size of the argument buffer
bool in : 1; //!< Guest is writing, we are reading
bool out : 1; //!< Guest is reading, we are writing
};
u32 raw;
};
static_assert(sizeof(IoctlDescriptor) == sizeof(u32));
/**
* @brief NvRm result codes that are translated from the POSIX error codes used internally
* @url https://switchbrew.org/wiki/NV_services#NvError
*/
enum class NvResult : i32 {
Success = 0x0,
NotImplemented = 0x1,
NotSupported = 0x2,
NotInitialized = 0x3,
BadParameter = 0x4,
Timeout = 0x5,
InsufficientMemory = 0x6,
ReadOnlyAttribute = 0x7,
InvalidState = 0x8,
InvalidAddress = 0x9,
InvalidSize = 0xA,
BadValue = 0xB,
AlreadyAllocated = 0xD,
Busy = 0xE,
ResourceError = 0xF,
CountMismatch = 0x10,
Overflow = 0x11,
FileOperationFailed = 0x30003,
AccessDenied = 0x30010,
IoctlFailed = 0x3000F
};
}

View File

@ -17,7 +17,8 @@
#include "glue/IStaticService.h" #include "glue/IStaticService.h"
#include "services/timesrv/core.h" #include "services/timesrv/core.h"
#include "fssrv/IFileSystemProxy.h" #include "fssrv/IFileSystemProxy.h"
#include "services/nvdrv/INvDrvServices.h" #include "nvdrv/INvDrvServices.h"
#include "nvdrv/driver.h"
#include "hosbinder/IHOSBinderDriver.h" #include "hosbinder/IHOSBinderDriver.h"
#include "visrv/IApplicationRootService.h" #include "visrv/IApplicationRootService.h"
#include "visrv/ISystemRootService.h" #include "visrv/ISystemRootService.h"
@ -46,8 +47,9 @@
namespace skyline::service { namespace skyline::service {
struct GlobalServiceState { struct GlobalServiceState {
timesrv::core::TimeServiceObject timesrv; timesrv::core::TimeServiceObject timesrv;
nvdrv::Driver nvdrv;
explicit GlobalServiceState(const DeviceState &state) : timesrv(state) {} explicit GlobalServiceState(const DeviceState &state) : timesrv(state), nvdrv(state) {}
}; };
ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared<sm::IUserInterface>(state, *this)), globalServiceState(std::make_shared<GlobalServiceState>(state)) {} ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared<sm::IUserInterface>(state, *this)), globalServiceState(std::make_shared<GlobalServiceState>(state)) {}
@ -73,11 +75,8 @@ namespace skyline::service {
SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetStaticServiceAsRepair(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceRepairPermissions) SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetStaticServiceAsRepair(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceRepairPermissions)
SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetStaticServiceAsUser(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions) SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetStaticServiceAsUser(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions)
SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv") SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv") SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv", globalServiceState->nvdrv, nvdrv::ApplicationSessionPermissions)
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:a") SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv", globalServiceState->nvdrv.core.nvMap)
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:s")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:t")
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv")
SERVICE_CASE(visrv::IApplicationRootService, "vi:u") SERVICE_CASE(visrv::IApplicationRootService, "vi:u")
SERVICE_CASE(visrv::ISystemRootService, "vi:s") SERVICE_CASE(visrv::ISystemRootService, "vi:s")
SERVICE_CASE(visrv::IManagerRootService, "vi:m") SERVICE_CASE(visrv::IManagerRootService, "vi:m")