mirror of
https://github.com/Takiiiiiiii/strato.git
synced 2025-07-17 08:46:39 +00:00
Framebuffer and NativeActivity
What was added: * Framebuffer * NativeActivity * NV Services * IOCTL Handler * NV Devices: * * /dev/nvmap - 0xC0080101, 0xC0080103, 0xC0200104, 0xC0180105, 0xC00C0109, 0xC008010E * * /dev/nvhost-as-gpu * * /dev/nvhost-channel - 0x40044801, 0xC0104809, 0xC010480B, 0xC018480C, 0x4004480D, 0xC020481A, 0x40084714 * * /dev/nvhost-ctrl * * /dev/nvhost-ctrl-gpu - 0x80044701, 0x80284702, 0xC0184706, 0xC0B04705, 0x80084714 * SVCs: * * SetMemoryAttribute * * CreateTransferMemory * * ResetSignal * * GetSystemTick * Addition of Compact Logger What was fixed: * SVCs: * * SetHeapSize * * SetMemoryAttribute * * QueryMemory * A release build would not set CMAKE_BUILD_TYPE to "RELEASE" * The logger code was simplified
This commit is contained in:
227
app/src/main/cpp/skyline/gpu/devices/nvdevice.h
Normal file
227
app/src/main/cpp/skyline/gpu/devices/nvdevice.h
Normal file
@ -0,0 +1,227 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
#include <kernel/ipc.h>
|
||||
|
||||
#define NFUNC(function) std::bind(&function, this, std::placeholders::_1)
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
/**
|
||||
* @brief An enumeration of all the devices that can be opened by nvdrv
|
||||
*/
|
||||
enum class NvDeviceType {
|
||||
nvhost_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl
|
||||
nvhost_gpu, //!< https://switchbrew.org/wiki/NV_services#Channels
|
||||
nvhost_nvdec, //!< https://switchbrew.org/wiki/NV_services#Channels
|
||||
nvhost_vic, //!< https://switchbrew.org/wiki/NV_services#Channels
|
||||
nvmap, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvmap
|
||||
nvdisp_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-ctrl
|
||||
nvdisp_disp0, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-disp0.2C_.2Fdev.2Fnvdisp-disp1
|
||||
nvdisp_disp1, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-disp0.2C_.2Fdev.2Fnvdisp-disp1
|
||||
nvcec_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvcec-ctrl
|
||||
nvhdcp_up_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhdcp_up-ctrl
|
||||
nvdcutil_disp0, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdcutil-disp0.2C_.2Fdev.2Fnvdcutil-disp1
|
||||
nvdcutil_disp1, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdcutil-disp0.2C_.2Fdev.2Fnvdcutil-disp1
|
||||
nvsched_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvsched-ctrl
|
||||
nverpt_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnverpt-ctrl
|
||||
nvhost_as_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu
|
||||
nvhost_dbg_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-dbg-gpu
|
||||
nvhost_prof_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-prof-gpu
|
||||
nvhost_ctrl_gpu //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A mapping from a device's path to it's nvDevice entry
|
||||
*/
|
||||
const static std::unordered_map<std::string, NvDeviceType> nvDeviceMap{
|
||||
{"/dev/nvhost-ctrl", NvDeviceType::nvhost_ctrl},
|
||||
{"/dev/nvhost-gpu", NvDeviceType::nvhost_gpu},
|
||||
{"/dev/nvhost-nvdec", NvDeviceType::nvhost_nvdec},
|
||||
{"/dev/nvhost-vic", NvDeviceType::nvhost_vic},
|
||||
{"/dev/nvmap", NvDeviceType::nvmap},
|
||||
{"/dev/nvdisp-ctrl", NvDeviceType::nvdisp_ctrl},
|
||||
{"/dev/nvdisp-disp0", NvDeviceType::nvdisp_disp0},
|
||||
{"/dev/nvdisp-disp1", NvDeviceType::nvdisp_disp1},
|
||||
{"/dev/nvcec-ctrl", NvDeviceType::nvcec_ctrl},
|
||||
{"/dev/nvhdcp_up-ctrl", NvDeviceType::nvhdcp_up_ctrl},
|
||||
{"/dev/nvdcutil-disp0", NvDeviceType::nvdcutil_disp0},
|
||||
{"/dev/nvdcutil-disp1", NvDeviceType::nvdcutil_disp1},
|
||||
{"/dev/nvsched-ctrl", NvDeviceType::nvsched_ctrl},
|
||||
{"/dev/nverpt-ctrl", NvDeviceType::nverpt_ctrl},
|
||||
{"/dev/nvhost-as-gpu", NvDeviceType::nvhost_as_gpu},
|
||||
{"/dev/nvhost-dbg-gpu", NvDeviceType::nvhost_dbg_gpu},
|
||||
{"/dev/nvhost-prof-gpu", NvDeviceType::nvhost_prof_gpu},
|
||||
{"/dev/nvhost-ctrl-gpu", NvDeviceType::nvhost_ctrl_gpu}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Describes a buffer by holding the address and size
|
||||
*/
|
||||
struct IoctlBuffer {
|
||||
u64 address; //!< The address of the buffer
|
||||
size_t size; //!< The size of the buffer
|
||||
|
||||
/**
|
||||
* @param address The address of the buffer
|
||||
* @param size The size of the buffer
|
||||
*/
|
||||
IoctlBuffer(u64 address, size_t size) : address(address), size(size) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrapper around IoctlBuffer that loads in the address from a A Buffer Descriptor
|
||||
*/
|
||||
struct InputBuffer : public IoctlBuffer {
|
||||
/**
|
||||
* @param aBuf The A Buffer Descriptor that has contains the input data
|
||||
*/
|
||||
InputBuffer(kernel::ipc::BufferDescriptorABW *aBuf) : IoctlBuffer(aBuf->Address(), aBuf->Size()) {}
|
||||
|
||||
/**
|
||||
* @param aBuf The X Buffer Descriptor that has contains the input data
|
||||
*/
|
||||
InputBuffer(kernel::ipc::BufferDescriptorX *xBuf) : IoctlBuffer(xBuf->Address(), xBuf->size) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Wrapper around IoctlBuffer that loads in the address from a B Buffer Descriptor
|
||||
*/
|
||||
struct OutputBuffer : public IoctlBuffer {
|
||||
/**
|
||||
* @param aBuf The B Buffer Descriptor that has to be outputted to
|
||||
*/
|
||||
OutputBuffer(kernel::ipc::BufferDescriptorABW *bBuf) : IoctlBuffer(bBuf->Address(), bBuf->Size()) {}
|
||||
|
||||
/**
|
||||
* @param xBuf The C Buffer Descriptor that has to be outputted to
|
||||
*/
|
||||
OutputBuffer(kernel::ipc::BufferDescriptorC *cBuf) : IoctlBuffer(cBuf->address, cBuf->size) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This enumerates all the possible error codes returned by the Nvidia driver (https://switchbrew.org/wiki/NV_services#Errors)
|
||||
*/
|
||||
enum 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 This holds all the IoctlBuffer objects in a coherent container
|
||||
*/
|
||||
struct IoctlBuffers {
|
||||
std::vector<InputBuffer> input; //!< A vector of all input IOCTL buffers
|
||||
std::vector<OutputBuffer> output; //!< A vector of all output IOCTL buffers
|
||||
NvStatus status{NvStatus::Success}; //!< The error code that is returned to the application
|
||||
|
||||
/**
|
||||
* @brief This constructor takes 1 input buffer and 1 output buffer, it's used for Ioctl
|
||||
* @param input An input buffer
|
||||
* @param output An output buffer
|
||||
*/
|
||||
IoctlBuffers(InputBuffer input, OutputBuffer output) : input({input}), output({output}) {}
|
||||
|
||||
/**
|
||||
* @brief This constructor takes 1 input buffer, it's used for Ioctl sometimes
|
||||
* @param output An output buffer
|
||||
*/
|
||||
IoctlBuffers(InputBuffer input) : input({input}) {}
|
||||
|
||||
/**
|
||||
* @brief This constructor takes 1 output buffer, it's used for Ioctl sometimes
|
||||
* @param output An output buffer
|
||||
*/
|
||||
IoctlBuffers(OutputBuffer output) : output({output}) {}
|
||||
|
||||
/**
|
||||
* @brief This constructor takes 2 input buffers and 1 output buffer, it's used for Ioctl1
|
||||
* @param input1 The first input buffer
|
||||
* @param input2 The second input buffer
|
||||
* @param output An output buffer
|
||||
*/
|
||||
IoctlBuffers(InputBuffer input1, InputBuffer input2, OutputBuffer output) : input({input1, input2}), output({output}) {}
|
||||
|
||||
/**
|
||||
* @brief This constructor takes 1 input buffer and 2 output buffers, it's used for Ioctl2
|
||||
* @param input An input buffer
|
||||
* @param output1 The first output buffer
|
||||
* @param output2 The second output buffer
|
||||
*/
|
||||
IoctlBuffers(InputBuffer input, OutputBuffer output1, OutputBuffer output2) : input({input}), output({output1, output2}) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief NvDevice is the base class all /dev/nv* devices inherit from
|
||||
*/
|
||||
class NvDevice {
|
||||
protected:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
std::unordered_map<u32, std::function<void(IoctlBuffers &)>> vTable; //!< This holds the mapping from an Ioctl to the actual function
|
||||
|
||||
public:
|
||||
u16 refCount{1}; //!< The amount of handles to the device
|
||||
NvDeviceType deviceType; //!< The type of the device
|
||||
|
||||
/**
|
||||
* @param state The state of the device
|
||||
* @param deviceType The type of the device
|
||||
* @param vTable The functions in this device
|
||||
*/
|
||||
NvDevice(const DeviceState &state, NvDeviceType deviceType, std::unordered_map<u32, std::function<void(IoctlBuffers &)>> vTable) : state(state), deviceType(deviceType), vTable(vTable) {}
|
||||
|
||||
/**
|
||||
* @brief This returns the name of the current service
|
||||
* @note It may not return the exact name the service was initialized with if there are multiple entries in ServiceString
|
||||
* @return The name of the service
|
||||
*/
|
||||
std::string getName() {
|
||||
std::string serviceName;
|
||||
for (const auto&[name, type] : nvDeviceMap)
|
||||
if (type == deviceType)
|
||||
serviceName = name;
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This handles IOCTL calls for devices
|
||||
* @param cmd The IOCTL command that was called
|
||||
* @param input The input to the IOCTL call
|
||||
*/
|
||||
void HandleIoctl(u32 cmd, IoctlBuffers &input) {
|
||||
std::function<void(IoctlBuffers &)> function;
|
||||
try {
|
||||
function = vTable.at(cmd);
|
||||
} catch (std::out_of_range &) {
|
||||
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", getName(), deviceType, cmd);
|
||||
input.status = NvStatus::NotImplemented;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
function(input);
|
||||
} catch (std::exception &e) {
|
||||
throw exception("{} (Device: {})", e.what(), getName());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
5
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.cpp
Normal file
5
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include "nvhost_as_gpu.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
NvHostAsGpu::NvHostAsGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_as_gpu, {}) {}
|
||||
}
|
13
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.h
Normal file
13
app/src/main/cpp/skyline/gpu/devices/nvhost_as_gpu.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
/**
|
||||
* @brief NvHostAsGpu (/dev/nvhost-as-gpu) is used to access GPU virtual address spaces (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu)
|
||||
*/
|
||||
class NvHostAsGpu : public NvDevice {
|
||||
public:
|
||||
NvHostAsGpu(const DeviceState &state);
|
||||
};
|
||||
}
|
42
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.cpp
Normal file
42
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "nvhost_channel.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
NvHostChannel::NvHostChannel(const DeviceState &state, NvDeviceType type) : NvDevice(state, type, {
|
||||
{0x40044801, NFUNC(NvHostChannel::SetNvmapFd)},
|
||||
{0xC0104809, NFUNC(NvHostChannel::AllocObjCtx)},
|
||||
{0xC010480B, NFUNC(NvHostChannel::ZcullBind)},
|
||||
{0xC018480C, NFUNC(NvHostChannel::SetErrorNotifier)},
|
||||
{0x4004480D, NFUNC(NvHostChannel::SetPriority)},
|
||||
{0xC020481A, NFUNC(NvHostChannel::AllocGpfifoEx2)},
|
||||
{0x40084714, NFUNC(NvHostChannel::SetUserData)}
|
||||
}) {}
|
||||
|
||||
void NvHostChannel::SetNvmapFd(skyline::gpu::device::IoctlBuffers &buffer) {}
|
||||
|
||||
void NvHostChannel::AllocObjCtx(skyline::gpu::device::IoctlBuffers &buffer) {}
|
||||
|
||||
void NvHostChannel::ZcullBind(IoctlBuffers &buffer) {}
|
||||
|
||||
void NvHostChannel::SetErrorNotifier(skyline::gpu::device::IoctlBuffers &buffer) {}
|
||||
|
||||
void NvHostChannel::SetPriority(skyline::gpu::device::IoctlBuffers &buffer) {
|
||||
auto priority = state.thisProcess->ReadMemory<NvChannelPriority>(buffer.input[0].address);
|
||||
switch (priority) {
|
||||
case NvChannelPriority::Low:
|
||||
timeslice = 1300;
|
||||
break;
|
||||
case NvChannelPriority::Medium:
|
||||
timeslice = 2600;
|
||||
break;
|
||||
case NvChannelPriority::High:
|
||||
timeslice = 5200;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NvHostChannel::AllocGpfifoEx2(skyline::gpu::device::IoctlBuffers &buffer) {}
|
||||
|
||||
void NvHostChannel::SetUserData(skyline::gpu::device::IoctlBuffers &buffer) {}
|
||||
|
||||
}
|
57
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.h
Normal file
57
app/src/main/cpp/skyline/gpu/devices/nvhost_channel.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
/**
|
||||
* @brief NvHostChannel is used as a common interface for all Channel devices (https://switchbrew.org/wiki/NV_services#Channels)
|
||||
*/
|
||||
class NvHostChannel : public NvDevice {
|
||||
private:
|
||||
enum class NvChannelPriority : u32 {
|
||||
Low = 0x32,
|
||||
Medium = 0x64,
|
||||
High = 0x94
|
||||
};
|
||||
|
||||
u32 timeslice{};
|
||||
|
||||
public:
|
||||
NvHostChannel(const DeviceState &state, NvDeviceType type);
|
||||
|
||||
/**
|
||||
* @brief This sets the nvmap file descriptor (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_NVMAP_FD)
|
||||
*/
|
||||
void SetNvmapFd(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This allocates a graphic context object (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_OBJ_CTX)
|
||||
*/
|
||||
void AllocObjCtx(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This initializes the error notifier for this channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ZCULL_BIND)
|
||||
*/
|
||||
void ZcullBind(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This initializes the error notifier for this channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_ERROR_NOTIFIER)
|
||||
*/
|
||||
void SetErrorNotifier(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This sets the priority of the channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_PRIORITY)
|
||||
*/
|
||||
void SetPriority(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This allocates a GPFIFO entry (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_GPFIFO_EX2)
|
||||
*/
|
||||
void AllocGpfifoEx2(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This sets the user specific data (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA)
|
||||
*/
|
||||
void SetUserData(IoctlBuffers &buffer);
|
||||
};
|
||||
}
|
5
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.cpp
Normal file
5
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include "nvhost_ctrl.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl, {}) {}
|
||||
}
|
13
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.h
Normal file
13
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
/**
|
||||
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl)
|
||||
*/
|
||||
class NvHostCtrl : public NvDevice {
|
||||
public:
|
||||
NvHostCtrl(const DeviceState &state);
|
||||
};
|
||||
}
|
138
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.cpp
Normal file
138
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
#include "nvhost_ctrl_gpu.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
NvHostCtrlGpu::NvHostCtrlGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl_gpu, {
|
||||
{0x80044701, NFUNC(NvHostCtrlGpu::ZCullGetCtxSize)},
|
||||
{0x80284702, NFUNC(NvHostCtrlGpu::ZCullGetInfo)},
|
||||
{0xC0184706, NFUNC(NvHostCtrlGpu::GetTpcMasks)},
|
||||
{0xC0B04705, NFUNC(NvHostCtrlGpu::GetCharacteristics)},
|
||||
{0x80084714, NFUNC(NvHostCtrlGpu::GetActiveSlotMask)}
|
||||
}) {}
|
||||
|
||||
void NvHostCtrlGpu::ZCullGetCtxSize(IoctlBuffers &buffer) {
|
||||
u32 size = 0x1;
|
||||
state.thisProcess->WriteMemory(size, buffer.output[0].address);
|
||||
}
|
||||
|
||||
void NvHostCtrlGpu::ZCullGetInfo(skyline::gpu::device::IoctlBuffers &buffer) {
|
||||
struct {
|
||||
u32 widthAlignPixels{0x20};
|
||||
u32 heightAlignPixels{0x20};
|
||||
u32 pixelSquaresByAliquots{0x400};
|
||||
u32 aliquotTotal{0x800};
|
||||
u32 regionByteMultiplier{0x20};
|
||||
u32 regionHeaderSize{0x20};
|
||||
u32 subregionHeaderSize{0xC0};
|
||||
u32 subregionWidthAlignPixels{0x20};
|
||||
u32 subregionHeightAlignPixels{0x40};
|
||||
u32 subregionCount{0x10};
|
||||
} zCullInfo;
|
||||
state.thisProcess->WriteMemory(zCullInfo, buffer.output[0].address);
|
||||
}
|
||||
|
||||
void NvHostCtrlGpu::GetCharacteristics(IoctlBuffers &buffer) {
|
||||
struct GpuCharacteristics {
|
||||
u32 arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
|
||||
u32 impl; // 0xB (NVGPU_GPU_IMPL_GM20B) or 0xE (NVGPU_GPU_IMPL_GM20B_B)
|
||||
u32 rev; // 0xA1 (Revision A1)
|
||||
u32 numGpc; // 0x1
|
||||
u64 l2CacheSize; // 0x40000
|
||||
u64 onBoardVideoMemorySize; // 0x0 (not used)
|
||||
u32 numTpcPerGpc; // 0x2
|
||||
u32 busType; // 0x20 (NVGPU_GPU_BUS_TYPE_AXI)
|
||||
u32 bigPageSize; // 0x20000
|
||||
u32 compressionPageSize; // 0x20000
|
||||
u32 pdeCoverageBitCount; // 0x1B
|
||||
u32 availableBigPageSizes; // 0x30000
|
||||
u32 gpcMask; // 0x1
|
||||
u32 smArchSmVersion; // 0x503 (Maxwell Generation 5.0.3)
|
||||
u32 smArchSpaVersion; // 0x503 (Maxwell Generation 5.0.3)
|
||||
u32 smArchWarpCount; // 0x80
|
||||
u32 gpuVaBitCount; // 0x28
|
||||
u32 reserved; // NULL
|
||||
u64 flags; // 0x55 (HAS_SYNCPOINTS | SUPPORT_SPARSE_ALLOCS | SUPPORT_CYCLE_STATS | SUPPORT_CYCLE_STATS_SNAPSHOT)
|
||||
u32 twodClass; // 0x902D (FERMI_TWOD_A)
|
||||
u32 threedClass; // 0xB197 (MAXWELL_B)
|
||||
u32 computeClass; // 0xB1C0 (MAXWELL_COMPUTE_B)
|
||||
u32 gpfifoClass; // 0xB06F (MAXWELL_CHANNEL_GPFIFO_A)
|
||||
u32 inlineToMemoryClass; // 0xA140 (KEPLER_INLINE_TO_MEMORY_B)
|
||||
u32 dmaCopyClass; // 0xB0B5 (MAXWELL_DMA_COPY_A)
|
||||
u32 maxFbpsCount; // 0x1
|
||||
u32 fbpEnMask; // 0x0 (disabled)
|
||||
u32 maxLtcPerFbp; // 0x2
|
||||
u32 maxLtsPerLtc; // 0x1
|
||||
u32 maxTexPerTpc; // 0x0 (not supported)
|
||||
u32 maxGpcCount; // 0x1
|
||||
u32 ropL2EnMask0; // 0x21D70 (fuse_status_opt_rop_l2_fbp_r)
|
||||
u32 ropL2EnMask1; // 0x0
|
||||
u64 chipName; // 0x6230326D67 ("gm20b")
|
||||
u64 grCompbitStoreBaseHw; // 0x0 (not supported)
|
||||
};
|
||||
struct Data {
|
||||
u64 gpuCharacteristicsBufSize; // InOut
|
||||
u64 gpuCharacteristicsBufAddr; // In
|
||||
GpuCharacteristics gpuCharacteristics; // Out
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
data.gpuCharacteristics = {
|
||||
.arch = 0x120,
|
||||
.impl = 0xB,
|
||||
.rev = 0xA1,
|
||||
.numGpc = 0x1,
|
||||
.l2CacheSize = 0x40000,
|
||||
.onBoardVideoMemorySize = 0x0,
|
||||
.numTpcPerGpc = 0x2,
|
||||
.busType = 0x20,
|
||||
.bigPageSize = 0x20000,
|
||||
.compressionPageSize = 0x20000,
|
||||
.pdeCoverageBitCount = 0x1B,
|
||||
.availableBigPageSizes = 0x30000,
|
||||
.gpcMask = 0x1,
|
||||
.smArchSmVersion = 0x503,
|
||||
.smArchSpaVersion = 0x503,
|
||||
.smArchWarpCount = 0x80,
|
||||
.gpuVaBitCount = 0x2,
|
||||
.flags = 0x55,
|
||||
.twodClass = 0x902D,
|
||||
.threedClass = 0xB197,
|
||||
.computeClass = 0xB1C0,
|
||||
.gpfifoClass = 0xB06F,
|
||||
.inlineToMemoryClass = 0xA140,
|
||||
.dmaCopyClass = 0xB0B5,
|
||||
.maxFbpsCount = 0x1,
|
||||
.fbpEnMask = 0x0,
|
||||
.maxLtcPerFbp = 0x2,
|
||||
.maxLtsPerLtc = 0x1,
|
||||
.maxTexPerTpc = 0x0,
|
||||
.maxGpcCount = 0x1,
|
||||
.ropL2EnMask0 = 0x21D70,
|
||||
.ropL2EnMask1 = 0x0,
|
||||
.chipName = 0x6230326D67,
|
||||
.grCompbitStoreBaseHw = 0x0
|
||||
};
|
||||
data.gpuCharacteristicsBufSize = 0xA0;
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
}
|
||||
|
||||
void NvHostCtrlGpu::GetTpcMasks(IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 maskBufSize; // In
|
||||
u32 reserved[3]; // In
|
||||
u64 maskBuf; // Out
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
if (data.maskBufSize)
|
||||
data.maskBuf = 0x3;
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
}
|
||||
|
||||
void NvHostCtrlGpu::GetActiveSlotMask(IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 slot; // Out
|
||||
u32 mask; // Out
|
||||
} data = {
|
||||
.slot = 0x07,
|
||||
.mask = 0x01
|
||||
};
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
}
|
||||
}
|
38
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.h
Normal file
38
app/src/main/cpp/skyline/gpu/devices/nvhost_ctrl_gpu.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
/**
|
||||
* @brief NvHostCtrlGpu (/dev/nvhost-ctrl-gpu) is used for context independent operations on the underlying GPU (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu)
|
||||
*/
|
||||
class NvHostCtrlGpu : public NvDevice {
|
||||
public:
|
||||
NvHostCtrlGpu(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This returns a u32 GPU ZCULL Context Size (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_CTX_SIZE)
|
||||
*/
|
||||
void ZCullGetCtxSize(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns a the GPU ZCULL Information (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_INFO)
|
||||
*/
|
||||
void ZCullGetInfo(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns a struct with certain GPU characteristics (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_CHARACTERISTICS)
|
||||
*/
|
||||
void GetCharacteristics(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns the TPC mask value for each GPC (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_TPC_MASKS)
|
||||
*/
|
||||
void GetTpcMasks(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns the mask value for a ZBC slot (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZBC_GET_ACTIVE_SLOT_MASK)
|
||||
*/
|
||||
void GetActiveSlotMask(IoctlBuffers &buffer);
|
||||
};
|
||||
}
|
140
app/src/main/cpp/skyline/gpu/devices/nvmap.cpp
Normal file
140
app/src/main/cpp/skyline/gpu/devices/nvmap.cpp
Normal file
@ -0,0 +1,140 @@
|
||||
#include "nvmap.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
NvMap::NvMapObject::NvMapObject(u32 id, u32 size) : id(id), size(size) {}
|
||||
|
||||
NvMap::NvMap(const DeviceState &state) : NvDevice(state, NvDeviceType::nvmap, {
|
||||
{0xC0080101, NFUNC(NvMap::Create)},
|
||||
{0xC0080103, NFUNC(NvMap::FromId)},
|
||||
{0xC0200104, NFUNC(NvMap::Alloc)},
|
||||
{0xC0180105, NFUNC(NvMap::Free)},
|
||||
{0xC00C0109, NFUNC(NvMap::Param)},
|
||||
{0xC008010E, NFUNC(NvMap::GetId)}
|
||||
}) {}
|
||||
|
||||
void NvMap::Create(IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 size; // In
|
||||
u32 handle; // Out
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
handleTable[handleIndex] = std::make_shared<NvMapObject>(idIndex++, data.size);
|
||||
data.handle = handleIndex++;
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
state.logger->Info("Create: Input: Size: 0x{:X}, Output: Handle: 0x{:X}, Status: {}", data.size, data.handle, buffer.status);
|
||||
}
|
||||
|
||||
void NvMap::FromId(skyline::gpu::device::IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 id; // In
|
||||
u32 handle; // Out
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
bool found{};
|
||||
for (const auto &object : handleTable) {
|
||||
if (object.second->id == data.id) {
|
||||
data.handle = object.first;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found)
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
else
|
||||
buffer.status = NvStatus::BadValue;
|
||||
state.logger->Info("FromId: Input: Handle: 0x{:X}, Output: ID: 0x{:X}, Status: {}", data.handle, data.id, buffer.status);
|
||||
}
|
||||
|
||||
void NvMap::Alloc(IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
u32 heapMask; // In
|
||||
u32 flags; // In
|
||||
u32 align; // In
|
||||
u8 kind; // In
|
||||
u8 _pad0_[7];
|
||||
u64 address; // InOut
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
auto &object = handleTable.at(data.handle);
|
||||
object->heapMask = data.heapMask;
|
||||
object->flags = data.flags;
|
||||
object->align = data.align;
|
||||
object->kind = data.kind;
|
||||
object->address = data.address;
|
||||
object->status = NvMapObject::Status::Allocated;
|
||||
state.logger->Info("Alloc: Input: Handle: 0x{:X}, HeapMask: 0x{:X}, Flags: {}, Align: 0x{:X}, Kind: {}, Address: 0x{:X}, Output: Status: {}", data.handle, data.heapMask, data.flags, data.align, data.kind, data.address, buffer.status);
|
||||
}
|
||||
|
||||
void NvMap::Free(skyline::gpu::device::IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
u32 _pad0_;
|
||||
u32 address; // Out
|
||||
u32 size; // Out
|
||||
u64 flags; // Out
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
const auto &object = handleTable.at(data.handle);
|
||||
if (object.use_count() > 1) {
|
||||
data.address = static_cast<u32>(object->address);
|
||||
data.flags = 0x0;
|
||||
} else {
|
||||
data.address = 0x0;
|
||||
data.flags = 0x1; // Not free yet
|
||||
}
|
||||
data.size = object->size;
|
||||
handleTable.erase(data.handle);
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
}
|
||||
|
||||
void NvMap::Param(IoctlBuffers &buffer) {
|
||||
enum class Parameter : u32 { Size = 1, Alignment = 2, Base = 3, HeapMask = 4, Kind = 5, Compr = 6 }; // https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
Parameter parameter; // In
|
||||
u32 result; // Out
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
auto &object = handleTable.at(data.handle);
|
||||
switch (data.parameter) {
|
||||
case Parameter::Size:
|
||||
data.result = object->size;
|
||||
break;
|
||||
case Parameter::Alignment:
|
||||
case Parameter::HeapMask:
|
||||
case Parameter::Kind: {
|
||||
if (object->status != NvMapObject::Status::Allocated)
|
||||
data.result = NvStatus::BadParameter;
|
||||
switch (data.parameter) {
|
||||
case Parameter::Alignment:
|
||||
data.result = object->align;
|
||||
break;
|
||||
case Parameter::HeapMask:
|
||||
data.result = object->heapMask;
|
||||
break;
|
||||
case Parameter::Kind:
|
||||
data.result = object->kind;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Parameter::Base:
|
||||
buffer.status = NvStatus::NotImplemented;
|
||||
break;
|
||||
case Parameter::Compr:
|
||||
buffer.status = NvStatus::NotImplemented;
|
||||
break;
|
||||
}
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
state.logger->Info("Param: Input: Handle: 0x{:X}, Parameter: {}, Output: Result: 0x{:X}, Status: {}", data.handle, data.parameter, data.result, buffer.status);
|
||||
}
|
||||
|
||||
void NvMap::GetId(skyline::gpu::device::IoctlBuffers &buffer) {
|
||||
struct Data {
|
||||
u32 id; // Out
|
||||
u32 handle; // In
|
||||
} data = state.thisProcess->ReadMemory<Data>(buffer.input[0].address);
|
||||
data.id = handleTable.at(data.handle)->id;
|
||||
state.thisProcess->WriteMemory(data, buffer.output[0].address);
|
||||
state.logger->Info("GetId: Input: Handle: 0x{:X}, Output: ID: 0x{:X}, Status: {}", data.handle, data.id, buffer.status);
|
||||
}
|
||||
}
|
71
app/src/main/cpp/skyline/gpu/devices/nvmap.h
Normal file
71
app/src/main/cpp/skyline/gpu/devices/nvmap.h
Normal file
@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::gpu::device {
|
||||
/**
|
||||
* @brief NvMap (/dev/nvmap) is used to map certain CPU memory as GPU memory (https://switchbrew.org/wiki/NV_services) (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; //!< The ID of this object
|
||||
u32 size; //!< The size of this object
|
||||
u64 address{}; //!< The address of the allocation
|
||||
u32 flags{}; //!< The flag of the memory (0 = Read Only, 1 = Read-Write)
|
||||
u32 align{}; //!< The alignment of the allocation
|
||||
u32 heapMask{}; //!< This is set during Alloc and returned during Param
|
||||
u8 kind{}; //!< This is same as heapMask
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* @param handle The ID of this object
|
||||
* @param size The size of the object in bytes
|
||||
*/
|
||||
NvMapObject(u32 id, u32 size);
|
||||
};
|
||||
|
||||
std::unordered_map<handle_t, std::shared_ptr<NvMapObject>> handleTable; //!< A mapping from a handle to it's corresponding NvMapObject
|
||||
handle_t handleIndex{1}; //!< This is used to keep track of the next handle to allocate
|
||||
u32 idIndex{1}; //!< This is used to keep track of the next ID to allocate
|
||||
|
||||
NvMap(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This creates an NvMapObject and returns an handle to it (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_CREATE)
|
||||
*/
|
||||
void Create(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns the handle of an NvMapObject from it's ID (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID)
|
||||
*/
|
||||
void FromId(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This allocates memory for an NvMapObject (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_ALLOC)
|
||||
*/
|
||||
void Alloc(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This frees previously allocated memory (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FREE)
|
||||
*/
|
||||
void Free(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns a particular parameter from an NvMapObject (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_PARAM)
|
||||
*/
|
||||
void Param(IoctlBuffers &buffer);
|
||||
|
||||
/**
|
||||
* @brief This returns the ID of an NvMapObject from it's handle (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID)
|
||||
*/
|
||||
void GetId(IoctlBuffers &buffer);
|
||||
};
|
||||
}
|
138
app/src/main/cpp/skyline/gpu/display.cpp
Normal file
138
app/src/main/cpp/skyline/gpu/display.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
#include "display.h"
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <gpu.h>
|
||||
|
||||
namespace skyline::gpu {
|
||||
Buffer::Buffer(const DeviceState &state, u32 slot, GbpBuffer &gbpBuffer) : state(state), slot(slot), gbpBuffer(gbpBuffer), resolution{gbpBuffer.width, gbpBuffer.height}, dataBuffer(gbpBuffer.size) {
|
||||
if (gbpBuffer.nvmapHandle)
|
||||
nvBuffer = state.gpu->GetDevice<device::NvMap>(device::NvDeviceType::nvmap)->handleTable.at(gbpBuffer.nvmapHandle);
|
||||
else {
|
||||
auto nvmap = state.gpu->GetDevice<device::NvMap>(device::NvDeviceType::nvmap);
|
||||
for (const auto &object : nvmap->handleTable) {
|
||||
if (object.second->id == gbpBuffer.nvmapId) {
|
||||
nvBuffer = object.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nvBuffer)
|
||||
throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId);
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::UpdateBuffer() {
|
||||
state.thisProcess->ReadMemory(dataBuffer.data(), nvBuffer->address + gbpBuffer.offset, gbpBuffer.size);
|
||||
}
|
||||
|
||||
BufferQueue::WaitContext::WaitContext(std::shared_ptr<kernel::type::KThread> thread, DequeueIn input, u64 address, u64 size) : thread(std::move(thread)), input(input), address(address), size(size) {}
|
||||
|
||||
BufferQueue::DequeueOut::DequeueOut(u32 slot) : slot(slot), _unk0_(0x1), _unk1_(0x24) {}
|
||||
|
||||
BufferQueue::BufferQueue(const DeviceState &state) : state(state) {}
|
||||
|
||||
void BufferQueue::RequestBuffer(Parcel &in, Parcel &out) {
|
||||
u32 slot = *reinterpret_cast<u32 *>(in.data.data() + constant::TokenLength);
|
||||
auto buffer = queue.at(slot);
|
||||
out.WriteData<u32>(1);
|
||||
out.WriteData<u32>(sizeof(GbpBuffer));
|
||||
out.WriteData<u32>(0);
|
||||
out.WriteData(buffer->gbpBuffer);
|
||||
state.logger->Debug("RequestBuffer: Slot: {}, Size: {}", slot, sizeof(GbpBuffer));
|
||||
}
|
||||
|
||||
bool BufferQueue::DequeueBuffer(Parcel &in, Parcel &out, u64 address, u64 size) {
|
||||
auto *data = reinterpret_cast<DequeueIn *>(in.data.data() + constant::TokenLength);
|
||||
i64 slot{-1};
|
||||
for (auto &buffer : queue) {
|
||||
if (buffer.second->status == BufferStatus::Free && buffer.second->resolution.width == data->width && buffer.second->resolution.height == data->height && buffer.second->gbpBuffer.format == data->format && buffer.second->gbpBuffer.usage == data->usage) {
|
||||
slot = buffer.first;
|
||||
buffer.second->status = BufferStatus::Dequeued;
|
||||
}
|
||||
}
|
||||
if (slot == -1) {
|
||||
state.thisThread->Sleep();
|
||||
waitVec.emplace_back(state.thisThread, *data, address, size);
|
||||
state.logger->Debug("DequeueBuffer: No Free Buffers");
|
||||
return true;
|
||||
}
|
||||
DequeueOut output(static_cast<u32>(slot));
|
||||
out.WriteData(output);
|
||||
state.logger->Debug("DequeueBuffer: Width: {}, Height: {}, Format: {}, Usage: {}, Timestamps: {}, Slot: {}", data->width, data->height, data->format, data->usage, data->timestamps, slot);
|
||||
return false;
|
||||
}
|
||||
|
||||
void BufferQueue::QueueBuffer(Parcel &in, Parcel &out) {
|
||||
struct Data {
|
||||
u32 slot;
|
||||
u64 timestamp;
|
||||
u32 autoTimestamp;
|
||||
ARect crop;
|
||||
u32 scalingMode;
|
||||
u32 transform;
|
||||
u32 stickyTransform;
|
||||
u64 _unk0_;
|
||||
u32 swapInterval;
|
||||
Fence fence[4];
|
||||
} *data = reinterpret_cast<Data *>(in.data.data() + constant::TokenLength);
|
||||
auto buffer = queue.at(data->slot);
|
||||
buffer->status = BufferStatus::Queued;
|
||||
displayQueue.emplace(buffer);
|
||||
state.gpu->bufferEvent->Signal();
|
||||
struct {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 _pad0_[3];
|
||||
} output{
|
||||
.width = buffer->gbpBuffer.width,
|
||||
.height = buffer->gbpBuffer.height
|
||||
};
|
||||
out.WriteData(output);
|
||||
state.logger->Debug("QueueBuffer: Timestamp: {}, Auto Timestamp: {}, Crop: [T: {}, B: {}, L: {}, R: {}], Scaling Mode: {}, Transform: {}, Sticky Transform: {}, Swap Interval: {}, Slot: {}", data->timestamp, data->autoTimestamp, data->crop.top, data->crop.bottom, data->crop.left, data->crop.right, data->scalingMode, data->transform, data->stickyTransform, data->swapInterval,
|
||||
data->slot);
|
||||
}
|
||||
|
||||
void BufferQueue::CancelBuffer(Parcel &parcel) {
|
||||
struct Data {
|
||||
u32 slot;
|
||||
Fence fence[4];
|
||||
} *data = reinterpret_cast<Data *>(parcel.data.data() + constant::TokenLength);
|
||||
FreeBuffer(data->slot);
|
||||
}
|
||||
|
||||
void BufferQueue::SetPreallocatedBuffer(Parcel &parcel) {
|
||||
auto pointer = parcel.data.data() + constant::TokenLength;
|
||||
struct Data {
|
||||
u32 slot;
|
||||
u32 _unk0_;
|
||||
u32 length;
|
||||
u32 _pad0_;
|
||||
} *data = reinterpret_cast<Data *>(pointer);
|
||||
pointer += sizeof(Data);
|
||||
auto gbpBuffer = reinterpret_cast<GbpBuffer *>(pointer);
|
||||
queue[data->slot] = std::make_shared<Buffer>(state, data->slot, *gbpBuffer);
|
||||
state.gpu->bufferEvent->Signal();
|
||||
state.logger->Debug("SetPreallocatedBuffer: Slot: {}, Length: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}", data->slot, data->length, gbpBuffer->magic, gbpBuffer->width, gbpBuffer->height, gbpBuffer->stride, gbpBuffer->format, gbpBuffer->usage, gbpBuffer->index, gbpBuffer->nvmapId,
|
||||
gbpBuffer->nvmapHandle, gbpBuffer->offset, (1U << gbpBuffer->blockHeightLog2));
|
||||
}
|
||||
|
||||
void BufferQueue::FreeBuffer(u32 slotNo) {
|
||||
auto &slot = queue.at(slotNo);
|
||||
if (waitVec.empty())
|
||||
slot->status = BufferStatus::Free;
|
||||
else {
|
||||
auto context = waitVec.begin();
|
||||
while (context != waitVec.end()) {
|
||||
if (slot->resolution.width == context->input.width && slot->resolution.height == context->input.height && slot->gbpBuffer.format == context->input.format && slot->gbpBuffer.usage == context->input.usage) {
|
||||
context->thread->WakeUp();
|
||||
gpu::Parcel out(state);
|
||||
DequeueOut output(slotNo);
|
||||
out.WriteData(output);
|
||||
out.WriteParcel(context->address, context->size, context->thread->pid);
|
||||
slot->status = BufferStatus::Dequeued;
|
||||
waitVec.erase(context);
|
||||
break;
|
||||
}
|
||||
context++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
218
app/src/main/cpp/skyline/gpu/display.h
Normal file
218
app/src/main/cpp/skyline/gpu/display.h
Normal file
@ -0,0 +1,218 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
#include <queue>
|
||||
#include <gpu/devices/nvmap.h>
|
||||
#include "parcel.h"
|
||||
|
||||
namespace skyline::gpu {
|
||||
/**
|
||||
* @brief A struct that encapsulates a resolution
|
||||
*/
|
||||
struct Resolution {
|
||||
u32 width; //!< The width component of the resolution
|
||||
u32 height; //!< The height component of the resolution
|
||||
|
||||
bool operator==(const Resolution &r) {
|
||||
return (width == r.width) && (height == r.height);
|
||||
}
|
||||
|
||||
bool operator!=(const Resolution &r) {
|
||||
return !operator==(r);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief An enumeration of all the possible display IDs (https://switchbrew.org/wiki/Display_services#DisplayName)
|
||||
*/
|
||||
enum class DisplayId : u64 {
|
||||
Default,
|
||||
External,
|
||||
Edid,
|
||||
Internal,
|
||||
Null
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A mapping from a display's name to it's displayType entry
|
||||
*/
|
||||
static const std::unordered_map<std::string, DisplayId> displayTypeMap{
|
||||
{"Default", DisplayId::Default},
|
||||
{"External", DisplayId::External},
|
||||
{"Edid", DisplayId::Edid},
|
||||
{"Internal", DisplayId::Internal},
|
||||
{"Null", DisplayId::Null},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The status of a specific layer
|
||||
*/
|
||||
enum class LayerStatus {
|
||||
Uninitialized,
|
||||
Initialized
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The status of a specific buffer
|
||||
*/
|
||||
enum class BufferStatus {
|
||||
Free,
|
||||
Dequeued,
|
||||
Queued,
|
||||
Acquired
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This struct holds information about the graphics buffer (https://github.com/reswitched/libtransistor/blob/0f0c36227842c344d163922fc98ee76229e9f0ee/lib/display/graphic_buffer_queue.c#L66)
|
||||
*/
|
||||
struct GbpBuffer {
|
||||
u32 magic; //!< The magic of the graphics buffer: 0x47424652
|
||||
u32 width; //!< The width of the buffer
|
||||
u32 height; //!< The height of the buffer
|
||||
u32 stride; //!< The stride of the buffer
|
||||
u32 format; //!< The format of the buffer, this corresponds to AHardwareBuffer_Format
|
||||
u32 usage; //!< The usage flags for the buffer
|
||||
u32 _pad0_;
|
||||
u32 index; //!< The index of the buffer
|
||||
u32 _pad1_[3];
|
||||
u32 nvmapId; //!< The ID of the buffer in regards to /dev/nvmap
|
||||
u32 _pad2_[8];
|
||||
u32 size; //!< The size of the buffer
|
||||
u32 _pad3_[8];
|
||||
u32 nvmapHandle; //!< The handle of the buffer in regards to /dev/nvmap
|
||||
u32 offset; //!< This is the offset of the pixel data in the GPU Buffer
|
||||
u32 _pad4_;
|
||||
u32 blockHeightLog2; //!< The log2 of the block height
|
||||
u32 _pad5_[58];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This represents conditions for the completion of an asynchronous graphics operation
|
||||
*/
|
||||
struct Fence {
|
||||
u32 syncptId;
|
||||
u32 syncptValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This holds the state and describes a single Buffer
|
||||
*/
|
||||
class Buffer {
|
||||
public:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
u32 slot; //!< The slot the buffer is in
|
||||
Resolution resolution; //!< The resolution of this buffer
|
||||
GbpBuffer gbpBuffer; //!< The information about the underlying buffer
|
||||
BufferStatus status{BufferStatus::Free}; //!< The status of this buffer
|
||||
std::vector<u8> dataBuffer; //!< The vector holding the actual pixel data
|
||||
std::shared_ptr<device::NvMap::NvMapObject> nvBuffer{}; //!< A shared pointer to the buffer's nvmap object
|
||||
|
||||
/**
|
||||
* @param state The state of the device
|
||||
* @param slot The slot this buffer is in
|
||||
* @param gpBuffer The GbpBuffer object for this buffer
|
||||
*/
|
||||
Buffer(const DeviceState &state, u32 slot, GbpBuffer &gbpBuffer);
|
||||
|
||||
/**
|
||||
* @brief This reads the buffer from the process into the dataBuffer vector
|
||||
*/
|
||||
void UpdateBuffer();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This holds the state of all the buffers used by the guest application
|
||||
*/
|
||||
class BufferQueue {
|
||||
private:
|
||||
const DeviceState &state; //!< The state of the device
|
||||
|
||||
/**
|
||||
* @brief This is the input struct for DequeueBuffer
|
||||
*/
|
||||
struct DequeueIn {
|
||||
u32 format;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 timestamps;
|
||||
u32 usage;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This is the output struct for DequeueBuffer
|
||||
*/
|
||||
struct DequeueOut {
|
||||
u32 slot; //!< The slot of the dequeued buffer
|
||||
u32 _unk0_;
|
||||
u32 _unk1_;
|
||||
u32 _unk2_[11]{};
|
||||
|
||||
/**
|
||||
* @param slot The slot of the dequeued buffer
|
||||
*/
|
||||
DequeueOut(u32 slot);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This holds the context of a thread waiting on a buffer
|
||||
*/
|
||||
struct WaitContext {
|
||||
std::shared_ptr<kernel::type::KThread> thread; //!< The thread that is waiting on a buffer
|
||||
DequeueIn input; //!< The input of DequeueBuffer
|
||||
u64 address; //!< The address of the parcel buffer
|
||||
u64 size; //!< The size of the parcel buffer
|
||||
|
||||
/**
|
||||
* @param thread The thread that is waiting on a buffer
|
||||
* @param input The input of DequeueBuffer
|
||||
* @param address The address of the parcel buffer
|
||||
* @param size The size of the parcel buffer
|
||||
*/
|
||||
WaitContext(std::shared_ptr<kernel::type::KThread> thread, DequeueIn input, u64 address, u64 size);
|
||||
};
|
||||
std::vector<WaitContext> waitVec; //!< A vector of shared pointers to threads waiting on a buffer
|
||||
|
||||
public:
|
||||
std::unordered_map<u32, std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers
|
||||
std::queue<std::shared_ptr<Buffer>> displayQueue; //!< A queue of all the buffers to be posted to the display
|
||||
|
||||
/**
|
||||
* @param state The state of the device
|
||||
*/
|
||||
BufferQueue(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This the GbpBuffer struct of the specified buffer
|
||||
*/
|
||||
void RequestBuffer(Parcel &in, Parcel &out);
|
||||
|
||||
/**
|
||||
* @brief This returns the slot of a free buffer
|
||||
* @param address The address of the parcel buffer
|
||||
* @param size The size of the parcel buffer
|
||||
* @return If the process is waiting for a buffer or not
|
||||
*/
|
||||
bool DequeueBuffer(Parcel &in, Parcel &out, u64 address, u64 size);
|
||||
|
||||
/**
|
||||
* @brief This queues a buffer to be displayed
|
||||
*/
|
||||
void QueueBuffer(Parcel &in, Parcel &out);
|
||||
|
||||
/**
|
||||
* @brief This removes a previously queued buffer
|
||||
*/
|
||||
void CancelBuffer(Parcel &parcel);
|
||||
|
||||
/**
|
||||
* @brief This adds a pre-existing buffer to the queue
|
||||
*/
|
||||
void SetPreallocatedBuffer(Parcel &parcel);
|
||||
|
||||
/**
|
||||
* @brief This frees a buffer which is currently queued
|
||||
* @param slotNo The slot of the buffer
|
||||
*/
|
||||
void FreeBuffer(u32 slotNo);
|
||||
};
|
||||
}
|
50
app/src/main/cpp/skyline/gpu/parcel.cpp
Normal file
50
app/src/main/cpp/skyline/gpu/parcel.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
#include "parcel.h"
|
||||
#include <os.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
|
||||
namespace skyline::gpu {
|
||||
Parcel::Parcel(kernel::ipc::BufferDescriptorABW *buffer, const DeviceState &state) : Parcel(buffer->Address(), buffer->Size(), state) {}
|
||||
|
||||
Parcel::Parcel(kernel::ipc::BufferDescriptorX *buffer, const DeviceState &state) : Parcel(buffer->Address(), buffer->size, state) {}
|
||||
|
||||
Parcel::Parcel(u64 address, u64 size, const DeviceState &state) : state(state) {
|
||||
state.thisProcess->ReadMemory(&header, address, sizeof(ParcelHeader));
|
||||
if (size < (sizeof(ParcelHeader) + header.dataSize + header.objectsSize))
|
||||
throw exception("The size of the parcel according to the header exceeds the specified size");
|
||||
data.resize(header.dataSize);
|
||||
state.thisProcess->ReadMemory(data.data(), address + header.dataOffset, header.dataSize);
|
||||
objects.resize(header.objectsSize);
|
||||
state.thisProcess->ReadMemory(objects.data(), address + header.objectsOffset, header.objectsSize);
|
||||
}
|
||||
|
||||
Parcel::Parcel(const DeviceState &state) : state(state) {}
|
||||
|
||||
u64 Parcel::WriteParcel(kernel::ipc::BufferDescriptorABW *buffer, pid_t process) {
|
||||
return WriteParcel(buffer->Address(), buffer->Size(), process);
|
||||
}
|
||||
|
||||
u64 Parcel::WriteParcel(kernel::ipc::BufferDescriptorC *buffer, pid_t process) {
|
||||
return WriteParcel(buffer->address, buffer->size, process);
|
||||
}
|
||||
|
||||
u64 Parcel::WriteParcel(u64 address, u64 maxSize, pid_t process) {
|
||||
header.dataSize = static_cast<u32>(data.size());
|
||||
header.dataOffset = sizeof(ParcelHeader);
|
||||
header.objectsSize = static_cast<u32>(objects.size());
|
||||
header.objectsOffset = sizeof(ParcelHeader) + data.size();
|
||||
u64 totalSize = sizeof(ParcelHeader) + header.dataSize + header.objectsSize;
|
||||
if (maxSize < totalSize)
|
||||
throw exception("The size of the parcel exceeds maxSize");
|
||||
if (process) {
|
||||
auto &object = state.os->processMap.at(process);
|
||||
object->WriteMemory(header, address);
|
||||
object->WriteMemory(data.data(), address + header.dataOffset, data.size());
|
||||
object->WriteMemory(objects.data(), address + header.objectsOffset, objects.size());
|
||||
} else {
|
||||
state.thisProcess->WriteMemory(header, address);
|
||||
state.thisProcess->WriteMemory(data.data(), address + header.dataOffset, data.size());
|
||||
state.thisProcess->WriteMemory(objects.data(), address + header.objectsOffset, objects.size());
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
}
|
112
app/src/main/cpp/skyline/gpu/parcel.h
Normal file
112
app/src/main/cpp/skyline/gpu/parcel.h
Normal file
@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <common.h>
|
||||
#include <kernel/ipc.h>
|
||||
|
||||
namespace skyline::gpu {
|
||||
/**
|
||||
* @brief This class encapsulates a Parcel object (https://switchbrew.org/wiki/Display_services#Parcel)
|
||||
*/
|
||||
class Parcel {
|
||||
private:
|
||||
/**
|
||||
* @brief This holds the header of a parcel
|
||||
*/
|
||||
struct ParcelHeader {
|
||||
u32 dataSize;
|
||||
u32 dataOffset;
|
||||
u32 objectsSize;
|
||||
u32 objectsOffset;
|
||||
} header{};
|
||||
static_assert(sizeof(ParcelHeader) == 0x10);
|
||||
|
||||
const DeviceState &state; //!< The state of the device
|
||||
|
||||
public:
|
||||
std::vector<u8> data; //!< A vector filled with data in the parcel
|
||||
std::vector<u8> objects; //!< A vector filled with objects in the parcel
|
||||
|
||||
/**
|
||||
* @brief This constructor fills in the Parcel object with data from a IPC buffer
|
||||
* @param buffer The buffer that contains the parcel
|
||||
* @param state The state of the device
|
||||
*/
|
||||
Parcel(kernel::ipc::BufferDescriptorABW *buffer, const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This constructor fills in the Parcel object with data from a IPC buffer
|
||||
* @param buffer The buffer that contains the parcel
|
||||
* @param state The state of the device
|
||||
*/
|
||||
Parcel(kernel::ipc::BufferDescriptorX *buffer, const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This constructor fills in the Parcel object with data from a Parcel on a remote process
|
||||
* @param address The remote address of the parcel
|
||||
* @param size The size of the parcel
|
||||
* @param state The state of the device
|
||||
*/
|
||||
Parcel(u64 address, u64 size, const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief This constructor is used to create an empty parcel then write to a process
|
||||
* @param state The state of the device
|
||||
*/
|
||||
Parcel(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Writes some data to the Parcel
|
||||
* @tparam ValueType The type of the object to write
|
||||
* @param value The object to be written
|
||||
*/
|
||||
template<typename ValueType>
|
||||
void WriteData(const ValueType &value) {
|
||||
data.reserve(data.size() + sizeof(ValueType));
|
||||
auto item = reinterpret_cast<const u8 *>(&value);
|
||||
for (uint index = 0; sizeof(ValueType) > index; index++) {
|
||||
data.push_back(*item);
|
||||
item++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes an object to the Parcel
|
||||
* @tparam ValueType The type of the object to write
|
||||
* @param value The object to be written
|
||||
*/
|
||||
template<typename ValueType>
|
||||
void WriteObject(const ValueType &value) {
|
||||
objects.reserve(objects.size() + sizeof(ValueType));
|
||||
auto item = reinterpret_cast<const u8 *>(&value);
|
||||
for (uint index = 0; sizeof(ValueType) > index; index++) {
|
||||
objects.push_back(*item);
|
||||
item++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes the Parcel object into a particular B buffer on a process
|
||||
* @param buffer The buffer to write into
|
||||
* @param process The process to write the Parcel to
|
||||
* @return The total size of the message
|
||||
*/
|
||||
u64 WriteParcel(kernel::ipc::BufferDescriptorABW *buffer, pid_t process = 0);
|
||||
|
||||
/**
|
||||
* @brief Writes the Parcel object into a particular C buffer on a process
|
||||
* @param buffer The buffer to write into
|
||||
* @param process The process to write the Parcel to
|
||||
* @return The total size of the message
|
||||
*/
|
||||
u64 WriteParcel(kernel::ipc::BufferDescriptorC *buffer, pid_t process = 0);
|
||||
|
||||
/**
|
||||
* @brief Writes the Parcel object into the process's memory
|
||||
* @param address The address to write the Parcel object to
|
||||
* @param maxSize The maximum size of the Parcel
|
||||
* @param process The process to write the Parcel to
|
||||
* @return The total size of the message
|
||||
*/
|
||||
u64 WriteParcel(u64 address, u64 maxSize, pid_t process = 0);
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user