Compare commits

...
Sign in to create a new pull request.

21 commits

Author SHA1 Message Date
Billy Laws
c9cafb394a Address feedback 2021-09-27 22:05:05 +01:00
Billy Laws
a0b4710282 Address feedback 2021-09-21 21:51:53 +01:00
Billy Laws
b57710f260 Implement AsGpu::FreeSpace and clean up locking and debug prints 2021-09-18 13:16:55 +01:00
Billy Laws
4a7700bd7c Cleanup IStorageAccessor validation code 2021-09-17 19:45:47 +01:00
Billy Laws
83eb88d78b Optimise GPFIFO command processing for higher throughput
Using a u32 for the loop index prevents masking on all increments,
giving a moderate performance increase.

Passing methods as u32 parameters and stopping subChannel being passed
gives quite a significant increase when combined with the inlining
allowed by subchannel based engine selection.
2021-09-17 19:42:17 +01:00
Billy Laws
67149ef7fb Misc code cleanup and more comments in AS map 2021-09-17 19:41:23 +01:00
Billy Laws
33f8be6f52 Use IPC raw data size to calculate C Buffer offset
Some games don't have a u16 size array, the code for this is also kinda
pointless when we can use the raw data size too.
2021-09-17 19:34:51 +01:00
Billy Laws
d094cc142d NEEDS CLEANUP: Reimplement GPU VMM and rewrite nvdrv VM impl 2021-08-14 20:44:16 +01:00
Billy Laws
b9098ac14a Add support for Ioctl2/3 and improve debug logging 2021-07-17 23:41:22 +01:00
Billy Laws
7468003381 Update CMakeLists for nvdrv rework 2021-07-17 17:50:02 +01:00
Billy Laws
abaca6d40e Move nvhost-ctrl-gpu to new device API 2021-07-17 17:50:02 +01:00
Billy Laws
6c6abd9a51 Move nvhost-gpu to new device API
The implementation of GPU channels and Host1X channels will be split up
as it allows a much cleaner implementation and less undefined behaviour
potential.
2021-07-17 17:50:02 +01:00
Billy Laws
a513f84fff Move nvhost-as-gpu to new device API 2021-07-17 17:50:02 +01:00
Billy Laws
054a7aa91e Make GraphicBufferProducer use the new nvmap API 2021-07-17 17:50:02 +01:00
Billy Laws
5123be6604 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.
2021-07-17 17:50:02 +01:00
Billy Laws
d159605caf Migrate syncpoint management over to the new device API
The syncpoint manager has beeen given convinience functions for fences
which remove the need to access the raw id/threshold most of the time
and various accuracy fixes and cleanups to match HOS 12.0.0 have also
been done.
2021-07-17 17:50:01 +01:00
Billy Laws
f6a7ccf7eb 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.
2021-07-17 17:15:23 +01:00
Billy Laws
01d58dee27 Use concepts for Backing::Read over enable_if 2021-07-17 17:12:18 +01:00
Billy Laws
b5b86f41a3 Implement efficient template based IOCTL deserialisation
This moves IOCTLs from frozen to a template based parser, this allows
IOCTL lookup to be performed much more optimally and some IOCTLs to be
inlined.
2021-07-17 17:10:12 +01:00
Billy Laws
f1bbf06cd8 Allow supplying a custom ResultValue result type and optimise span
This allows PosixResultValue to be created easily.
2021-07-17 14:01:39 +01:00
Billy Laws
85c48d0e7e Add common PosixResult and PosixResultValue types
Many services like bsd and nvdrv use these to represent the result
values of functions so add a common type for these as an alternative to
the macros.
2021-07-17 13:56:53 +01:00
64 changed files with 3424 additions and 2150 deletions

View file

@ -96,7 +96,7 @@ add_library(skyline SHARED
${source_DIR}/skyline/gpu/command_scheduler.cpp
${source_DIR}/skyline/gpu/texture/texture.cpp
${source_DIR}/skyline/gpu/presentation_engine.cpp
${source_DIR}/skyline/soc/gmmu.cpp
${source_DIR}/skyline/soc/gm20b.cpp
${source_DIR}/skyline/soc/host1x/syncpoint.cpp
${source_DIR}/skyline/soc/gm20b/gpfifo.cpp
${source_DIR}/skyline/soc/gm20b/engines/maxwell_3d.cpp
@ -178,13 +178,14 @@ add_library(skyline SHARED
${source_DIR}/skyline/services/fssrv/IDirectory.cpp
${source_DIR}/skyline/services/nvdrv/INvDrvServices.cpp
${source_DIR}/skyline/services/nvdrv/driver.cpp
${source_DIR}/skyline/services/nvdrv/core/nvmap.cpp
${source_DIR}/skyline/services/nvdrv/core/syncpoint_manager.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvdevice.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvmap.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost_ctrl_gpu.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost_ctrl.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost_channel.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost/as_gpu.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost/ctrl.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost/ctrl_gpu.cpp
${source_DIR}/skyline/services/nvdrv/devices/nvhost/gpu_channel.cpp
${source_DIR}/skyline/services/hosbinder/parcel.cpp
${source_DIR}/skyline/services/hosbinder/IHOSBinderDriver.cpp
${source_DIR}/skyline/services/hosbinder/GraphicBufferProducer.cpp

View file

@ -83,26 +83,25 @@ namespace skyline {
/**
* @brief A wrapper around std::optional that also stores a HOS result code
* @tparam T The object type to hold
*/
template<typename T>
template<typename ValueType, typename ResultType = Result>
class ResultValue {
static_assert(!std::is_same<T, Result>::value);
static_assert(!std::is_same<ValueType, ResultType>::value);
private:
std::optional<T> value;
std::optional<ValueType> value;
public:
Result result;
ResultType result{};
constexpr ResultValue(T value) : value(value) {};
constexpr ResultValue(ValueType value) : value(value) {};
constexpr ResultValue(Result result) : result(result) {};
constexpr ResultValue(ResultType result) : result(result) {};
template<typename U>
constexpr ResultValue(ResultValue<U> result) : result(result) {};
constexpr operator Result() const {
constexpr operator ResultType() const {
return result;
}
@ -110,11 +109,11 @@ namespace skyline {
return value.has_value();
}
constexpr T& operator*() {
constexpr ValueType& operator*() {
return *value;
}
constexpr T* operator->() {
constexpr ValueType* operator->() {
return &*value;
}
};
@ -362,6 +361,9 @@ namespace skyline {
template<typename Out>
constexpr Out &as() {
if constexpr (Extent != std::dynamic_extent && sizeof(T) * Extent >= sizeof(Out))
return *reinterpret_cast<Out *>(span::data());
if (span::size_bytes() >= sizeof(Out))
return *reinterpret_cast<Out *>(span::data());
throw exception("Span size is less than Out type size (0x{:X}/0x{:X})", span::size_bytes(), sizeof(Out));

View file

@ -0,0 +1,179 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <concepts>
#include <common.h>
namespace skyline {
template<typename VaType, size_t AddressSpaceBits>
concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= AddressSpaceBits;
struct EmptyStruct {};
/**
* @brief FlatAddressSpaceMap provides a generic VA->PA mapping implementation using a sorted vector
*/
template<typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo = EmptyStruct> requires AddressSpaceValid<VaType, AddressSpaceBits>
class FlatAddressSpaceMap {
private:
std::function<void(VaType, VaType)> unmapCallback{}; //!< Callback called when the mappings in an region have changed
protected:
/**
* @brief Represents a block of memory in the AS, the physical mapping is contiguous until another block with a different phys address is hit
*/
struct Block {
VaType virt{UnmappedVa}; //!< VA of the block
PaType phys{UnmappedPa}; //!< PA of the block, will increase 1-1 with VA until a new block is encountered
[[no_unique_address]] ExtraBlockInfo extraInfo;
Block() = default;
Block(VaType virt, PaType phys, ExtraBlockInfo extraInfo) : virt(virt), phys(phys), extraInfo(extraInfo) {}
constexpr bool Valid() {
return virt != UnmappedVa;
}
constexpr bool Mapped() {
return phys != UnmappedPa;
}
constexpr bool Unmapped() {
return phys == UnmappedPa;
}
bool operator<(const VaType &pVirt) const {
return virt < pVirt;
}
};
std::mutex blockMutex;
std::vector<Block> blocks{Block{}};
/**
* @brief Maps a PA range into the given AS region
* @note blockMutex MUST be locked when calling this
*/
void MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extraInfo);
/**
* @brief Unmaps the given range and merges it with other unmapped regions
* @note blockMutex MUST be locked when calling this
*/
void UnmapLocked(VaType virt, VaType size);
public:
static constexpr VaType VaMaximum{(1ULL << (AddressSpaceBits - 1)) + ((1ULL << (AddressSpaceBits - 1)) - 1)}; //!< The maximum VA that this AS can technically reach
VaType vaLimit{VaMaximum}; //!< A soft limit on the maximum VA of the AS
FlatAddressSpaceMap(VaType vaLimit, std::function<void(VaType, VaType)> unmapCallback = {});
FlatAddressSpaceMap() = default;
void Map(VaType virt, PaType phys, VaType size, ExtraBlockInfo extraInfo = {}) {
std::scoped_lock lock(blockMutex);
MapLocked(virt, phys, size, extraInfo);
}
void Unmap(VaType virt, VaType size) {
std::scoped_lock lock(blockMutex);
UnmapLocked(virt, size);
}
};
/**
* @brief Hold memory manager specific block info
*/
struct MemoryManagerBlockInfo {
bool sparseMapped;
};
/**
* @brief FlatMemoryManager specialises FlatAddressSpaceMap to focus on pointers as PAs, adding read/write functions and sparse mapping support
*/
template<typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> requires AddressSpaceValid<VaType, AddressSpaceBits>
class FlatMemoryManager : public FlatAddressSpaceMap<VaType, UnmappedVa, u8 *, nullptr, true, AddressSpaceBits, MemoryManagerBlockInfo> {
private:
static constexpr u64 SparseMapSize{0x400000000}; //!< 16GiB pool size for sparse mappings returned by TranslateRange, this number is arbritary and should be large enough to fit the largest sparse mapping in the AS
u8 *sparseMap; //!< Pointer to a zero filled memory region that is returned by TranslateRange for sparse mappings
public:
FlatMemoryManager();
~FlatMemoryManager();
/**
* @return A placeholder address for sparse mapped regions, this means nothing
*/
static u8 *SparsePlaceholderAddress() {
return reinterpret_cast<u8 *>(0xCAFEBABE);
}
/**
* @brief Returns a vector of all physical ranges inside of the given virtual range
*/
std::vector<span<u8>> TranslateRange(VaType virt, VaType size);
void Read(u8 *destination, VaType virt, VaType size);
template<typename T>
void Read(span <T> destination, VaType virt) {
Read(reinterpret_cast<u8 *>(destination.data()), virt, destination.size_bytes());
}
template<typename T>
T Read(VaType virt) {
T obj;
Read(reinterpret_cast<u8 *>(&obj), virt, sizeof(T));
return obj;
}
void Write(VaType virt, u8 *source, VaType size);
template<typename T>
void Write(VaType virt, span <T> source) {
Write(virt, reinterpret_cast<u8 *>(source.data()), source.size_bytes());
}
template<typename T>
void Write(VaType virt, T source) {
Write(virt, reinterpret_cast<u8 *>(&source), sizeof(T));
}
};
/**
* @brief FlatMemoryManager specialises FlatAddressSpaceMap to work as an allocator, with an initial, fast linear pass and a subsequent slower pass that iterates until it finds a free block
*/
template<typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> requires AddressSpaceValid<VaType, AddressSpaceBits>
class FlatAllocator : public FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits> {
private:
using Base = FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits>;
VaType currentLinearAllocEnd; //!< The end address for the initial linear allocation pass, once this reaches the AS limit the slower allocation path will be used
public:
VaType vaStart; //!< The base VA of the allocator, no allocations will be below this
FlatAllocator(VaType vaStart, VaType vaLimit);
/**
* @brief Allocates a region in the AS of the given size and returns its address
*/
VaType Allocate(VaType size);
/**
* @brief Marks the given region in the AS as allocated
*/
void AllocateFixed(VaType virt, VaType size);
/**
* @brief Frees an AS region so it can be used again
*/
void Free(VaType virt, VaType size);
};
}

View file

@ -0,0 +1,423 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <common/trace.h>
#include <kernel/types/KProcess.h>
#include "address_space.h"
#define MAP_MEMBER(returnType) template<typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatAddressSpaceMap<VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
#define MM_MEMBER(returnType) template<typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatMemoryManager<VaType, UnmappedVa, AddressSpaceBits>
#define ALLOC_MEMBER(returnType) template<typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
namespace skyline {
MAP_MEMBER()::FlatAddressSpaceMap(VaType vaLimit, std::function<void(VaType, VaType)> unmapCallback) :
vaLimit(vaLimit),
unmapCallback(std::move(unmapCallback)) {
if (vaLimit > VaMaximum)
throw exception("Invalid VA limit!");
}
MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extraInfo) {
TRACE_EVENT("containers", "FlatAddressSpaceMap::Map");
VaType virtEnd{virt + size};
if (virtEnd > vaLimit)
throw exception("Trying to map a block past the VA limit: virtEnd: 0x{:X}, vaLimit: 0x{:X}", virtEnd, vaLimit);
auto blockEndSuccessor{std::lower_bound(blocks.begin(), blocks.end(), virtEnd)};
if (blockEndSuccessor == blocks.begin())
throw exception("Trying to map a block before the VA start: virtEnd: 0x{:X}", virtEnd);
auto blockEndPredecessor{std::prev(blockEndSuccessor)};
if (blockEndSuccessor != blocks.end()) {
// We have blocks in front of us, if one is directly in front then we don't have to add a tail
if (blockEndSuccessor->virt != virtEnd) {
PaType tailPhys{[&]() -> PaType {
if (!PaContigSplit || blockEndPredecessor->Unmapped())
return blockEndPredecessor->phys; // Always propagate unmapped regions rather than calculating offset
else
return blockEndPredecessor->phys + virtEnd - blockEndPredecessor->virt;
}()};
if (blockEndPredecessor->virt >= virt) {
// If this block's start would be overlapped by the map then reuse it as a tail block
blockEndPredecessor->virt = virtEnd;
blockEndPredecessor->phys = tailPhys;
blockEndPredecessor->extraInfo = blockEndPredecessor->extraInfo;
} else {
// Else insert a new one and we're done
blocks.insert(blockEndSuccessor, {Block(virt, phys, extraInfo), Block(virtEnd, tailPhys, blockEndPredecessor->extraInfo)});
if (unmapCallback)
unmapCallback(virt, size);
return;
}
}
} else {
// blockEndPredecessor will always be unmapped as blocks has to be terminated by an unmapped chunk
if (blockEndPredecessor != blocks.begin() && blockEndPredecessor->virt >= virt) {
// Move the unmapped block start backwards
blockEndPredecessor->virt = virtEnd;
} else {
// Else insert a new one and we're done
blocks.insert(blockEndSuccessor, {Block(virt, phys, extraInfo), Block(virtEnd, UnmappedPa, {})});
if (unmapCallback)
unmapCallback(virt, size);
return;
}
}
auto blockStartSuccessor{blockEndPredecessor};
// Walk the block vector to find the start successor as this is more efficient than another binary search in most scenarios
while (std::prev(blockStartSuccessor)->virt >= virt)
blockStartSuccessor--;
// Check that the start successor is either the end block or something in between
if (blockStartSuccessor->virt > virtEnd) {
throw exception("Unsorted block in AS map: virt: 0x{:X}", blockStartSuccessor->virt);
} else if (blockStartSuccessor->virt == virtEnd) {
// We need to create a new block as there are none spare that we would overwrite
blocks.insert(blockStartSuccessor, Block(virt, phys, extraInfo));
} else {
// Reuse a block that would otherwise be overwritten as a start block
blockStartSuccessor->virt = virt;
blockStartSuccessor->phys = phys;
blockStartSuccessor->extraInfo = extraInfo;
// Erase overwritten blocks
if (auto eraseStart{std::next(blockStartSuccessor)}; blockStartSuccessor != blockEndPredecessor) {
if (eraseStart == blockEndPredecessor)
throw exception("Trying to erase the end block of a newly mapped region!");
blocks.erase(eraseStart, blockEndPredecessor);
}
}
if (unmapCallback)
unmapCallback(virt, size);
}
MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
TRACE_EVENT("containers", "FlatAddressSpaceMap::Unmap");
VaType virtEnd{virt + size};
if (virtEnd > vaLimit)
throw exception("Trying to map a block past the VA limit: virtEnd: 0x{:X}, vaLimit: 0x{:X}", virtEnd, vaLimit);
auto blockEndSuccessor{std::lower_bound(blocks.begin(), blocks.end(), virtEnd)};
if (blockEndSuccessor == blocks.begin())
throw exception("Trying to unmap a block before the VA start: virtEnd: 0x{:X}", virtEnd);
auto blockEndPredecessor{std::prev(blockEndSuccessor)};
auto walkBackToPredecessor{[&](auto iter) {
while (iter->virt >= virt)
iter--;
return iter;
}};
auto eraseBlocksWithEndUnmapped{[&](auto unmappedEnd) {
auto blockStartPredecessor{walkBackToPredecessor(unmappedEnd)};
auto blockStartSuccessor{std::next(blockStartPredecessor)};
auto eraseEnd{[&]() {
if (blockStartPredecessor->Unmapped()) {
// If the start predecessor is unmapped then we can erase everything in our region and be done
return std::next(unmappedEnd);
} else {
// Else reuse the end predecessor as the start of our unmapped region then erase all up to it
unmappedEnd->virt = virt;
return unmappedEnd;
}
}()};
// We can't have two unmapped regions after each other
if (eraseEnd == blockStartSuccessor || (blockStartPredecessor->Unmapped() && eraseEnd->Unmapped()))
throw exception("Multiple contiguous unmapped regions are unsupported!");
blocks.erase(blockStartSuccessor, eraseEnd);
}};
// We can avoid any splitting logic if these are the case
if (blockEndPredecessor->Unmapped()) {
if (blockEndPredecessor->virt > virt)
eraseBlocksWithEndUnmapped(blockEndPredecessor);
if (unmapCallback)
unmapCallback(virt, size);
return; // The region is unmapped, bail out early
} else if (blockEndSuccessor->virt == virtEnd && blockEndSuccessor->Unmapped()) {
eraseBlocksWithEndUnmapped(blockEndSuccessor);
if (unmapCallback)
unmapCallback(virt, size);
return; // The region is unmapped here and doesn't need splitting, bail out early
} else if (blockEndSuccessor == blocks.end()) {
// This should never happen as the end should always follow an unmapped block
throw exception("Unexpected Memory Manager state!");
} else if (blockEndSuccessor->virt != virtEnd) {
// If one block is directly in front then we don't have to add a tail
// The previous block is mapped so we will need to add a tail with an offset
PaType tailPhys{[&]() {
if constexpr (PaContigSplit)
return blockEndPredecessor->phys + virtEnd - blockEndPredecessor->virt;
else
return blockEndPredecessor->phys;
}()};
if (blockEndPredecessor->virt >= virt) {
// If this block's start would be overlapped by the unmap then reuse it as a tail block
blockEndPredecessor->virt = virtEnd;
blockEndPredecessor->phys = tailPhys;
} else {
blocks.insert(blockEndSuccessor, {Block(virt, UnmappedPa, {}), Block(virtEnd, tailPhys, blockEndPredecessor->extraInfo)});
if (unmapCallback)
unmapCallback(virt, size);
return; // The previous block is mapped and ends before
}
}
// Walk the block vector to find the start predecessor as this is more efficient than another binary search in most scenarios
auto blockStartPredecessor{walkBackToPredecessor(blockEndPredecessor)};
auto blockStartSuccessor{std::next(blockStartPredecessor)};
if (blockStartSuccessor->virt > virtEnd) {
throw exception("Unsorted block in AS map: virt: 0x{:X}", blockStartSuccessor->virt);
} else if (blockStartSuccessor->virt == virtEnd) {
// There are no blocks between the start and the end that would let us skip inserting a new one for head
// The previous block is may be unmapped, if so we don't need to insert any unmaps after it
if (blockStartPredecessor->Mapped())
blocks.insert(blockStartSuccessor, Block(virt, UnmappedPa, {}));
} else if (blockStartPredecessor->Unmapped()) {
// If the previous block is unmapped
blocks.erase(blockStartSuccessor, blockEndPredecessor);
} else {
// Add in the unmapped block header
blockStartSuccessor->virt = virt;
blockStartSuccessor->phys = UnmappedPa;
// Erase overwritten blocks, skipping the first one as we have written the unmapped start block there
if (auto eraseStart{std::next(blockStartSuccessor)}; blockStartSuccessor != blockEndPredecessor) {
if (eraseStart == blockEndPredecessor)
throw exception("Trying to erase the end block of a newly unmapped region!");
blocks.erase(eraseStart, blockEndPredecessor);
}
}
if (unmapCallback)
unmapCallback(virt, size);
}
MM_MEMBER()::FlatMemoryManager() {
sparseMap = static_cast<u8 *>(mmap(0, SparseMapSize, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (!sparseMap)
throw exception("Failed to mmap sparse map!");
}
MM_MEMBER()::~FlatMemoryManager() {
munmap(sparseMap, SparseMapSize);
}
MM_MEMBER(std::vector<span<u8>>)::TranslateRange(VaType virt, VaType size) {
TRACE_EVENT("containers", "FlatMemoryManager::TranslateRange");
std::scoped_lock lock(this->blockMutex);
VaType virtEnd{virt + size};
auto successor{std::upper_bound(this->blocks.begin(), this->blocks.end(), virt, [] (auto virt, const auto &block) {
return virt < block.virt;
})};
auto predecessor{std::prev(successor)};
u8 *blockPhys{predecessor->phys + (virt - predecessor->virt)};
VaType blockSize{std::min(successor->virt - virt, size)};
std::vector<span<u8>> ranges;
while (size) {
// Return a zeroed out map to emulate sparse mappings
if (predecessor->extraInfo.sparseMapped) {
if (blockSize > SparseMapSize)
throw exception("Size of the sparse map is too small to fit block of size: 0x{:X}", blockSize);
blockPhys = sparseMap;
}
ranges.push_back(span(blockPhys, blockSize));
size -= blockSize;
if (size) {
predecessor = successor++;
blockPhys = predecessor->phys;
blockSize = std::min(successor->virt - predecessor->virt, size);
}
}
return ranges;
}
MM_MEMBER(void)::Read(u8 *destination, VaType virt, VaType size) {
TRACE_EVENT("containers", "FlatMemoryManager::Read");
std::scoped_lock lock(this->blockMutex);
VaType virtEnd{virt + size};
auto successor{std::upper_bound(this->blocks.begin(), this->blocks.end(), virt, [] (auto virt, const auto &block) {
return virt < block.virt;
})};
auto predecessor{std::prev(successor)};
u8 *blockPhys{predecessor->phys + (virt - predecessor->virt)};
VaType blockReadSize{std::min(successor->virt - virt, size)};
// Reads may span across multiple individual blocks
while (size) {
if (predecessor->phys == nullptr) {
throw exception("Page fault at 0x{:X}", predecessor->virt);
} else {
if (predecessor->extraInfo.sparseMapped) // Sparse mappings read all zeroes
std::memset(destination, 0, blockReadSize);
else
std::memcpy(destination, blockPhys, blockReadSize);
}
destination += blockReadSize;
size -= blockReadSize;
if (size) {
predecessor = successor++;
blockPhys = predecessor->phys;
blockReadSize = std::min(successor->virt - predecessor->virt, size);
}
}
}
MM_MEMBER(void)::Write(VaType virt, u8 *source, VaType size) {
TRACE_EVENT("containers", "FlatMemoryManager::Write");
std::scoped_lock lock(this->blockMutex);
VaType virtEnd{virt + size};
auto successor{std::upper_bound(this->blocks.begin(), this->blocks.end(), virt, [] (auto virt, const auto &block) {
return virt < block.virt;
})};
auto predecessor{std::prev(successor)};
u8 *blockPhys{predecessor->phys + (virt - predecessor->virt)};
VaType blockWriteSize{std::min(successor->virt - virt, size)};
// Writes may span across multiple individual blocks
while (size) {
if (predecessor->phys == nullptr) {
throw exception("Page fault at 0x{:X}", predecessor->virt);
} else {
if (!predecessor->extraInfo.sparseMapped) // Sparse mappings ignore writes
std::memcpy(blockPhys, source, blockWriteSize);
}
source += blockWriteSize;
size -= blockWriteSize;
if (size) {
predecessor = successor++;
blockPhys = predecessor->phys;
blockWriteSize = std::min(successor->virt - predecessor->virt, size);
}
}
}
ALLOC_MEMBER()::FlatAllocator(VaType vaStart, VaType vaLimit) : Base(vaLimit), vaStart(vaStart), currentLinearAllocEnd(vaStart) {}
ALLOC_MEMBER(VaType)::Allocate(VaType size) {
TRACE_EVENT("containers", "FlatAllocator::Allocate");
std::scoped_lock lock(this->blockMutex);
VaType allocStart{UnmappedVa};
VaType allocEnd{currentLinearAllocEnd + size};
// Avoid searching backwards in the address space if possible
if (allocEnd >= currentLinearAllocEnd && allocEnd <= this->vaLimit) {
auto allocEndSuccessor{std::lower_bound(this->blocks.begin(), this->blocks.end(), allocEnd)};
if (allocEndSuccessor == this->blocks.begin())
throw exception("First block in AS map is invalid!");
auto allocEndPredecessor{std::prev(allocEndSuccessor)};
if (allocEndPredecessor->virt <= currentLinearAllocEnd) {
allocStart = currentLinearAllocEnd;
} else {
// Skip over fixed any mappings in front of us
while (allocEndSuccessor != this->blocks.end()) {
if (allocEndSuccessor->virt - allocEndPredecessor->virt < size || allocEndPredecessor->Mapped() ) {
allocStart = allocEndPredecessor->virt;
break;
}
allocEndPredecessor = allocEndSuccessor++;
// Use the VA limit to calculate if we can fit in the final block since it has no successor
if (allocEndSuccessor == this->blocks.end()) {
allocEnd = allocEndPredecessor->virt + size;
if (allocEnd >= allocEndPredecessor->virt && allocEnd <= this->vaLimit)
allocStart = allocEndPredecessor->virt;
}
}
}
}
if (allocStart != UnmappedVa) {
currentLinearAllocEnd = allocStart + size;
} else { // If linear allocation overflows the AS then find a gap
if (this->blocks.size() <= 2)
throw exception("Unexpected allocator state!");
auto searchPredecessor{this->blocks.begin()};
auto searchSuccessor{std::next(searchPredecessor)};
while (searchSuccessor != this->blocks.end() &&
(searchSuccessor->virt - searchPredecessor->virt < size || searchPredecessor->Mapped())) {
searchPredecessor = searchSuccessor++;
}
if (searchSuccessor != this->blocks.end())
allocStart = searchPredecessor->virt;
else
throw exception("Unexpected allocator state!");
}
this->MapLocked(allocStart, true, size, {});
return allocStart;
}
ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
this->Map(virt, true, size);
}
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
this->Unmap(virt, size);
}
}

View file

@ -3,6 +3,7 @@
#pragma once
#include <common/trace.h>
#include <common.h>
namespace skyline {
@ -51,10 +52,15 @@ namespace skyline {
*/
template<typename F>
[[noreturn]] void Process(F function) {
TRACE_EVENT_BEGIN("containers", "CircularQueue::Process");
while (true) {
if (start == end) {
std::unique_lock lock(productionMutex);
TRACE_EVENT_END("containers");
produceCondition.wait(lock, [this]() { return start != end; });
TRACE_EVENT_BEGIN("containers", "CircularQueue::Process");
}
while (start != end) {

View file

@ -13,7 +13,8 @@ PERFETTO_DEFINE_CATEGORIES(
perfetto::Category("kernel").SetDescription("Events from parts of the HLE kernel"),
perfetto::Category("guest").SetDescription("Events relating to guest code"),
perfetto::Category("gpu").SetDescription("Events from the emulated GPU"),
perfetto::Category("service").SetDescription("Events from the HLE sysmodule implementations")
perfetto::Category("service").SetDescription("Events from the HLE sysmodule implementations"),
perfetto::Category("containers").SetDescription("Events from custom container implementations")
);
namespace skyline::trace {

View file

@ -12,8 +12,6 @@ namespace skyline::kernel::ipc {
header = reinterpret_cast<CommandHeader *>(pointer);
pointer += sizeof(CommandHeader);
size_t cBufferLengthSize{util::AlignUp(((header->cFlag == BufferCFlag::None) ? 0 : ((header->cFlag > BufferCFlag::SingleDescriptor) ? (static_cast<u8>(header->cFlag) - 2) : 1)) * sizeof(u16), sizeof(u32))};
if (header->handleDesc) {
handleDesc = reinterpret_cast<HandleDescriptor *>(pointer);
pointer += sizeof(HandleDescriptor) + (handleDesc->sendPid ? sizeof(u64) : 0);
@ -64,6 +62,8 @@ namespace skyline::kernel::ipc {
pointer += sizeof(BufferDescriptorABW);
}
auto bufCPointer{pointer + header->rawSize * sizeof(u32)};
auto offset{pointer - tls}; // We calculate the relative offset as the absolute one might differ
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front
pointer += padding;
@ -88,8 +88,7 @@ namespace skyline::kernel::ipc {
pointer += sizeof(PayloadHeader);
cmdArg = pointer;
cmdArgSz = (header->rawSize * sizeof(u32)) - (constant::IpcPaddingSum + sizeof(PayloadHeader)) - cBufferLengthSize;
pointer += cmdArgSz;
cmdArgSz = header->rawSize * sizeof(u32);
}
payloadOffset = cmdArg;
@ -97,22 +96,21 @@ namespace skyline::kernel::ipc {
if (payload->magic != util::MakeMagic<u32>("SFCI") && (header->type != CommandType::Control && header->type != CommandType::ControlWithContext && header->type != CommandType::Close) && (!domain || domain->command != DomainCommand::CloseVHandle)) // SFCI is the magic in received IPC messages
state.logger->Debug("Unexpected Magic in PayloadHeader: 0x{:X}", static_cast<u32>(payload->magic));
pointer += constant::IpcPaddingSum - padding + cBufferLengthSize;
if (header->cFlag == BufferCFlag::SingleDescriptor) {
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)};
if (bufC->address) {
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
state.logger->Verbose("Buf C: 0x{:X}, 0x{:X}", bufC->Pointer(), static_cast<u16>(bufC->size));
}
} else if (header->cFlag > BufferCFlag::SingleDescriptor) {
for (u8 index{}; (static_cast<u8>(header->cFlag) - 2) > index; index++) { // (cFlag - 2) C descriptors are present
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)};
if (bufC->address) {
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
state.logger->Verbose("Buf C #{}: 0x{:X}, 0x{:X}", index, bufC->Pointer(), static_cast<u16>(bufC->size));
}
pointer += sizeof(BufferDescriptorC);
bufCPointer += sizeof(BufferDescriptorC);
}
}

View file

@ -13,7 +13,7 @@ namespace skyline::kernel::type {
if (fd < 0)
throw exception("An error occurred while creating shared memory: {}", fd);
host.ptr = reinterpret_cast<u8 *>(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0));
host.ptr = static_cast<u8 *>(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0));
if (host.ptr == MAP_FAILED)
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
@ -28,7 +28,7 @@ namespace skyline::kernel::type {
if (guest.Valid())
throw exception("Mapping KSharedMemory multiple times on guest is not supported: Requested Mapping: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", ptr, ptr + size, size, guest.ptr, guest.ptr + guest.size, guest.size);
guest.ptr = reinterpret_cast<u8 *>(mmap(ptr, size, permission.Get(), MAP_SHARED | (ptr ? MAP_FIXED : 0), fd, 0));
guest.ptr = static_cast<u8 *>(mmap(ptr, size, permission.Get(), MAP_SHARED | (ptr ? MAP_FIXED : 0), fd, 0));
if (guest.ptr == MAP_FAILED)
throw exception("An error occurred while mapping shared memory in guest: {}", strerror(errno));
guest.size = size;

View file

@ -14,11 +14,12 @@ namespace skyline::service::am {
Result IStorageAccessor::Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto offset{request.Pop<i64>()};
auto size{std::min(static_cast<i64>(request.inputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
if (offset > parent->content.size())
if (offset < 0 || offset > parent->content.size())
return result::OutOfBounds;
auto size{std::min(static_cast<i64>(request.inputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
if (size)
span(parent->content).copy_from(request.inputBuf.at(0), size);
@ -27,12 +28,13 @@ namespace skyline::service::am {
Result IStorageAccessor::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto offset{request.Pop<i64>()};
auto size{std::min(static_cast<i64>(request.outputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
if (offset > parent->content.size())
if (offset < 0 || offset > parent->content.size())
return result::OutOfBounds;
if (size > 0)
auto size{std::min(static_cast<i64>(request.outputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
if (size)
request.outputBuf.at(0).copy_from(span(parent->content.data() + offset, size));
return {};

View file

@ -3,22 +3,13 @@
#pragma once
#include <services/nvdrv/devices/nvhost_syncpoint.h>
namespace skyline::service::nvdrv {
/**
* @brief A Fence is a synchronization primitive that describes a point in a Syncpoint to synchronize at
*/
struct Fence {
u32 id{}; //!< The ID of the underlying syncpoint
u32 value{}; //!< The value of the syncpoint at which the fence is passed
/**
* @brief Synchronizes the fence's value with its underlying syncpoint
*/
void UpdateValue(NvHostSyncpoint &hostSyncpoint) {
value = hostSyncpoint.UpdateMin(id);
}
u32 threshold{}; //!< The value of the syncpoint at which the fence is signalled
};
static_assert(sizeof(Fence) == 0x8);
}

View file

@ -0,0 +1,23 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
namespace skyline::service {
enum class PosixResult : i32 {
Success = 0,
NotPermitted = 1, // EPERM
TryAgain = 11, // EAGAIN
Busy = 16, // EBUSY
InvalidArgument = 22, // EINVAL
InappropriateIoctlForDevice = 25, // ENOTTY
FunctionNotImplemented = 38, // ENOSYS
NotSupported = 95, // EOPNOTSUPP, ENOTSUP
TimedOut = 110, // ETIMEDOUT
};
template<typename ValueType>
using PosixResultValue = ResultValue<ValueType, PosixResult>;
}

View file

@ -6,13 +6,18 @@
#include <gpu.h>
#include <gpu/texture/format.h>
#include <soc.h>
#include <services/nvdrv/driver.h>
#include <services/nvdrv/devices/nvmap.h>
#include <services/common/fence.h>
#include "GraphicBufferProducer.h"
namespace skyline::service::hosbinder {
GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state) : state(state), bufferEvent(std::make_shared<kernel::type::KEvent>(state, true)) {}
GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state, nvdrv::core::NvMap &nvMap) : state(state), bufferEvent(std::make_shared<kernel::type::KEvent>(state, true)), nvMap(nvMap) {}
void GraphicBufferProducer::FreeGraphicBufferNvMap(GraphicBuffer &buffer) {
auto surface{buffer.graphicHandle.surfaces.at(0)};
u32 nvMapHandleId{surface.nvmapHandle ? surface.nvmapHandle : buffer.graphicHandle.nvmapId};
nvMap.FreeHandle(nvMapHandleId, true);
}
u32 GraphicBufferProducer::GetPendingBufferCount() {
u32 count{};
@ -64,6 +69,12 @@ namespace skyline::service::hosbinder {
for (auto &slot : queue) {
slot.state = BufferState::Free;
slot.frameNumber = std::numeric_limits<u32>::max();
if (slot.texture) {
slot.texture = {};
FreeGraphicBufferNvMap(*slot.graphicBuffer);
}
slot.graphicBuffer = nullptr;
}
} else if (preallocatedBufferCount < count) {
@ -160,6 +171,12 @@ namespace skyline::service::hosbinder {
bufferSlot.state = BufferState::Free;
bufferSlot.frameNumber = std::numeric_limits<u32>::max();
if (bufferSlot.texture) {
bufferSlot.texture = {};
FreeGraphicBufferNvMap(*bufferSlot.graphicBuffer);
}
bufferSlot.graphicBuffer = nullptr;
bufferEvent->Signal();
@ -183,6 +200,12 @@ namespace skyline::service::hosbinder {
bufferSlot->state = BufferState::Free;
bufferSlot->frameNumber = std::numeric_limits<u32>::max();
if (bufferSlot->texture) {
bufferSlot->texture = {};
FreeGraphicBufferNvMap(*bufferSlot->graphicBuffer);
}
graphicBuffer = *std::exchange(bufferSlot->graphicBuffer, nullptr);
fence = AndroidFence{};
@ -202,6 +225,11 @@ namespace skyline::service::hosbinder {
}
}
if (bufferSlot->texture) {
bufferSlot->texture = {};
FreeGraphicBufferNvMap(*bufferSlot->graphicBuffer);
}
if (bufferSlot == queue.end()) {
state.logger->Warn("Could not find any free slots to attach the graphic buffer to");
return AndroidStatus::NoMemory;
@ -304,27 +332,13 @@ namespace skyline::service::hosbinder {
if (surface.scanFormat != NvDisplayScanFormat::Progressive)
throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat));
std::shared_ptr<nvdrv::device::NvMap::NvMapObject> nvBuffer{};
{
auto driver{nvdrv::driver.lock()};
auto nvmap{driver->nvMap.lock()};
if (surface.nvmapHandle) {
nvBuffer = nvmap->GetObject(surface.nvmapHandle);
} else {
std::shared_lock nvmapLock(nvmap->mapMutex);
for (const auto &object : nvmap->maps) {
if (object->id == handle.nvmapId) {
nvBuffer = object;
break;
}
}
if (!nvBuffer)
throw exception("A QueueBuffer request has an invalid NvMap Handle ({}) and ID ({})", surface.nvmapHandle, handle.nvmapId);
}
}
// Duplicate the handle so it can't be freed by the guest
auto nvMapHandleObj{nvMap.GetHandle(surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId)};
if (auto err{nvMapHandleObj->Duplicate(true)}; err != PosixResult::Success)
throw exception("Failed to duplicate graphic buffer NvMap handle: {}!", static_cast<i32>(err));
if (surface.size > (nvBuffer->size - surface.offset))
throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvBuffer->size, surface.offset, surface.offset + surface.size);
if (surface.size > (nvMapHandleObj->origSize - surface.offset))
throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvMapHandleObj->origSize, surface.offset, surface.offset + surface.size);
gpu::texture::TileMode tileMode;
gpu::texture::TileConfig tileConfig{};
@ -344,7 +358,7 @@ namespace skyline::service::hosbinder {
throw exception("Legacy 16Bx16 tiled surfaces are not supported");
}
auto guestTexture{std::make_shared<gpu::GuestTexture>(state, nvBuffer->ptr + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)};
auto guestTexture{std::make_shared<gpu::GuestTexture>(state, nvMapHandleObj->GetPointer() + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)};
buffer.texture = guestTexture->CreateTexture({}, vk::ImageTiling::eLinear);
}
@ -529,6 +543,12 @@ namespace skyline::service::hosbinder {
for (auto &slot : queue) {
slot.state = BufferState::Free;
slot.frameNumber = std::numeric_limits<u32>::max();
if (slot.texture) {
slot.texture = {};
FreeGraphicBufferNvMap(*slot.graphicBuffer);
}
slot.graphicBuffer = nullptr;
}
@ -544,12 +564,16 @@ namespace skyline::service::hosbinder {
}
auto &buffer{queue[slot]};
if (buffer.texture) {
buffer.texture = {};
FreeGraphicBufferNvMap(*buffer.graphicBuffer);
}
buffer.state = BufferState::Free;
buffer.frameNumber = 0;
buffer.wasBufferRequested = false;
buffer.isPreallocated = graphicBuffer != nullptr;
buffer.graphicBuffer = graphicBuffer ? std::make_unique<GraphicBuffer>(*graphicBuffer) : nullptr;
buffer.texture = {};
if (graphicBuffer) {
if (graphicBuffer->magic != GraphicBuffer::Magic)

View file

@ -14,6 +14,10 @@ namespace skyline::gpu {
class Texture;
}
namespace skyline::service::nvdrv::core {
class NvMap;
}
namespace skyline::service::hosbinder {
/**
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h;l=52-91
@ -66,6 +70,9 @@ namespace skyline::service::hosbinder {
AndroidPixelFormat defaultFormat{AndroidPixelFormat::RGBA8888}; //!< The assumed format of a buffer if none is supplied in DequeueBuffer
NativeWindowApi connectedApi{NativeWindowApi::None}; //!< The API that the producer is currently connected to
u64 frameNumber{}; //!< The amount of frames that have been presented so far
nvdrv::core::NvMap &nvMap;
void FreeGraphicBufferNvMap(GraphicBuffer &buffer);
/**
* @return The amount of buffers which have been queued onto the consumer
@ -180,7 +187,7 @@ namespace skyline::service::hosbinder {
SetPreallocatedBuffer = 14, //!< A transaction specific to HOS, see the implementation for a description of its functionality
};
GraphicBufferProducer(const DeviceState &state);
GraphicBufferProducer(const DeviceState &state, nvdrv::core::NvMap &nvmap);
/**
* @brief The handler for Binder IPC transactions with IGraphicBufferProducer

View file

@ -7,7 +7,7 @@
#include "GraphicBufferProducer.h"
namespace skyline::service::hosbinder {
IHOSBinderDriver::IHOSBinderDriver(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
IHOSBinderDriver::IHOSBinderDriver(const DeviceState &state, ServiceManager &manager, nvdrv::core::NvMap &nvMap) : BaseService(state, manager), nvMap(nvMap) {}
Result IHOSBinderDriver::TransactParcel(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
// We opted for just supporting a single layer and display as it's what basically all games use and wasting cycles on it is pointless
@ -122,7 +122,7 @@ namespace skyline::service::hosbinder {
layerStrongReferenceCount = InitialStrongReferenceCount;
layerWeakReferenceCount = 0;
layer.emplace(state);
layer.emplace(state, nvMap);
return DefaultLayerId;
}

View file

@ -42,8 +42,10 @@ namespace skyline::service::hosbinder {
constexpr static u32 DefaultBinderLayerHandle{1}; //!< The handle as assigned by SurfaceFlinger of the default layer
std::optional<GraphicBufferProducer> layer; //!< The IGraphicBufferProducer backing the layer (NativeWindow)
nvdrv::core::NvMap &nvMap;
public:
IHOSBinderDriver(const DeviceState &state, ServiceManager &manager);
IHOSBinderDriver(const DeviceState &state, ServiceManager &manager, nvdrv::core::NvMap &nvMap);
/**
* @brief Emulates the transaction of parcels between a IGraphicBufferProducer and the application

View file

@ -64,7 +64,7 @@ namespace skyline::service::hosbinder {
throw exception("Wait has larger fence count ({}) than storage size ({})", fenceCount, fences.size());
for (auto it{fences.begin()}, end{fences.begin() + fenceCount}; it < end; it++)
if (it->id != InvalidFenceId)
host1x.syncpoints.at(it->id).Wait(it->value, std::chrono::steady_clock::duration::max());
host1x.syncpoints.at(it->id).Wait(it->threshold, std::chrono::steady_clock::duration::max());
}
};

View file

@ -1,77 +1,89 @@
// 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/)
#include <numeric>
#include <kernel/types/KProcess.h>
#include "INvDrvServices.h"
#include "driver.h"
#include "devices/nvdevice.h"
#define NVRESULT(x) [&response, this](NvResult err) { \
if (err != NvResult::Success) \
state.logger->Debug("IOCTL Failed: {}", err); \
\
response.Push<NvResult>(err); \
return Result{}; \
} (x)
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) {
if (nvdrv::driver.expired())
nvdrv::driver = driver;
}
INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms) : BaseService(state, manager), driver(driver), ctx(SessionContext{.perms = perms}) {}
Result INvDrvServices::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto path{request.inputBuf.at(0).as_string()};
constexpr FileDescriptor SessionFdLimit{std::numeric_limits<u64>::digits * 2}; //!< Nvdrv uses two 64 bit variables to store a bitset
response.Push<u32>(driver->OpenDevice(path));
response.Push(device::NvStatus::Success);
auto path{request.inputBuf.at(0).as_string(true)};
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);
}
static NvResultValue<span<u8>> GetMainIoctlBuffer(IoctlDescriptor ioctl, span<u8> inBuf, span<u8> outBuf) {
if (ioctl.in && inBuf.size() < ioctl.size)
return NvResult::InvalidSize;
if (ioctl.out && outBuf.size() < ioctl.size)
return NvResult::InvalidSize;
if (ioctl.in && ioctl.out) {
if (outBuf.size() < inBuf.size())
return NvResult::InvalidSize;
// Copy in buf to out buf for inout ioctls to avoid needing to pass around two buffers everywhere
if (outBuf.data() != inBuf.data())
outBuf.copy_from(inBuf, ioctl.size);
}
return ioctl.out ? outBuf : inBuf;
}
Result INvDrvServices::Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()};
auto cmd{request.Pop<u32>()};
auto fd{request.Pop<FileDescriptor>()};
auto ioctl{request.Pop<IoctlDescriptor>()};
auto device{driver->GetDevice(fd)};
// Strip the permissions from the command leaving only the ID
cmd &= 0xFFFF;
span<u8> buffer{};
if (request.inputBuf.empty() || request.outputBuf.empty()) {
if (!request.inputBuf.empty())
buffer = request.inputBuf.at(0);
else if (!request.outputBuf.empty())
buffer = request.outputBuf.at(0);
else
throw exception("No IOCTL Buffers");
} else if (request.inputBuf[0].data() == request.outputBuf[0].data()) {
if (request.inputBuf[0].size() >= request.outputBuf[0].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 {};
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
if (!buf)
return NVRESULT(buf);
else
return NVRESULT(driver.Ioctl(fd, ioctl, *buf));
}
Result INvDrvServices::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()};
state.logger->Debug("Closing NVDRV device ({})", fd);
driver->CloseDevice(fd);
driver.CloseDevice(fd);
response.Push(device::NvStatus::Success);
return {};
return NVRESULT(NvResult::Success);
}
Result INvDrvServices::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(device::NvStatus::Success);
return {};
return NVRESULT(NvResult::Success);
}
Result INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()};
auto eventId{request.Pop<u32>()};
auto device{driver->GetDevice(fd)};
auto event{device->QueryEvent(eventId)};
auto event{driver.QueryEvent(fd, eventId)};
if (event != nullptr) {
auto handle{state.process->InsertItem<type::KEvent>(event)};
@ -79,71 +91,45 @@ namespace skyline::service::nvdrv {
state.logger->Debug("FD: {}, Event ID: {}, Handle: 0x{:X}", fd, eventId, handle);
response.copyHandles.push_back(handle);
response.Push(device::NvStatus::Success);
return NVRESULT(NvResult::Success);
} else {
response.Push(device::NvStatus::BadValue);
return NVRESULT(NvResult::BadValue);
}
return {};
}
Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
response.Push(device::NvStatus::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 fd{request.Pop<FileDescriptor>()};
auto ioctl{request.Pop<IoctlDescriptor>()};
auto device{driver->GetDevice(fd)};
// Inline buffer is optional
auto inlineBuf{request.inputBuf.size() > 1 ? request.inputBuf.at(1) : span<u8>{}};
// 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];
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
if (!buf)
return NVRESULT(buf);
else
buffer = request.outputBuf[0];
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl2, buffer, request.inputBuf[1]));
return {};
return NVRESULT(driver.Ioctl2(fd, ioctl, *buf, inlineBuf));
}
Result INvDrvServices::Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
auto fd{request.Pop<u32>()};
auto cmd{request.Pop<u32>()};
auto fd{request.Pop<FileDescriptor>()};
auto ioctl{request.Pop<IoctlDescriptor>()};
auto device{driver->GetDevice(fd)};
// Inline buffer is optional
auto inlineBuf{request.outputBuf.size() > 1 ? request.outputBuf.at(1) : span<u8>{}};
// 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];
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
if (!buf)
return NVRESULT(buf);
else
buffer = request.outputBuf[0];
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl3, buffer, request.outputBuf.size() >= 2 ? request.outputBuf[1] : span<u8>()));
return {};
return NVRESULT(driver.Ioctl3(fd, ioctl, *buf, inlineBuf));
}
Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
return NVRESULT(NvResult::Success);
}
Result INvDrvServices::SetGraphicsFirmwareMemoryMarginEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
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/)
#pragma once
#include <services/serviceman.h>
#include "types.h"
namespace skyline::service::nvdrv {
class Driver;
@ -14,10 +15,13 @@ namespace skyline::service::nvdrv {
*/
class INvDrvServices : public BaseService {
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:
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

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

@ -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> handleDesc) {
std::scoped_lock lock(handlesLock);
handles.emplace(handleDesc->id, std::move(handleDesc));
}
bool NvMap::TryRemoveHandle(const std::shared_ptr<Handle> &handleDesc) {
// No dupes left, we can remove from handle map
if (handleDesc->dupes == 0 && handleDesc->internalDupes == 0) {
std::scoped_lock lock(handlesLock);
auto it{handles.find(handleDesc->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 handleDesc{std::make_shared<Handle>(size, id)};
AddHandle(handleDesc);
return handleDesc;
}
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 handleDesc = hWeak.lock()) {
if (!handleDesc) [[unlikely]]
return std::nullopt;
std::scoped_lock lock(handleDesc->mutex);
if (internalSession) {
if (--handleDesc->internalDupes < 0)
state.logger->Warn("Internal duplicate count imbalance detected!");
} else {
if (--handleDesc->dupes < 0) {
state.logger->Warn("User duplicate count imbalance detected!");
} else if (handleDesc->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(handleDesc))
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 = handleDesc->address,
.size = handleDesc->size,
.wasUncached = handleDesc->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:
/**
* @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
};
NvMap(const DeviceState &state);
/**
* @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
* @note 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,12 +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 © 2019-2020 Ryujinx Team and Contributors
#include <soc.h>
#include "nvhost_syncpoint.h"
#include "syncpoint_manager.h"
namespace skyline::service::nvdrv {
NvHostSyncpoint::NvHostSyncpoint(const DeviceState &state) : state(state) {
namespace skyline::service::nvdrv::core {
SyncpointManager::SyncpointManager(const DeviceState &state) : state(state) {
constexpr u32 VBlank0SyncpointId{26};
constexpr u32 VBlank1SyncpointId{27};
@ -17,7 +17,7 @@ namespace skyline::service::nvdrv {
ReserveSyncpoint(VBlank1SyncpointId, true);
}
u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) {
u32 SyncpointManager::ReserveSyncpoint(u32 id, bool clientManaged) {
if (syncpoints.at(id).reserved)
throw exception("Requested syncpoint is in use");
@ -27,7 +27,7 @@ namespace skyline::service::nvdrv {
return id;
}
u32 NvHostSyncpoint::FindFreeSyncpoint() {
u32 SyncpointManager::FindFreeSyncpoint() {
for (u32 i{1}; i < syncpoints.size(); i++)
if (!syncpoints[i].reserved)
return i;
@ -35,12 +35,12 @@ namespace skyline::service::nvdrv {
throw exception("Failed to find a free syncpoint!");
}
u32 NvHostSyncpoint::AllocateSyncpoint(bool clientManaged) {
u32 SyncpointManager::AllocateSyncpoint(bool clientManaged) {
std::lock_guard lock(reservationLock);
return ReserveSyncpoint(FindFreeSyncpoint(), clientManaged);
}
bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) {
bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) {
const SyncpointInfo &syncpoint{syncpoints.at(id)};
if (!syncpoint.reserved)
@ -53,25 +53,35 @@ namespace skyline::service::nvdrv {
return (syncpoint.counterMax - threshold) >= (syncpoint.counterMin - threshold);
}
u32 NvHostSyncpoint::IncrementSyncpointMaxExt(u32 id, u32 amount) {
u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
if (!syncpoints.at(id).reserved)
throw exception("Cannot increment an unreserved syncpoint!");
return syncpoints.at(id).counterMax += amount;
}
u32 NvHostSyncpoint::ReadSyncpointMinValue(u32 id) {
u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
if (!syncpoints.at(id).reserved)
throw exception("Cannot read an unreserved syncpoint!");
return syncpoints.at(id).counterMin;
}
u32 NvHostSyncpoint::UpdateMin(u32 id) {
u32 SyncpointManager::UpdateMin(u32 id) {
if (!syncpoints.at(id).reserved)
throw exception("Cannot update an unreserved syncpoint!");
syncpoints.at(id).counterMin = state.soc->host1x.syncpoints.at(id).Load();
return syncpoints.at(id).counterMin;
}
Fence SyncpointManager::GetSyncpointFence(u32 id) {
if (!syncpoints.at(id).reserved)
throw exception("Cannot access an unreserved syncpoint!");
return {
.id = id,
.threshold = syncpoints.at(id).counterMax
};
}
}

View file

@ -1,18 +1,20 @@
// 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 © 2019-2020 Ryujinx Team and Contributors
#pragma once
#include <soc/host1x.h>
#include <services/common/fence.h>
namespace skyline::service::nvdrv {
namespace skyline::service::nvdrv::core {
/**
* @brief NvHostSyncpoint handles allocating and accessing host1x syncpoints, these are cached versions of the HW syncpoints which are intermittently synced
* @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached versions of the HW syncpoints which are intermittently synced
* @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
* @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
* @url https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
*/
class NvHostSyncpoint {
class SyncpointManager {
private:
struct SyncpointInfo {
std::atomic<u32> counterMin; //!< The least value the syncpoint can be (The value it was when it was last synchronized with host1x)
@ -36,7 +38,7 @@ namespace skyline::service::nvdrv {
u32 FindFreeSyncpoint();
public:
NvHostSyncpoint(const DeviceState &state);
SyncpointManager(const DeviceState &state);
/**
* @brief Finds a free syncpoint and reserves it
@ -49,6 +51,10 @@ namespace skyline::service::nvdrv {
*/
bool HasSyncpointExpired(u32 id, u32 threshold);
bool IsFenceSignalled(Fence fence) {
return HasSyncpointExpired(fence.id, fence.threshold);
}
/**
* @brief Atomically increments the maximum value of a syncpoint by the given amount
* @return The new max value of the syncpoint
@ -65,5 +71,10 @@ namespace skyline::service::nvdrv {
* @return The new minimum value of the syncpoint
*/
u32 UpdateMin(u32 id);
/**
* @return A fence that will be signalled once this syncpoint hits its maximum value
*/
Fence GetSyncpointFence(u32 id);
};
}

View file

@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <type_traits>
#include <common.h>
#include "types.h"
namespace skyline::service::nvdrv::deserialisation {
template<typename Desc, typename ArgType> requires (Desc::In && IsIn<ArgType>::value)
constexpr ArgType DecodeArgument(span<u8, Desc::Size> buffer, size_t &offset) {
auto out{buffer.subspan(offset).template as<ArgType>()};
offset += sizeof(ArgType);
return out;
}
template<typename Desc, typename ArgType> requires (Desc::Out && Desc::In && IsInOut<ArgType>::value)
constexpr ArgType DecodeArgument(span<u8, Desc::Size> buffer, size_t &offset) {
auto &out{buffer.subspan(offset).template as<RemoveInOut<ArgType>>()};
offset += sizeof(RemoveInOut<ArgType>);
return out;
}
template<typename Desc, typename ArgType> requires (Desc::Out && IsOut<ArgType>::value)
constexpr ArgType DecodeArgument(span<u8, Desc::Size> buffer, size_t &offset) {
auto out{Out(buffer.subspan(offset).template as<RemoveOut<ArgType>>())};
offset += sizeof(RemoveOut<ArgType>);
return out;
}
/**
* @brief Creates a reference preserving tuple of the given types
*/
template <typename...Ts>
constexpr auto make_ref_tuple(Ts&&...ts) {
return std::tuple<Ts...>{std::forward<Ts>(ts)...};
}
template<typename Desc, typename ArgType, typename... ArgTypes>
constexpr auto DecodeArgumentsImpl(span<u8, Desc::Size> buffer, size_t &offset) {
if constexpr (IsAutoSizeSpan<ArgType>::value) {
// AutoSizeSpan needs to be the last argument
static_assert(sizeof...(ArgTypes) == 0);
size_t bytes{buffer.size() - offset};
size_t extent{bytes / sizeof(RemoveAutoSizeSpan<ArgType>)};
return make_ref_tuple(buffer.subspan(offset, extent * sizeof(RemoveAutoSizeSpan<ArgType>)).template cast<RemoveAutoSizeSpan<ArgType>>());
} else if constexpr (IsPad<ArgType>::value) {
offset += ArgType::Bytes;
return DecodeArgumentsImpl<Desc, ArgTypes...>(buffer, offset);
} else {
if constexpr(sizeof...(ArgTypes) == 0) {
return make_ref_tuple(DecodeArgument<Desc, ArgType>(buffer, offset));
} else {
return std::tuple_cat(make_ref_tuple(DecodeArgument<Desc, ArgType>(buffer, offset)),
DecodeArgumentsImpl<Desc, ArgTypes...>(buffer, offset));
}
}
}
/**
* @brief This fancy thing takes a varadic template of argument types and uses it to deserialise the given buffer
* @tparam Desc A MetaIoctlDescriptor or MetaVariableIoctlDescriptor corresponding to the IOCTL that takes these arguments
* @return A tuple containing the arguments to be passed to the IOCTL's handler
*/
template<typename Desc, typename... ArgTypes>
constexpr auto DecodeArguments(span<u8, Desc::Size> buffer) {
size_t offset{};
return DecodeArgumentsImpl<Desc, ArgTypes...>(buffer, offset);
}
}

View file

@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "deserialisation.h"
#define INLINE_IOCTL_HANDLER_FUNC(type, name, cases) \
PosixResult name::type(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) { \
using className = name; \
switch (cmd.raw) { \
cases; \
default: \
return PosixResult::InappropriateIoctlForDevice; \
} \
}
#define VARIABLE_IOCTL_HANDLER_FUNC(name, cases, variableCases) \
PosixResult name::Ioctl(IoctlDescriptor cmd, span<u8> buffer) { \
using className = name; \
switch (cmd.raw) { \
cases; \
default: \
cmd.size = 0; \
switch (cmd.raw) { \
variableCases; \
default: \
return PosixResult::InappropriateIoctlForDevice; \
} \
} \
}
#define IOCTL_HANDLER_FUNC(name, cases) \
PosixResult name::Ioctl(IoctlDescriptor cmd, span<u8> buffer) { \
using className = name; \
switch (cmd.raw) { \
cases; \
default: \
return PosixResult::InappropriateIoctlForDevice; \
} \
}
#define IOCTL_CASE_ARGS_I(out, in, size, magic, function, name, ...) \
case MetaIoctlDescriptor<out, in, size, magic, function>::Raw(): { \
using IoctlType = MetaIoctlDescriptor< out, in, size, magic, function>; \
auto args = DecodeArguments<IoctlType, __VA_ARGS__>(buffer.subspan<0, size>()); \
return std::apply(&className::name, std::tuple_cat(std::make_tuple(this), args)); \
}
#define IOCTL_CASE_NOARGS_I(out, in, size, magic, function, name) \
case MetaIoctlDescriptor<out, in, size, magic, function>::Raw(): \
return className::name();
#define IOCTL_CASE_RESULT_I(out, in, size, magic, function, result) \
case MetaIoctlDescriptor<out, in, size, magic, function>::Raw(): \
return result;
#define IOCTL_CASE_ARGS(...) IOCTL_CASE_ARGS_I(__VA_ARGS__)
#define IOCTL_CASE_NOARGS(...) IOCTL_CASE_NOARGS_I(__VA_ARGS__)
#define IOCTL_CASE_RESULT(...) IOCTL_CASE_RESULT_I(__VA_ARGS__)
#define VARIABLE_IOCTL_CASE_ARGS_I(out, in, magic, function, name, ...) \
case MetaVariableIoctlDescriptor<out, in, magic, function>::Raw(): { \
using IoctlType = MetaVariableIoctlDescriptor<out, in, magic, function>; \
auto args = DecodeArguments<IoctlType, __VA_ARGS__>(buffer); \
return std::apply(&className::name, std::tuple_cat(std::make_tuple(this), args)); \
}
#define VARIABLE_IOCTL_CASE_ARGS(...) VARIABLE_IOCTL_CASE_ARGS_I(__VA_ARGS__)
#define INLINE_IOCTL_CASE_ARGS_I(out, in, size, magic, function, name, ...) \
case MetaIoctlDescriptor<out, in, size, magic, function>::Raw(): { \
using IoctlType = MetaIoctlDescriptor< out, in, size, magic, function>; \
auto args = DecodeArguments<IoctlType, __VA_ARGS__>(buffer.subspan<0, size>()); \
return std::apply(&className::name, std::tuple_cat(std::make_tuple(this, inlineBuffer), args)); \
}
#define INLINE_IOCTL_CASE_ARGS(...) INLINE_IOCTL_CASE_ARGS_I(__VA_ARGS__)
#define IN false, true
#define OUT true, false
#define INOUT true, true
#define NONE false, false
#define SIZE(size) size
#define FUNC(func) func
#define MAGIC(magic) magic
#define ARGS(...) __VA_ARGS__

View file

@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#undef IOCTL_HANDLER_FUNC
#undef VARIABLE_IOCTL_HANDLER_FUNC
#undef INLINE_IOCTL_HANDLER_FUNC
#undef IOCTL_CASE_ARGS_I
#undef IOCTL_CASE_NOARGS_I
#undef IOCTL_CASE_RESULT_I
#undef IOCTL_CASE_ARGS
#undef IOCTL_CASE_NOARGS
#undef IOCTL_CASE_RESULT
#undef VARIABLE_IOCTL_CASE_ARGS_I
#undef VARIABLE_IOCTL_CASE_ARGS
#undef INLINE_IOCTL_CASE_ARGS_I
#undef INLINE_IOCTL_CASE_ARGS
#undef IN
#undef OUT
#undef INOUT
#undef NONE
#undef SIZE
#undef FUNC
#undef MAGIC
#undef ARGS

View file

@ -0,0 +1,140 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <concepts>
#include <common.h>
#include <services/nvdrv/types.h>
namespace skyline::service::nvdrv::deserialisation {
// IOCTL parameters can't be pointers and must be trivially copyable
template<typename T>
concept BufferData = std::is_trivially_copyable_v<T> && !std::is_pointer_v<T>;
// Input IOCTL template types
template<typename T> requires BufferData<T>
using In = const T;
template<typename>
struct IsIn : std::false_type {};
template<typename T>
struct IsIn<In<T>> : std::true_type {};
// Input/Output IOCTL template types
template<typename T> requires BufferData<T>
using InOut = T &;
template<typename>
struct IsInOut : std::false_type {};
template<typename T>
struct IsInOut<InOut<T>> : std::true_type {};
template<typename T> requires IsInOut<T>::value
using RemoveInOut = typename std::remove_reference<T>::type;
// Output IOCTL template types
template<typename T> requires BufferData<T>
class Out {
private:
T &ref;
public:
Out(T &ref) : ref(ref) {}
using ValueType = T;
Out& operator=(T val) {
ref = std::move(val);
return *this;
}
};
template<typename>
struct IsOut : std::false_type {};
template<typename T>
struct IsOut<Out<T>> : std::true_type {};
template<typename T> requires IsOut<T>::value
using RemoveOut = typename T::ValueType;
template<typename T> requires BufferData<T>
using AutoSizeSpan = span<T>;
template<typename>
struct IsAutoSizeSpan : std::false_type {};
template<typename T>
struct IsAutoSizeSpan<AutoSizeSpan<T>> : std::true_type {};
template<typename T> requires IsAutoSizeSpan<T>::value
using RemoveAutoSizeSpan = typename T::element_type;
// Padding template type
template<typename T, size_t Count = 1> requires BufferData<T>
struct Pad {
static constexpr auto Bytes{Count * sizeof(T)};
};
template<typename>
struct IsPad : std::false_type {};
template<typename T, size_t TCount>
struct IsPad<Pad<T, TCount>> : std::true_type {};
/**
* @brief Describes an IOCTL as a type for use in deserialisation
*/
template <bool TOut, bool TIn, u16 TSize, i8 TMagic, u8 TFunction>
struct MetaIoctlDescriptor {
static constexpr auto Out{TOut};
static constexpr auto In{TIn};
static constexpr auto Size{TSize};
static constexpr auto Magic{TMagic};
static constexpr auto Function{TFunction};
constexpr operator IoctlDescriptor() const {
return {Out, In, Size, Magic, Function};
}
constexpr static u32 Raw() {
u32 raw{Function};
i8 offset{8};
raw |= Magic << offset; offset += 8;
raw |= Size << offset; offset += 14;
raw |= In << offset; offset++;
raw |= Out << offset; offset++;
return raw;
}
};
/**
* @brief Describes a variable length IOCTL as a type for use in deserialisation
*/
template <bool TOut, bool TIn, i8 TMagic, u8 TFunction>
struct MetaVariableIoctlDescriptor {
static constexpr auto Out{TOut};
static constexpr auto In{TIn};
static constexpr auto Size{std::dynamic_extent};
static constexpr auto Magic{TMagic};
static constexpr auto Function{TFunction};
constexpr static u32 Raw() {
u32 raw{Function};
i8 offset{8};
raw |= Magic << offset; offset += 8;
offset += 14; // Use a 0 size for raw
raw |= In << offset; offset++;
raw |= Out << offset; offset++;
return raw;
}
};
}

View file

@ -1,11 +1,12 @@
// 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 <cxxabi.h>
#include <common/trace.h>
#include "nvdevice.h"
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() {
if (name.empty()) {
auto mangledName{typeid(*this).name()};
@ -18,32 +19,4 @@ namespace skyline::service::nvdrv::device {
}
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
// 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
#include <kernel/ipc.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
#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 \
}; \
}
#include "deserialisation/types.h"
namespace skyline::service::nvdrv::device {
using namespace kernel;
/**
* @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
};
using namespace deserialisation;
/**
* @brief NvDevice is the base class that all /dev/nv* devices inherit from
@ -71,40 +24,29 @@ namespace skyline::service::nvdrv::device {
protected:
const DeviceState &state;
class DerivedDevice; //!< A placeholder derived class which is used for class function semantics
/**
* @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);
}
};
Core &core;
SessionContext ctx;
public:
NvDevice(const DeviceState &state) : state(state) {}
NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx);
virtual ~NvDevice() = default;
virtual NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) = 0;
/**
* @return The name of the class
* @note The lifetime of the returned string is tied to that of the class
*/
const std::string &GetName();
/**
* @brief Handles IOCTL calls for devices
* @param cmd The IOCTL command that was called
*/
NvStatus HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
virtual PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) = 0;
virtual PosixResult Ioctl2(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineOutput) {
return PosixResult::InappropriateIoctlForDevice;
}
virtual PosixResult Ioctl3(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineInput) {
return PosixResult::InappropriateIoctlForDevice;
}
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
return nullptr;

View file

@ -0,0 +1,366 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <common/address_space.inc>
#include <soc.h>
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
#include "as_gpu.h"
namespace skyline {
template class FlatAddressSpaceMap<u32, 0, bool, false, false, 32>;
template class FlatAllocator<u32, 0, 32>;
}
namespace skyline::service::nvdrv::device::nvhost {
using GMMU = soc::gm20b::GM20B::GMMU;
AsGpu::AsGpu(const DeviceState &state, Core &core, const SessionContext &ctx) : NvDevice(state, core, ctx) {}
PosixResult AsGpu::BindChannel(In<FileDescriptor> channelFd) {
// TODO: support once multiple address spaces are supported
return PosixResult::Success;
}
PosixResult AsGpu::AllocSpace(In<u32> pages, In<u32> pageSize, In<MappingFlags> flags, InOut<u64> offset) {
state.logger->Debug("pages: 0x{:X}, pageSize: 0x{:X}, flags: ( fixed: {}, sparse: {} ), offset: 0x{:X}",
pages, pageSize, flags.fixed, flags.sparse, offset);
std::scoped_lock lock(mutex);
if (!vm.initialised)
return PosixResult::InvalidArgument;
if (pageSize != VM::PageSize && pageSize != vm.bigPageSize)
return PosixResult::InvalidArgument;
if (pageSize != vm.bigPageSize && flags.sparse)
return PosixResult::FunctionNotImplemented;
u32 pageSizeBits{pageSize == VM::PageSize ? VM::PageSizeBits : vm.bigPageSizeBits};
auto &allocator{[&] () -> auto & {
if (pageSize == VM::PageSize)
return vm.smallPageAllocator;
else
return vm.bigPageAllocator;
}()};
if (flags.fixed)
allocator->AllocateFixed(offset >> pageSizeBits, pages);
else
offset = static_cast<u64>(allocator->Allocate(pages)) << pageSizeBits;
u64 size{static_cast<u64>(pages) * pageSize};
if (flags.sparse)
state.soc->gm20b.gmmu.Map(offset, GMMU::SparsePlaceholderAddress(), size, {true});
allocationMap[offset] = {
.size = size,
.pageSize = pageSize,
.sparse = flags.sparse
};
return PosixResult::Success;
}
void AsGpu::FreeMappingLocked(u64 offset) {
auto mapping{mappingMap.at(offset)};
if (!mapping->fixed) {
auto &allocator{mapping->bigPage ? vm.bigPageAllocator : vm.smallPageAllocator};
u32 pageSizeBits{mapping->bigPage ? vm.bigPageSizeBits : VM::PageSizeBits};
allocator->Free(mapping->offset >> pageSizeBits, mapping->size >> pageSizeBits);
}
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully
if (mapping->sparseAlloc)
state.soc->gm20b.gmmu.Map(offset, GMMU::SparsePlaceholderAddress(), mapping->size, {true});
else
state.soc->gm20b.gmmu.Unmap(offset, mapping->size);
mappingMap.erase(offset);
}
PosixResult AsGpu::FreeSpace(In<u64> offset, In<u32> pages, In<u32> pageSize) {
state.logger->Debug("offset: 0x{:X}, pages: 0x{:X}, pageSize: 0x{:X}", offset, pages, pageSize);
std::scoped_lock lock(mutex);
if (!vm.initialised)
return PosixResult::InvalidArgument;
try {
auto allocation{allocationMap[offset]};
if (allocation.pageSize != pageSize || allocation.size != (static_cast<u64>(pages) * pageSize))
return PosixResult::InvalidArgument;
for (const auto &mapping : allocation.mappings)
FreeMappingLocked(mapping->offset);
// Unset sparse flag if required
if (allocation.sparse)
state.soc->gm20b.gmmu.Unmap(offset, allocation.size);
auto &allocator{pageSize == VM::PageSize ? vm.smallPageAllocator : vm.bigPageAllocator};
u32 pageSizeBits{pageSize == VM::PageSize ? VM::PageSizeBits : vm.bigPageSizeBits};
allocator->Free(offset >> pageSizeBits, allocation.size >> pageSizeBits);
allocationMap.erase(offset);
} catch (const std::out_of_range &e) {
return PosixResult::InvalidArgument;
}
return PosixResult::Success;
}
PosixResult AsGpu::UnmapBuffer(In<u64> offset) {
state.logger->Debug("offset: 0x{:X}", offset);
std::scoped_lock lock(mutex);
if (!vm.initialised)
return PosixResult::InvalidArgument;
try {
auto mapping{mappingMap.at(offset)};
if (!mapping->fixed) {
auto &allocator{mapping->bigPage ? vm.bigPageAllocator : vm.smallPageAllocator};
u32 pageSizeBits{mapping->bigPage ? vm.bigPageSizeBits : VM::PageSizeBits};
allocator->Free(mapping->offset >> pageSizeBits, mapping->size >> pageSizeBits);
}
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
// Only FreeSpace can unmap them fully
if (mapping->sparseAlloc)
state.soc->gm20b.gmmu.Map(offset, GMMU::SparsePlaceholderAddress(), mapping->size, {true});
else
state.soc->gm20b.gmmu.Unmap(offset, mapping->size);
mappingMap.erase(offset);
} catch (const std::out_of_range &e) {
state.logger->Warn("Couldn't find region to unmap at 0x{:X}", offset);
}
return PosixResult::Success;
}
PosixResult AsGpu::MapBufferEx(In<MappingFlags> flags, In<u32> kind, In<core::NvMap::Handle::Id> handle, In<u64> bufferOffset, In<u64> mappingSize, InOut<u64> offset) {
state.logger->Debug("flags: ( fixed: {}, remap: {} ), kind: {}, handle: {}, bufferOffset: 0x{:X}, mappingSize: 0x{:X}, offset: 0x{:X}",
flags.fixed, flags.remap, kind, handle, bufferOffset, mappingSize, offset);
std::scoped_lock lock(mutex);
if (!vm.initialised)
return PosixResult::InvalidArgument;
// Remaps a subregion of an existing mapping to a different PA
if (flags.remap) {
try {
auto mapping{mappingMap.at(offset)};
if (mapping->size < mappingSize) {
state.logger->Warn("Cannot remap a partially mapped GPU address space region: 0x{:X}", offset);
return PosixResult::InvalidArgument;
}
u64 gpuAddress{offset + bufferOffset};
u8 *cpuPtr{mapping->ptr + bufferOffset};
state.soc->gm20b.gmmu.Map(gpuAddress, cpuPtr, mappingSize);
return PosixResult::Success;
} catch (const std::out_of_range &e) {
state.logger->Warn("Cannot remap an unmapped GPU address space region: 0x{:X}", offset);
return PosixResult::InvalidArgument;
}
}
auto h{core.nvMap.GetHandle(handle)};
if (!h)
return PosixResult::InvalidArgument;
u8 *cpuPtr{reinterpret_cast<u8 *>(h->address + bufferOffset)};
u64 size{mappingSize ? mappingSize : h->origSize};
if (flags.fixed) {
auto alloc{allocationMap.upper_bound(offset)};
if (alloc-- == allocationMap.begin() || (offset - alloc->first) + size > alloc->second.size)
throw exception("Cannot perform a fixed mapping into an unallocated region!");
state.soc->gm20b.gmmu.Map(offset, cpuPtr, size);
auto mapping{std::make_shared<Mapping>(cpuPtr, offset, size, true, false, alloc->second.sparse)};
alloc->second.mappings.push_back(mapping);
mappingMap[offset] = mapping;
} else {
bool bigPage{[&] () {
if (util::IsAligned(h->align, vm.bigPageSize))
return true;
else if (util::IsAligned(h->align, VM::PageSize))
return false;
else
throw exception("Invalid handle alignment: 0x{:X}", h->align);
}()};
auto &allocator{bigPage ? vm.bigPageAllocator : vm.smallPageAllocator};
u32 pageSize{bigPage ? vm.bigPageSize : VM::PageSize};
u32 pageSizeBits{bigPage ? vm.bigPageSizeBits : VM::PageSizeBits};
offset = static_cast<u64>(allocator->Allocate(util::AlignUp(size, pageSize) >> pageSizeBits)) << pageSizeBits;
state.soc->gm20b.gmmu.Map(offset, cpuPtr, size);
auto mapping{std::make_shared<Mapping>(cpuPtr, offset, size, false, bigPage, false)};
mappingMap[offset] = mapping;
}
state.logger->Debug("Mapped to 0x{:X}", offset);
return PosixResult::Success;
}
PosixResult AsGpu::GetVaRegions(In<u64> bufAddr, InOut<u32> bufSize, Out<std::array<VaRegion, 2>> vaRegions) {
std::scoped_lock lock(mutex);
if (!vm.initialised)
return PosixResult::InvalidArgument;
vaRegions = std::array<VaRegion, 2> {
VaRegion{
.pageSize = VM::PageSize,
.pages = vm.smallPageAllocator->vaLimit - vm.smallPageAllocator->vaStart,
.offset = vm.smallPageAllocator->vaStart << VM::PageSizeBits,
},
VaRegion{
.pageSize = vm.bigPageSize,
.pages = vm.bigPageAllocator->vaLimit - vm.bigPageAllocator->vaStart,
.offset = vm.bigPageAllocator->vaStart << vm.bigPageSizeBits,
}
};
return PosixResult::Success;
}
PosixResult AsGpu::GetVaRegions3(span<u8> inlineBufer, In<u64> bufAddr, InOut<u32> bufSize, Out<std::array<VaRegion, 2>> vaRegions) {
return GetVaRegions(bufAddr, bufSize, vaRegions);
}
PosixResult AsGpu::AllocAsEx(In<u32> flags, In<FileDescriptor> asFd, In<u32> bigPageSize, In<u64> vaRangeStart, In<u64> vaRangeEnd, In<u64> vaRangeSplit) {
std::scoped_lock lock(mutex);
if (vm.initialised)
throw exception("Cannot initialise an address space twice!");
state.logger->Debug("bigPageSize: 0x{:X}, asFd: {}, flags: 0x{:X}, vaRangeStart: 0x{:X}, vaRangeEnd: 0x{:X}, vaRangeSplit: 0x{:X}",
bigPageSize, asFd, flags, vaRangeStart, vaRangeEnd, vaRangeSplit);
if (bigPageSize) {
if (!std::ispow2(bigPageSize)) {
state.logger->Error("Non power-of-2 big page size: 0x{:X}!", bigPageSize);
return PosixResult::InvalidArgument;
}
if (!(bigPageSize & VM::SupportedBigPageSizes)) {
state.logger->Error("Unsupported big page size: 0x{:X}!", bigPageSize);
return PosixResult::InvalidArgument;
}
vm.bigPageSize = bigPageSize;
vm.bigPageSizeBits = std::countr_zero(bigPageSize);
vm.vaRangeStart = bigPageSize << VM::VaStartShift;
}
// If this is unspecified then default values should be used
if (vaRangeStart) {
vm.vaRangeStart = vaRangeStart;
vm.vaRangeSplit = vaRangeSplit;
vm.vaRangeEnd = vaRangeEnd;
}
u64 startPages{vm.vaRangeStart >> VM::PageSizeBits};
u64 endPages{vm.vaRangeSplit >> VM::PageSizeBits};
vm.smallPageAllocator = std::make_unique<VM::Allocator>(startPages, endPages);
u64 startBigPages{vm.vaRangeSplit >> vm.bigPageSizeBits};
u64 endBigPages{(vm.vaRangeEnd - vm.vaRangeSplit) >> vm.bigPageSizeBits};
vm.bigPageAllocator = std::make_unique<VM::Allocator>(startBigPages, endBigPages);
vm.initialised = true;
return PosixResult::Success;
}
PosixResult AsGpu::Remap(span<RemapEntry> entries) {
std::scoped_lock lock(mutex);
if (!vm.initialised)
return PosixResult::InvalidArgument;
for (const auto &entry : entries) {
u64 virtAddr{static_cast<u64>(entry.asOffsetBigPages) << vm.bigPageSizeBits};
u64 size{static_cast<u64>(entry.bigPages) << vm.bigPageSizeBits};
auto alloc{allocationMap.upper_bound(virtAddr)};
if (alloc-- == allocationMap.begin() || (virtAddr - alloc->first) + size > alloc->second.size) {
state.logger->Warn("Cannot remap into an unallocated region!");
return PosixResult::InvalidArgument;
}
if (!alloc->second.sparse) {
state.logger->Warn("Cannot remap a non-sparse mapping!");
return PosixResult::InvalidArgument;
}
if (!entry.handle) {
state.soc->gm20b.gmmu.Map(virtAddr, soc::gm20b::GM20B::GMMU::SparsePlaceholderAddress(), size, {true});
} else {
auto h{core.nvMap.GetHandle(entry.handle)};
if (!h)
return PosixResult::InvalidArgument;
u8 *cpuPtr{reinterpret_cast<u8 *>(h->address + (static_cast<u64>(entry.handleOffsetBigPages) << vm.bigPageSizeBits))};
state.soc->gm20b.gmmu.Map(virtAddr, cpuPtr, size);
}
}
return PosixResult::Success;
}
#include <services/nvdrv/devices/deserialisation/macro_def.inc>
static constexpr u32 AsGpuMagic{0x41};
VARIABLE_IOCTL_HANDLER_FUNC(AsGpu, ({
IOCTL_CASE_ARGS(IN, SIZE(0x4), MAGIC(AsGpuMagic), FUNC(0x1),
BindChannel, ARGS(In<FileDescriptor>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(AsGpuMagic), FUNC(0x2),
AllocSpace, ARGS(In<u32>, In<u32>, In<MappingFlags>, Pad<u32>, InOut<u64>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(AsGpuMagic), FUNC(0x3),
FreeSpace, ARGS(In<u64>, In<u32>, In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(AsGpuMagic), FUNC(0x5),
UnmapBuffer, ARGS(In<u64>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x28), MAGIC(AsGpuMagic), FUNC(0x6),
MapBufferEx, ARGS(In<MappingFlags>, In<u32>, In<core::NvMap::Handle::Id>, Pad<u32>, In<u64>, In<u64>, InOut<u64>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x40), MAGIC(AsGpuMagic), FUNC(0x8),
GetVaRegions, ARGS(In<u64>, InOut<u32>, Pad<u32>, Out<std::array<VaRegion, 2>>))
IOCTL_CASE_ARGS(IN, SIZE(0x28), MAGIC(AsGpuMagic), FUNC(0x9),
AllocAsEx, ARGS(In<u32>, In<FileDescriptor>, In<u32>, Pad<u32>, In<u64>, In<u64>, In<u64>))
}), ({
VARIABLE_IOCTL_CASE_ARGS(INOUT, MAGIC(AsGpuMagic), FUNC(0x14),
Remap, ARGS(AutoSizeSpan<RemapEntry>))
}))
INLINE_IOCTL_HANDLER_FUNC(Ioctl3, AsGpu, ({
INLINE_IOCTL_CASE_ARGS(INOUT, SIZE(0x40), MAGIC(AsGpuMagic), FUNC(0x8),
GetVaRegions3, ARGS(In<u64>, InOut<u32>, Pad<u32>, Out<std::array<VaRegion, 2>>))
}))
#include <services/nvdrv/devices/deserialisation/macro_undef.inc>
}

View file

@ -0,0 +1,157 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common/address_space.h>
#include <services/nvdrv/devices/nvdevice.h>
namespace skyline::service::nvdrv::device::nvhost {
/**
* @brief nvhost::AsGpu (/dev/nvhost-as-gpu) is used to access a GPU virtual address space
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu
*/
class AsGpu : public NvDevice {
private:
struct Mapping {
u8 *ptr;
u64 offset;
u64 size;
bool fixed;
bool bigPage; // Only valid if fixed == false
bool sparseAlloc;
Mapping(u8 *ptr, u64 offset, u64 size, bool fixed, bool bigPage, bool sparseAlloc) : ptr(ptr),
offset(offset),
size(size),
fixed(fixed),
bigPage(bigPage),
sparseAlloc(sparseAlloc) {}
};
struct Allocation {
u64 size;
std::list<std::shared_ptr<Mapping>> mappings;
u32 pageSize;
bool sparse;
};
std::map<u64, std::shared_ptr<Mapping>> mappingMap; //!< This maps the base addresses of mapped buffers to their total sizes and mapping type, this is needed as what was originally a single buffer may have been split into multiple GPU side buffers with the remap flag.
std::map<u64, Allocation> allocationMap; //!< Holds allocations created by AllocSpace from which fixed buffers can be mapped into
std::mutex mutex; //!< Locks all AS operations
struct VM {
static constexpr u32 PageSize{0x1000};
static constexpr u32 PageSizeBits{std::countr_zero(PageSize)};
static constexpr u32 SupportedBigPageSizes{0x30000};
static constexpr u32 DefaultBigPageSize{0x20000};
u32 bigPageSize{DefaultBigPageSize};
u32 bigPageSizeBits{std::countr_zero(DefaultBigPageSize)};
static constexpr u32 VaStartShift{10};
static constexpr u64 DefaultVaSplit{1ULL << 34};
static constexpr u64 DefaultVaRange{1ULL << 37};
u64 vaRangeStart{DefaultBigPageSize << VaStartShift};
u64 vaRangeSplit{DefaultVaSplit};
u64 vaRangeEnd{DefaultVaRange};
using Allocator = FlatAllocator<u32, 0, 32>;
std::unique_ptr<Allocator> bigPageAllocator{};
std::unique_ptr<Allocator> smallPageAllocator{};
bool initialised{};
} vm;
void FreeMappingLocked(u64 offset);
public:
struct MappingFlags {
bool fixed : 1;
bool sparse : 1;
u8 _pad0_ : 6;
bool remap : 1;
u32 _pad1_ : 23;
};
static_assert(sizeof(MappingFlags) == sizeof(u32));
struct VaRegion {
u64 offset;
u32 pageSize;
u32 _pad0_;
u64 pages;
};
static_assert(sizeof(VaRegion) == 0x18);
struct RemapEntry {
u16 flags;
u16 kind;
core::NvMap::Handle::Id handle;
u32 handleOffsetBigPages;
u32 asOffsetBigPages;
u32 bigPages;
};
static_assert(sizeof(RemapEntry) == 0x14);
AsGpu(const DeviceState &state, Core &core, const SessionContext &ctx);
/**
* @brief Binds this address space to a channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_BIND_CHANNEL
*/
PosixResult BindChannel(In<FileDescriptor> channelFd);
/**
* @brief Reserves a region in this address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_FREE_SPACE
*/
PosixResult AllocSpace(In<u32> pages, In<u32> pageSize, In<MappingFlags> flags, InOut<u64> offset);
/**
* @brief Frees an allocated region in this address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_FREE_SPACE
*/
PosixResult FreeSpace(In<u64> offset, In<u32> pages, In<u32> pageSize);
/**
* @brief Unmaps a region in this address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_UNMAP_BUFFER
*/
PosixResult UnmapBuffer(In<u64> offset);
/**
* @brief Maps a region into this address space with extra parameters
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_MAP_BUFFER_EX
*/
PosixResult MapBufferEx(In<MappingFlags> flags, In<u32> kind, In<core::NvMap::Handle::Id> handle, In<u64> bufferOffset, In<u64> mappingSize, InOut<u64> offset);
/**
* @brief Returns info about the address space and its page sizes
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_GET_VA_REGIONS
*/
PosixResult GetVaRegions(In<u64> bufAddr, InOut<u32> bufSize, Out<std::array<VaRegion, 2>> vaRegions);
/**
* @brief Ioctl3 variant of GetVaRegions
*/
PosixResult GetVaRegions3(span<u8> inlineBuffer, In<u64> bufAddr, InOut<u32> bufSize, Out<std::array<VaRegion, 2>> vaRegions);
/**
* @brief Allocates this address space with the given parameters
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_ALLOC_AS_EX
*/
PosixResult AllocAsEx(In<u32> flags, In<FileDescriptor> asFd, In<u32> bigPageSize, In<u64> vaRangeStart, In<u64> vaRangeEnd, In<u64> vaRangeSplit);
/**
* @brief Remaps a region of the GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_REMAP
*/
PosixResult Remap(span<RemapEntry> entries);
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
PosixResult Ioctl3(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) override;
};
}

View file

@ -0,0 +1,267 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2019-2020 Ryujinx Team and Contributors
#include <soc.h>
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
#include "ctrl.h"
namespace skyline::service::nvdrv::device::nvhost {
Ctrl::SyncpointEvent::SyncpointEvent(const DeviceState &state) : event(std::make_shared<type::KEvent>(state, false)) {}
void Ctrl::SyncpointEvent::Signal() {
// We should only signal the KEvent if the event is actively being waited on
if (state.exchange(State::Signalling) == State::Waiting)
event->Signal();
state = State::Signalled;
}
void Ctrl::SyncpointEvent::Cancel(soc::host1x::Host1X &host1x) {
host1x.syncpoints.at(fence.id).DeregisterWaiter(waiterHandle);
waiterHandle = {};
}
void Ctrl::SyncpointEvent::RegisterWaiter(soc::host1x::Host1X &host1x, const Fence &pFence) {
fence = pFence;
state = State::Waiting;
waiterHandle = host1x.syncpoints.at(fence.id).RegisterWaiter(fence.threshold, [this] { Signal(); });
}
bool Ctrl::SyncpointEvent::IsInUse() {
return state == SyncpointEvent::State::Waiting ||
state == SyncpointEvent::State::Cancelling ||
state == SyncpointEvent::State::Signalling;
}
Ctrl::Ctrl(const DeviceState &state, Core &core, const SessionContext &ctx) : NvDevice(state, core, ctx) {}
u32 Ctrl::FindFreeSyncpointEvent(u32 syncpointId) {
u32 eventSlot{SyncpointEventCount}; //!< Holds the slot of the last populated event in the event array
u32 freeSlot{SyncpointEventCount}; //!< Holds the slot of the first unused event id
for (u32 i{}; i < SyncpointEventCount; i++) {
if (syncpointEvents[i]) {
const auto &event{syncpointEvents[i]};
if (!event->IsInUse()) {
eventSlot = i;
// This event is already attached to the requested syncpoint, so use it
if (event->fence.id == syncpointId)
return eventSlot;
}
} else if (freeSlot == SyncpointEventCount) {
freeSlot = i;
}
}
// Use an unused event if possible
if (freeSlot < SyncpointEventCount) {
syncpointEvents[freeSlot] = std::make_unique<SyncpointEvent>(state);
return freeSlot;
}
// Recycle an existing event if all else fails
if (eventSlot < SyncpointEventCount)
return eventSlot;
throw exception("Failed to find a free nvhost event!");
}
PosixResult Ctrl::SyncpointWaitEventImpl(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value, bool allocate) {
state.logger->Debug("fence: ( id: {}, threshold: {} ), timeout: {}, value: {}, allocate: {}",
fence.id, fence.threshold, timeout, value.val, allocate);
if (fence.id >= soc::host1x::SyncpointCount)
return PosixResult::InvalidArgument;
// Check if the syncpoint has already expired using the last known values
if (core.syncpointManager.IsFenceSignalled(fence)) {
value.val = core.syncpointManager.ReadSyncpointMinValue(fence.id);
return PosixResult::Success;
}
// Sync the syncpoint with the GPU then check again
auto minVal{core.syncpointManager.UpdateMin(fence.id)};
if (core.syncpointManager.IsFenceSignalled(fence)) {
value.val = minVal;
return PosixResult::Success;
}
// Don't try to register any waits if there is no timeout for them
if (!timeout)
return PosixResult::TryAgain;
std::lock_guard lock(syncpointEventMutex);
u32 slot = [&]() {
if (allocate) {
value.val = 0;
return FindFreeSyncpointEvent(fence.id);
} else {
return value.val;
}
}();
if (slot >= SyncpointEventCount)
return PosixResult::InvalidArgument;
auto &event{syncpointEvents[slot]};
if (!event)
return PosixResult::InvalidArgument;
if (!event->IsInUse()) {
state.logger->Debug("Waiting on syncpoint event: {} with fence: ({}, {})", slot, fence.id, fence.threshold);
event->RegisterWaiter(state.soc->host1x, fence);
value.val = 0;
if (allocate) {
value.syncpointIdForAllocation = fence.id;
value.eventAllocated = true;
} else {
value.syncpointId = fence.id;
}
// Slot will overwrite some of syncpointId here... it makes no sense for Nvidia to do this
value.val |= slot;
return PosixResult::TryAgain;
} else {
return PosixResult::InvalidArgument;
}
}
PosixResult Ctrl::SyncpointFreeEventLocked(In<u32> slot) {
if (slot >= SyncpointEventCount)
return PosixResult::InvalidArgument;
auto &event{syncpointEvents[slot]};
if (!event)
return PosixResult::Success; // If the event doesn't already exist then we don't need to do anything
if (event->IsInUse()) // Avoid freeing events when they are still waiting etc.
return PosixResult::Busy;
syncpointEvents[slot] = nullptr;
return PosixResult::Success;
}
PosixResult Ctrl::SyncpointClearEventWait(In<SyncpointEventValue> value) {
state.logger->Debug("slot: {}", value.slot);
u16 slot{value.slot};
if (slot >= SyncpointEventCount)
return PosixResult::InvalidArgument;
std::lock_guard lock(syncpointEventMutex);
auto &event{syncpointEvents[slot]};
if (!event)
return PosixResult::InvalidArgument;
if (event->state.exchange(SyncpointEvent::State::Cancelling) == SyncpointEvent::State::Waiting) {
state.logger->Debug("Cancelling waiting syncpoint event: {}", slot);
event->Cancel(state.soc->host1x);
core.syncpointManager.UpdateMin(event->fence.id);
}
event->state = SyncpointEvent::State::Cancelled;
event->event->ResetSignal();
return PosixResult::Success;
}
PosixResult Ctrl::SyncpointWaitEvent(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value) {
return SyncpointWaitEventImpl(fence, timeout, value, true);
}
PosixResult Ctrl::SyncpointWaitEventSingle(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value) {
return SyncpointWaitEventImpl(fence, timeout, value, false);
}
PosixResult Ctrl::SyncpointAllocateEvent(In<u32> slot) {
state.logger->Debug("slot: {}", slot);
if (slot >= SyncpointEventCount)
return PosixResult::InvalidArgument;
std::lock_guard lock(syncpointEventMutex);
auto &event{syncpointEvents[slot]};
if (event) // Recreate event if it already exists
if (auto err{SyncpointFreeEventLocked(slot)}; err != PosixResult::Success)
return err;
event = std::make_unique<SyncpointEvent>(state);
return PosixResult::Success;
}
PosixResult Ctrl::SyncpointFreeEvent(In<u32> slot) {
state.logger->Debug("slot: {}", slot);
std::lock_guard lock(syncpointEventMutex);
return SyncpointFreeEventLocked(slot);
}
PosixResult Ctrl::SyncpointFreeEventBatch(In<u64> bitmask) {
state.logger->Debug("bitmask: 0x{:X}", bitmask);
auto err{PosixResult::Success};
// Avoid repeated locks/unlocks by just locking now
std::lock_guard lock(syncpointEventMutex);
for (int i{}; i < 64; i++) {
if (bitmask & (1 << i))
if (auto freeErr{SyncpointFreeEventLocked(i)}; freeErr != PosixResult::Success)
err = freeErr;
}
return err;
}
std::shared_ptr<type::KEvent> Ctrl::QueryEvent(u32 valueRaw) {
SyncpointEventValue value{.val = valueRaw};
// I have no idea why nvidia does this
u16 slot{value.eventAllocated ? static_cast<u16>(value.partialSlot) : value.slot};
if (slot >= SyncpointEventCount)
return nullptr;
u32 syncpointId{value.eventAllocated ? static_cast<u32>(value.syncpointIdForAllocation) : value.syncpointId};
std::lock_guard lock(syncpointEventMutex);
auto &event{syncpointEvents[slot]};
if (event && event->fence.id == syncpointId)
return event->event;
return nullptr;
}
#include <services/nvdrv/devices/deserialisation/macro_def.inc>
static constexpr u32 CtrlMagic{0};
IOCTL_HANDLER_FUNC(Ctrl, ({
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x1C),
SyncpointClearEventWait, ARGS(In<SyncpointEventValue>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(CtrlMagic), FUNC(0x1D),
SyncpointWaitEvent, ARGS(In<Fence>, In<i32>, InOut<SyncpointEventValue>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(CtrlMagic), FUNC(0x1E),
SyncpointWaitEventSingle, ARGS(In<Fence>, In<i32>, InOut<SyncpointEventValue>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x1F),
SyncpointAllocateEvent, ARGS(In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(CtrlMagic), FUNC(0x20),
SyncpointFreeEvent, ARGS(In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(CtrlMagic), FUNC(0x21),
SyncpointFreeEventBatch, ARGS(In<u64>))
IOCTL_CASE_RESULT(INOUT, SIZE(0x183), MAGIC(CtrlMagic), FUNC(0x1B),
PosixResult::InvalidArgument) // GetConfig isn't available in production
}))
#include <services/nvdrv/devices/deserialisation/macro_undef.inc>
}

View file

@ -0,0 +1,141 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/nvdrv/devices/nvdevice.h>
#include <services/common/fence.h>
namespace skyline::service::nvdrv::device::nvhost {
/**
* @brief nvhost::Ctrl (/dev/nvhost-ctrl) provides IOCTLs for synchronization using syncpoints
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl
*/
class Ctrl : public NvDevice {
public:
/**
* @brief Metadata about a syncpoint event, used by QueryEvent and SyncpointEventWait
*/
union SyncpointEventValue {
u32 val;
struct {
u8 partialSlot : 4;
u32 syncpointId : 28;
};
struct {
u16 slot;
u16 syncpointIdForAllocation : 12;
bool eventAllocated : 1;
u8 _pad12_ : 3;
};
};
static_assert(sizeof(SyncpointEventValue) == sizeof(u32));
private:
/**
* @brief Syncpoint Events are used to expose fences to the userspace, they can be waited on using an IOCTL or be converted into a native HOS KEvent object that can be waited on just like any other KEvent on the guest
*/
class SyncpointEvent {
private:
soc::host1x::Syncpoint::WaiterHandle waiterHandle{};
void Signal();
public:
enum class State {
Available = 0,
Waiting = 1,
Cancelling = 2,
Signalling = 3,
Signalled = 4,
Cancelled = 5,
};
SyncpointEvent(const DeviceState &state);
std::atomic<State> state{State::Available};
Fence fence{}; //!< The fence that is associated with this syncpoint event
std::shared_ptr<type::KEvent> event{}; //!< Returned by 'QueryEvent'
/**
* @brief Removes any wait requests on a syncpoint event and resets its state
* @note Accesses to this function for a specific event should be locked
*/
void Cancel(soc::host1x::Host1X &host1x);
/**
* @brief Asynchronously waits on a syncpoint event using the given fence
* @note Accesses to this function for a specific event should be locked
*/
void RegisterWaiter(soc::host1x::Host1X &host1x, const Fence &fence);
bool IsInUse();
};
static constexpr u32 SyncpointEventCount{64}; //!< The maximum number of nvhost syncpoint events
std::mutex syncpointEventMutex;
std::array<std::unique_ptr<SyncpointEvent>, SyncpointEventCount> syncpointEvents{};
/**
* @brief Finds a free syncpoint event for the given syncpoint ID
* @note syncpointEventMutex MUST be locked when calling this
* @return The free event slot
*/
u32 FindFreeSyncpointEvent(u32 syncpointId);
PosixResult SyncpointWaitEventImpl(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value, bool allocate);
/**
* @brief Frees a single syncpoint event
* @note syncpointEventMutex MUST be locked when calling this
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_UNREGISTER_EVENT
*/
PosixResult SyncpointFreeEventLocked(In<u32> slot);
public:
Ctrl(const DeviceState &state, Core &core, const SessionContext &ctx);
/**
* @brief Clears a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_CLEAR_EVENT_WAIT
*/
PosixResult SyncpointClearEventWait(In<SyncpointEventValue> value);
/**
* @brief Allocates a syncpoint event for the given syncpoint and registers as it waiting for the given fence
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_WAIT_EVENT
*/
PosixResult SyncpointWaitEvent(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value);
/**
* @brief Waits on a specific syncpoint event and registers as it waiting for the given fence
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_WAIT_EVENT_SINGLE
*/
PosixResult SyncpointWaitEventSingle(In<Fence> fence, In<i32> timeout, InOut<SyncpointEventValue> value);
/**
* @brief Allocates a new syncpoint event at the given slot
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_ALLOC_EVENT
*/
PosixResult SyncpointAllocateEvent(In<u32> slot);
/**
* @brief Frees a single syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_UNREGISTER_EVENT
*/
PosixResult SyncpointFreeEvent(In<u32> slot);
/**
* @brief Frees a bitmask of a syncpoint events
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_FREE_EVENTS
*/
PosixResult SyncpointFreeEventBatch(In<u64> bitmask);
std::shared_ptr<type::KEvent> QueryEvent(u32 slot) override;
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
};
}

View file

@ -0,0 +1,81 @@
// 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 "ctrl_gpu.h"
namespace skyline::service::nvdrv::device::nvhost {
CtrlGpu::CtrlGpu(const DeviceState &state, Core &core, const SessionContext &ctx) :
NvDevice(state, core, ctx),
errorNotifierEvent(std::make_shared<type::KEvent>(state, false)),
unknownEvent(std::make_shared<type::KEvent>(state, false)) {}
PosixResult CtrlGpu::ZCullGetCtxSize(Out<u32> size) {
size = 0x1;
return PosixResult::Success;
}
PosixResult CtrlGpu::ZCullGetInfo(Out<ZCullInfo> info) {
info = {};
return PosixResult::Success;
}
PosixResult CtrlGpu::GetCharacteristics(InOut<u64> size, In<u64> userAddress, Out<GpuCharacteristics> characteristics) {
characteristics = {};
size = sizeof(GpuCharacteristics);
return PosixResult::Success;
}
PosixResult CtrlGpu::GetCharacteristics3(span<u8> inlineBuffer, InOut<u64> size, In<u64> userAddress, Out<GpuCharacteristics> characteristics) {
inlineBuffer.as<GpuCharacteristics>() = {};
return GetCharacteristics(size, userAddress, characteristics);
}
PosixResult CtrlGpu::GetTpcMasks(In<u32> bufSize, Out<u32> mask) {
if (bufSize)
mask = 0x3;
return PosixResult::Success;
}
PosixResult CtrlGpu::GetTpcMasks3(span<u8> inlineBuffer, In<u32> bufSize, Out<u32> mask) {
if (bufSize)
mask = inlineBuffer.as<u32>() = 0x3;
return PosixResult::Success;
}
PosixResult CtrlGpu::GetActiveSlotMask(Out<u32> slot, Out<u32> mask) {
slot = 0x7;
mask = 0x1;
return PosixResult::Success;
}
std::shared_ptr<type::KEvent> CtrlGpu::QueryEvent(u32 eventId) {
switch (eventId) {
case 1:
return errorNotifierEvent;
case 2:
return unknownEvent;
default:
return nullptr;
}
}
#include <services/nvdrv/devices/deserialisation/macro_def.inc>
static constexpr u32 CtrlGpuMagic{0x47};
IOCTL_HANDLER_FUNC(CtrlGpu, ({
IOCTL_CASE_ARGS(OUT, SIZE(0x4), MAGIC(CtrlGpuMagic), FUNC(0x1),
ZCullGetCtxSize, ARGS(Out<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x28), MAGIC(CtrlGpuMagic), FUNC(0x2),
ZCullGetInfo, ARGS(Out<ZCullInfo>))
IOCTL_CASE_ARGS(INOUT, SIZE(0xB0), MAGIC(CtrlGpuMagic), FUNC(0x5),
GetCharacteristics, ARGS(InOut<u64>, In<u64>, Out<GpuCharacteristics>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(CtrlGpuMagic), FUNC(0x6),
GetTpcMasks, ARGS(In<u32>, Pad<u32, 3>, Out<u32>))
IOCTL_CASE_ARGS(OUT, SIZE(0x8), MAGIC(CtrlGpuMagic), FUNC(0x14),
GetActiveSlotMask, ARGS(Out<u32>, Out<u32>))
}))
#include <services/nvdrv/devices/deserialisation/macro_undef.inc>
}

View file

@ -0,0 +1,122 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "services/nvdrv/devices/nvdevice.h"
namespace skyline::service::nvdrv::device::nvhost {
/**
* @brief nvhost::CtrlGpu (/dev/nvhost-ctrl-gpu) is used for context independent operations on the underlying GPU
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu
*/
class CtrlGpu : public NvDevice {
private:
std::shared_ptr<type::KEvent> errorNotifierEvent;
std::shared_ptr<type::KEvent> unknownEvent;
public:
/**
* @brief Holds hardware characteristics about a GPU, initialised to the GM20B values
*/
struct GpuCharacteristics {
u32 arch{0x120}; // NVGPU_GPU_ARCH_GM200
u32 impl{0xB}; // 0xB (NVGPU_GPU_IMPL_GM20B) or 0xE (NVGPU_GPU_IMPL_GM20B_B)
u32 rev{0xA1};
u32 numGpc{0x1};
u64 l2CacheSize{0x40000};
u64 onBoardVideoMemorySize{}; // UMA
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 _res_{};
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{0xA140}; // MAXWELL_DMA_COPY_A
u32 maxFbpsCount{0x1}; // 0x1
u32 fbpEnMask{}; // Disabled
u32 maxLtcPerFbp{0x2};
u32 maxLtsPerLtc{0x1};
u32 maxTexPerTpc{}; // Not Supported
u32 maxGpcCount{0x1};
u32 ropL2EnMask0{0x21D70}; // fuse_status_opt_rop_l2_fbp_r
u32 ropL2EnMask1{};
u64 chipName{util::MakeMagic<u64>("gm20b")};
u64 grCompbitStoreBaseHw{}; // Not Supported
};
/**
* @brief Contains the Maxwell ZCULL capabilities and configuration
*/
struct ZCullInfo {
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};
};
CtrlGpu(const DeviceState &state, Core &core, const SessionContext &ctx);
/**
* @brief Returns the zcull context size
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_CTX_SIZE
*/
PosixResult ZCullGetCtxSize(Out<u32> size);
/**
* @brief Returns information about the GPU ZCULL parameters
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_INFO
*/
PosixResult ZCullGetInfo(Out<ZCullInfo> info);
/**
* @brief Returns a struct with certain GPU characteristics
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_CHARACTERISTICS
*/
PosixResult GetCharacteristics(InOut<u64> size, In<u64> userAddress, Out<GpuCharacteristics> characteristics);
/**
* @brief Ioctl3 variant of GetTpcMasks
*/
PosixResult GetCharacteristics3(span<u8> inlineBuffer, InOut<u64> size, In<u64> userAddress, Out<GpuCharacteristics> characteristics);
/**
* @brief Returns the TPC mask value for each GPC
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_TPC_MASKS
*/
PosixResult GetTpcMasks(In<u32> bufSize, Out<u32> mask);
/**
* @brief Ioctl3 variant of GetTpcMasks
*/
PosixResult GetTpcMasks3(span<u8> inlineBuffer, In<u32> bufSize, Out<u32> mask);
/**
* @brief Returns the mask value for a ZBC slot
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZBC_GET_ACTIVE_SLOT_MASK
*/
PosixResult GetActiveSlotMask(Out<u32> slot, Out<u32> mask);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId) override;
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
};
}

View file

@ -0,0 +1,158 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <soc.h>
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
#include "gpu_channel.h"
namespace skyline::service::nvdrv::device::nvhost {
GpuChannel::GpuChannel(const DeviceState &state, Core &core, const SessionContext &ctx) :
NvDevice(state, core, ctx),
smExceptionBreakpointIntReportEvent(std::make_shared<type::KEvent>(state, false)),
smExceptionBreakpointPauseReportEvent(std::make_shared<type::KEvent>(state, false)),
errorNotifierEvent(std::make_shared<type::KEvent>(state, false)) {
channelSyncpoint = core.syncpointManager.AllocateSyncpoint(false);
}
PosixResult GpuChannel::SetNvmapFd(In<core::NvMap::Handle::Id> id) {
state.logger->Debug("id: {}", id);
return PosixResult::Success;
}
PosixResult GpuChannel::SetTimeout(In<u32> timeout) {
state.logger->Debug("timeout: {}", timeout);
return PosixResult::Success;
}
PosixResult GpuChannel::SubmitGpfifo(In<u64> userAddress, In<u32> numEntries, InOut<SubmitGpfifoFlags> flags, InOut<Fence> fence, span<soc::gm20b::GpEntry> gpEntries) {
state.logger->Debug("userAddress: 0x{:X}, numEntries: {},"
"flags ( fenceWait: {}, fenceIncrement: {}, hwFormat: {}, suppressWfi: {}, incrementWithValue: {}),"
"fence ( id: {}, threshold: {} )",
userAddress, numEntries,
+flags.fenceWait, +flags.fenceIncrement, +flags.hwFormat, +flags.suppressWfi, +flags.incrementWithValue,
fence.id, fence.threshold);
if (numEntries > gpEntries.size())
throw exception("GpEntry size mismatch!");
if (flags.fenceWait) {
if (flags.incrementWithValue)
return PosixResult::InvalidArgument;
if (core.syncpointManager.IsFenceSignalled(fence))
throw exception("Waiting on a fence through SubmitGpfifo is unimplemented");
}
state.soc->gm20b.gpfifo.Push(gpEntries.subspan(0, numEntries));
fence.id = channelSyncpoint;
u32 increment{(flags.fenceIncrement ? 2 : 0) + (flags.incrementWithValue ? fence.threshold : 0)};
fence.threshold = core.syncpointManager.IncrementSyncpointMaxExt(channelSyncpoint, increment);
if (flags.fenceIncrement)
throw exception("Incrementing a fence through SubmitGpfifo is unimplemented");
flags.raw = 0;
return PosixResult::Success;
}
PosixResult GpuChannel::SubmitGpfifo2(span<u8> inlineBuffer, In<u64> userAddress, In<u32> numEntries, InOut<GpuChannel::SubmitGpfifoFlags> flags, InOut<Fence> fence) {
return SubmitGpfifo(userAddress, numEntries, flags, fence, inlineBuffer.cast<soc::gm20b::GpEntry>());
}
PosixResult GpuChannel::AllocObjCtx(In<u32> classId, In<u32> flags, Out<u64> objId) {
state.logger->Debug("classId: 0x{:X}, flags: 0x{:X}", classId, flags);
return PosixResult::Success;
}
PosixResult GpuChannel::ZcullBind(In<u64> gpuVa, In<u32> mode) {
state.logger->Debug("gpuVa: 0x{:X}, mode: {}", gpuVa, mode);
return PosixResult::Success;
}
PosixResult GpuChannel::SetErrorNotifier(In<u64> offset, In<u64> size, In<u32> mem) {
state.logger->Debug("offset: 0x{:X}, size: 0x{:X}, mem: 0x{:X}", offset, size, mem);
return PosixResult::Success;
}
PosixResult GpuChannel::SetPriority(In<u32> priority) {
state.logger->Debug("priority: {}", priority);
return PosixResult::Success;
}
PosixResult GpuChannel::AllocGpfifoEx2(In<u32> numEntries, In<u32> numJobs, In<u32> flags, Out<Fence> fence) {
state.logger->Debug("numEntries: {}, numJobs: {}, flags: 0x{:X}", numEntries, numJobs, flags);
state.soc->gm20b.gpfifo.Initialize(numEntries);
fence = core.syncpointManager.GetSyncpointFence(channelSyncpoint);
return PosixResult::Success;
}
PosixResult GpuChannel::SetTimeslice(In<u32> timeslice) {
state.logger->Debug("timeslice: {}", timeslice);
return PosixResult::Success;
}
PosixResult GpuChannel::SetUserData(In<u64> userData) {
state.logger->Debug("userData: 0x{:X}", userData);
channelUserData = userData;
return PosixResult::Success;
}
PosixResult GpuChannel::GetUserData(Out<u64> userData) {
userData = channelUserData;
return PosixResult::Success;
}
std::shared_ptr<type::KEvent> GpuChannel::QueryEvent(u32 eventId) {
switch (eventId) {
case 1:
return smExceptionBreakpointIntReportEvent;
case 2:
return smExceptionBreakpointPauseReportEvent;
case 3:
return errorNotifierEvent;
default:
return nullptr;
}
}
#include <services/nvdrv/devices/deserialisation/macro_def.inc>
static constexpr u32 GpuChannelUserMagic{0x47};
static constexpr u32 GpuChannelMagic{0x48};
VARIABLE_IOCTL_HANDLER_FUNC(GpuChannel, ({
IOCTL_CASE_ARGS(IN, SIZE(0x4), MAGIC(GpuChannelMagic), FUNC(0x1),
SetNvmapFd, ARGS(In<core::NvMap::Handle::Id>))
IOCTL_CASE_ARGS(IN, SIZE(0x4), MAGIC(GpuChannelMagic), FUNC(0x3),
SetTimeout, ARGS(In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(GpuChannelMagic), FUNC(0x9),
AllocObjCtx, ARGS(In<u32>, In<u32>, Out<u64>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x10), MAGIC(GpuChannelMagic), FUNC(0xB),
ZcullBind, ARGS(In<u64>, In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(GpuChannelMagic), FUNC(0xC),
SetErrorNotifier, ARGS(In<u64>, In<u64>, In<u32>))
IOCTL_CASE_ARGS(IN, SIZE(0x4), MAGIC(GpuChannelMagic), FUNC(0xD),
SetPriority, ARGS(In<u32>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x20), MAGIC(GpuChannelMagic), FUNC(0x1A),
AllocGpfifoEx2, ARGS(In<u32>, In<u32>, In<u32>, Out<Fence>))
IOCTL_CASE_ARGS(INOUT, SIZE(0x4), MAGIC(GpuChannelMagic), FUNC(0x1D),
SetTimeslice, ARGS(In<u32>))
IOCTL_CASE_ARGS(IN, SIZE(0x8), MAGIC(GpuChannelUserMagic), FUNC(0x14),
SetUserData, ARGS(In<u64>))
IOCTL_CASE_ARGS(OUT, SIZE(0x8), MAGIC(GpuChannelUserMagic), FUNC(0x15),
GetUserData, ARGS(Out<u64>))
}), ({
VARIABLE_IOCTL_CASE_ARGS(INOUT, MAGIC(GpuChannelMagic), FUNC(0x8),
SubmitGpfifo, ARGS(In<u64>, In<u32>, InOut<SubmitGpfifoFlags>, InOut<Fence>, AutoSizeSpan<soc::gm20b::GpEntry>))
}))
INLINE_IOCTL_HANDLER_FUNC(Ioctl2, GpuChannel, ({
INLINE_IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(GpuChannelMagic), FUNC(0x1B),
SubmitGpfifo2, ARGS(In<u64>, In<u32>, InOut<SubmitGpfifoFlags>, InOut<Fence>))
}))
#include <services/nvdrv/devices/deserialisation/macro_undef.inc>
}

View file

@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT OR MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <soc/gm20b/gpfifo.h>
#include <services/common/fence.h>
#include "services/nvdrv/devices/nvdevice.h"
namespace skyline::service::nvdrv::device::nvhost {
/**
* @brief nvhost::GpuChannel is used to create and submit commands to channels which are effectively GPU processes
* @url https://switchbrew.org/wiki/NV_services#Channels
*/
class GpuChannel : public NvDevice {
private:
u32 channelSyncpoint{};
u32 channelUserData{};
std::shared_ptr<type::KEvent> smExceptionBreakpointIntReportEvent;
std::shared_ptr<type::KEvent> smExceptionBreakpointPauseReportEvent;
std::shared_ptr<type::KEvent> errorNotifierEvent;
public:
/**
* @brief A bitfield of the flags that can be supplied for a specific GPFIFO submission
*/
union SubmitGpfifoFlags {
struct __attribute__((__packed__)) {
bool fenceWait : 1;
bool fenceIncrement : 1;
bool hwFormat : 1;
u8 _pad0_ : 1;
bool suppressWfi : 1;
u8 _pad1_ : 3;
bool incrementWithValue : 1;
};
u32 raw;
};
GpuChannel(const DeviceState &state, Core &core, const SessionContext &ctx);
/**
* @brief Sets the nvmap handle id to be used for channel submits (does nothing for GPU channels)
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_NVMAP_FD
*/
PosixResult SetNvmapFd(In<core::NvMap::Handle::Id> id);
/**
* @brief Sets the timeout for channel submits
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_TIMEOUT
*/
PosixResult SetTimeout(In<u32> timeout);
/**
* @brief Submits GPFIFO entries for this channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO
*/
PosixResult SubmitGpfifo(In<u64> userAddress, In<u32> numEntries, InOut<SubmitGpfifoFlags> flags, InOut<Fence> fence, span<soc::gm20b::GpEntry> gpEntries);
/**
* @brief Ioctl2 variant of SubmitGpfifo
*/
PosixResult SubmitGpfifo2(span<u8> inlineBuffer, In<u64> userAddress, In<u32> numEntries, InOut<SubmitGpfifoFlags> flags, InOut<Fence> fence);
/**
* @brief Allocates a graphic context object
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_OBJ_CTX
*/
PosixResult AllocObjCtx(In<u32> classId, In<u32> flags, Out<u64> objId);
/**
* @brief Binds a zcull context to the channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ZCULL_BIND
*/
PosixResult ZcullBind(In<u64> gpuVa, In<u32> mode);
/**
* @brief Initializes the error notifier for this channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_ERROR_NOTIFIER
*/
PosixResult SetErrorNotifier(In<u64> offset, In<u64> size, In<u32> mem);
/**
* @brief Sets the priority of the channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_PRIORITY
*/
PosixResult SetPriority(In<u32> priority);
/**
* @brief Allocates a GPFIFO entry
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_GPFIFO_EX2
*/
PosixResult AllocGpfifoEx2(In<u32> numEntries, In<u32> numJobs, In<u32> flags, Out<Fence> fence);
/**
* @brief Sets the timeslice of the channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_TIMESLICE)
*/
PosixResult SetTimeslice(In<u32> timeslice);
/**
* @brief Sets the user specific data
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA
*/
PosixResult SetUserData(In<u64> userData);
/**
* @brief Sets the user specific data
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_GET_USER_DATA
*/
PosixResult GetUserData(Out<u64> userData);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId) override;
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
PosixResult Ioctl2(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) override;
};
}

View file

@ -1,196 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <soc.h>
#include <services/nvdrv/driver.h>
#include "nvmap.h"
#include "nvhost_as_gpu.h"
namespace skyline::service::nvdrv::device {
struct MappingFlags {
bool fixed : 1;
u8 _pad0_ : 7;
bool remap : 1;
u32 _pad1_ : 23;
};
static_assert(sizeof(MappingFlags) == sizeof(u32));
NvHostAsGpu::NvHostAsGpu(const DeviceState &state) : NvDevice(state) {}
NvStatus NvHostAsGpu::BindChannel(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostAsGpu::AllocSpace(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 pages; // In
u32 pageSize; // In
MappingFlags flags; // In
u32 _pad_;
union {
u64 offset; // InOut
u64 align; // In
};
} &region = buffer.as<Data>();
u64 size{static_cast<u64>(region.pages) * static_cast<u64>(region.pageSize)};
if (region.flags.fixed)
region.offset = state.soc->gmmu.ReserveFixed(region.offset, size);
else
region.offset = state.soc->gmmu.ReserveSpace(size, region.align);
if (region.offset == 0) {
state.logger->Warn("Failed to allocate GPU address space region!");
return NvStatus::BadParameter;
}
return NvStatus::Success;
}
NvStatus NvHostAsGpu::UnmapBuffer(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
u64 offset{buffer.as<u64>()};
try {
auto region{regionMap.at(offset)};
// Non-fixed regions are unmapped so that they can be used by future non-fixed mappings
if (!region.fixed)
if (!state.soc->gmmu.Unmap(offset, region.size))
state.logger->Warn("Failed to unmap region at 0x{:X}", offset);
regionMap.erase(offset);
} catch (const std::out_of_range &e) {
state.logger->Warn("Couldn't find region to unmap at 0x{:X}", offset);
}
return NvStatus::Success;
}
NvStatus NvHostAsGpu::Modify(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
MappingFlags flags; // In
u32 kind; // In
u32 nvmapHandle; // In
u32 pageSize; // InOut
u64 bufferOffset; // In
u64 mappingSize; // In
u64 offset; // InOut
} &data = buffer.as<Data>();
try {
if (data.flags.remap) {
auto region{regionMap.lower_bound(data.offset)};
if (region == regionMap.end()) {
state.logger->Warn("Cannot remap an unmapped GPU address space region: 0x{:X}", data.offset);
return NvStatus::BadParameter;
}
if (region->second.size < data.mappingSize) {
state.logger->Warn("Cannot remap an partially mapped GPU address space region: 0x{:X}", data.offset);
return NvStatus::BadParameter;
}
u64 gpuAddress{data.offset + data.bufferOffset};
u8 *cpuPtr{region->second.ptr + data.bufferOffset};
if (!state.soc->gmmu.MapFixed(gpuAddress, cpuPtr, data.mappingSize)) {
state.logger->Warn("Failed to remap GPU address space region: 0x{:X}", gpuAddress);
return NvStatus::BadParameter;
}
return NvStatus::Success;
}
auto driver{nvdrv::driver.lock()};
auto nvmap{driver->nvMap.lock()};
auto mapping{nvmap->GetObject(data.nvmapHandle)};
u8 *cpuPtr{data.bufferOffset + mapping->ptr};
u64 size{data.mappingSize ? data.mappingSize : mapping->size};
if (data.flags.fixed)
data.offset = state.soc->gmmu.MapFixed(data.offset, cpuPtr, size);
else
data.offset = state.soc->gmmu.MapAllocate(cpuPtr, size);
if (data.offset == 0) {
state.logger->Warn("Failed to map GPU address space region!");
return NvStatus::BadParameter;
}
regionMap[data.offset] = {cpuPtr, size, data.flags.fixed};
return NvStatus::Success;
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.nvmapHandle);
return NvStatus::BadParameter;
}
}
NvStatus NvHostAsGpu::GetVaRegions(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
/*
struct Data {
u64 _pad0_;
u32 bufferSize; // InOut
u32 _pad1_;
struct {
u64 offset;
u32 page_size;
u32 pad;
u64 pages;
} regions[2]; // Out
} &regionInfo = buffer.as<Data>();
*/
return NvStatus::Success;
}
NvStatus NvHostAsGpu::AllocAsEx(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
/*
struct Data {
u32 bigPageSize; // In
i32 asFd; // In
u32 flags; // In
u32 reserved; // In
u64 vaRangeStart; // In
u64 vaRangeEnd; // In
u64 vaRangeSplit; // In
} addressSpace = buffer.as<Data>();
*/
return NvStatus::Success;
}
NvStatus NvHostAsGpu::Remap(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Entry {
u16 flags; // In
u16 kind; // In
u32 nvmapHandle; // In
u32 mapOffset; // In
u32 gpuOffset; // In
u32 pages; // In
};
constexpr u32 MinAlignmentShift{0x10}; // This shift is applied to all addresses passed to Remap
auto entries{buffer.cast<Entry>()};
for (const auto &entry : entries) {
try {
auto driver{nvdrv::driver.lock()};
auto nvmap{driver->nvMap.lock()};
auto mapping{nvmap->GetObject(entry.nvmapHandle)};
u64 virtAddr{static_cast<u64>(entry.gpuOffset) << MinAlignmentShift};
u8 *cpuPtr{mapping->ptr + (static_cast<u64>(entry.mapOffset) << MinAlignmentShift)};
u64 size{static_cast<u64>(entry.pages) << MinAlignmentShift};
state.soc->gmmu.MapFixed(virtAddr, cpuPtr, size);
} catch (const std::out_of_range &) {
state.logger->Warn("Invalid NvMap handle: 0x{:X}", entry.nvmapHandle);
return NvStatus::BadParameter;
}
}
return NvStatus::Success;
}
}

View file

@ -1,78 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "nvdevice.h"
namespace skyline::service::nvdrv::device {
/**
* @brief NvHostAsGpu (/dev/nvhost-as-gpu) is used to access GPU virtual address spaces
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu
*/
class NvHostAsGpu : public NvDevice {
private:
struct AddressSpaceRegion {
u8 *ptr;
u64 size;
bool fixed;
};
std::map<u64, AddressSpaceRegion> regionMap; //!< This maps the base addresses of mapped buffers to their total sizes and mapping type, this is needed as what was originally a single buffer may have been split into multiple GPU side buffers with the remap flag.
public:
NvHostAsGpu(const DeviceState &state);
/**
* @brief Binds a channel to the address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_BIND_CHANNEL
*/
NvStatus BindChannel(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
/**
* @brief Reserves a region in the GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_ALLOC_SPACE
*/
NvStatus AllocSpace(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
/**
* @brief Unmaps a region in the GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_UNMAP_BUFFER
*/
NvStatus UnmapBuffer(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
/**
* @brief Maps a region in the GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_MODIFY
*/
NvStatus Modify(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
/**
* @brief Returns the application's GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_GET_VA_REGIONS
*/
NvStatus GetVaRegions(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
/**
* @brief Initializes the application's GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_ALLOC_AS_EX
*/
NvStatus AllocAsEx(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
/**
* @brief Remaps a region of the GPU address space
* @url https://switchbrew.org/wiki/NV_services#NVGPU_AS_IOCTL_REMAP
*/
NvStatus Remap(IoctlType type, span <u8> buffer, span <u8> inlineBuffer);
NVDEVICE_DECL(
NVFUNC(0x4101, NvHostAsGpu, BindChannel),
NVFUNC(0x4102, NvHostAsGpu, AllocSpace),
NVFUNC(0x4105, NvHostAsGpu, UnmapBuffer),
NVFUNC(0x4106, NvHostAsGpu, Modify),
NVFUNC(0x4108, NvHostAsGpu, GetVaRegions),
NVFUNC(0x4109, NvHostAsGpu, AllocAsEx),
NVFUNC(0x4114, NvHostAsGpu, Remap)
)
};
}

View file

@ -1,144 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <soc.h>
#include <kernel/types/KProcess.h>
#include <services/nvdrv/driver.h>
#include "nvhost_channel.h"
namespace skyline::service::nvdrv::device {
NvHostChannel::NvHostChannel(const DeviceState &state) : smExceptionBreakpointIntReportEvent(std::make_shared<type::KEvent>(state, false)), smExceptionBreakpointPauseReportEvent(std::make_shared<type::KEvent>(state, false)), errorNotifierEvent(std::make_shared<type::KEvent>(state, false)), NvDevice(state) {
auto driver{nvdrv::driver.lock()};
auto &hostSyncpoint{driver->hostSyncpoint};
channelFence.id = hostSyncpoint.AllocateSyncpoint(false);
channelFence.UpdateValue(hostSyncpoint);
}
NvStatus NvHostChannel::SetNvmapFd(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostChannel::SetSubmitTimeout(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostChannel::SubmitGpfifo(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
soc::gm20b::GpEntry *entries; // In
u32 numEntries; // In
union {
struct __attribute__((__packed__)) {
bool fenceWait : 1;
bool fenceIncrement : 1;
bool hwFormat : 1;
u8 _pad0_ : 1;
bool suppressWfi : 1;
u8 _pad1_ : 3;
bool incrementWithValue : 1;
};
u32 raw;
} flags; // In
Fence fence; // InOut
} &data = buffer.as<Data>();
auto driver{nvdrv::driver.lock()};
auto &hostSyncpoint{driver->hostSyncpoint};
if (data.flags.fenceWait) {
if (data.flags.incrementWithValue)
return NvStatus::BadValue;
if (hostSyncpoint.HasSyncpointExpired(data.fence.id, data.fence.value))
throw exception("Waiting on a fence through SubmitGpfifo is unimplemented");
}
state.soc->gm20b.gpfifo.Push([&]() {
if (type == IoctlType::Ioctl2)
return inlineBuffer.cast<soc::gm20b::GpEntry>();
else
return span(data.entries, data.numEntries);
}());
data.fence.id = channelFence.id;
u32 increment{(data.flags.fenceIncrement ? 2 : 0) + (data.flags.incrementWithValue ? data.fence.value : 0)};
data.fence.value = hostSyncpoint.IncrementSyncpointMaxExt(data.fence.id, increment);
if (data.flags.fenceIncrement)
throw exception("Incrementing a fence through SubmitGpfifo is unimplemented");
data.flags.raw = 0;
return NvStatus::Success;
}
NvStatus NvHostChannel::AllocObjCtx(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostChannel::ZcullBind(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostChannel::SetErrorNotifier(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
NvStatus NvHostChannel::SetPriority(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
switch (buffer.as<NvChannelPriority>()) {
case NvChannelPriority::Low:
timeslice = 1300;
break;
case NvChannelPriority::Medium:
timeslice = 2600;
break;
case NvChannelPriority::High:
timeslice = 5200;
break;
}
return NvStatus::Success;
}
NvStatus NvHostChannel::AllocGpfifoEx2(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 numEntries; // In
u32 numJobs; // In
u32 flags; // In
Fence fence; // Out
u32 _res_[3]; // In
} &data = buffer.as<Data>();
state.soc->gm20b.gpfifo.Initialize(data.numEntries);
auto driver{nvdrv::driver.lock()};
channelFence.UpdateValue(driver->hostSyncpoint);
data.fence = channelFence;
return NvStatus::Success;
}
NvStatus NvHostChannel::SetTimeslice(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
timeslice = buffer.as<u32>();
return NvStatus::Success;
}
NvStatus NvHostChannel::SetUserData(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::Success;
}
std::shared_ptr<type::KEvent> NvHostChannel::QueryEvent(u32 eventId) {
switch (eventId) {
case 1:
return smExceptionBreakpointIntReportEvent;
case 2:
return smExceptionBreakpointPauseReportEvent;
case 3:
return errorNotifierEvent;
default:
return nullptr;
}
}
}

View file

@ -1,107 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/common/fence.h>
#include "nvdevice.h"
namespace skyline::service::nvdrv::device {
/**
* @brief NvHostChannel is used as a common interface for all Channel devices
* @url https://switchbrew.org/wiki/NV_services#Channels
*/
class NvHostChannel : public NvDevice {
private:
enum class NvChannelPriority : u32 {
Low = 0x32,
Medium = 0x64,
High = 0x94,
};
Fence channelFence{};
u32 timeslice{};
std::shared_ptr<type::KEvent> smExceptionBreakpointIntReportEvent;
std::shared_ptr<type::KEvent> smExceptionBreakpointPauseReportEvent;
std::shared_ptr<type::KEvent> errorNotifierEvent;
public:
NvHostChannel(const DeviceState &state);
/**
* @brief Sets the nvmap file descriptor
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_NVMAP_FD
*/
NvStatus SetNvmapFd(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Sets the timeout for the channel
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CHANNEL_SET_SUBMIT_TIMEOUT
*/
NvStatus SetSubmitTimeout(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Submits a command to the GPFIFO
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SUBMIT_GPFIFO
*/
NvStatus SubmitGpfifo(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Allocates a graphic context object
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_OBJ_CTX
*/
NvStatus AllocObjCtx(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Initializes the error notifier for this channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ZCULL_BIND
*/
NvStatus ZcullBind(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Initializes the error notifier for this channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_ERROR_NOTIFIER
*/
NvStatus SetErrorNotifier(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Sets the priority of the channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_PRIORITY
*/
NvStatus SetPriority(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Allocates a GPFIFO entry
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_GPFIFO_EX2
*/
NvStatus AllocGpfifoEx2(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Sets the timeslice of the channel
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_TIMESLICE)
*/
NvStatus SetTimeslice(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Sets the user specific data
* @url https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA
*/
NvStatus SetUserData(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId) override;
NVDEVICE_DECL(
NVFUNC(0x4801, NvHostChannel, SetNvmapFd),
NVFUNC(0x4803, NvHostChannel, SetSubmitTimeout),
NVFUNC(0x4808, NvHostChannel, SubmitGpfifo),
NVFUNC(0x4809, NvHostChannel, AllocObjCtx),
NVFUNC(0x480B, NvHostChannel, ZcullBind),
NVFUNC(0x480C, NvHostChannel, SetErrorNotifier),
NVFUNC(0x480D, NvHostChannel, SetPriority),
NVFUNC(0x481A, NvHostChannel, AllocGpfifoEx2),
NVFUNC(0x481B, NvHostChannel, SubmitGpfifo), // Our SubmitGpfifo implementation also handles SubmitGpfifoEx
NVFUNC(0x481D, NvHostChannel, SetTimeslice),
NVFUNC(0x4714, NvHostChannel, SetUserData)
)
};
}

View file

@ -1,242 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
// Copyright © 2019-2020 Ryujinx Team and Contributors
#include <soc.h>
#include <kernel/types/KProcess.h>
#include <services/nvdrv/driver.h>
#include "nvhost_ctrl.h"
namespace skyline::service::nvdrv::device {
SyncpointEvent::SyncpointEvent(const DeviceState &state) : event(std::make_shared<type::KEvent>(state, false)) {}
/**
* @brief Metadata about a syncpoint event, used by QueryEvent and SyncpointEventWait
*/
union SyncpointEventValue {
u32 val;
struct {
u8 _pad0_ : 4;
u32 syncpointIdAsync : 28;
};
struct {
union {
u8 eventSlotAsync;
u16 eventSlotNonAsync;
};
u16 syncpointIdNonAsync : 12;
bool nonAsync : 1;
u8 _pad12_ : 3;
};
};
static_assert(sizeof(SyncpointEventValue) == sizeof(u32));
void SyncpointEvent::Signal() {
std::lock_guard lock(mutex);
auto oldState{state};
state = State::Signalling;
// We should only signal the KEvent if the event is actively being waited on
if (oldState == State::Waiting)
event->Signal();
state = State::Signalled;
}
void SyncpointEvent::Cancel(soc::host1x::Host1X &host1x) {
std::lock_guard lock(mutex);
host1x.syncpoints.at(fence.id).DeregisterWaiter(waiterHandle);
waiterHandle = {};
Signal();
event->ResetSignal();
}
void SyncpointEvent::Wait(soc::host1x::Host1X &host1x, const Fence &pFence) {
std::lock_guard lock(mutex);
fence = pFence;
state = State::Waiting;
waiterHandle = host1x.syncpoints.at(fence.id).RegisterWaiter(fence.value, [this] { Signal(); });
}
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state) {}
u32 NvHostCtrl::FindFreeSyncpointEvent(u32 syncpointId) {
u32 eventSlot{constant::NvHostEventCount}; //!< Holds the slot of the last populated event in the event array
u32 freeSlot{constant::NvHostEventCount}; //!< Holds the slot of the first unused event id
std::lock_guard lock(syncpointEventMutex);
for (u32 i{}; i < constant::NvHostEventCount; i++) {
if (syncpointEvents[i]) {
auto event{syncpointEvents[i]};
if (event->state == SyncpointEvent::State::Cancelled || event->state == SyncpointEvent::State::Available || event->state == SyncpointEvent::State::Signalled) {
eventSlot = i;
// This event is already attached to the requested syncpoint, so use it
if (event->fence.id == syncpointId)
return eventSlot;
}
} else if (freeSlot == constant::NvHostEventCount) {
freeSlot = i;
}
}
// Use an unused event if possible
if (freeSlot < constant::NvHostEventCount) {
syncpointEvents[eventSlot] = std::make_shared<SyncpointEvent>(state);
return freeSlot;
}
// Recycle an existing event if all else fails
if (eventSlot < constant::NvHostEventCount)
return eventSlot;
throw exception("Failed to find a free nvhost event!");
}
NvStatus NvHostCtrl::SyncpointEventWaitImpl(span<u8> buffer, bool async) {
struct Data {
Fence fence; // In
u32 timeout; // In
SyncpointEventValue value; // InOut
} &data = buffer.as<Data>();
if (data.fence.id >= soc::host1x::SyncpointCount)
return NvStatus::BadValue;
if (data.timeout == 0)
return NvStatus::Timeout;
auto driver{nvdrv::driver.lock()};
auto &hostSyncpoint{driver->hostSyncpoint};
// Check if the syncpoint has already expired using the last known values
if (hostSyncpoint.HasSyncpointExpired(data.fence.id, data.fence.value)) {
data.value.val = hostSyncpoint.ReadSyncpointMinValue(data.fence.id);
return NvStatus::Success;
}
// Sync the syncpoint with the GPU then check again
auto minVal{hostSyncpoint.UpdateMin(data.fence.id)};
if (hostSyncpoint.HasSyncpointExpired(data.fence.id, data.fence.value)) {
data.value.val = minVal;
return NvStatus::Success;
}
u32 eventSlot{};
if (async) {
if (data.value.val >= constant::NvHostEventCount)
return NvStatus::BadValue;
eventSlot = data.value.val;
} else {
data.fence.value = 0;
eventSlot = FindFreeSyncpointEvent(data.fence.id);
}
std::lock_guard lock(syncpointEventMutex);
auto event{syncpointEvents[eventSlot]};
if (!event)
return NvStatus::BadValue;
std::lock_guard eventLock(event->mutex);
if (event->state == SyncpointEvent::State::Cancelled || event->state == SyncpointEvent::State::Available || event->state == SyncpointEvent::State::Signalled) {
state.logger->Debug("Waiting on syncpoint event: {} with fence: ({}, {})", eventSlot, data.fence.id, data.fence.value);
event->Wait(state.soc->host1x, data.fence);
data.value.val = 0;
if (async) {
data.value.syncpointIdAsync = data.fence.id;
} else {
data.value.syncpointIdNonAsync = data.fence.id;
data.value.nonAsync = true;
}
data.value.val |= eventSlot;
return NvStatus::Timeout;
} else {
return NvStatus::BadValue;
}
}
NvStatus NvHostCtrl::GetConfig(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return NvStatus::BadValue;
}
NvStatus NvHostCtrl::SyncpointClearEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
auto eventSlot{buffer.as<u16>()};
if (eventSlot >= constant::NvHostEventCount)
return NvStatus::BadValue;
std::lock_guard lock(syncpointEventMutex);
auto event{syncpointEvents[eventSlot]};
if (!event)
return NvStatus::BadValue;
std::lock_guard eventLock(event->mutex);
if (event->state == SyncpointEvent::State::Waiting) {
event->state = SyncpointEvent::State::Cancelling;
state.logger->Debug("Cancelling waiting syncpoint event: {}", eventSlot);
event->Cancel(state.soc->host1x);
}
event->state = SyncpointEvent::State::Cancelled;
auto driver{nvdrv::driver.lock()};
auto &hostSyncpoint{driver->hostSyncpoint};
hostSyncpoint.UpdateMin(event->fence.id);
return NvStatus::Success;
}
NvStatus NvHostCtrl::SyncpointEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return SyncpointEventWaitImpl(buffer, false);
}
NvStatus NvHostCtrl::SyncpointEventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
return SyncpointEventWaitImpl(buffer, true);
}
NvStatus NvHostCtrl::SyncpointRegisterEvent(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
auto eventSlot{buffer.as<u32>()};
state.logger->Debug("Registering syncpoint event: {}", eventSlot);
if (eventSlot >= constant::NvHostEventCount)
return NvStatus::BadValue;
std::lock_guard lock(syncpointEventMutex);
auto &event{syncpointEvents[eventSlot]};
if (event)
throw exception("Recreating events is unimplemented");
event = std::make_shared<SyncpointEvent>(state);
return NvStatus::Success;
}
std::shared_ptr<type::KEvent> NvHostCtrl::QueryEvent(u32 eventId) {
SyncpointEventValue eventValue{.val = eventId};
std::lock_guard lock(syncpointEventMutex);
auto event{syncpointEvents.at(eventValue.nonAsync ? eventValue.eventSlotNonAsync : eventValue.eventSlotAsync)};
if (event && event->fence.id == (eventValue.nonAsync ? eventValue.syncpointIdNonAsync : eventValue.syncpointIdAsync))
return event->event;
return nullptr;
}
}

View file

@ -1,113 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <services/common/fence.h>
#include "nvdevice.h"
namespace skyline {
namespace constant {
constexpr u32 NvHostEventCount{64}; //!< The maximum number of nvhost events
}
namespace service::nvdrv::device {
/**
* @brief Syncpoint Events are used to expose fences to the userspace, they can be waited on using an IOCTL or be converted into a native HOS KEvent object that can be waited on just like any other KEvent on the guest
*/
class SyncpointEvent {
private:
soc::host1x::Syncpoint::WaiterHandle waiterHandle{};
void Signal();
public:
enum class State {
Available = 0,
Waiting = 1,
Cancelling = 2,
Signalling = 3,
Signalled = 4,
Cancelled = 5,
};
SyncpointEvent(const DeviceState &state);
std::recursive_mutex mutex; //!< Protects access to the entire event
State state{State::Available};
Fence fence{}; //!< The fence that is associated with this syncpoint event
std::shared_ptr<type::KEvent> event{}; //!< Returned by 'QueryEvent'
/**
* @brief Removes any wait requests on a syncpoint event and resets its state
*/
void Cancel(soc::host1x::Host1X &host1x);
/**
* @brief Asynchronously waits on a syncpoint event using the given fence
*/
void Wait(soc::host1x::Host1X &host1x, const Fence &fence);
};
/**
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for NvHost management and synchronisation
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl
*/
class NvHostCtrl : public NvDevice {
private:
std::mutex syncpointEventMutex;
std::array<std::shared_ptr<SyncpointEvent>, constant::NvHostEventCount> syncpointEvents{};
/**
* @brief Finds a free syncpoint event for the given id
* @return The index of the syncpoint event in the map
*/
u32 FindFreeSyncpointEvent(u32 syncpointId);
NvStatus SyncpointEventWaitImpl(span<u8> buffer, bool async);
public:
NvHostCtrl(const DeviceState &state);
/**
* @brief Gets the value of an nvdrv setting, it returns an error code on production switches
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_GET_CONFIG
*/
NvStatus GetConfig(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Clears a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_CLEAR_EVENT_WAIT
*/
NvStatus SyncpointClearEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Synchronously waits on a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT
*/
NvStatus SyncpointEventWait(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Asynchronously waits on a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_EVENT_WAIT_ASYNC
*/
NvStatus SyncpointEventWaitAsync(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Registers a syncpoint event
* @url https://switchbrew.org/wiki/NV_services#NVHOST_IOCTL_CTRL_SYNCPT_REGISTER_EVENT
*/
NvStatus SyncpointRegisterEvent(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId) override;
NVDEVICE_DECL(
NVFUNC(0x001B, NvHostCtrl, GetConfig),
NVFUNC(0x001C, NvHostCtrl, SyncpointClearEventWait),
NVFUNC(0x001D, NvHostCtrl, SyncpointEventWait),
NVFUNC(0x001E, NvHostCtrl, SyncpointEventWaitAsync),
NVFUNC(0x001F, NvHostCtrl, SyncpointRegisterEvent)
)
};
}
}

View file

@ -1,130 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include "nvhost_ctrl_gpu.h"
namespace skyline::service::nvdrv::device {
NvHostCtrlGpu::NvHostCtrlGpu(const DeviceState &state) : errorNotifierEvent(std::make_shared<type::KEvent>(state, false)), unknownEvent(std::make_shared<type::KEvent>(state, false)), NvDevice(state) {}
NvStatus NvHostCtrlGpu::ZCullGetCtxSize(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
buffer.as<u32>() = 0x1;
return NvStatus::Success;
}
NvStatus NvHostCtrlGpu::ZCullGetInfo(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct ZCullInfo {
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;
buffer.as<ZCullInfo>() = zCullInfo;
return NvStatus::Success;
}
NvStatus NvHostCtrlGpu::GetCharacteristics(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct GpuCharacteristics {
u32 arch{0x120}; // NVGPU_GPU_ARCH_GM200
u32 impl{0xB}; // 0xB (NVGPU_GPU_IMPL_GM20B) or 0xE (NVGPU_GPU_IMPL_GM20B_B)
u32 rev{0xA1};
u32 numGpc{0x1};
u64 l2CacheSize{0x40000};
u64 onBoardVideoMemorySize{}; // UMA
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 _res_{};
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{0xA140}; // MAXWELL_DMA_COPY_A
u32 maxFbpsCount{0x1}; // 0x1
u32 fbpEnMask{}; // Disabled
u32 maxLtcPerFbp{0x2};
u32 maxLtsPerLtc{0x1};
u32 maxTexPerTpc{}; // Not Supported
u32 maxGpcCount{0x1};
u32 ropL2EnMask0{0x21D70}; // fuse_status_opt_rop_l2_fbp_r
u32 ropL2EnMask1{};
u64 chipName{util::MakeMagic<u64>("gm20b")};
u64 grCompbitStoreBaseHw{}; // Not Supported
};
struct Data {
u64 gpuCharacteristicsBufSize; // InOut
u64 gpuCharacteristicsBufAddr; // In
GpuCharacteristics gpuCharacteristics; // Out
} &data = buffer.as<Data>();
if (data.gpuCharacteristicsBufSize < sizeof(GpuCharacteristics))
return NvStatus::InvalidSize;
// The IOCTL3 version of GetCharacteristics additionally outputs to the inline output buffer
if (type == IoctlType::Ioctl3) {
auto &inlineCharacteristics{inlineBuffer.as<GpuCharacteristics>()};
data.gpuCharacteristics = inlineCharacteristics = GpuCharacteristics{};
} else {
data.gpuCharacteristics = GpuCharacteristics{};
}
data.gpuCharacteristicsBufSize = sizeof(GpuCharacteristics);
return NvStatus::Success;
}
NvStatus NvHostCtrlGpu::GetTpcMasks(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 maskBufSize; // In
u32 _res_[3]; // In
u64 maskBuf; // Out
} &data = buffer.as<Data>();
if (data.maskBufSize) {
if (type == IoctlType::Ioctl3) {
auto &inlineMask{inlineBuffer.as<u32>()};
data.maskBuf = inlineMask = 0x3;
} else {
data.maskBuf = 0x3;
}
}
return NvStatus::Success;
}
NvStatus NvHostCtrlGpu::GetActiveSlotMask(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
struct Data {
u32 slot{0x07}; // Out
u32 mask{0x01}; // Out
} data;
buffer.as<Data>() = data;
return NvStatus::Success;
}
std::shared_ptr<type::KEvent> NvHostCtrlGpu::QueryEvent(u32 eventId) {
switch (eventId) {
case 1:
return errorNotifierEvent;
case 2:
return unknownEvent;
default:
return nullptr;
}
}
}

View file

@ -1,61 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include "nvdevice.h"
namespace skyline::service::nvdrv::device {
/**
* @brief NvHostCtrlGpu (/dev/nvhost-ctrl-gpu) is used for context independent operations on the underlying GPU
* @url https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu
*/
class NvHostCtrlGpu : public NvDevice {
private:
std::shared_ptr<type::KEvent> errorNotifierEvent;
std::shared_ptr<type::KEvent> unknownEvent;
public:
NvHostCtrlGpu(const DeviceState &state);
/**
* @brief Returns a u32 GPU ZCULL Context Size
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_CTX_SIZE
*/
NvStatus ZCullGetCtxSize(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Returns a the GPU ZCULL Information
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_INFO
*/
NvStatus ZCullGetInfo(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Returns a struct with certain GPU characteristics
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_CHARACTERISTICS
*/
NvStatus GetCharacteristics(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Returns the TPC mask value for each GPC
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_TPC_MASKS
*/
NvStatus GetTpcMasks(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Returns the mask value for a ZBC slot
* @url https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZBC_GET_ACTIVE_SLOT_MASK
*/
NvStatus GetActiveSlotMask(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
std::shared_ptr<type::KEvent> QueryEvent(u32 eventId) override;
NVDEVICE_DECL(
NVFUNC(0x4701, NvHostCtrlGpu, ZCullGetCtxSize),
NVFUNC(0x4702, NvHostCtrlGpu, ZCullGetInfo),
NVFUNC(0x4706, NvHostCtrlGpu, GetTpcMasks),
NVFUNC(0x4705, NvHostCtrlGpu, GetCharacteristics),
NVFUNC(0x4714, NvHostCtrlGpu, GetActiveSlotMask)
)
};
}

View file

@ -1,172 +1,146 @@
// 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 handleDesc{core.nvMap.CreateHandle(util::AlignUp(size, PAGE_SIZE))};
if (handleDesc) {
(*handleDesc)->origSize = size; // Orig size is the unaligned size
handle = (*handleDesc)->id;
state.logger->Debug("handle: {}, size: 0x{:X}", (*handleDesc)->id, size);
}
state.logger->Warn("Handle not found for ID: 0x{:X}", data.id);
return NvStatus::BadValue;
return handleDesc;
}
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) {
state.logger->Debug("id: {}", id);
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;
// 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;
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;
auto handleDesc{core.nvMap.GetHandle(id)};
if (!handleDesc) [[unlikely]]
return PosixResult::InvalidArgument;
return handleDesc->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) {
state.logger->Debug("handle: {}, flags: ( mapUncached: {}, keepUncachedAfterFree: {} ), align: 0x{:X}, kind: {}, address: 0x{:X}", handle, flags.mapUncached, flags.keepUncachedAfterFree, align, kind, 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 handleDesc{core.nvMap.GetHandle(handle)};
if (!handleDesc) [[unlikely]]
return PosixResult::InvalidArgument;
return handleDesc->Alloc(flags, align, kind, address);
}
PosixResult NvMap::Free(In<NvMapCore::Handle::Id> handle, Out<u64> address, Out<u32> size, Out<NvMapCore::Handle::Flags> flags) {
state.logger->Debug("handle: {}", handle);
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 };
} else {
state.logger->Debug("Handle not freed");
}
return PosixResult::Success;
}
PosixResult NvMap::Param(In<NvMapCore::Handle::Id> handle, In<HandleParameterType> param, Out<u32> result) {
state.logger->Debug("handle: {}, param: {}", handle, param);
if (!handle)
return PosixResult::InvalidArgument;
auto handleDesc{core.nvMap.GetHandle(handle)};
if (!handleDesc) [[unlikely]]
return PosixResult::InvalidArgument;
switch (param) {
case HandleParameterType::Size:
result = handleDesc->origSize;
return PosixResult::Success;
case HandleParameterType::Alignment:
result = handleDesc->align;
return PosixResult::Success;
case HandleParameterType::Base:
result = -static_cast<i32>(PosixResult::InvalidArgument);
return PosixResult::Success;
case HandleParameterType::Heap:
if (handleDesc->allocated)
result = 0x40000000;
else
result = 0;
return PosixResult::Success;
case HandleParameterType::Kind:
result = handleDesc->kind;
return PosixResult::Success;
case HandleParameterType::IsSharedMemMapped:
result = handleDesc->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) {
state.logger->Debug("handle: {}", handle);
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
}
// See the comment in FromId for extra info on this function
if (!handle) [[unlikely]]
return PosixResult::InvalidArgument;
data.size = object->size;
object = nullptr;
auto handleDesc{core.nvMap.GetHandle(handle)};
if (!handleDesc) [[unlikely]]
return PosixResult::NotPermitted; // This will always return EPERM irrespective of if the handle exists or not
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 = handleDesc->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.inc"
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.inc"
}

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;
};
}

View file

@ -1,61 +1,122 @@
// 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/)
#include "driver.h"
#include "devices/nvhost_ctrl.h"
#include "devices/nvhost_ctrl_gpu.h"
#include "devices/nvmap.h"
#include "devices/nvhost_channel.h"
#include "devices/nvhost_as_gpu.h"
#include "devices/nvhost/ctrl.h"
#include "devices/nvhost/ctrl_gpu.h"
#include "devices/nvhost/gpu_channel.h"
#include "devices/nvhost/as_gpu.h"
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) {
state.logger->Debug("Opening NVDRV device ({}): {}", fdIndex, path);
NvResult Driver::OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx) {
state.logger->Debug("Opening NvDrv device ({}): {}", fd, path);
auto pathHash{util::Hash(path)};
switch (util::Hash(path)) {
#define NVDEVICE(type, name, devicePath) \
case util::Hash(devicePath): { \
std::shared_ptr<device::type> device{}; \
if (name.expired()) { \
device = std::make_shared<device::type>(state); \
name = device; \
} else { \
device = name.lock(); \
} \
devices.push_back(device); \
break; \
}
NVDEVICE_LIST
#undef NVDEVICE
#define DEVICE_SWITCH(cases) \
switch (pathHash) { \
cases; \
default: \
break; \
}
default:
throw exception("Cannot find NVDRV device");
#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)
);
}
return fdIndex++;
#undef DEVICE_CASE
#undef DEVICE_SWITCH
// Device doesn't exist/no permissions
return NvResult::FileOperationFailed;
}
std::shared_ptr<device::NvDevice> Driver::GetDevice(u32 fd) {
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) {
state.logger->Debug("fd: {}, cmd: 0x{:X}, device: {}", fd, cmd.raw, devices.at(fd)->GetName());
try {
auto item{devices.at(fd)};
if (!item)
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);
return ConvertResult(devices.at(fd)->Ioctl(cmd, buffer));
} catch (const std::out_of_range &) {
throw exception("Ioctl was called with invalid file descriptor: 0x{:X}", fd);
}
}
NvResult Driver::Ioctl2(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) {
state.logger->Debug("fd: {}, cmd: 0x{:X}, device: {}", fd, cmd.raw, devices.at(fd)->GetName());
try {
return ConvertResult(devices.at(fd)->Ioctl2(cmd, buffer, inlineBuffer));
} catch (const std::out_of_range &) {
throw exception("Ioctl2 was called with invalid file descriptor: 0x{:X}", fd);
}
}
NvResult Driver::Ioctl3(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) {
state.logger->Debug("fd: {}, cmd: 0x{:X}, device: {}", fd, cmd.raw, devices.at(fd)->GetName());
try {
return ConvertResult(devices.at(fd)->Ioctl3(cmd, buffer, inlineBuffer));
} catch (const std::out_of_range &) {
throw exception("Ioctl3 was called with invalid file descriptor: 0x{:X}", fd);
}
}
void Driver::CloseDevice(u32 fd) {
try {
auto &device{devices.at(fd)};
device.reset();
devices.at(fd).reset();
} catch (const std::out_of_range &) {
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) {
state.logger->Debug("fd: {}, eventId: 0x{:X}, device: {}", fd, eventId, devices.at(fd)->GetName());
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,55 @@
// 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/)
#pragma once
#include "devices/nvhost_syncpoint.h"
#define NVDEVICE_LIST \
NVDEVICE(NvHostCtrl, nvHostCtrl, "/dev/nvhost-ctrl") \
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")
#include <common.h>
#include "types.h"
#include "devices/nvdevice.h"
#include "core/core.h"
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 {
private:
const DeviceState &state;
std::vector<std::shared_ptr<device::NvDevice>> devices; //!< A vector of shared pointers to NvDevice object that correspond to FDs
u32 fdIndex{}; //!< The next file descriptor to assign
std::unordered_map<FileDescriptor, std::unique_ptr<device::NvDevice>> devices;
public:
NvHostSyncpoint hostSyncpoint;
#define NVDEVICE(type, name, path) std::weak_ptr<device::type> name;
NVDEVICE_LIST
#undef NVDEVICE
Core core; //!< The core global state object of nvdrv that is accessed by devices
Driver(const DeviceState &state);
/**
* @brief Open a specific device and return a FD
* @param path The path of the device to open an FD to
* @return The file descriptor to the device
* @brief Creates a new device as specified by path
* @param path The /dev path that corresponds 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
* @param fd The file descriptor to retrieve
* @return A shared pointer to the device
* @brief Calls an IOCTL on the device specified by `fd`
*/
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
* @tparam objectClass The class of the device to return
* @param fd The file descriptor to retrieve
* @return A shared pointer to the device
* @brief Calls an IOCTL on the device specified by `fd` using the given inline input buffer
*/
template<typename objectClass>
std::shared_ptr<objectClass> GetDevice(u32 fd) {
return std::static_pointer_cast<objectClass>(GetDevice(fd));
}
NvResult Ioctl2(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Closes the specified device with its file descriptor
* @brief Calls an IOCTL on the device specified by `fd` using the given inline output buffer
*/
NvResult Ioctl3(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer);
/**
* @brief Queries a KEvent for the given `eventId` for the device specified by `fd`
*/
std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 fd, u32 eventId);
/**
* @brief Closes the device specified by `fd`
*/
void CloseDevice(u32 fd);
};
extern std::weak_ptr<Driver> driver; //!< A globally shared instance of the Driver
}

View file

@ -0,0 +1,90 @@
// 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};
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;
};
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 A 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
};
template<typename ValueType>
using NvResultValue = ResultValue<ValueType, NvResult>;
}

View file

@ -17,7 +17,8 @@
#include "glue/IStaticService.h"
#include "services/timesrv/core.h"
#include "fssrv/IFileSystemProxy.h"
#include "services/nvdrv/INvDrvServices.h"
#include "nvdrv/INvDrvServices.h"
#include "nvdrv/driver.h"
#include "hosbinder/IHOSBinderDriver.h"
#include "visrv/IApplicationRootService.h"
#include "visrv/ISystemRootService.h"
@ -46,8 +47,9 @@
namespace skyline::service {
struct GlobalServiceState {
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)) {}
@ -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:u", globalServiceState->timesrv.managerServer.GetStaticServiceAsUser(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions)
SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:a")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:s")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:t")
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv")
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv", globalServiceState->nvdrv, nvdrv::ApplicationSessionPermissions)
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv", globalServiceState->nvdrv.core.nvMap)
SERVICE_CASE(visrv::IApplicationRootService, "vi:u")
SERVICE_CASE(visrv::ISystemRootService, "vi:s")
SERVICE_CASE(visrv::IManagerRootService, "vi:m")

View file

@ -3,7 +3,6 @@
#pragma once
#include "soc/gmmu.h"
#include "soc/host1x.h"
#include "soc/gm20b.h"
@ -14,10 +13,9 @@ namespace skyline::soc {
*/
class SOC {
public:
gmmu::GraphicsMemoryManager gmmu;
host1x::Host1X host1x;
gm20b::GM20B gm20b;
SOC(const DeviceState &state) : gmmu(state), gm20b(state) {}
SOC(const DeviceState &state) : gm20b(state) {}
};
}

View file

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <common/address_space.inc>
#include "gm20b.h"
namespace skyline {
template class FlatAddressSpaceMap<u64, 0, u8 *, nullptr, true, soc::gm20b::GM20B::AddressSpaceBits>;
template class FlatMemoryManager<u64, 0, soc::gm20b::GM20B::AddressSpaceBits>;
}
namespace skyline::soc::gm20b {
GM20B::GM20B(const DeviceState &state) :
fermi2D(state),
keplerMemory(state),
maxwell3D(state),
maxwellCompute(state),
maxwellDma(state),
gpfifo(state) {}
}

View file

@ -3,23 +3,28 @@
#pragma once
#include <common/address_space.h>
#include "gm20b/engines/maxwell_3d.h"
#include "gm20b/gpfifo.h"
namespace skyline::soc::gm20b {
/**
* @brief The GPU block in the X1, it contains all GPU engines required for accelerating graphics operations
* @note We omit parts of components related to external access such as the GM20B Host, all accesses to the external components are done directly
* @note We omit parts of components related to external access such as the grhost, all accesses to the external components are done directly
*/
class GM20B {
public:
static constexpr u8 AddressSpaceBits{40}; //!< The width of the GMMU AS
using GMMU = FlatMemoryManager<u64, 0, AddressSpaceBits>;
engine::Engine fermi2D;
engine::maxwell3d::Maxwell3D maxwell3D;
engine::Engine maxwellCompute;
engine::Engine maxwellDma;
engine::Engine keplerMemory;
GPFIFO gpfifo;
GMMU gmmu;
GM20B(const DeviceState &state) : fermi2D(state), keplerMemory(state), maxwell3D(state), maxwellCompute(state), maxwellDma(state), gpfifo(state) {}
GM20B(const DeviceState &state);
};
}

View file

@ -16,16 +16,6 @@ namespace skyline::soc::gm20b {
MaxwellDma = 0xB0B5,
};
/**
* @brief The parameters of a GPU engine method call
*/
struct MethodParams {
u16 method;
u32 argument;
u32 subChannel;
bool lastCall; //!< If this is the last call in the pushbuffer entry to this specific macro
};
namespace engine {
/**
* @brief The Engine class provides an interface that can be used to communicate with the GPU's internal engines
@ -37,13 +27,11 @@ namespace skyline::soc::gm20b {
public:
Engine(const DeviceState &state) : state(state) {}
virtual ~Engine() = default;
/**
* @brief Calls an engine method with the given parameters
*/
virtual void CallMethod(MethodParams params) {
state.logger->Warn("Called method in unimplemented engine: 0x{:X} args: 0x{:X}", params.method, params.argument);
void CallMethod(u32 method, u32 argument, bool lastCall) {
state.logger->Warn("Called method in unimplemented engine: 0x{:X} args: 0x{:X}", method, argument);
};
};
}

View file

@ -167,10 +167,10 @@ namespace skyline::soc::gm20b::engine {
public:
GPFIFO(const DeviceState &state) : Engine(state) {}
void CallMethod(MethodParams params) override {
state.logger->Debug("Called method in GPFIFO: 0x{:X} args: 0x{:X}", params.method, params.argument);
void CallMethod(u32 method, u32 argument, bool lastCall) {
state.logger->Debug("Called method in GPFIFO: 0x{:X} args: 0x{:X}", method, argument);
registers.raw[params.method] = params.argument;
registers.raw[method] = argument;
};
};
}

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <soc/gmmu.h>
#include <common/address_space.h>
#include <soc/gm20b/engines/maxwell_3d.h>
namespace skyline::soc::gm20b::engine::maxwell3d {
@ -193,7 +193,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
}
FORCE_INLINE void MacroInterpreter::Send(u32 pArgument) {
maxwell3D.CallMethod(MethodParams{methodAddress.address, pArgument, 0, true});
maxwell3D.CallMethod(methodAddress.address, pArgument, true);
methodAddress.address += methodAddress.increment;
}

View file

@ -72,48 +72,62 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
registers.viewportTransformEnable = true;
}
void Maxwell3D::CallMethod(MethodParams params) {
state.logger->Debug("Called method in Maxwell 3D: 0x{:X} args: 0x{:X}", params.method, params.argument);
void Maxwell3D::CallMethod(u32 method, u32 argument, bool lastCall) {
state.logger->Debug("Called method in Maxwell 3D: 0x{:X} args: 0x{:X}", method, argument);
// Methods that are greater than the register size are for macro control
if (params.method > RegisterCount) {
if (!(params.method & 1))
macroInvocation.index = ((params.method - RegisterCount) >> 1) % macroPositions.size();
if (method > RegisterCount) [[unlikely]] {
// Starting a new macro at index 'method - RegisterCount'
if (!(method & 1)) {
if (macroInvocation.index != -1) {
// Flush the current macro as we are switching to another one
macroInterpreter.Execute(macroPositions[macroInvocation.index], macroInvocation.arguments);
macroInvocation.arguments.clear();
}
macroInvocation.arguments.push_back(params.argument);
// Macros are always executed on the last method call in a pushbuffer entry
if (params.lastCall) {
macroInterpreter.Execute(macroPositions[macroInvocation.index], macroInvocation.arguments);
macroInvocation.arguments.clear();
macroInvocation.index = 0;
// Setup for the new macro index
macroInvocation.index = ((method - RegisterCount) >> 1) % macroPositions.size();
}
macroInvocation.arguments.emplace_back(argument);
// Flush macro after all of the data in the method call has been sent
if (lastCall && macroInvocation.index != -1) {
macroInterpreter.Execute(macroPositions[macroInvocation.index], macroInvocation.arguments);
macroInvocation.arguments.clear();
macroInvocation.index = -1;
}
// Bail out early
return;
}
registers.raw[params.method] = params.argument;
registers.raw[method] = argument;
if (shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodTrack || shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodTrackWithFilter)
shadowRegisters.raw[params.method] = params.argument;
shadowRegisters.raw[method] = argument;
else if (shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodReplay)
params.argument = shadowRegisters.raw[params.method];
argument = shadowRegisters.raw[method];
switch (params.method) {
switch (method) {
case MAXWELL3D_OFFSET(mme.instructionRamLoad):
if (registers.mme.instructionRamPointer >= macroCode.size())
throw exception("Macro memory is full!");
macroCode[registers.mme.instructionRamPointer++] = params.argument;
macroCode[registers.mme.instructionRamPointer++] = argument;
// Wraparound writes
registers.mme.instructionRamPointer %= macroCode.size();
break;
case MAXWELL3D_OFFSET(mme.startAddressRamLoad):
if (registers.mme.startAddressRamPointer >= macroPositions.size())
throw exception("Maximum amount of macros reached!");
macroPositions[registers.mme.startAddressRamPointer++] = params.argument;
macroPositions[registers.mme.startAddressRamPointer++] = argument;
break;
case MAXWELL3D_OFFSET(mme.shadowRamControl):
shadowRegisters.mme.shadowRamControl = static_cast<Registers::MmeShadowRamControl>(params.argument);
shadowRegisters.mme.shadowRamControl = static_cast<Registers::MmeShadowRamControl>(argument);
break;
case MAXWELL3D_OFFSET(syncpointAction):
state.logger->Debug("Increment syncpoint: {}", static_cast<u16>(registers.syncpointAction.id));
@ -135,6 +149,8 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
case MAXWELL3D_OFFSET(firmwareCall[4]):
registers.raw[0xD00] = 1;
break;
default:
break;
}
}
@ -157,7 +173,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
switch (registers.semaphore.info.structureSize) {
case Registers::SemaphoreInfo::StructureSize::OneWord:
state.soc->gmmu.Write<u32>(static_cast<u32>(result), registers.semaphore.address.Pack());
state.soc->gm20b.gmmu.Write<u32>(registers.semaphore.address.Pack(), static_cast<u32>(result));
break;
case Registers::SemaphoreInfo::StructureSize::FourWords: {
// Convert the current nanosecond time to GPU ticks
@ -167,7 +183,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
u64 nsTime{util::GetTimeNs()};
u64 timestamp{(nsTime / NsToTickDenominator) * NsToTickNumerator + ((nsTime % NsToTickDenominator) * NsToTickNumerator) / NsToTickDenominator};
state.soc->gmmu.Write<FourWordResult>(FourWordResult{result, timestamp}, registers.semaphore.address.Pack());
state.soc->gm20b.gmmu.Write<FourWordResult>(registers.semaphore.address.Pack(), FourWordResult{result, timestamp});
break;
}
}

View file

@ -17,7 +17,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
std::array<size_t, 0x80> macroPositions{}; //!< The positions of each individual macro in macro memory, there can be a maximum of 0x80 macros at any one time
struct {
u32 index;
i32 index{-1};
std::vector<u32> arguments;
} macroInvocation{}; //!< Data for a macro that is pending execution
@ -557,7 +557,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
Registers registers{};
Registers shadowRegisters{}; //!< The shadow registers, their function is controlled by the 'shadowRamControl' register
std::array<u32, 0x10000> macroCode{}; //!< This stores GPU macros, the 256KiB size is from Ryujinx
std::array<u32, 0x2000> macroCode{}; //!< This stores GPU macros, writes to it will wraparound on overflow
Maxwell3D(const DeviceState &state);
@ -566,6 +566,6 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
*/
void ResetRegs();
void CallMethod(MethodParams params) override;
void CallMethod(u32 method, u32 argument, bool lastCall);
};
}

View file

@ -5,41 +5,90 @@
#include <loader/loader.h>
#include <kernel/types/KProcess.h>
#include <soc.h>
#include <os.h>
namespace skyline::soc::gm20b {
void GPFIFO::Send(MethodParams params) {
state.logger->Debug("Called GPU method - method: 0x{:X} argument: 0x{:X} subchannel: 0x{:X} last: {}", params.method, params.argument, params.subChannel, params.lastCall);
/**
* @brief A single pushbuffer method header that describes a compressed method sequence
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_ram.ref.txt#L850
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/clb06f.h#L179
*/
union PushBufferMethodHeader {
u32 raw;
if (params.method == 0) {
switch (static_cast<EngineID>(params.argument)) {
case EngineID::Fermi2D:
subchannels.at(params.subChannel) = &state.soc->gm20b.fermi2D;
enum class TertOp : u8 {
Grp0IncMethod = 0,
Grp0SetSubDevMask = 1,
Grp0StoreSubDevMask = 2,
Grp0UseSubDevMask = 3,
Grp2NonIncMethod = 0,
};
enum class SecOp : u8 {
Grp0UseTert = 0,
IncMethod = 1,
Grp2UseTert = 2,
NonIncMethod = 3,
ImmdDataMethod = 4,
OneInc = 5,
Reserved6 = 6,
EndPbSegment = 7,
};
u16 methodAddress : 12;
struct {
u8 _pad0_ : 4;
u16 subDeviceMask : 12;
};
struct {
u16 _pad1_ : 13;
u8 methodSubChannel : 3;
union {
TertOp tertOp : 3;
u16 methodCount : 13;
u16 immdData : 13;
};
};
struct {
u32 _pad2_ : 29;
SecOp secOp : 3;
};
};
static_assert(sizeof(PushBufferMethodHeader) == sizeof(u32));
void GPFIFO::Send(u32 method, u32 argument, u32 subChannel, bool lastCall) {
constexpr u32 ThreeDSubChannel{0};
constexpr u32 ComputeSubChannel{1};
constexpr u32 Inline2MemorySubChannel{2};
constexpr u32 TwoDSubChannel{3};
constexpr u32 CopySubChannel{4}; // HW forces a memory flush on a switch from this subchannel to others
state.logger->Debug("Called GPU method - method: 0x{:X} argument: 0x{:X} subchannel: 0x{:X} last: {}", method, argument, subChannel, lastCall);
if (method < engine::GPFIFO::RegisterCount) {
gpfifoEngine.CallMethod(method, argument, lastCall);
} else {
switch (subChannel) {
case ThreeDSubChannel:
state.soc->gm20b.maxwell3D.CallMethod(method, argument, lastCall);
break;
case EngineID::KeplerMemory:
subchannels.at(params.subChannel) = &state.soc->gm20b.keplerMemory;
case ComputeSubChannel:
state.soc->gm20b.maxwellCompute.CallMethod(method, argument, lastCall);
break;
case EngineID::Maxwell3D:
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwell3D;
case Inline2MemorySubChannel:
state.soc->gm20b.keplerMemory.CallMethod(method, argument, lastCall);
break;
case EngineID::MaxwellCompute:
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwellCompute;
case TwoDSubChannel:
state.soc->gm20b.fermi2D.CallMethod(method, argument, lastCall);
break;
case EngineID::MaxwellDma:
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwellDma;
case CopySubChannel:
state.soc->gm20b.maxwellDma.CallMethod(method, argument, lastCall);
break;
default:
throw exception("Unknown engine 0x{:X} cannot be bound to subchannel {}", params.argument, params.subChannel);
throw exception("Tried to call into a software subchannel: {}!", subChannel);
}
state.logger->Info("Bound GPU engine 0x{:X} to subchannel {}", params.argument, params.subChannel);
return;
} else if (params.method < engine::GPFIFO::RegisterCount) {
gpfifoEngine.CallMethod(params);
} else {
if (subchannels.at(params.subChannel) == nullptr)
throw exception("Calling method on unbound channel");
subchannels.at(params.subChannel)->CallMethod(params);
}
}
@ -56,7 +105,7 @@ namespace skyline::soc::gm20b {
}
pushBufferData.resize(gpEntry.size);
state.soc->gmmu.Read<u32>(pushBufferData, gpEntry.Address());
state.soc->gm20b.gmmu.Read<u32>(pushBufferData, gpEntry.Address());
for (auto entry{pushBufferData.begin()}; entry != pushBufferData.end(); entry++) {
// An entry containing all zeroes is a NOP, skip over it
@ -66,30 +115,29 @@ namespace skyline::soc::gm20b {
PushBufferMethodHeader methodHeader{.raw = *entry};
switch (methodHeader.secOp) {
case PushBufferMethodHeader::SecOp::IncMethod:
for (u16 i{}; i < methodHeader.methodCount; i++)
Send(MethodParams{static_cast<u16>(methodHeader.methodAddress + i), *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
for (u32 i{}; i < methodHeader.methodCount; i++)
Send(methodHeader.methodAddress + i, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
break;
case PushBufferMethodHeader::SecOp::NonIncMethod:
for (u16 i{}; i < methodHeader.methodCount; i++)
Send(MethodParams{methodHeader.methodAddress, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
for (u32 i{}; i < methodHeader.methodCount; i++)
Send(methodHeader.methodAddress, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
break;
case PushBufferMethodHeader::SecOp::OneInc:
for (u16 i{}; i < methodHeader.methodCount; i++)
Send(MethodParams{static_cast<u16>(methodHeader.methodAddress + static_cast<bool>(i)), *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
for (u32 i{}; i < methodHeader.methodCount; i++)
Send(methodHeader.methodAddress + !!i, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
break;
case PushBufferMethodHeader::SecOp::ImmdDataMethod:
Send(MethodParams{methodHeader.methodAddress, methodHeader.immdData, methodHeader.methodSubChannel, true});
Send(methodHeader.methodAddress, methodHeader.immdData, methodHeader.methodSubChannel, true);
break;
case PushBufferMethodHeader::SecOp::EndPbSegment:
return;
default:
state.logger->Warn("Unsupported pushbuffer method SecOp: {}", static_cast<u8>(methodHeader.secOp));
break;
throw exception("Unsupported pushbuffer method SecOp: {}", static_cast<u8>(methodHeader.secOp));
}
}
}
@ -106,7 +154,7 @@ namespace skyline::soc::gm20b {
try {
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler);
pushBuffers->Process([this](GpEntry gpEntry) {
state.logger->Debug("Processing pushbuffer: 0x{:X}", gpEntry.Address());
state.logger->Warn("Processing pushbuffer: 0x{:X}", gpEntry.Address());
Process(gpEntry);
});
} catch (const signal::SignalException &e) {

View file

@ -72,56 +72,6 @@ namespace skyline::soc::gm20b {
};
static_assert(sizeof(GpEntry) == sizeof(u64));
/**
* @brief A single pushbuffer method header that describes a compressed method sequence
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_ram.ref.txt#L850
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/clb06f.h#L179
*/
union PushBufferMethodHeader {
u32 raw;
enum class TertOp : u8 {
Grp0IncMethod = 0,
Grp0SetSubDevMask = 1,
Grp0StoreSubDevMask = 2,
Grp0UseSubDevMask = 3,
Grp2NonIncMethod = 0,
};
enum class SecOp : u8 {
Grp0UseTert = 0,
IncMethod = 1,
Grp2UseTert = 2,
NonIncMethod = 3,
ImmdDataMethod = 4,
OneInc = 5,
Reserved6 = 6,
EndPbSegment = 7,
};
u16 methodAddress : 12;
struct {
u8 _pad0_ : 4;
u16 subDeviceMask : 12;
};
struct {
u16 _pad1_ : 13;
u8 methodSubChannel : 3;
union {
TertOp tertOp : 3;
u16 methodCount : 13;
u16 immdData : 13;
};
};
struct {
u32 _pad2_ : 29;
SecOp secOp : 3;
};
};
static_assert(sizeof(PushBufferMethodHeader) == sizeof(u32));
/**
* @brief The GPFIFO class handles creating pushbuffers from GP entries and then processing them
* @note This class doesn't perfectly map to any particular hardware component on the X1, it does a mix of the GPU Host PBDMA (With and handling the GPFIFO entries
@ -138,7 +88,7 @@ namespace skyline::soc::gm20b {
/**
* @brief Sends a method call to the GPU hardware
*/
void Send(MethodParams params);
void Send(u32 method, u32 argument, u32 subchannel, bool lastCall);
/**
* @brief Processes the pushbuffer contained within the given GpEntry, calling methods as needed

View file

@ -1,214 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#include <kernel/types/KProcess.h>
#include "gmmu.h"
namespace skyline::soc::gmmu {
constexpr u64 GpuPageSize{1 << 16}; //!< The page size of the GPU address space
GraphicsMemoryManager::GraphicsMemoryManager(const DeviceState &state) : state(state) {
constexpr u64 gpuAddressSpaceSize{1UL << 40}; //!< The size of the GPU address space
constexpr u64 gpuAddressSpaceBase{0x100000}; //!< The base of the GPU address space - must be non-zero
// Create the initial chunk that will be split to create new chunks
ChunkDescriptor baseChunk(gpuAddressSpaceBase, gpuAddressSpaceSize, nullptr, ChunkState::Unmapped);
chunks.push_back(baseChunk);
}
std::optional<ChunkDescriptor> GraphicsMemoryManager::FindChunk(ChunkState desiredState, u64 size, u64 alignment) {
auto chunk{std::find_if(chunks.begin(), chunks.end(), [desiredState, size, alignment](const ChunkDescriptor &chunk) -> bool {
return (alignment ? util::IsAligned(chunk.virtualAddress, alignment) : true) && chunk.size > size && chunk.state == desiredState;
})};
if (chunk != chunks.end())
return *chunk;
return std::nullopt;
}
u64 GraphicsMemoryManager::InsertChunk(const ChunkDescriptor &newChunk) {
auto chunkEnd{chunks.end()};
for (auto chunk{chunks.begin()}; chunk != chunkEnd; chunk++) {
if (chunk->CanContain(newChunk)) {
auto oldChunk{*chunk};
u64 newSize{newChunk.virtualAddress - chunk->virtualAddress};
u64 extension{chunk->size - newSize - newChunk.size};
if (newSize == 0) {
*chunk = newChunk;
} else {
chunk->size = newSize;
chunk = chunks.insert(std::next(chunk), newChunk);
}
if (extension)
chunks.insert(std::next(chunk), ChunkDescriptor(newChunk.virtualAddress + newChunk.size, extension, (oldChunk.state == ChunkState::Mapped) ? (oldChunk.cpuPtr + newSize + newChunk.size) : nullptr, oldChunk.state));
return newChunk.virtualAddress;
} else if (chunk->virtualAddress + chunk->size > newChunk.virtualAddress) {
chunk->size = newChunk.virtualAddress - chunk->virtualAddress;
// Deletes all chunks that are within the chunk being inserted and split the final one
auto tailChunk{std::next(chunk)};
while (tailChunk != chunkEnd) {
if (tailChunk->virtualAddress + tailChunk->size >= newChunk.virtualAddress + newChunk.size)
break;
tailChunk = chunks.erase(tailChunk);
chunkEnd = chunks.end();
}
// The given chunk is too large to fit into existing chunks
if (tailChunk == chunkEnd)
break;
u64 chunkSliceOffset{newChunk.virtualAddress + newChunk.size - tailChunk->virtualAddress};
tailChunk->virtualAddress += chunkSliceOffset;
tailChunk->size -= chunkSliceOffset;
if (tailChunk->state == ChunkState::Mapped)
tailChunk->cpuPtr += chunkSliceOffset;
// If the size of the head chunk is zero then we can directly replace it with our new one rather than inserting it
auto headChunk{std::prev(tailChunk)};
if (headChunk->size == 0)
*headChunk = newChunk;
else
chunks.insert(std::next(headChunk), newChunk);
return newChunk.virtualAddress;
}
}
throw exception("Failed to insert chunk into GPU address space!");
}
u64 GraphicsMemoryManager::ReserveSpace(u64 size, u64 alignment) {
size = util::AlignUp(size, GpuPageSize);
std::unique_lock lock(mutex);
auto newChunk{FindChunk(ChunkState::Unmapped, size, alignment)};
if (!newChunk) [[unlikely]]
return 0;
auto chunk{*newChunk};
chunk.size = size;
chunk.state = ChunkState::Reserved;
return InsertChunk(chunk);
}
u64 GraphicsMemoryManager::ReserveFixed(u64 virtualAddress, u64 size) {
if (!util::IsAligned(virtualAddress, GpuPageSize)) [[unlikely]]
return 0;
size = util::AlignUp(size, GpuPageSize);
std::unique_lock lock(mutex);
return InsertChunk(ChunkDescriptor(virtualAddress, size, nullptr, ChunkState::Reserved));
}
u64 GraphicsMemoryManager::MapAllocate(u8 *cpuPtr, u64 size) {
size = util::AlignUp(size, GpuPageSize);
std::unique_lock lock(mutex);
auto mappedChunk{FindChunk(ChunkState::Unmapped, size)};
if (!mappedChunk) [[unlikely]]
return 0;
auto chunk{*mappedChunk};
chunk.cpuPtr = cpuPtr;
chunk.size = size;
chunk.state = ChunkState::Mapped;
return InsertChunk(chunk);
}
u64 GraphicsMemoryManager::MapFixed(u64 virtualAddress, u8 *cpuPtr, u64 size) {
if (!util::IsAligned(virtualAddress, GpuPageSize)) [[unlikely]]
return 0;
size = util::AlignUp(size, GpuPageSize);
std::unique_lock lock(mutex);
return InsertChunk(ChunkDescriptor(virtualAddress, size, cpuPtr, ChunkState::Mapped));
}
bool GraphicsMemoryManager::Unmap(u64 virtualAddress, u64 size) {
if (!util::IsAligned(virtualAddress, GpuPageSize)) [[unlikely]]
return false;
try {
std::unique_lock lock(mutex);
InsertChunk(ChunkDescriptor(virtualAddress, size, nullptr, ChunkState::Unmapped));
} catch (const std::exception &e) {
return false;
}
return true;
}
void GraphicsMemoryManager::Read(u8 *destination, u64 virtualAddress, u64 size) {
std::shared_lock lock(mutex);
auto chunk{std::upper_bound(chunks.begin(), chunks.end(), virtualAddress, [](const u64 address, const ChunkDescriptor &chunk) -> bool {
return address < chunk.virtualAddress;
})};
if (chunk == chunks.end() || chunk->state != ChunkState::Mapped)
throw exception("Failed to read region in GPU address space: Address: 0x{:X}, Size: 0x{:X}", virtualAddress, size);
chunk--;
u64 initialSize{size};
u64 chunkOffset{virtualAddress - chunk->virtualAddress};
u8 *source{chunk->cpuPtr + chunkOffset};
u64 sourceSize{std::min(chunk->size - chunkOffset, size)};
// A continuous region in the GPU address space may be made up of several discontinuous regions in physical memory so we have to iterate over all chunks
while (size) {
std::memcpy(destination + (initialSize - size), source, sourceSize);
size -= sourceSize;
if (size) {
if (++chunk == chunks.end() || chunk->state != ChunkState::Mapped)
throw exception("Failed to read region in GPU address space: Address: 0x{:X}, Size: 0x{:X}", virtualAddress, size);
source = chunk->cpuPtr;
sourceSize = std::min(chunk->size, size);
}
}
}
void GraphicsMemoryManager::Write(u8 *source, u64 virtualAddress, u64 size) {
std::shared_lock lock(mutex);
auto chunk{std::upper_bound(chunks.begin(), chunks.end(), virtualAddress, [](const u64 address, const ChunkDescriptor &chunk) -> bool {
return address < chunk.virtualAddress;
})};
if (chunk == chunks.end() || chunk->state != ChunkState::Mapped)
throw exception("Failed to write region in GPU address space: Address: 0x{:X}, Size: 0x{:X}", virtualAddress, size);
chunk--;
u64 initialSize{size};
u64 chunkOffset{virtualAddress - chunk->virtualAddress};
u8 *destination{chunk->cpuPtr + chunkOffset};
u64 destinationSize{std::min(chunk->size - chunkOffset, size)};
// A continuous region in the GPU address space may be made up of several discontinuous regions in physical memory so we have to iterate over all chunks
while (size) {
std::memcpy(destination, source + (initialSize - size), destinationSize);
size -= destinationSize;
if (size) {
if (++chunk == chunks.end() || chunk->state != ChunkState::Mapped)
throw exception("Failed to write region in GPU address space: Address: 0x{:X}, Size: 0x{:X}", virtualAddress, size);
destination = chunk->cpuPtr;
destinationSize = std::min(chunk->size, size);
}
}
}
}

View file

@ -1,140 +0,0 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
#pragma once
#include <common.h>
namespace skyline::soc::gmmu {
enum class ChunkState {
Unmapped, //!< The chunk is unmapped
Reserved, //!< The chunk is reserved
Mapped //!< The chunk is mapped and a CPU side address is present
};
struct ChunkDescriptor {
u64 virtualAddress; //!< The address of the chunk in the virtual address space
u64 size; //!< The size of the chunk in bytes
u8 *cpuPtr; //!< A pointer to the chunk in the application's address space (if mapped)
ChunkState state;
ChunkDescriptor(u64 virtualAddress, u64 size, u8 *cpuPtr, ChunkState state) : virtualAddress(virtualAddress), size(size), cpuPtr(cpuPtr), state(state) {}
/**
* @return If the given chunk can be contained wholly within this chunk
*/
inline bool CanContain(const ChunkDescriptor &chunk) {
return (chunk.virtualAddress >= virtualAddress) && ((size + virtualAddress) >= (chunk.size + chunk.virtualAddress));
}
};
/**
* @brief The GraphicsMemoryManager class handles mapping between a Maxwell GPU virtual address space and an application's address space and is meant to roughly emulate the GMMU on the X1
* @note This is not accurate to the X1 as it would have an SMMU between the GMMU and physical memory but we don't emulate this abstraction at the moment
*/
class GraphicsMemoryManager {
private:
const DeviceState &state;
std::vector<ChunkDescriptor> chunks;
std::shared_mutex mutex;
/**
* @brief Finds a chunk in the virtual address space that is larger than meets the given requirements
* @note vmmMutex MUST be locked when calling this
* @param desiredState The state of the chunk to find
* @param size The minimum size of the chunk to find
* @param alignment The minimum alignment of the chunk to find
* @return The first applicable chunk
*/
std::optional<ChunkDescriptor> FindChunk(ChunkState desiredState, u64 size, u64 alignment = 0);
/**
* @brief Inserts a chunk into the chunk list, resizing and splitting as necessary
* @note vmmMutex MUST be locked when calling this
* @param newChunk The chunk to insert
* @return The base virtual address of the inserted chunk
*/
u64 InsertChunk(const ChunkDescriptor &newChunk);
public:
GraphicsMemoryManager(const DeviceState &state);
/**
* @brief Reserves a region of the virtual address space so it will not be chosen automatically when mapping
* @param size The size of the region to reserve
* @param alignment The alignment of the region to reserve
* @return The base virtual address of the reserved region
*/
u64 ReserveSpace(u64 size, u64 alignment);
/**
* @brief Reserves a fixed region of the virtual address space so it will not be chosen automatically when mapping
* @param virtualAddress The virtual base address of the region to allocate
* @param size The size of the region to allocate
* @return The base virtual address of the reserved region
*/
u64 ReserveFixed(u64 virtualAddress, u64 size);
/**
* @brief Maps a CPU memory region into an automatically chosen region of the virtual address space
* @param cpuPtr A pointer to the region to be mapped into the virtual address space
* @param size The size of the region to map
* @return The base virtual address of the mapped region
*/
u64 MapAllocate(u8 *cpuPtr, u64 size);
/**
* @brief Maps a CPU memory region to a fixed region in the virtual address space
* @param virtualAddress The target virtual address of the region
* @param cpuPtr A pointer to the region to be mapped into the virtual address space
* @param size The size of the region to map
* @return The base virtual address of the mapped region
*/
u64 MapFixed(u64 virtualAddress, u8 *cpuPtr, u64 size);
/**
* @brief Unmaps all chunks in the given region from the virtual address space
* @return Whether the operation succeeded
*/
bool Unmap(u64 virtualAddress, u64 size);
void Read(u8 *destination, u64 virtualAddress, u64 size);
/**
* @brief Reads in a span from a region of the virtual address space
*/
template<typename T>
void Read(span <T> destination, u64 virtualAddress) {
Read(reinterpret_cast<u8 *>(destination.data()), virtualAddress, destination.size_bytes());
}
/**
* @brief Reads in an object from a region of the virtual address space
* @tparam T The type of object to return
*/
template<typename T>
T Read(u64 virtualAddress) {
T obj;
Read(reinterpret_cast<u8 *>(&obj), virtualAddress, sizeof(T));
return obj;
}
void Write(u8 *source, u64 virtualAddress, u64 size);
/**
* @brief Writes out a span to a region of the virtual address space
*/
template<typename T>
void Write(span <T> source, u64 virtualAddress) {
Write(reinterpret_cast<u8 *>(source.data()), virtualAddress, source.size_bytes());
}
/**
* @brief Reads in an object from a region of the virtual address space
*/
template<typename T>
void Write(T source, u64 virtualAddress) {
Write(reinterpret_cast<u8 *>(&source), virtualAddress, sizeof(T));
}
};
}

View file

@ -3,6 +3,7 @@
#pragma once
#include <concepts>
#include <common.h>
namespace skyline::vfs {
@ -82,7 +83,7 @@ namespace skyline::vfs {
/**
* @brief Implicit casting for reading into spans of different types
*/
template<typename T, typename std::enable_if<!std::is_same_v<T, u8>, bool>::type = true>
template<typename T> requires (!std::same_as<T, u8>)
size_t Read(span <T> output, size_t offset = 0) {
return Read(output.template cast<u8>(), offset);
}