Compare commits
21 commits
ftx1
...
nvdrv-gpu-
Author | SHA1 | Date | |
---|---|---|---|
|
c9cafb394a | ||
|
a0b4710282 | ||
|
b57710f260 | ||
|
4a7700bd7c | ||
|
83eb88d78b | ||
|
67149ef7fb | ||
|
33f8be6f52 | ||
|
d094cc142d | ||
|
b9098ac14a | ||
|
7468003381 | ||
|
abaca6d40e | ||
|
6c6abd9a51 | ||
|
a513f84fff | ||
|
054a7aa91e | ||
|
5123be6604 | ||
|
d159605caf | ||
|
f6a7ccf7eb | ||
|
01d58dee27 | ||
|
b5b86f41a3 | ||
|
f1bbf06cd8 | ||
|
85c48d0e7e |
64 changed files with 3424 additions and 2150 deletions
|
@ -96,7 +96,7 @@ add_library(skyline SHARED
|
||||||
${source_DIR}/skyline/gpu/command_scheduler.cpp
|
${source_DIR}/skyline/gpu/command_scheduler.cpp
|
||||||
${source_DIR}/skyline/gpu/texture/texture.cpp
|
${source_DIR}/skyline/gpu/texture/texture.cpp
|
||||||
${source_DIR}/skyline/gpu/presentation_engine.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/host1x/syncpoint.cpp
|
||||||
${source_DIR}/skyline/soc/gm20b/gpfifo.cpp
|
${source_DIR}/skyline/soc/gm20b/gpfifo.cpp
|
||||||
${source_DIR}/skyline/soc/gm20b/engines/maxwell_3d.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/fssrv/IDirectory.cpp
|
||||||
${source_DIR}/skyline/services/nvdrv/INvDrvServices.cpp
|
${source_DIR}/skyline/services/nvdrv/INvDrvServices.cpp
|
||||||
${source_DIR}/skyline/services/nvdrv/driver.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/nvdevice.cpp
|
||||||
${source_DIR}/skyline/services/nvdrv/devices/nvmap.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/as_gpu.cpp
|
||||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_ctrl.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/ctrl_gpu.cpp
|
||||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp
|
${source_DIR}/skyline/services/nvdrv/devices/nvhost/gpu_channel.cpp
|
||||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp
|
|
||||||
${source_DIR}/skyline/services/hosbinder/parcel.cpp
|
${source_DIR}/skyline/services/hosbinder/parcel.cpp
|
||||||
${source_DIR}/skyline/services/hosbinder/IHOSBinderDriver.cpp
|
${source_DIR}/skyline/services/hosbinder/IHOSBinderDriver.cpp
|
||||||
${source_DIR}/skyline/services/hosbinder/GraphicBufferProducer.cpp
|
${source_DIR}/skyline/services/hosbinder/GraphicBufferProducer.cpp
|
||||||
|
|
|
@ -83,26 +83,25 @@ namespace skyline {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A wrapper around std::optional that also stores a HOS result code
|
* @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 {
|
class ResultValue {
|
||||||
static_assert(!std::is_same<T, Result>::value);
|
static_assert(!std::is_same<ValueType, ResultType>::value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<T> value;
|
std::optional<ValueType> value;
|
||||||
|
|
||||||
public:
|
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>
|
template<typename U>
|
||||||
constexpr ResultValue(ResultValue<U> result) : result(result) {};
|
constexpr ResultValue(ResultValue<U> result) : result(result) {};
|
||||||
|
|
||||||
constexpr operator Result() const {
|
constexpr operator ResultType() const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,11 +109,11 @@ namespace skyline {
|
||||||
return value.has_value();
|
return value.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr T& operator*() {
|
constexpr ValueType& operator*() {
|
||||||
return *value;
|
return *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr T* operator->() {
|
constexpr ValueType* operator->() {
|
||||||
return &*value;
|
return &*value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -362,6 +361,9 @@ namespace skyline {
|
||||||
|
|
||||||
template<typename Out>
|
template<typename Out>
|
||||||
constexpr Out &as() {
|
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))
|
if (span::size_bytes() >= sizeof(Out))
|
||||||
return *reinterpret_cast<Out *>(span::data());
|
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));
|
throw exception("Span size is less than Out type size (0x{:X}/0x{:X})", span::size_bytes(), sizeof(Out));
|
||||||
|
|
179
app/src/main/cpp/skyline/common/address_space.h
Normal file
179
app/src/main/cpp/skyline/common/address_space.h
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
423
app/src/main/cpp/skyline/common/address_space.inc
Normal file
423
app/src/main/cpp/skyline/common/address_space.inc
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <common/trace.h>
|
||||||
#include <common.h>
|
#include <common.h>
|
||||||
|
|
||||||
namespace skyline {
|
namespace skyline {
|
||||||
|
@ -51,10 +52,15 @@ namespace skyline {
|
||||||
*/
|
*/
|
||||||
template<typename F>
|
template<typename F>
|
||||||
[[noreturn]] void Process(F function) {
|
[[noreturn]] void Process(F function) {
|
||||||
|
TRACE_EVENT_BEGIN("containers", "CircularQueue::Process");
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (start == end) {
|
if (start == end) {
|
||||||
std::unique_lock lock(productionMutex);
|
std::unique_lock lock(productionMutex);
|
||||||
|
|
||||||
|
TRACE_EVENT_END("containers");
|
||||||
produceCondition.wait(lock, [this]() { return start != end; });
|
produceCondition.wait(lock, [this]() { return start != end; });
|
||||||
|
TRACE_EVENT_BEGIN("containers", "CircularQueue::Process");
|
||||||
}
|
}
|
||||||
|
|
||||||
while (start != end) {
|
while (start != end) {
|
||||||
|
|
|
@ -13,7 +13,8 @@ PERFETTO_DEFINE_CATEGORIES(
|
||||||
perfetto::Category("kernel").SetDescription("Events from parts of the HLE kernel"),
|
perfetto::Category("kernel").SetDescription("Events from parts of the HLE kernel"),
|
||||||
perfetto::Category("guest").SetDescription("Events relating to guest code"),
|
perfetto::Category("guest").SetDescription("Events relating to guest code"),
|
||||||
perfetto::Category("gpu").SetDescription("Events from the emulated GPU"),
|
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 {
|
namespace skyline::trace {
|
||||||
|
|
|
@ -12,8 +12,6 @@ namespace skyline::kernel::ipc {
|
||||||
header = reinterpret_cast<CommandHeader *>(pointer);
|
header = reinterpret_cast<CommandHeader *>(pointer);
|
||||||
pointer += sizeof(CommandHeader);
|
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) {
|
if (header->handleDesc) {
|
||||||
handleDesc = reinterpret_cast<HandleDescriptor *>(pointer);
|
handleDesc = reinterpret_cast<HandleDescriptor *>(pointer);
|
||||||
pointer += sizeof(HandleDescriptor) + (handleDesc->sendPid ? sizeof(u64) : 0);
|
pointer += sizeof(HandleDescriptor) + (handleDesc->sendPid ? sizeof(u64) : 0);
|
||||||
|
@ -64,6 +62,8 @@ namespace skyline::kernel::ipc {
|
||||||
pointer += sizeof(BufferDescriptorABW);
|
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 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
|
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front
|
||||||
pointer += padding;
|
pointer += padding;
|
||||||
|
@ -88,8 +88,7 @@ namespace skyline::kernel::ipc {
|
||||||
pointer += sizeof(PayloadHeader);
|
pointer += sizeof(PayloadHeader);
|
||||||
|
|
||||||
cmdArg = pointer;
|
cmdArg = pointer;
|
||||||
cmdArgSz = (header->rawSize * sizeof(u32)) - (constant::IpcPaddingSum + sizeof(PayloadHeader)) - cBufferLengthSize;
|
cmdArgSz = header->rawSize * sizeof(u32);
|
||||||
pointer += cmdArgSz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payloadOffset = cmdArg;
|
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
|
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));
|
state.logger->Debug("Unexpected Magic in PayloadHeader: 0x{:X}", static_cast<u32>(payload->magic));
|
||||||
|
|
||||||
pointer += constant::IpcPaddingSum - padding + cBufferLengthSize;
|
|
||||||
|
|
||||||
if (header->cFlag == BufferCFlag::SingleDescriptor) {
|
if (header->cFlag == BufferCFlag::SingleDescriptor) {
|
||||||
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
|
auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)};
|
||||||
if (bufC->address) {
|
if (bufC->address) {
|
||||||
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
|
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));
|
state.logger->Verbose("Buf C: 0x{:X}, 0x{:X}", bufC->Pointer(), static_cast<u16>(bufC->size));
|
||||||
}
|
}
|
||||||
} else if (header->cFlag > BufferCFlag::SingleDescriptor) {
|
} else if (header->cFlag > BufferCFlag::SingleDescriptor) {
|
||||||
for (u8 index{}; (static_cast<u8>(header->cFlag) - 2) > index; index++) { // (cFlag - 2) C descriptors are present
|
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) {
|
if (bufC->address) {
|
||||||
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
|
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));
|
state.logger->Verbose("Buf C #{}: 0x{:X}, 0x{:X}", index, bufC->Pointer(), static_cast<u16>(bufC->size));
|
||||||
}
|
}
|
||||||
pointer += sizeof(BufferDescriptorC);
|
bufCPointer += sizeof(BufferDescriptorC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace skyline::kernel::type {
|
||||||
if (fd < 0)
|
if (fd < 0)
|
||||||
throw exception("An error occurred while creating shared memory: {}", fd);
|
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)
|
if (host.ptr == MAP_FAILED)
|
||||||
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
|
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace skyline::kernel::type {
|
||||||
if (guest.Valid())
|
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);
|
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)
|
if (guest.ptr == MAP_FAILED)
|
||||||
throw exception("An error occurred while mapping shared memory in guest: {}", strerror(errno));
|
throw exception("An error occurred while mapping shared memory in guest: {}", strerror(errno));
|
||||||
guest.size = size;
|
guest.size = size;
|
||||||
|
|
|
@ -14,11 +14,12 @@ namespace skyline::service::am {
|
||||||
|
|
||||||
Result IStorageAccessor::Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result IStorageAccessor::Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto offset{request.Pop<i64>()};
|
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;
|
return result::OutOfBounds;
|
||||||
|
|
||||||
|
auto size{std::min(static_cast<i64>(request.inputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
|
||||||
|
|
||||||
if (size)
|
if (size)
|
||||||
span(parent->content).copy_from(request.inputBuf.at(0), 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) {
|
Result IStorageAccessor::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto offset{request.Pop<i64>()};
|
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;
|
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));
|
request.outputBuf.at(0).copy_from(span(parent->content.data() + offset, size));
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|
|
@ -3,22 +3,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <services/nvdrv/devices/nvhost_syncpoint.h>
|
|
||||||
|
|
||||||
namespace skyline::service::nvdrv {
|
namespace skyline::service::nvdrv {
|
||||||
/**
|
/**
|
||||||
* @brief A Fence is a synchronization primitive that describes a point in a Syncpoint to synchronize at
|
* @brief A Fence is a synchronization primitive that describes a point in a Syncpoint to synchronize at
|
||||||
*/
|
*/
|
||||||
struct Fence {
|
struct Fence {
|
||||||
u32 id{}; //!< The ID of the underlying syncpoint
|
u32 id{}; //!< The ID of the underlying syncpoint
|
||||||
u32 value{}; //!< The value of the syncpoint at which the fence is passed
|
u32 threshold{}; //!< The value of the syncpoint at which the fence is signalled
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Synchronizes the fence's value with its underlying syncpoint
|
|
||||||
*/
|
|
||||||
void UpdateValue(NvHostSyncpoint &hostSyncpoint) {
|
|
||||||
value = hostSyncpoint.UpdateMin(id);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(Fence) == 0x8);
|
static_assert(sizeof(Fence) == 0x8);
|
||||||
}
|
}
|
||||||
|
|
23
app/src/main/cpp/skyline/services/common/result.h
Normal file
23
app/src/main/cpp/skyline/services/common/result.h
Normal 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>;
|
||||||
|
}
|
|
@ -6,13 +6,18 @@
|
||||||
#include <gpu.h>
|
#include <gpu.h>
|
||||||
#include <gpu/texture/format.h>
|
#include <gpu/texture/format.h>
|
||||||
#include <soc.h>
|
#include <soc.h>
|
||||||
#include <services/nvdrv/driver.h>
|
|
||||||
#include <services/nvdrv/devices/nvmap.h>
|
#include <services/nvdrv/devices/nvmap.h>
|
||||||
#include <services/common/fence.h>
|
#include <services/common/fence.h>
|
||||||
#include "GraphicBufferProducer.h"
|
#include "GraphicBufferProducer.h"
|
||||||
|
|
||||||
namespace skyline::service::hosbinder {
|
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 GraphicBufferProducer::GetPendingBufferCount() {
|
||||||
u32 count{};
|
u32 count{};
|
||||||
|
@ -64,6 +69,12 @@ namespace skyline::service::hosbinder {
|
||||||
for (auto &slot : queue) {
|
for (auto &slot : queue) {
|
||||||
slot.state = BufferState::Free;
|
slot.state = BufferState::Free;
|
||||||
slot.frameNumber = std::numeric_limits<u32>::max();
|
slot.frameNumber = std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
if (slot.texture) {
|
||||||
|
slot.texture = {};
|
||||||
|
FreeGraphicBufferNvMap(*slot.graphicBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
slot.graphicBuffer = nullptr;
|
slot.graphicBuffer = nullptr;
|
||||||
}
|
}
|
||||||
} else if (preallocatedBufferCount < count) {
|
} else if (preallocatedBufferCount < count) {
|
||||||
|
@ -160,6 +171,12 @@ namespace skyline::service::hosbinder {
|
||||||
|
|
||||||
bufferSlot.state = BufferState::Free;
|
bufferSlot.state = BufferState::Free;
|
||||||
bufferSlot.frameNumber = std::numeric_limits<u32>::max();
|
bufferSlot.frameNumber = std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
if (bufferSlot.texture) {
|
||||||
|
bufferSlot.texture = {};
|
||||||
|
FreeGraphicBufferNvMap(*bufferSlot.graphicBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
bufferSlot.graphicBuffer = nullptr;
|
bufferSlot.graphicBuffer = nullptr;
|
||||||
|
|
||||||
bufferEvent->Signal();
|
bufferEvent->Signal();
|
||||||
|
@ -183,6 +200,12 @@ namespace skyline::service::hosbinder {
|
||||||
|
|
||||||
bufferSlot->state = BufferState::Free;
|
bufferSlot->state = BufferState::Free;
|
||||||
bufferSlot->frameNumber = std::numeric_limits<u32>::max();
|
bufferSlot->frameNumber = std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
if (bufferSlot->texture) {
|
||||||
|
bufferSlot->texture = {};
|
||||||
|
FreeGraphicBufferNvMap(*bufferSlot->graphicBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
graphicBuffer = *std::exchange(bufferSlot->graphicBuffer, nullptr);
|
graphicBuffer = *std::exchange(bufferSlot->graphicBuffer, nullptr);
|
||||||
fence = AndroidFence{};
|
fence = AndroidFence{};
|
||||||
|
|
||||||
|
@ -202,6 +225,11 @@ namespace skyline::service::hosbinder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bufferSlot->texture) {
|
||||||
|
bufferSlot->texture = {};
|
||||||
|
FreeGraphicBufferNvMap(*bufferSlot->graphicBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
if (bufferSlot == queue.end()) {
|
if (bufferSlot == queue.end()) {
|
||||||
state.logger->Warn("Could not find any free slots to attach the graphic buffer to");
|
state.logger->Warn("Could not find any free slots to attach the graphic buffer to");
|
||||||
return AndroidStatus::NoMemory;
|
return AndroidStatus::NoMemory;
|
||||||
|
@ -304,27 +332,13 @@ namespace skyline::service::hosbinder {
|
||||||
if (surface.scanFormat != NvDisplayScanFormat::Progressive)
|
if (surface.scanFormat != NvDisplayScanFormat::Progressive)
|
||||||
throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat));
|
throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat));
|
||||||
|
|
||||||
std::shared_ptr<nvdrv::device::NvMap::NvMapObject> nvBuffer{};
|
// Duplicate the handle so it can't be freed by the guest
|
||||||
{
|
auto nvMapHandleObj{nvMap.GetHandle(surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId)};
|
||||||
auto driver{nvdrv::driver.lock()};
|
if (auto err{nvMapHandleObj->Duplicate(true)}; err != PosixResult::Success)
|
||||||
auto nvmap{driver->nvMap.lock()};
|
throw exception("Failed to duplicate graphic buffer NvMap handle: {}!", static_cast<i32>(err));
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (surface.size > (nvBuffer->size - surface.offset))
|
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}", nvBuffer->size, surface.offset, surface.offset + surface.size);
|
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::TileMode tileMode;
|
||||||
gpu::texture::TileConfig tileConfig{};
|
gpu::texture::TileConfig tileConfig{};
|
||||||
|
@ -344,7 +358,7 @@ namespace skyline::service::hosbinder {
|
||||||
throw exception("Legacy 16Bx16 tiled surfaces are not supported");
|
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);
|
buffer.texture = guestTexture->CreateTexture({}, vk::ImageTiling::eLinear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +543,12 @@ namespace skyline::service::hosbinder {
|
||||||
for (auto &slot : queue) {
|
for (auto &slot : queue) {
|
||||||
slot.state = BufferState::Free;
|
slot.state = BufferState::Free;
|
||||||
slot.frameNumber = std::numeric_limits<u32>::max();
|
slot.frameNumber = std::numeric_limits<u32>::max();
|
||||||
|
|
||||||
|
if (slot.texture) {
|
||||||
|
slot.texture = {};
|
||||||
|
FreeGraphicBufferNvMap(*slot.graphicBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
slot.graphicBuffer = nullptr;
|
slot.graphicBuffer = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,12 +564,16 @@ namespace skyline::service::hosbinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto &buffer{queue[slot]};
|
auto &buffer{queue[slot]};
|
||||||
|
if (buffer.texture) {
|
||||||
|
buffer.texture = {};
|
||||||
|
FreeGraphicBufferNvMap(*buffer.graphicBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
buffer.state = BufferState::Free;
|
buffer.state = BufferState::Free;
|
||||||
buffer.frameNumber = 0;
|
buffer.frameNumber = 0;
|
||||||
buffer.wasBufferRequested = false;
|
buffer.wasBufferRequested = false;
|
||||||
buffer.isPreallocated = graphicBuffer != nullptr;
|
buffer.isPreallocated = graphicBuffer != nullptr;
|
||||||
buffer.graphicBuffer = graphicBuffer ? std::make_unique<GraphicBuffer>(*graphicBuffer) : nullptr;
|
buffer.graphicBuffer = graphicBuffer ? std::make_unique<GraphicBuffer>(*graphicBuffer) : nullptr;
|
||||||
buffer.texture = {};
|
|
||||||
|
|
||||||
if (graphicBuffer) {
|
if (graphicBuffer) {
|
||||||
if (graphicBuffer->magic != GraphicBuffer::Magic)
|
if (graphicBuffer->magic != GraphicBuffer::Magic)
|
||||||
|
|
|
@ -14,6 +14,10 @@ namespace skyline::gpu {
|
||||||
class Texture;
|
class Texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace skyline::service::nvdrv::core {
|
||||||
|
class NvMap;
|
||||||
|
}
|
||||||
|
|
||||||
namespace skyline::service::hosbinder {
|
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
|
* @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
|
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
|
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
|
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
|
* @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
|
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
|
* @brief The handler for Binder IPC transactions with IGraphicBufferProducer
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include "GraphicBufferProducer.h"
|
#include "GraphicBufferProducer.h"
|
||||||
|
|
||||||
namespace skyline::service::hosbinder {
|
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) {
|
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
|
// 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;
|
layerStrongReferenceCount = InitialStrongReferenceCount;
|
||||||
layerWeakReferenceCount = 0;
|
layerWeakReferenceCount = 0;
|
||||||
layer.emplace(state);
|
layer.emplace(state, nvMap);
|
||||||
|
|
||||||
return DefaultLayerId;
|
return DefaultLayerId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,10 @@ namespace skyline::service::hosbinder {
|
||||||
constexpr static u32 DefaultBinderLayerHandle{1}; //!< The handle as assigned by SurfaceFlinger of the default layer
|
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)
|
std::optional<GraphicBufferProducer> layer; //!< The IGraphicBufferProducer backing the layer (NativeWindow)
|
||||||
|
|
||||||
|
nvdrv::core::NvMap &nvMap;
|
||||||
|
|
||||||
public:
|
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
|
* @brief Emulates the transaction of parcels between a IGraphicBufferProducer and the application
|
||||||
|
|
|
@ -64,7 +64,7 @@ namespace skyline::service::hosbinder {
|
||||||
throw exception("Wait has larger fence count ({}) than storage size ({})", fenceCount, fences.size());
|
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++)
|
for (auto it{fences.begin()}, end{fences.begin() + fenceCount}; it < end; it++)
|
||||||
if (it->id != InvalidFenceId)
|
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());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
|
#include <numeric>
|
||||||
#include <kernel/types/KProcess.h>
|
#include <kernel/types/KProcess.h>
|
||||||
#include "INvDrvServices.h"
|
#include "INvDrvServices.h"
|
||||||
#include "driver.h"
|
#include "driver.h"
|
||||||
#include "devices/nvdevice.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 {
|
namespace skyline::service::nvdrv {
|
||||||
INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager) : driver(nvdrv::driver.expired() ? std::make_shared<Driver>(state) : nvdrv::driver.lock()), BaseService(state, manager) {
|
INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms) : BaseService(state, manager), driver(driver), ctx(SessionContext{.perms = perms}) {}
|
||||||
if (nvdrv::driver.expired())
|
|
||||||
nvdrv::driver = driver;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result INvDrvServices::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result INvDrvServices::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto path{request.inputBuf.at(0).as_string()};
|
constexpr FileDescriptor SessionFdLimit{std::numeric_limits<u64>::digits * 2}; //!< Nvdrv uses two 64 bit variables to store a bitset
|
||||||
|
|
||||||
response.Push<u32>(driver->OpenDevice(path));
|
auto path{request.inputBuf.at(0).as_string(true)};
|
||||||
response.Push(device::NvStatus::Success);
|
if (path.empty() || nextFdIndex == SessionFdLimit) {
|
||||||
|
response.Push<FileDescriptor>(InvalidFileDescriptor);
|
||||||
|
return NVRESULT(NvResult::FileOperationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
if (auto err{driver.OpenDevice(path, nextFdIndex, ctx)}; err != NvResult::Success) {
|
||||||
|
response.Push<FileDescriptor>(InvalidFileDescriptor);
|
||||||
|
return NVRESULT(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.Push(nextFdIndex++);
|
||||||
|
return NVRESULT(NvResult::Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
Result INvDrvServices::Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto fd{request.Pop<u32>()};
|
auto fd{request.Pop<FileDescriptor>()};
|
||||||
auto cmd{request.Pop<u32>()};
|
auto ioctl{request.Pop<IoctlDescriptor>()};
|
||||||
|
|
||||||
auto device{driver->GetDevice(fd)};
|
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
|
||||||
|
if (!buf)
|
||||||
// Strip the permissions from the command leaving only the ID
|
return NVRESULT(buf);
|
||||||
cmd &= 0xFFFF;
|
else
|
||||||
|
return NVRESULT(driver.Ioctl(fd, ioctl, *buf));
|
||||||
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 {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result INvDrvServices::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result INvDrvServices::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto fd{request.Pop<u32>()};
|
auto fd{request.Pop<u32>()};
|
||||||
state.logger->Debug("Closing NVDRV device ({})", fd);
|
state.logger->Debug("Closing NVDRV device ({})", fd);
|
||||||
|
|
||||||
driver->CloseDevice(fd);
|
driver.CloseDevice(fd);
|
||||||
|
|
||||||
response.Push(device::NvStatus::Success);
|
return NVRESULT(NvResult::Success);
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result INvDrvServices::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result INvDrvServices::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
response.Push(device::NvStatus::Success);
|
return NVRESULT(NvResult::Success);
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto fd{request.Pop<u32>()};
|
auto fd{request.Pop<u32>()};
|
||||||
auto eventId{request.Pop<u32>()};
|
auto eventId{request.Pop<u32>()};
|
||||||
|
|
||||||
auto device{driver->GetDevice(fd)};
|
auto event{driver.QueryEvent(fd, eventId)};
|
||||||
auto event{device->QueryEvent(eventId)};
|
|
||||||
|
|
||||||
if (event != nullptr) {
|
if (event != nullptr) {
|
||||||
auto handle{state.process->InsertItem<type::KEvent>(event)};
|
auto handle{state.process->InsertItem<type::KEvent>(event)};
|
||||||
|
@ -79,71 +91,45 @@ namespace skyline::service::nvdrv {
|
||||||
state.logger->Debug("FD: {}, Event ID: {}, Handle: 0x{:X}", fd, eventId, handle);
|
state.logger->Debug("FD: {}, Event ID: {}, Handle: 0x{:X}", fd, eventId, handle);
|
||||||
response.copyHandles.push_back(handle);
|
response.copyHandles.push_back(handle);
|
||||||
|
|
||||||
response.Push(device::NvStatus::Success);
|
return NVRESULT(NvResult::Success);
|
||||||
} else {
|
} else {
|
||||||
response.Push(device::NvStatus::BadValue);
|
return NVRESULT(NvResult::BadValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
|
||||||
response.Push(device::NvStatus::Success);
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result INvDrvServices::Ioctl2(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result INvDrvServices::Ioctl2(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto fd{request.Pop<u32>()};
|
auto fd{request.Pop<FileDescriptor>()};
|
||||||
auto cmd{request.Pop<u32>()};
|
auto ioctl{request.Pop<IoctlDescriptor>()};
|
||||||
|
|
||||||
auto device{driver->GetDevice(fd)};
|
// 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
|
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
|
||||||
cmd &= 0xFFFF;
|
if (!buf)
|
||||||
|
return NVRESULT(buf);
|
||||||
if (request.inputBuf.size() < 2 || request.outputBuf.empty())
|
|
||||||
throw exception("Inadequate amount of buffers for IOCTL2: I - {}, O - {}", request.inputBuf.size(), request.outputBuf.size());
|
|
||||||
else if (request.inputBuf[0].data() != request.outputBuf[0].data())
|
|
||||||
throw exception("IOCTL2 Input Buffer (0x{:X}) != Output Buffer (0x{:X}) [Input Buffer #2: 0x{:X}]", request.inputBuf[0].data(), request.outputBuf[0].data(), request.inputBuf[1].data());
|
|
||||||
|
|
||||||
span<u8> buffer{};
|
|
||||||
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
|
|
||||||
buffer = request.inputBuf[0];
|
|
||||||
else
|
else
|
||||||
buffer = request.outputBuf[0];
|
return NVRESULT(driver.Ioctl2(fd, ioctl, *buf, inlineBuf));
|
||||||
|
|
||||||
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl2, buffer, request.inputBuf[1]));
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Result INvDrvServices::Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
Result INvDrvServices::Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
auto fd{request.Pop<u32>()};
|
auto fd{request.Pop<FileDescriptor>()};
|
||||||
auto cmd{request.Pop<u32>()};
|
auto ioctl{request.Pop<IoctlDescriptor>()};
|
||||||
|
|
||||||
auto device{driver->GetDevice(fd)};
|
// 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
|
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
|
||||||
cmd &= 0xFFFF;
|
if (!buf)
|
||||||
|
return NVRESULT(buf);
|
||||||
if (request.inputBuf.empty() || request.outputBuf.empty()) {
|
|
||||||
throw exception("Inadequate amount of buffers for IOCTL3: I - {}, O - {}", request.inputBuf.size(), request.outputBuf.size());
|
|
||||||
} else if (request.inputBuf[0].data() != request.outputBuf[0].data()) {
|
|
||||||
if (request.outputBuf.size() >= 2)
|
|
||||||
throw exception("IOCTL3 Input Buffer (0x{:X}) != Output Buffer (0x{:X}) [Output Buffer #2: 0x{:X}]", request.inputBuf[0].data(), request.outputBuf[0].data(), request.outputBuf[1].data());
|
|
||||||
else
|
|
||||||
throw exception("IOCTL3 Input Buffer (0x{:X}) != Output Buffer (0x{:X})", request.inputBuf[0].data(), request.outputBuf[0].data());
|
|
||||||
}
|
|
||||||
|
|
||||||
span<u8> buffer{};
|
|
||||||
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
|
|
||||||
buffer = request.inputBuf[0];
|
|
||||||
else
|
else
|
||||||
buffer = request.outputBuf[0];
|
return NVRESULT(driver.Ioctl3(fd, ioctl, *buf, inlineBuf));
|
||||||
|
|
||||||
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl3, buffer, request.outputBuf.size() >= 2 ? request.outputBuf[1] : span<u8>()));
|
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
Result INvDrvServices::SetGraphicsFirmwareMemoryMarginEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <services/serviceman.h>
|
#include <services/serviceman.h>
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
namespace skyline::service::nvdrv {
|
namespace skyline::service::nvdrv {
|
||||||
class Driver;
|
class Driver;
|
||||||
|
@ -14,10 +15,13 @@ namespace skyline::service::nvdrv {
|
||||||
*/
|
*/
|
||||||
class INvDrvServices : public BaseService {
|
class INvDrvServices : public BaseService {
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<Driver> driver;
|
Driver &driver; //!< The global nvdrv driver this session accesses
|
||||||
|
SessionContext ctx; //!< Session specific context
|
||||||
|
|
||||||
|
FileDescriptor nextFdIndex{1}; //!< The index for the next allocated file descriptor
|
||||||
|
|
||||||
public:
|
public:
|
||||||
INvDrvServices(const DeviceState &state, ServiceManager &manager);
|
INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Open a specific device and return a FD
|
* @brief Open a specific device and return a FD
|
||||||
|
|
19
app/src/main/cpp/skyline/services/nvdrv/core/core.h
Normal file
19
app/src/main/cpp/skyline/services/nvdrv/core/core.h
Normal 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) {}
|
||||||
|
};
|
||||||
|
}
|
144
app/src/main/cpp/skyline/services/nvdrv/core/nvmap.cpp
Normal file
144
app/src/main/cpp/skyline/services/nvdrv/core/nvmap.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
116
app/src/main/cpp/skyline/services/nvdrv/core/nvmap.h
Normal file
116
app/src/main/cpp/skyline/services/nvdrv/core/nvmap.h
Normal 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);
|
||||||
|
};
|
||||||
|
}
|
|
@ -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 © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
// Copyright © 2019-2020 Ryujinx Team and Contributors
|
||||||
|
|
||||||
#include <soc.h>
|
#include <soc.h>
|
||||||
#include "nvhost_syncpoint.h"
|
#include "syncpoint_manager.h"
|
||||||
|
|
||||||
namespace skyline::service::nvdrv {
|
namespace skyline::service::nvdrv::core {
|
||||||
NvHostSyncpoint::NvHostSyncpoint(const DeviceState &state) : state(state) {
|
SyncpointManager::SyncpointManager(const DeviceState &state) : state(state) {
|
||||||
constexpr u32 VBlank0SyncpointId{26};
|
constexpr u32 VBlank0SyncpointId{26};
|
||||||
constexpr u32 VBlank1SyncpointId{27};
|
constexpr u32 VBlank1SyncpointId{27};
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace skyline::service::nvdrv {
|
||||||
ReserveSyncpoint(VBlank1SyncpointId, true);
|
ReserveSyncpoint(VBlank1SyncpointId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) {
|
u32 SyncpointManager::ReserveSyncpoint(u32 id, bool clientManaged) {
|
||||||
if (syncpoints.at(id).reserved)
|
if (syncpoints.at(id).reserved)
|
||||||
throw exception("Requested syncpoint is in use");
|
throw exception("Requested syncpoint is in use");
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ namespace skyline::service::nvdrv {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NvHostSyncpoint::FindFreeSyncpoint() {
|
u32 SyncpointManager::FindFreeSyncpoint() {
|
||||||
for (u32 i{1}; i < syncpoints.size(); i++)
|
for (u32 i{1}; i < syncpoints.size(); i++)
|
||||||
if (!syncpoints[i].reserved)
|
if (!syncpoints[i].reserved)
|
||||||
return i;
|
return i;
|
||||||
|
@ -35,12 +35,12 @@ namespace skyline::service::nvdrv {
|
||||||
throw exception("Failed to find a free syncpoint!");
|
throw exception("Failed to find a free syncpoint!");
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NvHostSyncpoint::AllocateSyncpoint(bool clientManaged) {
|
u32 SyncpointManager::AllocateSyncpoint(bool clientManaged) {
|
||||||
std::lock_guard lock(reservationLock);
|
std::lock_guard lock(reservationLock);
|
||||||
return ReserveSyncpoint(FindFreeSyncpoint(), clientManaged);
|
return ReserveSyncpoint(FindFreeSyncpoint(), clientManaged);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) {
|
bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) {
|
||||||
const SyncpointInfo &syncpoint{syncpoints.at(id)};
|
const SyncpointInfo &syncpoint{syncpoints.at(id)};
|
||||||
|
|
||||||
if (!syncpoint.reserved)
|
if (!syncpoint.reserved)
|
||||||
|
@ -53,25 +53,35 @@ namespace skyline::service::nvdrv {
|
||||||
return (syncpoint.counterMax - threshold) >= (syncpoint.counterMin - threshold);
|
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)
|
if (!syncpoints.at(id).reserved)
|
||||||
throw exception("Cannot increment an unreserved syncpoint!");
|
throw exception("Cannot increment an unreserved syncpoint!");
|
||||||
|
|
||||||
return syncpoints.at(id).counterMax += amount;
|
return syncpoints.at(id).counterMax += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NvHostSyncpoint::ReadSyncpointMinValue(u32 id) {
|
u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
|
||||||
if (!syncpoints.at(id).reserved)
|
if (!syncpoints.at(id).reserved)
|
||||||
throw exception("Cannot read an unreserved syncpoint!");
|
throw exception("Cannot read an unreserved syncpoint!");
|
||||||
|
|
||||||
return syncpoints.at(id).counterMin;
|
return syncpoints.at(id).counterMin;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 NvHostSyncpoint::UpdateMin(u32 id) {
|
u32 SyncpointManager::UpdateMin(u32 id) {
|
||||||
if (!syncpoints.at(id).reserved)
|
if (!syncpoints.at(id).reserved)
|
||||||
throw exception("Cannot update an unreserved syncpoint!");
|
throw exception("Cannot update an unreserved syncpoint!");
|
||||||
|
|
||||||
syncpoints.at(id).counterMin = state.soc->host1x.syncpoints.at(id).Load();
|
syncpoints.at(id).counterMin = state.soc->host1x.syncpoints.at(id).Load();
|
||||||
return syncpoints.at(id).counterMin;
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
// Copyright © 2019-2020 Ryujinx Team and Contributors
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <soc/host1x.h>
|
#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
|
* @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://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
|
* @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:
|
private:
|
||||||
struct SyncpointInfo {
|
struct SyncpointInfo {
|
||||||
std::atomic<u32> counterMin; //!< The least value the syncpoint can be (The value it was when it was last synchronized with host1x)
|
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();
|
u32 FindFreeSyncpoint();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NvHostSyncpoint(const DeviceState &state);
|
SyncpointManager(const DeviceState &state);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Finds a free syncpoint and reserves it
|
* @brief Finds a free syncpoint and reserves it
|
||||||
|
@ -49,6 +51,10 @@ namespace skyline::service::nvdrv {
|
||||||
*/
|
*/
|
||||||
bool HasSyncpointExpired(u32 id, u32 threshold);
|
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
|
* @brief Atomically increments the maximum value of a syncpoint by the given amount
|
||||||
* @return The new max value of the syncpoint
|
* @return The new max value of the syncpoint
|
||||||
|
@ -65,5 +71,10 @@ namespace skyline::service::nvdrv {
|
||||||
* @return The new minimum value of the syncpoint
|
* @return The new minimum value of the syncpoint
|
||||||
*/
|
*/
|
||||||
u32 UpdateMin(u32 id);
|
u32 UpdateMin(u32 id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A fence that will be signalled once this syncpoint hits its maximum value
|
||||||
|
*/
|
||||||
|
Fence GetSyncpointFence(u32 id);
|
||||||
};
|
};
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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__
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,11 +1,12 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
#include <common/trace.h>
|
|
||||||
#include "nvdevice.h"
|
#include "nvdevice.h"
|
||||||
|
|
||||||
namespace skyline::service::nvdrv::device {
|
namespace skyline::service::nvdrv::device {
|
||||||
|
NvDevice::NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx) : state(state), core(core), ctx(ctx) {}
|
||||||
|
|
||||||
const std::string &NvDevice::GetName() {
|
const std::string &NvDevice::GetName() {
|
||||||
if (name.empty()) {
|
if (name.empty()) {
|
||||||
auto mangledName{typeid(*this).name()};
|
auto mangledName{typeid(*this).name()};
|
||||||
|
@ -18,32 +19,4 @@ namespace skyline::service::nvdrv::device {
|
||||||
}
|
}
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvStatus NvDevice::HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
|
||||||
std::string_view typeString{[type] {
|
|
||||||
switch (type) {
|
|
||||||
case IoctlType::Ioctl:
|
|
||||||
return "IOCTL";
|
|
||||||
case IoctlType::Ioctl2:
|
|
||||||
return "IOCTL2";
|
|
||||||
case IoctlType::Ioctl3:
|
|
||||||
return "IOCTL3";
|
|
||||||
}
|
|
||||||
}()};
|
|
||||||
|
|
||||||
NvDeviceFunctionDescriptor function;
|
|
||||||
try {
|
|
||||||
function = GetIoctlFunction(cmd);
|
|
||||||
state.logger->DebugNoPrefix("{}: {}", typeString, function.name);
|
|
||||||
} catch (std::out_of_range &) {
|
|
||||||
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", GetName(), cmd);
|
|
||||||
return NvStatus::NotImplemented;
|
|
||||||
}
|
|
||||||
TRACE_EVENT("service", perfetto::StaticString{function.name});
|
|
||||||
try {
|
|
||||||
return function(type, buffer, inlineBuffer);
|
|
||||||
} catch (const std::exception &e) {
|
|
||||||
throw exception("{} ({}: {})", e.what(), typeString, function.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,66 +1,19 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <kernel/ipc.h>
|
#include <kernel/ipc.h>
|
||||||
#include <kernel/types/KEvent.h>
|
#include <kernel/types/KEvent.h>
|
||||||
|
#include <services/common/result.h>
|
||||||
|
#include <services/nvdrv/types.h>
|
||||||
|
#include <services/nvdrv/core/core.h>
|
||||||
|
|
||||||
#define NV_STRINGIFY(string) #string
|
#include "deserialisation/types.h"
|
||||||
#define NVFUNC(id, Class, Function) std::pair<u32, std::pair<NvStatus(Class::*)(IoctlType, span<u8>, span<u8>), const char*>>{id, {&Class::Function, NV_STRINGIFY(Class::Function)}}
|
|
||||||
#define NVDEVICE_DECL_AUTO(name, value) decltype(value) name = value
|
|
||||||
#define NVDEVICE_DECL(...) \
|
|
||||||
NVDEVICE_DECL_AUTO(functions, frz::make_unordered_map({__VA_ARGS__})); \
|
|
||||||
NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) override { \
|
|
||||||
auto& function{functions.at(id)}; \
|
|
||||||
return NvDeviceFunctionDescriptor{ \
|
|
||||||
reinterpret_cast<DerivedDevice*>(this), \
|
|
||||||
reinterpret_cast<decltype(NvDeviceFunctionDescriptor::function)>(function.first), \
|
|
||||||
function.second \
|
|
||||||
}; \
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace skyline::service::nvdrv::device {
|
namespace skyline::service::nvdrv::device {
|
||||||
using namespace kernel;
|
using namespace kernel;
|
||||||
|
using namespace deserialisation;
|
||||||
/**
|
|
||||||
* @brief All the possible error codes returned by the Nvidia driver
|
|
||||||
* @url https://switchbrew.org/wiki/NV_services#Errors
|
|
||||||
*/
|
|
||||||
enum class NvStatus : u32 {
|
|
||||||
Success = 0x0, //!< The operation has succeeded
|
|
||||||
NotImplemented = 0x1, //!< The operation is not implemented
|
|
||||||
NotSupported = 0x2, //!< The operation is not supported
|
|
||||||
NotInitialized = 0x3, //!< The operation uses an uninitialized object
|
|
||||||
BadParameter = 0x4, //!< The operation was provided a bad parameter
|
|
||||||
Timeout = 0x5, //!< The operation has timed out
|
|
||||||
InsufficientMemory = 0x6, //!< The device ran out of memory during the operation
|
|
||||||
ReadOnlyAttribute = 0x7, //!< The mutating operation was performed on a read only section
|
|
||||||
InvalidState = 0x8, //!< The state of the device was invalid
|
|
||||||
InvalidAddress = 0x9, //!< The provided address is invalid
|
|
||||||
InvalidSize = 0xA, //!< The provided size is invalid
|
|
||||||
BadValue = 0xB, //!< The operation was provided a bad value
|
|
||||||
AlreadyAllocated = 0xD, //!< An object was tried to be reallocated
|
|
||||||
Busy = 0xE, //!< The device is busy
|
|
||||||
ResourceError = 0xF, //!< There was an error accessing the resource
|
|
||||||
CountMismatch = 0x10, //!< ?
|
|
||||||
SharedMemoryTooSmall = 0x1000, //!< The shared memory segment is too small
|
|
||||||
FileOperationFailed = 0x30003, //!< The file operation has failed
|
|
||||||
DirOperationFailed = 0x30004, //!< The directory operation has failed
|
|
||||||
IoctlFailed = 0x3000F, //!< The IOCTL operation has failed
|
|
||||||
AccessDenied = 0x30010, //!< The access to a resource was denied
|
|
||||||
FileNotFound = 0x30013, //!< A file was not found
|
|
||||||
ModuleNotPresent = 0xA000E, //!< A module was not present
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The IOCTL call variants, they have different buffer configurations
|
|
||||||
*/
|
|
||||||
enum class IoctlType : u8 {
|
|
||||||
Ioctl, //!< 1 input/output buffer
|
|
||||||
Ioctl2, //!< 1 input/output buffer + 1 input buffer
|
|
||||||
Ioctl3, //!< 1 input/output buffer + 1 output buffer
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief NvDevice is the base class that all /dev/nv* devices inherit from
|
* @brief NvDevice is the base class that all /dev/nv* devices inherit from
|
||||||
|
@ -71,40 +24,29 @@ namespace skyline::service::nvdrv::device {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const DeviceState &state;
|
const DeviceState &state;
|
||||||
|
Core &core;
|
||||||
class DerivedDevice; //!< A placeholder derived class which is used for class function semantics
|
SessionContext ctx;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief A per-function descriptor for NvDevice functions
|
|
||||||
*/
|
|
||||||
struct NvDeviceFunctionDescriptor {
|
|
||||||
DerivedDevice *clazz; //!< A pointer to the class that this was derived from, it's used as the 'this' pointer for the function
|
|
||||||
NvStatus (DerivedDevice::*function)(IoctlType, span<u8>, span<u8>); //!< A function pointer to the implementation of the function
|
|
||||||
const char *name; //!< A pointer to a static string in the format "Class::Function" for the specific device class/function
|
|
||||||
|
|
||||||
constexpr NvStatus operator()(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
|
||||||
return (clazz->*function)(type, buffer, inlineBuffer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NvDevice(const DeviceState &state) : state(state) {}
|
NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx);
|
||||||
|
|
||||||
virtual ~NvDevice() = default;
|
virtual ~NvDevice() = default;
|
||||||
|
|
||||||
virtual NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) = 0;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The name of the class
|
* @return The name of the class
|
||||||
* @note The lifetime of the returned string is tied to that of the class
|
* @note The lifetime of the returned string is tied to that of the class
|
||||||
*/
|
*/
|
||||||
const std::string &GetName();
|
const std::string &GetName();
|
||||||
|
|
||||||
/**
|
virtual PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) = 0;
|
||||||
* @brief Handles IOCTL calls for devices
|
|
||||||
* @param cmd The IOCTL command that was called
|
virtual PosixResult Ioctl2(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineOutput) {
|
||||||
*/
|
return PosixResult::InappropriateIoctlForDevice;
|
||||||
NvStatus HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
}
|
||||||
|
|
||||||
|
virtual PosixResult Ioctl3(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineInput) {
|
||||||
|
return PosixResult::InappropriateIoctlForDevice;
|
||||||
|
}
|
||||||
|
|
||||||
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
|
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
|
@ -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>
|
||||||
|
}
|
157
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/as_gpu.h
Normal file
157
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/as_gpu.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
267
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp
Normal file
267
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.cpp
Normal 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>
|
||||||
|
}
|
141
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h
Normal file
141
app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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>
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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>
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
|
@ -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
|
|
||||||
};
|
|
||||||
} ®ion = 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
|
|
||||||
} ®ionInfo = 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,172 +1,146 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
|
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
|
||||||
|
#include <services/nvdrv/core/nvmap.h>
|
||||||
#include "nvmap.h"
|
#include "nvmap.h"
|
||||||
|
|
||||||
namespace skyline::service::nvdrv::device {
|
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) {}
|
PosixResult NvMap::Create(In<u32> size, Out<NvMapCore::Handle::Id> handle) {
|
||||||
|
auto handleDesc{core.nvMap.CreateHandle(util::AlignUp(size, PAGE_SIZE))};
|
||||||
NvStatus NvMap::Create(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
if (handleDesc) {
|
||||||
struct Data {
|
(*handleDesc)->origSize = size; // Orig size is the unaligned size
|
||||||
u32 size; // In
|
handle = (*handleDesc)->id;
|
||||||
u32 handle; // Out
|
state.logger->Debug("handle: {}, size: 0x{:X}", (*handleDesc)->id, size);
|
||||||
} &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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.logger->Warn("Handle not found for ID: 0x{:X}", data.id);
|
return handleDesc;
|
||||||
return NvStatus::BadValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NvStatus NvMap::Alloc(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
PosixResult NvMap::FromId(In<NvMapCore::Handle::Id> id, Out<NvMapCore::Handle::Id> handle) {
|
||||||
struct Data {
|
state.logger->Debug("id: {}", id);
|
||||||
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>();
|
|
||||||
|
|
||||||
try {
|
// Handles and IDs are always the same value in nvmap however IDs can be used globally given the right permissions.
|
||||||
auto object{GetObject(data.handle)};
|
// 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.
|
||||||
object->heapMask = data.heapMask;
|
if (!id) [[unlikely]]
|
||||||
object->flags = data.flags;
|
return PosixResult::InvalidArgument;
|
||||||
object->align = data.align;
|
|
||||||
object->kind = data.kind;
|
|
||||||
object->ptr = data.ptr;
|
|
||||||
object->status = NvMapObject::Status::Allocated;
|
|
||||||
|
|
||||||
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);
|
auto handleDesc{core.nvMap.GetHandle(id)};
|
||||||
return NvStatus::Success;
|
if (!handleDesc) [[unlikely]]
|
||||||
} catch (const std::out_of_range &) {
|
return PosixResult::InvalidArgument;
|
||||||
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
|
|
||||||
return NvStatus::BadParameter;
|
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) {
|
PosixResult NvMap::GetId(Out<NvMapCore::Handle::Id> id, In<NvMapCore::Handle::Id> handle) {
|
||||||
struct Data {
|
state.logger->Debug("handle: {}", handle);
|
||||||
u32 handle; // In
|
|
||||||
u32 _pad0_;
|
|
||||||
u8 *ptr; // Out
|
|
||||||
u32 size; // Out
|
|
||||||
u32 flags; // Out
|
|
||||||
} &data = buffer.as<Data>();
|
|
||||||
|
|
||||||
std::unique_lock lock(mapMutex);
|
// See the comment in FromId for extra info on this function
|
||||||
try {
|
if (!handle) [[unlikely]]
|
||||||
auto &object{maps.at(data.handle - 1)};
|
return PosixResult::InvalidArgument;
|
||||||
if (object.use_count() > 1) {
|
|
||||||
data.ptr = object->ptr;
|
|
||||||
data.flags = 0x0;
|
|
||||||
} else {
|
|
||||||
data.ptr = nullptr;
|
|
||||||
data.flags = 0x1; // Not free yet
|
|
||||||
}
|
|
||||||
|
|
||||||
data.size = object->size;
|
auto handleDesc{core.nvMap.GetHandle(handle)};
|
||||||
object = nullptr;
|
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);
|
id = handleDesc->id;
|
||||||
return NvStatus::Success;
|
return PosixResult::Success;
|
||||||
} catch (const std::out_of_range &) {
|
|
||||||
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
|
|
||||||
return NvStatus::BadParameter;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NvStatus NvMap::Param(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
#include "deserialisation/macro_def.inc"
|
||||||
// https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102
|
static constexpr u32 NvMapMagic{1};
|
||||||
enum class Parameter : u32 {
|
|
||||||
Size = 1,
|
|
||||||
Alignment = 2,
|
|
||||||
Base = 3,
|
|
||||||
HeapMask = 4,
|
|
||||||
Kind = 5,
|
|
||||||
Compr = 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Data {
|
IOCTL_HANDLER_FUNC(NvMap, ({
|
||||||
u32 handle; // In
|
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x1),
|
||||||
Parameter parameter; // In
|
Create, ARGS(In<u32>, Out<NvMapCore::Handle::Id>))
|
||||||
u32 result; // Out
|
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x3),
|
||||||
} &data = buffer.as<Data>();
|
FromId, ARGS(In<NvMapCore::Handle::Id>, Out<NvMapCore::Handle::Id>))
|
||||||
|
IOCTL_CASE_ARGS(INOUT, SIZE(0x20), MAGIC(NvMapMagic), FUNC(0x4),
|
||||||
try {
|
Alloc, ARGS(In<NvMapCore::Handle::Id>, In<u32>, In<NvMapCore::Handle::Flags>, InOut<u32>, In<u8>, Pad<u8, 0x7>, In<u64>))
|
||||||
auto object{GetObject(data.handle)};
|
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>))
|
||||||
switch (data.parameter) {
|
IOCTL_CASE_ARGS(INOUT, SIZE(0xC), MAGIC(NvMapMagic), FUNC(0x9),
|
||||||
case Parameter::Size:
|
Param, ARGS(In<NvMapCore::Handle::Id>, In<HandleParameterType>, Out<u32>))
|
||||||
data.result = object->size;
|
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0xE),
|
||||||
break;
|
GetId, ARGS(Out<NvMapCore::Handle::Id>, In<NvMapCore::Handle::Id>))
|
||||||
|
}))
|
||||||
case Parameter::Alignment:
|
#include "deserialisation/macro_undef.inc"
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -7,91 +7,60 @@
|
||||||
|
|
||||||
namespace skyline::service::nvdrv::device {
|
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
|
* @url https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h
|
||||||
*/
|
*/
|
||||||
class NvMap : public NvDevice {
|
class NvMap : public NvDevice {
|
||||||
public:
|
public:
|
||||||
/**
|
using NvMapCore = core::NvMap;
|
||||||
* @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
|
|
||||||
|
|
||||||
enum class Status {
|
enum class HandleParameterType : u32 {
|
||||||
Created, //!< The object has been created but memory has not been allocated
|
Size = 1,
|
||||||
Allocated //!< The object has been allocated
|
Alignment = 2,
|
||||||
} status{Status::Created}; //!< This holds the status of the object
|
Base = 3,
|
||||||
|
Heap = 4,
|
||||||
NvMapObject(u32 id, u32 size);
|
Kind = 5,
|
||||||
|
IsSharedMemMapped = 6
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_mutex mapMutex; //!< Synchronizes mutations and accesses of the mappings
|
NvMap(const DeviceState &state, Core &core, const SessionContext &ctx);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
* @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
|
* @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
|
* @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
|
* @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
|
* @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
|
* @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(
|
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
|
||||||
NVFUNC(0x0101, NvMap, Create),
|
|
||||||
NVFUNC(0x0103, NvMap, FromId),
|
|
||||||
NVFUNC(0x0104, NvMap, Alloc),
|
|
||||||
NVFUNC(0x0105, NvMap, Free),
|
|
||||||
NVFUNC(0x0109, NvMap, Param),
|
|
||||||
NVFUNC(0x010E, NvMap, GetId)
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#include "driver.h"
|
#include "driver.h"
|
||||||
#include "devices/nvhost_ctrl.h"
|
|
||||||
#include "devices/nvhost_ctrl_gpu.h"
|
|
||||||
#include "devices/nvmap.h"
|
#include "devices/nvmap.h"
|
||||||
#include "devices/nvhost_channel.h"
|
#include "devices/nvhost/ctrl.h"
|
||||||
#include "devices/nvhost_as_gpu.h"
|
#include "devices/nvhost/ctrl_gpu.h"
|
||||||
|
#include "devices/nvhost/gpu_channel.h"
|
||||||
|
#include "devices/nvhost/as_gpu.h"
|
||||||
|
|
||||||
|
|
||||||
namespace skyline::service::nvdrv {
|
namespace skyline::service::nvdrv {
|
||||||
Driver::Driver(const DeviceState &state) : state(state), hostSyncpoint(state) {}
|
Driver::Driver(const DeviceState &state) : state(state), core(state) {}
|
||||||
|
|
||||||
u32 Driver::OpenDevice(std::string_view path) {
|
NvResult Driver::OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx) {
|
||||||
state.logger->Debug("Opening NVDRV device ({}): {}", fdIndex, path);
|
state.logger->Debug("Opening NvDrv device ({}): {}", fd, path);
|
||||||
|
auto pathHash{util::Hash(path)};
|
||||||
|
|
||||||
switch (util::Hash(path)) {
|
#define DEVICE_SWITCH(cases) \
|
||||||
#define NVDEVICE(type, name, devicePath) \
|
switch (pathHash) { \
|
||||||
case util::Hash(devicePath): { \
|
cases; \
|
||||||
std::shared_ptr<device::type> device{}; \
|
default: \
|
||||||
if (name.expired()) { \
|
break; \
|
||||||
device = std::make_shared<device::type>(state); \
|
}
|
||||||
name = device; \
|
|
||||||
} else { \
|
|
||||||
device = name.lock(); \
|
|
||||||
} \
|
|
||||||
devices.push_back(device); \
|
|
||||||
break; \
|
|
||||||
}
|
|
||||||
NVDEVICE_LIST
|
|
||||||
#undef NVDEVICE
|
|
||||||
|
|
||||||
default:
|
#define DEVICE_CASE(path, object) \
|
||||||
throw exception("Cannot find NVDRV device");
|
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 {
|
try {
|
||||||
auto item{devices.at(fd)};
|
return ConvertResult(devices.at(fd)->Ioctl(cmd, buffer));
|
||||||
if (!item)
|
} catch (const std::out_of_range &) {
|
||||||
throw exception("GetDevice was called with a closed file descriptor: 0x{:X}", fd);
|
throw exception("Ioctl was called with invalid file descriptor: 0x{:X}", fd);
|
||||||
return item;
|
}
|
||||||
} catch (std::out_of_range) {
|
}
|
||||||
throw exception("GetDevice 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) {
|
void Driver::CloseDevice(u32 fd) {
|
||||||
try {
|
try {
|
||||||
auto &device{devices.at(fd)};
|
devices.at(fd).reset();
|
||||||
device.reset();
|
|
||||||
} catch (const std::out_of_range &) {
|
} catch (const std::out_of_range &) {
|
||||||
state.logger->Warn("Trying to close non-existent FD");
|
state.logger->Warn("Trying to close non-existent FD");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::weak_ptr<Driver> driver{};
|
std::shared_ptr<kernel::type::KEvent> Driver::QueryEvent(u32 fd, u32 eventId) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "devices/nvhost_syncpoint.h"
|
#include <common.h>
|
||||||
|
#include "types.h"
|
||||||
#define NVDEVICE_LIST \
|
#include "devices/nvdevice.h"
|
||||||
NVDEVICE(NvHostCtrl, nvHostCtrl, "/dev/nvhost-ctrl") \
|
#include "core/core.h"
|
||||||
NVDEVICE(NvHostChannel, nvHostGpu, "/dev/nvhost-gpu") \
|
|
||||||
NVDEVICE(NvHostChannel, nvHostNvdec, "/dev/nvhost-nvdec") \
|
|
||||||
NVDEVICE(NvHostChannel, nvHostVic, "/dev/nvhost-vic") \
|
|
||||||
NVDEVICE(NvMap, nvMap, "/dev/nvmap") \
|
|
||||||
NVDEVICE(NvHostAsGpu, nvHostAsGpu, "/dev/nvhost-as-gpu") \
|
|
||||||
NVDEVICE(NvHostCtrlGpu, nvHostCtrlGpu, "/dev/nvhost-ctrl-gpu")
|
|
||||||
|
|
||||||
namespace skyline::service::nvdrv {
|
namespace skyline::service::nvdrv {
|
||||||
namespace device {
|
|
||||||
class NvDevice;
|
|
||||||
|
|
||||||
#define NVDEVICE(type, name, path) class type;
|
|
||||||
NVDEVICE_LIST
|
|
||||||
#undef NVDEVICE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief nvnflinger:dispdrv or nns::hosbinder::IHOSBinderDriver is responsible for writing buffers to the display
|
|
||||||
*/
|
|
||||||
class Driver {
|
class Driver {
|
||||||
private:
|
private:
|
||||||
const DeviceState &state;
|
const DeviceState &state;
|
||||||
std::vector<std::shared_ptr<device::NvDevice>> devices; //!< A vector of shared pointers to NvDevice object that correspond to FDs
|
std::unordered_map<FileDescriptor, std::unique_ptr<device::NvDevice>> devices;
|
||||||
u32 fdIndex{}; //!< The next file descriptor to assign
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
NvHostSyncpoint hostSyncpoint;
|
Core core; //!< The core global state object of nvdrv that is accessed by devices
|
||||||
|
|
||||||
#define NVDEVICE(type, name, path) std::weak_ptr<device::type> name;
|
|
||||||
NVDEVICE_LIST
|
|
||||||
#undef NVDEVICE
|
|
||||||
|
|
||||||
Driver(const DeviceState &state);
|
Driver(const DeviceState &state);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Open a specific device and return a FD
|
* @brief Creates a new device as specified by path
|
||||||
* @param path The path of the device to open an FD to
|
* @param path The /dev path that corresponds to the device
|
||||||
* @return The file descriptor to the device
|
* @param fd The fd that will be used to refer to the device
|
||||||
|
* @param ctx The context to be attached to the device
|
||||||
*/
|
*/
|
||||||
u32 OpenDevice(std::string_view path);
|
NvResult OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a particular device with a specific FD
|
* @brief Calls an IOCTL on the device specified by `fd`
|
||||||
* @param fd The file descriptor to retrieve
|
|
||||||
* @return A shared pointer to the device
|
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<device::NvDevice> GetDevice(u32 fd);
|
NvResult Ioctl(u32 fd, IoctlDescriptor cmd, span<u8> buffer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a particular device with a specific FD
|
* @brief Calls an IOCTL on the device specified by `fd` using the given inline input buffer
|
||||||
* @tparam objectClass The class of the device to return
|
|
||||||
* @param fd The file descriptor to retrieve
|
|
||||||
* @return A shared pointer to the device
|
|
||||||
*/
|
*/
|
||||||
template<typename objectClass>
|
NvResult Ioctl2(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer);
|
||||||
std::shared_ptr<objectClass> GetDevice(u32 fd) {
|
|
||||||
return std::static_pointer_cast<objectClass>(GetDevice(fd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Closes the specified device with its file descriptor
|
* @brief 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);
|
void CloseDevice(u32 fd);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern std::weak_ptr<Driver> driver; //!< A globally shared instance of the Driver
|
|
||||||
}
|
}
|
||||||
|
|
90
app/src/main/cpp/skyline/services/nvdrv/types.h
Normal file
90
app/src/main/cpp/skyline/services/nvdrv/types.h
Normal 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>;
|
||||||
|
}
|
|
@ -17,7 +17,8 @@
|
||||||
#include "glue/IStaticService.h"
|
#include "glue/IStaticService.h"
|
||||||
#include "services/timesrv/core.h"
|
#include "services/timesrv/core.h"
|
||||||
#include "fssrv/IFileSystemProxy.h"
|
#include "fssrv/IFileSystemProxy.h"
|
||||||
#include "services/nvdrv/INvDrvServices.h"
|
#include "nvdrv/INvDrvServices.h"
|
||||||
|
#include "nvdrv/driver.h"
|
||||||
#include "hosbinder/IHOSBinderDriver.h"
|
#include "hosbinder/IHOSBinderDriver.h"
|
||||||
#include "visrv/IApplicationRootService.h"
|
#include "visrv/IApplicationRootService.h"
|
||||||
#include "visrv/ISystemRootService.h"
|
#include "visrv/ISystemRootService.h"
|
||||||
|
@ -46,8 +47,9 @@
|
||||||
namespace skyline::service {
|
namespace skyline::service {
|
||||||
struct GlobalServiceState {
|
struct GlobalServiceState {
|
||||||
timesrv::core::TimeServiceObject timesrv;
|
timesrv::core::TimeServiceObject timesrv;
|
||||||
|
nvdrv::Driver nvdrv;
|
||||||
|
|
||||||
explicit GlobalServiceState(const DeviceState &state) : timesrv(state) {}
|
explicit GlobalServiceState(const DeviceState &state) : timesrv(state), nvdrv(state) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared<sm::IUserInterface>(state, *this)), globalServiceState(std::make_shared<GlobalServiceState>(state)) {}
|
ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared<sm::IUserInterface>(state, *this)), globalServiceState(std::make_shared<GlobalServiceState>(state)) {}
|
||||||
|
@ -73,11 +75,8 @@ namespace skyline::service {
|
||||||
SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetStaticServiceAsRepair(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceRepairPermissions)
|
SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetStaticServiceAsRepair(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceRepairPermissions)
|
||||||
SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetStaticServiceAsUser(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions)
|
SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetStaticServiceAsUser(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions)
|
||||||
SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv")
|
SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv")
|
||||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv")
|
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv", globalServiceState->nvdrv, nvdrv::ApplicationSessionPermissions)
|
||||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:a")
|
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv", globalServiceState->nvdrv.core.nvMap)
|
||||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:s")
|
|
||||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:t")
|
|
||||||
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv")
|
|
||||||
SERVICE_CASE(visrv::IApplicationRootService, "vi:u")
|
SERVICE_CASE(visrv::IApplicationRootService, "vi:u")
|
||||||
SERVICE_CASE(visrv::ISystemRootService, "vi:s")
|
SERVICE_CASE(visrv::ISystemRootService, "vi:s")
|
||||||
SERVICE_CASE(visrv::IManagerRootService, "vi:m")
|
SERVICE_CASE(visrv::IManagerRootService, "vi:m")
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "soc/gmmu.h"
|
|
||||||
#include "soc/host1x.h"
|
#include "soc/host1x.h"
|
||||||
#include "soc/gm20b.h"
|
#include "soc/gm20b.h"
|
||||||
|
|
||||||
|
@ -14,10 +13,9 @@ namespace skyline::soc {
|
||||||
*/
|
*/
|
||||||
class SOC {
|
class SOC {
|
||||||
public:
|
public:
|
||||||
gmmu::GraphicsMemoryManager gmmu;
|
|
||||||
host1x::Host1X host1x;
|
host1x::Host1X host1x;
|
||||||
gm20b::GM20B gm20b;
|
gm20b::GM20B gm20b;
|
||||||
|
|
||||||
SOC(const DeviceState &state) : gmmu(state), gm20b(state) {}
|
SOC(const DeviceState &state) : gm20b(state) {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
20
app/src/main/cpp/skyline/soc/gm20b.cpp
Normal file
20
app/src/main/cpp/skyline/soc/gm20b.cpp
Normal 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) {}
|
||||||
|
}
|
|
@ -3,23 +3,28 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <common/address_space.h>
|
||||||
#include "gm20b/engines/maxwell_3d.h"
|
#include "gm20b/engines/maxwell_3d.h"
|
||||||
#include "gm20b/gpfifo.h"
|
#include "gm20b/gpfifo.h"
|
||||||
|
|
||||||
namespace skyline::soc::gm20b {
|
namespace skyline::soc::gm20b {
|
||||||
/**
|
/**
|
||||||
* @brief The GPU block in the X1, it contains all GPU engines required for accelerating graphics operations
|
* @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 {
|
class GM20B {
|
||||||
public:
|
public:
|
||||||
|
static constexpr u8 AddressSpaceBits{40}; //!< The width of the GMMU AS
|
||||||
|
using GMMU = FlatMemoryManager<u64, 0, AddressSpaceBits>;
|
||||||
|
|
||||||
engine::Engine fermi2D;
|
engine::Engine fermi2D;
|
||||||
engine::maxwell3d::Maxwell3D maxwell3D;
|
engine::maxwell3d::Maxwell3D maxwell3D;
|
||||||
engine::Engine maxwellCompute;
|
engine::Engine maxwellCompute;
|
||||||
engine::Engine maxwellDma;
|
engine::Engine maxwellDma;
|
||||||
engine::Engine keplerMemory;
|
engine::Engine keplerMemory;
|
||||||
GPFIFO gpfifo;
|
GPFIFO gpfifo;
|
||||||
|
GMMU gmmu;
|
||||||
|
|
||||||
GM20B(const DeviceState &state) : fermi2D(state), keplerMemory(state), maxwell3D(state), maxwellCompute(state), maxwellDma(state), gpfifo(state) {}
|
GM20B(const DeviceState &state);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,6 @@ namespace skyline::soc::gm20b {
|
||||||
MaxwellDma = 0xB0B5,
|
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 {
|
namespace engine {
|
||||||
/**
|
/**
|
||||||
* @brief The Engine class provides an interface that can be used to communicate with the GPU's internal engines
|
* @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:
|
public:
|
||||||
Engine(const DeviceState &state) : state(state) {}
|
Engine(const DeviceState &state) : state(state) {}
|
||||||
|
|
||||||
virtual ~Engine() = default;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Calls an engine method with the given parameters
|
* @brief Calls an engine method with the given parameters
|
||||||
*/
|
*/
|
||||||
virtual void CallMethod(MethodParams params) {
|
void CallMethod(u32 method, u32 argument, bool lastCall) {
|
||||||
state.logger->Warn("Called method in unimplemented engine: 0x{:X} args: 0x{:X}", params.method, params.argument);
|
state.logger->Warn("Called method in unimplemented engine: 0x{:X} args: 0x{:X}", method, argument);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,10 +167,10 @@ namespace skyline::soc::gm20b::engine {
|
||||||
public:
|
public:
|
||||||
GPFIFO(const DeviceState &state) : Engine(state) {}
|
GPFIFO(const DeviceState &state) : Engine(state) {}
|
||||||
|
|
||||||
void CallMethod(MethodParams params) override {
|
void CallMethod(u32 method, u32 argument, bool lastCall) {
|
||||||
state.logger->Debug("Called method in GPFIFO: 0x{:X} args: 0x{:X}", params.method, params.argument);
|
state.logger->Debug("Called method in GPFIFO: 0x{:X} args: 0x{:X}", method, argument);
|
||||||
|
|
||||||
registers.raw[params.method] = params.argument;
|
registers.raw[method] = argument;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||||
|
|
||||||
#include <soc/gmmu.h>
|
#include <common/address_space.h>
|
||||||
#include <soc/gm20b/engines/maxwell_3d.h>
|
#include <soc/gm20b/engines/maxwell_3d.h>
|
||||||
|
|
||||||
namespace skyline::soc::gm20b::engine::maxwell3d {
|
namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
|
@ -193,7 +193,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE void MacroInterpreter::Send(u32 pArgument) {
|
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;
|
methodAddress.address += methodAddress.increment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,48 +72,62 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
registers.viewportTransformEnable = true;
|
registers.viewportTransformEnable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Maxwell3D::CallMethod(MethodParams params) {
|
void Maxwell3D::CallMethod(u32 method, u32 argument, bool lastCall) {
|
||||||
state.logger->Debug("Called method in Maxwell 3D: 0x{:X} args: 0x{:X}", params.method, params.argument);
|
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
|
// Methods that are greater than the register size are for macro control
|
||||||
if (params.method > RegisterCount) {
|
if (method > RegisterCount) [[unlikely]] {
|
||||||
if (!(params.method & 1))
|
// Starting a new macro at index 'method - RegisterCount'
|
||||||
macroInvocation.index = ((params.method - RegisterCount) >> 1) % macroPositions.size();
|
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);
|
// Setup for the new macro index
|
||||||
|
macroInvocation.index = ((method - RegisterCount) >> 1) % macroPositions.size();
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
registers.raw[params.method] = params.argument;
|
registers.raw[method] = argument;
|
||||||
|
|
||||||
if (shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodTrack || shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodTrackWithFilter)
|
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)
|
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):
|
case MAXWELL3D_OFFSET(mme.instructionRamLoad):
|
||||||
if (registers.mme.instructionRamPointer >= macroCode.size())
|
if (registers.mme.instructionRamPointer >= macroCode.size())
|
||||||
throw exception("Macro memory is full!");
|
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;
|
break;
|
||||||
case MAXWELL3D_OFFSET(mme.startAddressRamLoad):
|
case MAXWELL3D_OFFSET(mme.startAddressRamLoad):
|
||||||
if (registers.mme.startAddressRamPointer >= macroPositions.size())
|
if (registers.mme.startAddressRamPointer >= macroPositions.size())
|
||||||
throw exception("Maximum amount of macros reached!");
|
throw exception("Maximum amount of macros reached!");
|
||||||
|
|
||||||
macroPositions[registers.mme.startAddressRamPointer++] = params.argument;
|
macroPositions[registers.mme.startAddressRamPointer++] = argument;
|
||||||
break;
|
break;
|
||||||
case MAXWELL3D_OFFSET(mme.shadowRamControl):
|
case MAXWELL3D_OFFSET(mme.shadowRamControl):
|
||||||
shadowRegisters.mme.shadowRamControl = static_cast<Registers::MmeShadowRamControl>(params.argument);
|
shadowRegisters.mme.shadowRamControl = static_cast<Registers::MmeShadowRamControl>(argument);
|
||||||
break;
|
break;
|
||||||
case MAXWELL3D_OFFSET(syncpointAction):
|
case MAXWELL3D_OFFSET(syncpointAction):
|
||||||
state.logger->Debug("Increment syncpoint: {}", static_cast<u16>(registers.syncpointAction.id));
|
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]):
|
case MAXWELL3D_OFFSET(firmwareCall[4]):
|
||||||
registers.raw[0xD00] = 1;
|
registers.raw[0xD00] = 1;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +173,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
|
|
||||||
switch (registers.semaphore.info.structureSize) {
|
switch (registers.semaphore.info.structureSize) {
|
||||||
case Registers::SemaphoreInfo::StructureSize::OneWord:
|
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;
|
break;
|
||||||
case Registers::SemaphoreInfo::StructureSize::FourWords: {
|
case Registers::SemaphoreInfo::StructureSize::FourWords: {
|
||||||
// Convert the current nanosecond time to GPU ticks
|
// Convert the current nanosecond time to GPU ticks
|
||||||
|
@ -167,7 +183,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
u64 nsTime{util::GetTimeNs()};
|
u64 nsTime{util::GetTimeNs()};
|
||||||
u64 timestamp{(nsTime / NsToTickDenominator) * NsToTickNumerator + ((nsTime % NsToTickDenominator) * NsToTickNumerator) / NsToTickDenominator};
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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 {
|
struct {
|
||||||
u32 index;
|
i32 index{-1};
|
||||||
std::vector<u32> arguments;
|
std::vector<u32> arguments;
|
||||||
} macroInvocation{}; //!< Data for a macro that is pending execution
|
} macroInvocation{}; //!< Data for a macro that is pending execution
|
||||||
|
|
||||||
|
@ -557,7 +557,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
Registers registers{};
|
Registers registers{};
|
||||||
Registers shadowRegisters{}; //!< The shadow registers, their function is controlled by the 'shadowRamControl' register
|
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);
|
Maxwell3D(const DeviceState &state);
|
||||||
|
|
||||||
|
@ -566,6 +566,6 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||||
*/
|
*/
|
||||||
void ResetRegs();
|
void ResetRegs();
|
||||||
|
|
||||||
void CallMethod(MethodParams params) override;
|
void CallMethod(u32 method, u32 argument, bool lastCall);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,41 +5,90 @@
|
||||||
#include <loader/loader.h>
|
#include <loader/loader.h>
|
||||||
#include <kernel/types/KProcess.h>
|
#include <kernel/types/KProcess.h>
|
||||||
#include <soc.h>
|
#include <soc.h>
|
||||||
|
#include <os.h>
|
||||||
|
|
||||||
namespace skyline::soc::gm20b {
|
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) {
|
enum class TertOp : u8 {
|
||||||
switch (static_cast<EngineID>(params.argument)) {
|
Grp0IncMethod = 0,
|
||||||
case EngineID::Fermi2D:
|
Grp0SetSubDevMask = 1,
|
||||||
subchannels.at(params.subChannel) = &state.soc->gm20b.fermi2D;
|
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;
|
break;
|
||||||
case EngineID::KeplerMemory:
|
case ComputeSubChannel:
|
||||||
subchannels.at(params.subChannel) = &state.soc->gm20b.keplerMemory;
|
state.soc->gm20b.maxwellCompute.CallMethod(method, argument, lastCall);
|
||||||
break;
|
break;
|
||||||
case EngineID::Maxwell3D:
|
case Inline2MemorySubChannel:
|
||||||
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwell3D;
|
state.soc->gm20b.keplerMemory.CallMethod(method, argument, lastCall);
|
||||||
break;
|
break;
|
||||||
case EngineID::MaxwellCompute:
|
case TwoDSubChannel:
|
||||||
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwellCompute;
|
state.soc->gm20b.fermi2D.CallMethod(method, argument, lastCall);
|
||||||
break;
|
break;
|
||||||
case EngineID::MaxwellDma:
|
case CopySubChannel:
|
||||||
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwellDma;
|
state.soc->gm20b.maxwellDma.CallMethod(method, argument, lastCall);
|
||||||
break;
|
break;
|
||||||
default:
|
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);
|
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++) {
|
for (auto entry{pushBufferData.begin()}; entry != pushBufferData.end(); entry++) {
|
||||||
// An entry containing all zeroes is a NOP, skip over it
|
// An entry containing all zeroes is a NOP, skip over it
|
||||||
|
@ -66,30 +115,29 @@ namespace skyline::soc::gm20b {
|
||||||
PushBufferMethodHeader methodHeader{.raw = *entry};
|
PushBufferMethodHeader methodHeader{.raw = *entry};
|
||||||
switch (methodHeader.secOp) {
|
switch (methodHeader.secOp) {
|
||||||
case PushBufferMethodHeader::SecOp::IncMethod:
|
case PushBufferMethodHeader::SecOp::IncMethod:
|
||||||
for (u16 i{}; i < methodHeader.methodCount; i++)
|
for (u32 i{}; i < methodHeader.methodCount; i++)
|
||||||
Send(MethodParams{static_cast<u16>(methodHeader.methodAddress + i), *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
|
Send(methodHeader.methodAddress + i, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PushBufferMethodHeader::SecOp::NonIncMethod:
|
case PushBufferMethodHeader::SecOp::NonIncMethod:
|
||||||
for (u16 i{}; i < methodHeader.methodCount; i++)
|
for (u32 i{}; i < methodHeader.methodCount; i++)
|
||||||
Send(MethodParams{methodHeader.methodAddress, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
|
Send(methodHeader.methodAddress, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PushBufferMethodHeader::SecOp::OneInc:
|
case PushBufferMethodHeader::SecOp::OneInc:
|
||||||
for (u16 i{}; i < methodHeader.methodCount; i++)
|
for (u32 i{}; i < methodHeader.methodCount; i++)
|
||||||
Send(MethodParams{static_cast<u16>(methodHeader.methodAddress + static_cast<bool>(i)), *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
|
Send(methodHeader.methodAddress + !!i, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PushBufferMethodHeader::SecOp::ImmdDataMethod:
|
case PushBufferMethodHeader::SecOp::ImmdDataMethod:
|
||||||
Send(MethodParams{methodHeader.methodAddress, methodHeader.immdData, methodHeader.methodSubChannel, true});
|
Send(methodHeader.methodAddress, methodHeader.immdData, methodHeader.methodSubChannel, true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PushBufferMethodHeader::SecOp::EndPbSegment:
|
case PushBufferMethodHeader::SecOp::EndPbSegment:
|
||||||
return;
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
state.logger->Warn("Unsupported pushbuffer method SecOp: {}", static_cast<u8>(methodHeader.secOp));
|
throw exception("Unsupported pushbuffer method SecOp: {}", static_cast<u8>(methodHeader.secOp));
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +154,7 @@ namespace skyline::soc::gm20b {
|
||||||
try {
|
try {
|
||||||
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler);
|
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler);
|
||||||
pushBuffers->Process([this](GpEntry gpEntry) {
|
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);
|
Process(gpEntry);
|
||||||
});
|
});
|
||||||
} catch (const signal::SignalException &e) {
|
} catch (const signal::SignalException &e) {
|
||||||
|
|
|
@ -72,56 +72,6 @@ namespace skyline::soc::gm20b {
|
||||||
};
|
};
|
||||||
static_assert(sizeof(GpEntry) == sizeof(u64));
|
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
|
* @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
|
* @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
|
* @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
|
* @brief Processes the pushbuffer contained within the given GpEntry, calling methods as needed
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
#include <common.h>
|
#include <common.h>
|
||||||
|
|
||||||
namespace skyline::vfs {
|
namespace skyline::vfs {
|
||||||
|
@ -82,7 +83,7 @@ namespace skyline::vfs {
|
||||||
/**
|
/**
|
||||||
* @brief Implicit casting for reading into spans of different types
|
* @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) {
|
size_t Read(span <T> output, size_t offset = 0) {
|
||||||
return Read(output.template cast<u8>(), offset);
|
return Read(output.template cast<u8>(), offset);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue