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/texture/texture.cpp
|
||||
${source_DIR}/skyline/gpu/presentation_engine.cpp
|
||||
${source_DIR}/skyline/soc/gmmu.cpp
|
||||
${source_DIR}/skyline/soc/gm20b.cpp
|
||||
${source_DIR}/skyline/soc/host1x/syncpoint.cpp
|
||||
${source_DIR}/skyline/soc/gm20b/gpfifo.cpp
|
||||
${source_DIR}/skyline/soc/gm20b/engines/maxwell_3d.cpp
|
||||
|
@ -178,13 +178,14 @@ add_library(skyline SHARED
|
|||
${source_DIR}/skyline/services/fssrv/IDirectory.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/INvDrvServices.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/driver.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/core/nvmap.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/core/syncpoint_manager.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvdevice.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvmap.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_ctrl_gpu.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_ctrl.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_channel.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_as_gpu.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost_syncpoint.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost/as_gpu.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost/ctrl.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost/ctrl_gpu.cpp
|
||||
${source_DIR}/skyline/services/nvdrv/devices/nvhost/gpu_channel.cpp
|
||||
${source_DIR}/skyline/services/hosbinder/parcel.cpp
|
||||
${source_DIR}/skyline/services/hosbinder/IHOSBinderDriver.cpp
|
||||
${source_DIR}/skyline/services/hosbinder/GraphicBufferProducer.cpp
|
||||
|
|
|
@ -83,26 +83,25 @@ namespace skyline {
|
|||
|
||||
/**
|
||||
* @brief A wrapper around std::optional that also stores a HOS result code
|
||||
* @tparam T The object type to hold
|
||||
*/
|
||||
template<typename T>
|
||||
template<typename ValueType, typename ResultType = Result>
|
||||
class ResultValue {
|
||||
static_assert(!std::is_same<T, Result>::value);
|
||||
static_assert(!std::is_same<ValueType, ResultType>::value);
|
||||
|
||||
private:
|
||||
std::optional<T> value;
|
||||
std::optional<ValueType> value;
|
||||
|
||||
public:
|
||||
Result result;
|
||||
ResultType result{};
|
||||
|
||||
constexpr ResultValue(T value) : value(value) {};
|
||||
constexpr ResultValue(ValueType value) : value(value) {};
|
||||
|
||||
constexpr ResultValue(Result result) : result(result) {};
|
||||
constexpr ResultValue(ResultType result) : result(result) {};
|
||||
|
||||
template<typename U>
|
||||
constexpr ResultValue(ResultValue<U> result) : result(result) {};
|
||||
|
||||
constexpr operator Result() const {
|
||||
constexpr operator ResultType() const {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -110,11 +109,11 @@ namespace skyline {
|
|||
return value.has_value();
|
||||
}
|
||||
|
||||
constexpr T& operator*() {
|
||||
constexpr ValueType& operator*() {
|
||||
return *value;
|
||||
}
|
||||
|
||||
constexpr T* operator->() {
|
||||
constexpr ValueType* operator->() {
|
||||
return &*value;
|
||||
}
|
||||
};
|
||||
|
@ -362,6 +361,9 @@ namespace skyline {
|
|||
|
||||
template<typename Out>
|
||||
constexpr Out &as() {
|
||||
if constexpr (Extent != std::dynamic_extent && sizeof(T) * Extent >= sizeof(Out))
|
||||
return *reinterpret_cast<Out *>(span::data());
|
||||
|
||||
if (span::size_bytes() >= sizeof(Out))
|
||||
return *reinterpret_cast<Out *>(span::data());
|
||||
throw exception("Span size is less than Out type size (0x{:X}/0x{:X})", span::size_bytes(), sizeof(Out));
|
||||
|
|
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
|
||||
|
||||
#include <common/trace.h>
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline {
|
||||
|
@ -51,10 +52,15 @@ namespace skyline {
|
|||
*/
|
||||
template<typename F>
|
||||
[[noreturn]] void Process(F function) {
|
||||
TRACE_EVENT_BEGIN("containers", "CircularQueue::Process");
|
||||
|
||||
while (true) {
|
||||
if (start == end) {
|
||||
std::unique_lock lock(productionMutex);
|
||||
|
||||
TRACE_EVENT_END("containers");
|
||||
produceCondition.wait(lock, [this]() { return start != end; });
|
||||
TRACE_EVENT_BEGIN("containers", "CircularQueue::Process");
|
||||
}
|
||||
|
||||
while (start != end) {
|
||||
|
|
|
@ -13,7 +13,8 @@ PERFETTO_DEFINE_CATEGORIES(
|
|||
perfetto::Category("kernel").SetDescription("Events from parts of the HLE kernel"),
|
||||
perfetto::Category("guest").SetDescription("Events relating to guest code"),
|
||||
perfetto::Category("gpu").SetDescription("Events from the emulated GPU"),
|
||||
perfetto::Category("service").SetDescription("Events from the HLE sysmodule implementations")
|
||||
perfetto::Category("service").SetDescription("Events from the HLE sysmodule implementations"),
|
||||
perfetto::Category("containers").SetDescription("Events from custom container implementations")
|
||||
);
|
||||
|
||||
namespace skyline::trace {
|
||||
|
|
|
@ -12,8 +12,6 @@ namespace skyline::kernel::ipc {
|
|||
header = reinterpret_cast<CommandHeader *>(pointer);
|
||||
pointer += sizeof(CommandHeader);
|
||||
|
||||
size_t cBufferLengthSize{util::AlignUp(((header->cFlag == BufferCFlag::None) ? 0 : ((header->cFlag > BufferCFlag::SingleDescriptor) ? (static_cast<u8>(header->cFlag) - 2) : 1)) * sizeof(u16), sizeof(u32))};
|
||||
|
||||
if (header->handleDesc) {
|
||||
handleDesc = reinterpret_cast<HandleDescriptor *>(pointer);
|
||||
pointer += sizeof(HandleDescriptor) + (handleDesc->sendPid ? sizeof(u64) : 0);
|
||||
|
@ -64,6 +62,8 @@ namespace skyline::kernel::ipc {
|
|||
pointer += sizeof(BufferDescriptorABW);
|
||||
}
|
||||
|
||||
auto bufCPointer{pointer + header->rawSize * sizeof(u32)};
|
||||
|
||||
auto offset{pointer - tls}; // We calculate the relative offset as the absolute one might differ
|
||||
auto padding{util::AlignUp(offset, constant::IpcPaddingSum) - offset}; // Calculate the amount of padding at the front
|
||||
pointer += padding;
|
||||
|
@ -88,8 +88,7 @@ namespace skyline::kernel::ipc {
|
|||
pointer += sizeof(PayloadHeader);
|
||||
|
||||
cmdArg = pointer;
|
||||
cmdArgSz = (header->rawSize * sizeof(u32)) - (constant::IpcPaddingSum + sizeof(PayloadHeader)) - cBufferLengthSize;
|
||||
pointer += cmdArgSz;
|
||||
cmdArgSz = header->rawSize * sizeof(u32);
|
||||
}
|
||||
|
||||
payloadOffset = cmdArg;
|
||||
|
@ -97,22 +96,21 @@ namespace skyline::kernel::ipc {
|
|||
if (payload->magic != util::MakeMagic<u32>("SFCI") && (header->type != CommandType::Control && header->type != CommandType::ControlWithContext && header->type != CommandType::Close) && (!domain || domain->command != DomainCommand::CloseVHandle)) // SFCI is the magic in received IPC messages
|
||||
state.logger->Debug("Unexpected Magic in PayloadHeader: 0x{:X}", static_cast<u32>(payload->magic));
|
||||
|
||||
pointer += constant::IpcPaddingSum - padding + cBufferLengthSize;
|
||||
|
||||
if (header->cFlag == BufferCFlag::SingleDescriptor) {
|
||||
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
|
||||
auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)};
|
||||
if (bufC->address) {
|
||||
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
|
||||
state.logger->Verbose("Buf C: 0x{:X}, 0x{:X}", bufC->Pointer(), static_cast<u16>(bufC->size));
|
||||
}
|
||||
} else if (header->cFlag > BufferCFlag::SingleDescriptor) {
|
||||
for (u8 index{}; (static_cast<u8>(header->cFlag) - 2) > index; index++) { // (cFlag - 2) C descriptors are present
|
||||
auto bufC{reinterpret_cast<BufferDescriptorC *>(pointer)};
|
||||
auto bufC{reinterpret_cast<BufferDescriptorC *>(bufCPointer)};
|
||||
if (bufC->address) {
|
||||
outputBuf.emplace_back(bufC->Pointer(), static_cast<u16>(bufC->size));
|
||||
state.logger->Verbose("Buf C #{}: 0x{:X}, 0x{:X}", index, bufC->Pointer(), static_cast<u16>(bufC->size));
|
||||
}
|
||||
pointer += sizeof(BufferDescriptorC);
|
||||
bufCPointer += sizeof(BufferDescriptorC);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace skyline::kernel::type {
|
|||
if (fd < 0)
|
||||
throw exception("An error occurred while creating shared memory: {}", fd);
|
||||
|
||||
host.ptr = reinterpret_cast<u8 *>(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0));
|
||||
host.ptr = static_cast<u8 *>(mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0));
|
||||
if (host.ptr == MAP_FAILED)
|
||||
throw exception("An occurred while mapping shared memory: {}", strerror(errno));
|
||||
|
||||
|
@ -28,7 +28,7 @@ namespace skyline::kernel::type {
|
|||
if (guest.Valid())
|
||||
throw exception("Mapping KSharedMemory multiple times on guest is not supported: Requested Mapping: 0x{:X} - 0x{:X} (0x{:X}), Current Mapping: 0x{:X} - 0x{:X} (0x{:X})", ptr, ptr + size, size, guest.ptr, guest.ptr + guest.size, guest.size);
|
||||
|
||||
guest.ptr = reinterpret_cast<u8 *>(mmap(ptr, size, permission.Get(), MAP_SHARED | (ptr ? MAP_FIXED : 0), fd, 0));
|
||||
guest.ptr = static_cast<u8 *>(mmap(ptr, size, permission.Get(), MAP_SHARED | (ptr ? MAP_FIXED : 0), fd, 0));
|
||||
if (guest.ptr == MAP_FAILED)
|
||||
throw exception("An error occurred while mapping shared memory in guest: {}", strerror(errno));
|
||||
guest.size = size;
|
||||
|
|
|
@ -14,11 +14,12 @@ namespace skyline::service::am {
|
|||
|
||||
Result IStorageAccessor::Write(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto offset{request.Pop<i64>()};
|
||||
auto size{std::min(static_cast<i64>(request.inputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
|
||||
|
||||
if (offset > parent->content.size())
|
||||
if (offset < 0 || offset > parent->content.size())
|
||||
return result::OutOfBounds;
|
||||
|
||||
auto size{std::min(static_cast<i64>(request.inputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
|
||||
|
||||
if (size)
|
||||
span(parent->content).copy_from(request.inputBuf.at(0), size);
|
||||
|
||||
|
@ -27,12 +28,13 @@ namespace skyline::service::am {
|
|||
|
||||
Result IStorageAccessor::Read(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto offset{request.Pop<i64>()};
|
||||
auto size{std::min(static_cast<i64>(request.outputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
|
||||
|
||||
if (offset > parent->content.size())
|
||||
if (offset < 0 || offset > parent->content.size())
|
||||
return result::OutOfBounds;
|
||||
|
||||
if (size > 0)
|
||||
auto size{std::min(static_cast<i64>(request.outputBuf.at(0).size()), static_cast<i64>(parent->content.size()) - offset)};
|
||||
|
||||
if (size)
|
||||
request.outputBuf.at(0).copy_from(span(parent->content.data() + offset, size));
|
||||
|
||||
return {};
|
||||
|
|
|
@ -3,22 +3,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <services/nvdrv/devices/nvhost_syncpoint.h>
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
/**
|
||||
* @brief A Fence is a synchronization primitive that describes a point in a Syncpoint to synchronize at
|
||||
*/
|
||||
struct Fence {
|
||||
u32 id{}; //!< The ID of the underlying syncpoint
|
||||
u32 value{}; //!< The value of the syncpoint at which the fence is passed
|
||||
|
||||
/**
|
||||
* @brief Synchronizes the fence's value with its underlying syncpoint
|
||||
*/
|
||||
void UpdateValue(NvHostSyncpoint &hostSyncpoint) {
|
||||
value = hostSyncpoint.UpdateMin(id);
|
||||
}
|
||||
u32 threshold{}; //!< The value of the syncpoint at which the fence is signalled
|
||||
};
|
||||
static_assert(sizeof(Fence) == 0x8);
|
||||
}
|
||||
|
|
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/texture/format.h>
|
||||
#include <soc.h>
|
||||
#include <services/nvdrv/driver.h>
|
||||
#include <services/nvdrv/devices/nvmap.h>
|
||||
#include <services/common/fence.h>
|
||||
#include "GraphicBufferProducer.h"
|
||||
|
||||
namespace skyline::service::hosbinder {
|
||||
GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state) : state(state), bufferEvent(std::make_shared<kernel::type::KEvent>(state, true)) {}
|
||||
GraphicBufferProducer::GraphicBufferProducer(const DeviceState &state, nvdrv::core::NvMap &nvMap) : state(state), bufferEvent(std::make_shared<kernel::type::KEvent>(state, true)), nvMap(nvMap) {}
|
||||
|
||||
void GraphicBufferProducer::FreeGraphicBufferNvMap(GraphicBuffer &buffer) {
|
||||
auto surface{buffer.graphicHandle.surfaces.at(0)};
|
||||
u32 nvMapHandleId{surface.nvmapHandle ? surface.nvmapHandle : buffer.graphicHandle.nvmapId};
|
||||
nvMap.FreeHandle(nvMapHandleId, true);
|
||||
}
|
||||
|
||||
u32 GraphicBufferProducer::GetPendingBufferCount() {
|
||||
u32 count{};
|
||||
|
@ -64,6 +69,12 @@ namespace skyline::service::hosbinder {
|
|||
for (auto &slot : queue) {
|
||||
slot.state = BufferState::Free;
|
||||
slot.frameNumber = std::numeric_limits<u32>::max();
|
||||
|
||||
if (slot.texture) {
|
||||
slot.texture = {};
|
||||
FreeGraphicBufferNvMap(*slot.graphicBuffer);
|
||||
}
|
||||
|
||||
slot.graphicBuffer = nullptr;
|
||||
}
|
||||
} else if (preallocatedBufferCount < count) {
|
||||
|
@ -160,6 +171,12 @@ namespace skyline::service::hosbinder {
|
|||
|
||||
bufferSlot.state = BufferState::Free;
|
||||
bufferSlot.frameNumber = std::numeric_limits<u32>::max();
|
||||
|
||||
if (bufferSlot.texture) {
|
||||
bufferSlot.texture = {};
|
||||
FreeGraphicBufferNvMap(*bufferSlot.graphicBuffer);
|
||||
}
|
||||
|
||||
bufferSlot.graphicBuffer = nullptr;
|
||||
|
||||
bufferEvent->Signal();
|
||||
|
@ -183,6 +200,12 @@ namespace skyline::service::hosbinder {
|
|||
|
||||
bufferSlot->state = BufferState::Free;
|
||||
bufferSlot->frameNumber = std::numeric_limits<u32>::max();
|
||||
|
||||
if (bufferSlot->texture) {
|
||||
bufferSlot->texture = {};
|
||||
FreeGraphicBufferNvMap(*bufferSlot->graphicBuffer);
|
||||
}
|
||||
|
||||
graphicBuffer = *std::exchange(bufferSlot->graphicBuffer, nullptr);
|
||||
fence = AndroidFence{};
|
||||
|
||||
|
@ -202,6 +225,11 @@ namespace skyline::service::hosbinder {
|
|||
}
|
||||
}
|
||||
|
||||
if (bufferSlot->texture) {
|
||||
bufferSlot->texture = {};
|
||||
FreeGraphicBufferNvMap(*bufferSlot->graphicBuffer);
|
||||
}
|
||||
|
||||
if (bufferSlot == queue.end()) {
|
||||
state.logger->Warn("Could not find any free slots to attach the graphic buffer to");
|
||||
return AndroidStatus::NoMemory;
|
||||
|
@ -304,27 +332,13 @@ namespace skyline::service::hosbinder {
|
|||
if (surface.scanFormat != NvDisplayScanFormat::Progressive)
|
||||
throw exception("Non-Progressive surfaces are not supported: {}", ToString(surface.scanFormat));
|
||||
|
||||
std::shared_ptr<nvdrv::device::NvMap::NvMapObject> nvBuffer{};
|
||||
{
|
||||
auto driver{nvdrv::driver.lock()};
|
||||
auto nvmap{driver->nvMap.lock()};
|
||||
if (surface.nvmapHandle) {
|
||||
nvBuffer = nvmap->GetObject(surface.nvmapHandle);
|
||||
} else {
|
||||
std::shared_lock nvmapLock(nvmap->mapMutex);
|
||||
for (const auto &object : nvmap->maps) {
|
||||
if (object->id == handle.nvmapId) {
|
||||
nvBuffer = object;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!nvBuffer)
|
||||
throw exception("A QueueBuffer request has an invalid NvMap Handle ({}) and ID ({})", surface.nvmapHandle, handle.nvmapId);
|
||||
}
|
||||
}
|
||||
// Duplicate the handle so it can't be freed by the guest
|
||||
auto nvMapHandleObj{nvMap.GetHandle(surface.nvmapHandle ? surface.nvmapHandle : handle.nvmapId)};
|
||||
if (auto err{nvMapHandleObj->Duplicate(true)}; err != PosixResult::Success)
|
||||
throw exception("Failed to duplicate graphic buffer NvMap handle: {}!", static_cast<i32>(err));
|
||||
|
||||
if (surface.size > (nvBuffer->size - surface.offset))
|
||||
throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvBuffer->size, surface.offset, surface.offset + surface.size);
|
||||
if (surface.size > (nvMapHandleObj->origSize - surface.offset))
|
||||
throw exception("Surface doesn't fit into NvMap mapping of size 0x{:X} when mapped at 0x{:X} -> 0x{:X}", nvMapHandleObj->origSize, surface.offset, surface.offset + surface.size);
|
||||
|
||||
gpu::texture::TileMode tileMode;
|
||||
gpu::texture::TileConfig tileConfig{};
|
||||
|
@ -344,7 +358,7 @@ namespace skyline::service::hosbinder {
|
|||
throw exception("Legacy 16Bx16 tiled surfaces are not supported");
|
||||
}
|
||||
|
||||
auto guestTexture{std::make_shared<gpu::GuestTexture>(state, nvBuffer->ptr + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)};
|
||||
auto guestTexture{std::make_shared<gpu::GuestTexture>(state, nvMapHandleObj->GetPointer() + surface.offset, gpu::texture::Dimensions(surface.width, surface.height), format, tileMode, tileConfig)};
|
||||
buffer.texture = guestTexture->CreateTexture({}, vk::ImageTiling::eLinear);
|
||||
}
|
||||
|
||||
|
@ -529,6 +543,12 @@ namespace skyline::service::hosbinder {
|
|||
for (auto &slot : queue) {
|
||||
slot.state = BufferState::Free;
|
||||
slot.frameNumber = std::numeric_limits<u32>::max();
|
||||
|
||||
if (slot.texture) {
|
||||
slot.texture = {};
|
||||
FreeGraphicBufferNvMap(*slot.graphicBuffer);
|
||||
}
|
||||
|
||||
slot.graphicBuffer = nullptr;
|
||||
}
|
||||
|
||||
|
@ -544,12 +564,16 @@ namespace skyline::service::hosbinder {
|
|||
}
|
||||
|
||||
auto &buffer{queue[slot]};
|
||||
if (buffer.texture) {
|
||||
buffer.texture = {};
|
||||
FreeGraphicBufferNvMap(*buffer.graphicBuffer);
|
||||
}
|
||||
|
||||
buffer.state = BufferState::Free;
|
||||
buffer.frameNumber = 0;
|
||||
buffer.wasBufferRequested = false;
|
||||
buffer.isPreallocated = graphicBuffer != nullptr;
|
||||
buffer.graphicBuffer = graphicBuffer ? std::make_unique<GraphicBuffer>(*graphicBuffer) : nullptr;
|
||||
buffer.texture = {};
|
||||
|
||||
if (graphicBuffer) {
|
||||
if (graphicBuffer->magic != GraphicBuffer::Magic)
|
||||
|
|
|
@ -14,6 +14,10 @@ namespace skyline::gpu {
|
|||
class Texture;
|
||||
}
|
||||
|
||||
namespace skyline::service::nvdrv::core {
|
||||
class NvMap;
|
||||
}
|
||||
|
||||
namespace skyline::service::hosbinder {
|
||||
/**
|
||||
* @url https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h;l=52-91
|
||||
|
@ -66,6 +70,9 @@ namespace skyline::service::hosbinder {
|
|||
AndroidPixelFormat defaultFormat{AndroidPixelFormat::RGBA8888}; //!< The assumed format of a buffer if none is supplied in DequeueBuffer
|
||||
NativeWindowApi connectedApi{NativeWindowApi::None}; //!< The API that the producer is currently connected to
|
||||
u64 frameNumber{}; //!< The amount of frames that have been presented so far
|
||||
nvdrv::core::NvMap &nvMap;
|
||||
|
||||
void FreeGraphicBufferNvMap(GraphicBuffer &buffer);
|
||||
|
||||
/**
|
||||
* @return The amount of buffers which have been queued onto the consumer
|
||||
|
@ -180,7 +187,7 @@ namespace skyline::service::hosbinder {
|
|||
SetPreallocatedBuffer = 14, //!< A transaction specific to HOS, see the implementation for a description of its functionality
|
||||
};
|
||||
|
||||
GraphicBufferProducer(const DeviceState &state);
|
||||
GraphicBufferProducer(const DeviceState &state, nvdrv::core::NvMap &nvmap);
|
||||
|
||||
/**
|
||||
* @brief The handler for Binder IPC transactions with IGraphicBufferProducer
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "GraphicBufferProducer.h"
|
||||
|
||||
namespace skyline::service::hosbinder {
|
||||
IHOSBinderDriver::IHOSBinderDriver(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {}
|
||||
IHOSBinderDriver::IHOSBinderDriver(const DeviceState &state, ServiceManager &manager, nvdrv::core::NvMap &nvMap) : BaseService(state, manager), nvMap(nvMap) {}
|
||||
|
||||
Result IHOSBinderDriver::TransactParcel(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
// We opted for just supporting a single layer and display as it's what basically all games use and wasting cycles on it is pointless
|
||||
|
@ -122,7 +122,7 @@ namespace skyline::service::hosbinder {
|
|||
|
||||
layerStrongReferenceCount = InitialStrongReferenceCount;
|
||||
layerWeakReferenceCount = 0;
|
||||
layer.emplace(state);
|
||||
layer.emplace(state, nvMap);
|
||||
|
||||
return DefaultLayerId;
|
||||
}
|
||||
|
|
|
@ -42,8 +42,10 @@ namespace skyline::service::hosbinder {
|
|||
constexpr static u32 DefaultBinderLayerHandle{1}; //!< The handle as assigned by SurfaceFlinger of the default layer
|
||||
std::optional<GraphicBufferProducer> layer; //!< The IGraphicBufferProducer backing the layer (NativeWindow)
|
||||
|
||||
nvdrv::core::NvMap &nvMap;
|
||||
|
||||
public:
|
||||
IHOSBinderDriver(const DeviceState &state, ServiceManager &manager);
|
||||
IHOSBinderDriver(const DeviceState &state, ServiceManager &manager, nvdrv::core::NvMap &nvMap);
|
||||
|
||||
/**
|
||||
* @brief Emulates the transaction of parcels between a IGraphicBufferProducer and the application
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace skyline::service::hosbinder {
|
|||
throw exception("Wait has larger fence count ({}) than storage size ({})", fenceCount, fences.size());
|
||||
for (auto it{fences.begin()}, end{fences.begin() + fenceCount}; it < end; it++)
|
||||
if (it->id != InvalidFenceId)
|
||||
host1x.syncpoints.at(it->id).Wait(it->value, std::chrono::steady_clock::duration::max());
|
||||
host1x.syncpoints.at(it->id).Wait(it->threshold, std::chrono::steady_clock::duration::max());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,77 +1,89 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <numeric>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include "INvDrvServices.h"
|
||||
#include "driver.h"
|
||||
#include "devices/nvdevice.h"
|
||||
|
||||
#define NVRESULT(x) [&response, this](NvResult err) { \
|
||||
if (err != NvResult::Success) \
|
||||
state.logger->Debug("IOCTL Failed: {}", err); \
|
||||
\
|
||||
response.Push<NvResult>(err); \
|
||||
return Result{}; \
|
||||
} (x)
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager) : driver(nvdrv::driver.expired() ? std::make_shared<Driver>(state) : nvdrv::driver.lock()), BaseService(state, manager) {
|
||||
if (nvdrv::driver.expired())
|
||||
nvdrv::driver = driver;
|
||||
}
|
||||
INvDrvServices::INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms) : BaseService(state, manager), driver(driver), ctx(SessionContext{.perms = perms}) {}
|
||||
|
||||
Result INvDrvServices::Open(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto path{request.inputBuf.at(0).as_string()};
|
||||
constexpr FileDescriptor SessionFdLimit{std::numeric_limits<u64>::digits * 2}; //!< Nvdrv uses two 64 bit variables to store a bitset
|
||||
|
||||
response.Push<u32>(driver->OpenDevice(path));
|
||||
response.Push(device::NvStatus::Success);
|
||||
auto path{request.inputBuf.at(0).as_string(true)};
|
||||
if (path.empty() || nextFdIndex == SessionFdLimit) {
|
||||
response.Push<FileDescriptor>(InvalidFileDescriptor);
|
||||
return NVRESULT(NvResult::FileOperationFailed);
|
||||
}
|
||||
|
||||
return {};
|
||||
if (auto err{driver.OpenDevice(path, nextFdIndex, ctx)}; err != NvResult::Success) {
|
||||
response.Push<FileDescriptor>(InvalidFileDescriptor);
|
||||
return NVRESULT(err);
|
||||
}
|
||||
|
||||
response.Push(nextFdIndex++);
|
||||
return NVRESULT(NvResult::Success);
|
||||
}
|
||||
|
||||
static NvResultValue<span<u8>> GetMainIoctlBuffer(IoctlDescriptor ioctl, span<u8> inBuf, span<u8> outBuf) {
|
||||
if (ioctl.in && inBuf.size() < ioctl.size)
|
||||
return NvResult::InvalidSize;
|
||||
|
||||
if (ioctl.out && outBuf.size() < ioctl.size)
|
||||
return NvResult::InvalidSize;
|
||||
|
||||
if (ioctl.in && ioctl.out) {
|
||||
if (outBuf.size() < inBuf.size())
|
||||
return NvResult::InvalidSize;
|
||||
|
||||
// Copy in buf to out buf for inout ioctls to avoid needing to pass around two buffers everywhere
|
||||
if (outBuf.data() != inBuf.data())
|
||||
outBuf.copy_from(inBuf, ioctl.size);
|
||||
}
|
||||
|
||||
return ioctl.out ? outBuf : inBuf;
|
||||
}
|
||||
|
||||
Result INvDrvServices::Ioctl(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto fd{request.Pop<u32>()};
|
||||
auto cmd{request.Pop<u32>()};
|
||||
auto fd{request.Pop<FileDescriptor>()};
|
||||
auto ioctl{request.Pop<IoctlDescriptor>()};
|
||||
|
||||
auto device{driver->GetDevice(fd)};
|
||||
|
||||
// Strip the permissions from the command leaving only the ID
|
||||
cmd &= 0xFFFF;
|
||||
|
||||
span<u8> buffer{};
|
||||
if (request.inputBuf.empty() || request.outputBuf.empty()) {
|
||||
if (!request.inputBuf.empty())
|
||||
buffer = request.inputBuf.at(0);
|
||||
else if (!request.outputBuf.empty())
|
||||
buffer = request.outputBuf.at(0);
|
||||
else
|
||||
throw exception("No IOCTL Buffers");
|
||||
} else if (request.inputBuf[0].data() == request.outputBuf[0].data()) {
|
||||
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
|
||||
buffer = request.inputBuf[0];
|
||||
else
|
||||
buffer = request.outputBuf[0];
|
||||
} else {
|
||||
throw exception("IOCTL Input Buffer (0x{:X}) != Output Buffer (0x{:X})", request.inputBuf[0].data(), request.outputBuf[0].data());
|
||||
}
|
||||
|
||||
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl, buffer, {}));
|
||||
return {};
|
||||
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
|
||||
if (!buf)
|
||||
return NVRESULT(buf);
|
||||
else
|
||||
return NVRESULT(driver.Ioctl(fd, ioctl, *buf));
|
||||
}
|
||||
|
||||
Result INvDrvServices::Close(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto fd{request.Pop<u32>()};
|
||||
state.logger->Debug("Closing NVDRV device ({})", fd);
|
||||
|
||||
driver->CloseDevice(fd);
|
||||
driver.CloseDevice(fd);
|
||||
|
||||
response.Push(device::NvStatus::Success);
|
||||
return {};
|
||||
return NVRESULT(NvResult::Success);
|
||||
}
|
||||
|
||||
Result INvDrvServices::Initialize(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(device::NvStatus::Success);
|
||||
return {};
|
||||
return NVRESULT(NvResult::Success);
|
||||
}
|
||||
|
||||
Result INvDrvServices::QueryEvent(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto fd{request.Pop<u32>()};
|
||||
auto eventId{request.Pop<u32>()};
|
||||
|
||||
auto device{driver->GetDevice(fd)};
|
||||
auto event{device->QueryEvent(eventId)};
|
||||
auto event{driver.QueryEvent(fd, eventId)};
|
||||
|
||||
if (event != nullptr) {
|
||||
auto handle{state.process->InsertItem<type::KEvent>(event)};
|
||||
|
@ -79,71 +91,45 @@ namespace skyline::service::nvdrv {
|
|||
state.logger->Debug("FD: {}, Event ID: {}, Handle: 0x{:X}", fd, eventId, handle);
|
||||
response.copyHandles.push_back(handle);
|
||||
|
||||
response.Push(device::NvStatus::Success);
|
||||
return NVRESULT(NvResult::Success);
|
||||
} else {
|
||||
response.Push(device::NvStatus::BadValue);
|
||||
return NVRESULT(NvResult::BadValue);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
response.Push(device::NvStatus::Success);
|
||||
return {};
|
||||
}
|
||||
|
||||
Result INvDrvServices::Ioctl2(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto fd{request.Pop<u32>()};
|
||||
auto cmd{request.Pop<u32>()};
|
||||
auto fd{request.Pop<FileDescriptor>()};
|
||||
auto ioctl{request.Pop<IoctlDescriptor>()};
|
||||
|
||||
auto device{driver->GetDevice(fd)};
|
||||
// Inline buffer is optional
|
||||
auto inlineBuf{request.inputBuf.size() > 1 ? request.inputBuf.at(1) : span<u8>{}};
|
||||
|
||||
// Strip the permissions from the command leaving only the ID
|
||||
cmd &= 0xFFFF;
|
||||
|
||||
if (request.inputBuf.size() < 2 || request.outputBuf.empty())
|
||||
throw exception("Inadequate amount of buffers for IOCTL2: I - {}, O - {}", request.inputBuf.size(), request.outputBuf.size());
|
||||
else if (request.inputBuf[0].data() != request.outputBuf[0].data())
|
||||
throw exception("IOCTL2 Input Buffer (0x{:X}) != Output Buffer (0x{:X}) [Input Buffer #2: 0x{:X}]", request.inputBuf[0].data(), request.outputBuf[0].data(), request.inputBuf[1].data());
|
||||
|
||||
span<u8> buffer{};
|
||||
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
|
||||
buffer = request.inputBuf[0];
|
||||
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
|
||||
if (!buf)
|
||||
return NVRESULT(buf);
|
||||
else
|
||||
buffer = request.outputBuf[0];
|
||||
|
||||
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl2, buffer, request.inputBuf[1]));
|
||||
return {};
|
||||
return NVRESULT(driver.Ioctl2(fd, ioctl, *buf, inlineBuf));
|
||||
}
|
||||
|
||||
Result INvDrvServices::Ioctl3(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
auto fd{request.Pop<u32>()};
|
||||
auto cmd{request.Pop<u32>()};
|
||||
auto fd{request.Pop<FileDescriptor>()};
|
||||
auto ioctl{request.Pop<IoctlDescriptor>()};
|
||||
|
||||
auto device{driver->GetDevice(fd)};
|
||||
// Inline buffer is optional
|
||||
auto inlineBuf{request.outputBuf.size() > 1 ? request.outputBuf.at(1) : span<u8>{}};
|
||||
|
||||
// Strip the permissions from the command leaving only the ID
|
||||
cmd &= 0xFFFF;
|
||||
|
||||
if (request.inputBuf.empty() || request.outputBuf.empty()) {
|
||||
throw exception("Inadequate amount of buffers for IOCTL3: I - {}, O - {}", request.inputBuf.size(), request.outputBuf.size());
|
||||
} else if (request.inputBuf[0].data() != request.outputBuf[0].data()) {
|
||||
if (request.outputBuf.size() >= 2)
|
||||
throw exception("IOCTL3 Input Buffer (0x{:X}) != Output Buffer (0x{:X}) [Output Buffer #2: 0x{:X}]", request.inputBuf[0].data(), request.outputBuf[0].data(), request.outputBuf[1].data());
|
||||
else
|
||||
throw exception("IOCTL3 Input Buffer (0x{:X}) != Output Buffer (0x{:X})", request.inputBuf[0].data(), request.outputBuf[0].data());
|
||||
}
|
||||
|
||||
span<u8> buffer{};
|
||||
if (request.inputBuf[0].size() >= request.outputBuf[0].size())
|
||||
buffer = request.inputBuf[0];
|
||||
auto buf{GetMainIoctlBuffer(ioctl, request.inputBuf.at(0), request.outputBuf.at(0))};
|
||||
if (!buf)
|
||||
return NVRESULT(buf);
|
||||
else
|
||||
buffer = request.outputBuf[0];
|
||||
|
||||
response.Push(device->HandleIoctl(cmd, device::IoctlType::Ioctl3, buffer, request.outputBuf.size() >= 2 ? request.outputBuf[1] : span<u8>()));
|
||||
return {};
|
||||
return NVRESULT(driver.Ioctl3(fd, ioctl, *buf, inlineBuf));
|
||||
}
|
||||
|
||||
Result INvDrvServices::SetAruid(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
return NVRESULT(NvResult::Success);
|
||||
}
|
||||
|
||||
|
||||
Result INvDrvServices::SetGraphicsFirmwareMemoryMarginEnabled(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <services/serviceman.h>
|
||||
#include "types.h"
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
class Driver;
|
||||
|
@ -14,10 +15,13 @@ namespace skyline::service::nvdrv {
|
|||
*/
|
||||
class INvDrvServices : public BaseService {
|
||||
private:
|
||||
std::shared_ptr<Driver> driver;
|
||||
Driver &driver; //!< The global nvdrv driver this session accesses
|
||||
SessionContext ctx; //!< Session specific context
|
||||
|
||||
FileDescriptor nextFdIndex{1}; //!< The index for the next allocated file descriptor
|
||||
|
||||
public:
|
||||
INvDrvServices(const DeviceState &state, ServiceManager &manager);
|
||||
INvDrvServices(const DeviceState &state, ServiceManager &manager, Driver &driver, const SessionPermissions &perms);
|
||||
|
||||
/**
|
||||
* @brief Open a specific device and return a FD
|
||||
|
|
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 © 2019-2020 Ryujinx Team and Contributors
|
||||
|
||||
#include <soc.h>
|
||||
#include "nvhost_syncpoint.h"
|
||||
#include "syncpoint_manager.h"
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
NvHostSyncpoint::NvHostSyncpoint(const DeviceState &state) : state(state) {
|
||||
namespace skyline::service::nvdrv::core {
|
||||
SyncpointManager::SyncpointManager(const DeviceState &state) : state(state) {
|
||||
constexpr u32 VBlank0SyncpointId{26};
|
||||
constexpr u32 VBlank1SyncpointId{27};
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace skyline::service::nvdrv {
|
|||
ReserveSyncpoint(VBlank1SyncpointId, true);
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::ReserveSyncpoint(u32 id, bool clientManaged) {
|
||||
u32 SyncpointManager::ReserveSyncpoint(u32 id, bool clientManaged) {
|
||||
if (syncpoints.at(id).reserved)
|
||||
throw exception("Requested syncpoint is in use");
|
||||
|
||||
|
@ -27,7 +27,7 @@ namespace skyline::service::nvdrv {
|
|||
return id;
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::FindFreeSyncpoint() {
|
||||
u32 SyncpointManager::FindFreeSyncpoint() {
|
||||
for (u32 i{1}; i < syncpoints.size(); i++)
|
||||
if (!syncpoints[i].reserved)
|
||||
return i;
|
||||
|
@ -35,12 +35,12 @@ namespace skyline::service::nvdrv {
|
|||
throw exception("Failed to find a free syncpoint!");
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::AllocateSyncpoint(bool clientManaged) {
|
||||
u32 SyncpointManager::AllocateSyncpoint(bool clientManaged) {
|
||||
std::lock_guard lock(reservationLock);
|
||||
return ReserveSyncpoint(FindFreeSyncpoint(), clientManaged);
|
||||
}
|
||||
|
||||
bool NvHostSyncpoint::HasSyncpointExpired(u32 id, u32 threshold) {
|
||||
bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) {
|
||||
const SyncpointInfo &syncpoint{syncpoints.at(id)};
|
||||
|
||||
if (!syncpoint.reserved)
|
||||
|
@ -53,25 +53,35 @@ namespace skyline::service::nvdrv {
|
|||
return (syncpoint.counterMax - threshold) >= (syncpoint.counterMin - threshold);
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::IncrementSyncpointMaxExt(u32 id, u32 amount) {
|
||||
u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
|
||||
if (!syncpoints.at(id).reserved)
|
||||
throw exception("Cannot increment an unreserved syncpoint!");
|
||||
|
||||
return syncpoints.at(id).counterMax += amount;
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::ReadSyncpointMinValue(u32 id) {
|
||||
u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
|
||||
if (!syncpoints.at(id).reserved)
|
||||
throw exception("Cannot read an unreserved syncpoint!");
|
||||
|
||||
return syncpoints.at(id).counterMin;
|
||||
}
|
||||
|
||||
u32 NvHostSyncpoint::UpdateMin(u32 id) {
|
||||
u32 SyncpointManager::UpdateMin(u32 id) {
|
||||
if (!syncpoints.at(id).reserved)
|
||||
throw exception("Cannot update an unreserved syncpoint!");
|
||||
|
||||
syncpoints.at(id).counterMin = state.soc->host1x.syncpoints.at(id).Load();
|
||||
return syncpoints.at(id).counterMin;
|
||||
}
|
||||
|
||||
Fence SyncpointManager::GetSyncpointFence(u32 id) {
|
||||
if (!syncpoints.at(id).reserved)
|
||||
throw exception("Cannot access an unreserved syncpoint!");
|
||||
|
||||
return {
|
||||
.id = id,
|
||||
.threshold = syncpoints.at(id).counterMax
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,18 +1,20 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// Copyright © 2019-2020 Ryujinx Team and Contributors
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <soc/host1x.h>
|
||||
#include <services/common/fence.h>
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
namespace skyline::service::nvdrv::core {
|
||||
/**
|
||||
* @brief NvHostSyncpoint handles allocating and accessing host1x syncpoints, these are cached versions of the HW syncpoints which are intermittently synced
|
||||
* @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached versions of the HW syncpoints which are intermittently synced
|
||||
* @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
|
||||
* @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
|
||||
* @url https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
|
||||
*/
|
||||
class NvHostSyncpoint {
|
||||
class SyncpointManager {
|
||||
private:
|
||||
struct SyncpointInfo {
|
||||
std::atomic<u32> counterMin; //!< The least value the syncpoint can be (The value it was when it was last synchronized with host1x)
|
||||
|
@ -36,7 +38,7 @@ namespace skyline::service::nvdrv {
|
|||
u32 FindFreeSyncpoint();
|
||||
|
||||
public:
|
||||
NvHostSyncpoint(const DeviceState &state);
|
||||
SyncpointManager(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Finds a free syncpoint and reserves it
|
||||
|
@ -49,6 +51,10 @@ namespace skyline::service::nvdrv {
|
|||
*/
|
||||
bool HasSyncpointExpired(u32 id, u32 threshold);
|
||||
|
||||
bool IsFenceSignalled(Fence fence) {
|
||||
return HasSyncpointExpired(fence.id, fence.threshold);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Atomically increments the maximum value of a syncpoint by the given amount
|
||||
* @return The new max value of the syncpoint
|
||||
|
@ -65,5 +71,10 @@ namespace skyline::service::nvdrv {
|
|||
* @return The new minimum value of the syncpoint
|
||||
*/
|
||||
u32 UpdateMin(u32 id);
|
||||
|
||||
/**
|
||||
* @return A fence that will be signalled once this syncpoint hits its maximum value
|
||||
*/
|
||||
Fence GetSyncpointFence(u32 id);
|
||||
};
|
||||
}
|
|
@ -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
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <cxxabi.h>
|
||||
#include <common/trace.h>
|
||||
#include "nvdevice.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
NvDevice::NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx) : state(state), core(core), ctx(ctx) {}
|
||||
|
||||
const std::string &NvDevice::GetName() {
|
||||
if (name.empty()) {
|
||||
auto mangledName{typeid(*this).name()};
|
||||
|
@ -18,32 +19,4 @@ namespace skyline::service::nvdrv::device {
|
|||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
NvStatus NvDevice::HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
std::string_view typeString{[type] {
|
||||
switch (type) {
|
||||
case IoctlType::Ioctl:
|
||||
return "IOCTL";
|
||||
case IoctlType::Ioctl2:
|
||||
return "IOCTL2";
|
||||
case IoctlType::Ioctl3:
|
||||
return "IOCTL3";
|
||||
}
|
||||
}()};
|
||||
|
||||
NvDeviceFunctionDescriptor function;
|
||||
try {
|
||||
function = GetIoctlFunction(cmd);
|
||||
state.logger->DebugNoPrefix("{}: {}", typeString, function.name);
|
||||
} catch (std::out_of_range &) {
|
||||
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", GetName(), cmd);
|
||||
return NvStatus::NotImplemented;
|
||||
}
|
||||
TRACE_EVENT("service", perfetto::StaticString{function.name});
|
||||
try {
|
||||
return function(type, buffer, inlineBuffer);
|
||||
} catch (const std::exception &e) {
|
||||
throw exception("{} ({}: {})", e.what(), typeString, function.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +1,19 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kernel/ipc.h>
|
||||
#include <kernel/types/KEvent.h>
|
||||
#include <services/common/result.h>
|
||||
#include <services/nvdrv/types.h>
|
||||
#include <services/nvdrv/core/core.h>
|
||||
|
||||
#define NV_STRINGIFY(string) #string
|
||||
#define NVFUNC(id, Class, Function) std::pair<u32, std::pair<NvStatus(Class::*)(IoctlType, span<u8>, span<u8>), const char*>>{id, {&Class::Function, NV_STRINGIFY(Class::Function)}}
|
||||
#define NVDEVICE_DECL_AUTO(name, value) decltype(value) name = value
|
||||
#define NVDEVICE_DECL(...) \
|
||||
NVDEVICE_DECL_AUTO(functions, frz::make_unordered_map({__VA_ARGS__})); \
|
||||
NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) override { \
|
||||
auto& function{functions.at(id)}; \
|
||||
return NvDeviceFunctionDescriptor{ \
|
||||
reinterpret_cast<DerivedDevice*>(this), \
|
||||
reinterpret_cast<decltype(NvDeviceFunctionDescriptor::function)>(function.first), \
|
||||
function.second \
|
||||
}; \
|
||||
}
|
||||
#include "deserialisation/types.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
using namespace kernel;
|
||||
|
||||
/**
|
||||
* @brief All the possible error codes returned by the Nvidia driver
|
||||
* @url https://switchbrew.org/wiki/NV_services#Errors
|
||||
*/
|
||||
enum class NvStatus : u32 {
|
||||
Success = 0x0, //!< The operation has succeeded
|
||||
NotImplemented = 0x1, //!< The operation is not implemented
|
||||
NotSupported = 0x2, //!< The operation is not supported
|
||||
NotInitialized = 0x3, //!< The operation uses an uninitialized object
|
||||
BadParameter = 0x4, //!< The operation was provided a bad parameter
|
||||
Timeout = 0x5, //!< The operation has timed out
|
||||
InsufficientMemory = 0x6, //!< The device ran out of memory during the operation
|
||||
ReadOnlyAttribute = 0x7, //!< The mutating operation was performed on a read only section
|
||||
InvalidState = 0x8, //!< The state of the device was invalid
|
||||
InvalidAddress = 0x9, //!< The provided address is invalid
|
||||
InvalidSize = 0xA, //!< The provided size is invalid
|
||||
BadValue = 0xB, //!< The operation was provided a bad value
|
||||
AlreadyAllocated = 0xD, //!< An object was tried to be reallocated
|
||||
Busy = 0xE, //!< The device is busy
|
||||
ResourceError = 0xF, //!< There was an error accessing the resource
|
||||
CountMismatch = 0x10, //!< ?
|
||||
SharedMemoryTooSmall = 0x1000, //!< The shared memory segment is too small
|
||||
FileOperationFailed = 0x30003, //!< The file operation has failed
|
||||
DirOperationFailed = 0x30004, //!< The directory operation has failed
|
||||
IoctlFailed = 0x3000F, //!< The IOCTL operation has failed
|
||||
AccessDenied = 0x30010, //!< The access to a resource was denied
|
||||
FileNotFound = 0x30013, //!< A file was not found
|
||||
ModuleNotPresent = 0xA000E, //!< A module was not present
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The IOCTL call variants, they have different buffer configurations
|
||||
*/
|
||||
enum class IoctlType : u8 {
|
||||
Ioctl, //!< 1 input/output buffer
|
||||
Ioctl2, //!< 1 input/output buffer + 1 input buffer
|
||||
Ioctl3, //!< 1 input/output buffer + 1 output buffer
|
||||
};
|
||||
using namespace deserialisation;
|
||||
|
||||
/**
|
||||
* @brief NvDevice is the base class that all /dev/nv* devices inherit from
|
||||
|
@ -71,40 +24,29 @@ namespace skyline::service::nvdrv::device {
|
|||
|
||||
protected:
|
||||
const DeviceState &state;
|
||||
|
||||
class DerivedDevice; //!< A placeholder derived class which is used for class function semantics
|
||||
|
||||
/**
|
||||
* @brief A per-function descriptor for NvDevice functions
|
||||
*/
|
||||
struct NvDeviceFunctionDescriptor {
|
||||
DerivedDevice *clazz; //!< A pointer to the class that this was derived from, it's used as the 'this' pointer for the function
|
||||
NvStatus (DerivedDevice::*function)(IoctlType, span<u8>, span<u8>); //!< A function pointer to the implementation of the function
|
||||
const char *name; //!< A pointer to a static string in the format "Class::Function" for the specific device class/function
|
||||
|
||||
constexpr NvStatus operator()(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
return (clazz->*function)(type, buffer, inlineBuffer);
|
||||
}
|
||||
};
|
||||
Core &core;
|
||||
SessionContext ctx;
|
||||
|
||||
public:
|
||||
NvDevice(const DeviceState &state) : state(state) {}
|
||||
NvDevice(const DeviceState &state, Core &core, const SessionContext &ctx);
|
||||
|
||||
virtual ~NvDevice() = default;
|
||||
|
||||
virtual NvDeviceFunctionDescriptor GetIoctlFunction(u32 id) = 0;
|
||||
|
||||
/**
|
||||
* @return The name of the class
|
||||
* @note The lifetime of the returned string is tied to that of the class
|
||||
*/
|
||||
const std::string &GetName();
|
||||
|
||||
/**
|
||||
* @brief Handles IOCTL calls for devices
|
||||
* @param cmd The IOCTL command that was called
|
||||
*/
|
||||
NvStatus HandleIoctl(u32 cmd, IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
virtual PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) = 0;
|
||||
|
||||
virtual PosixResult Ioctl2(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineOutput) {
|
||||
return PosixResult::InappropriateIoctlForDevice;
|
||||
}
|
||||
|
||||
virtual PosixResult Ioctl3(IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineInput) {
|
||||
return PosixResult::InappropriateIoctlForDevice;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 eventId) {
|
||||
return nullptr;
|
||||
|
|
|
@ -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
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <services/nvdrv/devices/deserialisation/deserialisation.h>
|
||||
#include <services/nvdrv/core/nvmap.h>
|
||||
#include "nvmap.h"
|
||||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
NvMap::NvMapObject::NvMapObject(u32 id, u32 size) : id(id), size(size) {}
|
||||
NvMap::NvMap(const DeviceState &state, Core &core, const SessionContext &ctx) : NvDevice(state, core, ctx) {}
|
||||
|
||||
NvMap::NvMap(const DeviceState &state) : NvDevice(state) {}
|
||||
|
||||
NvStatus NvMap::Create(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
struct Data {
|
||||
u32 size; // In
|
||||
u32 handle; // Out
|
||||
} &data = buffer.as<Data>();
|
||||
|
||||
std::unique_lock lock(mapMutex);
|
||||
maps.push_back(std::make_shared<NvMapObject>(idIndex++, data.size));
|
||||
data.handle = maps.size();
|
||||
|
||||
state.logger->Debug("Size: 0x{:X} -> Handle: 0x{:X}", data.size, data.handle);
|
||||
return NvStatus::Success;
|
||||
}
|
||||
|
||||
NvStatus NvMap::FromId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
struct Data {
|
||||
u32 id; // In
|
||||
u32 handle; // Out
|
||||
} &data = buffer.as<Data>();
|
||||
|
||||
std::shared_lock lock(mapMutex);
|
||||
for (auto it{maps.begin()}; it < maps.end(); it++) {
|
||||
if ((*it)->id == data.id) {
|
||||
data.handle = (it - maps.begin()) + 1;
|
||||
state.logger->Debug("ID: 0x{:X} -> Handle: 0x{:X}", data.id, data.handle);
|
||||
return NvStatus::Success;
|
||||
}
|
||||
PosixResult NvMap::Create(In<u32> size, Out<NvMapCore::Handle::Id> handle) {
|
||||
auto handleDesc{core.nvMap.CreateHandle(util::AlignUp(size, PAGE_SIZE))};
|
||||
if (handleDesc) {
|
||||
(*handleDesc)->origSize = size; // Orig size is the unaligned size
|
||||
handle = (*handleDesc)->id;
|
||||
state.logger->Debug("handle: {}, size: 0x{:X}", (*handleDesc)->id, size);
|
||||
}
|
||||
|
||||
state.logger->Warn("Handle not found for ID: 0x{:X}", data.id);
|
||||
return NvStatus::BadValue;
|
||||
return handleDesc;
|
||||
}
|
||||
|
||||
NvStatus NvMap::Alloc(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
u32 heapMask; // In
|
||||
u32 flags; // In
|
||||
u32 align; // In
|
||||
u8 kind; // In
|
||||
u8 _pad0_[7];
|
||||
u8 *ptr; // InOut
|
||||
} &data = buffer.as<Data>();
|
||||
PosixResult NvMap::FromId(In<NvMapCore::Handle::Id> id, Out<NvMapCore::Handle::Id> handle) {
|
||||
state.logger->Debug("id: {}", id);
|
||||
|
||||
try {
|
||||
auto object{GetObject(data.handle)};
|
||||
object->heapMask = data.heapMask;
|
||||
object->flags = data.flags;
|
||||
object->align = data.align;
|
||||
object->kind = data.kind;
|
||||
object->ptr = data.ptr;
|
||||
object->status = NvMapObject::Status::Allocated;
|
||||
// Handles and IDs are always the same value in nvmap however IDs can be used globally given the right permissions.
|
||||
// Since we don't plan on ever supporting multiprocess we can skip implementing handle refs and so this function just does simple validation and passes through the handle id.
|
||||
if (!id) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
state.logger->Debug("Handle: 0x{:X}, HeapMask: 0x{:X}, Flags: {}, Align: 0x{:X}, Kind: {}, Pointer: 0x{:X}", data.handle, data.heapMask, data.flags, data.align, data.kind, data.ptr);
|
||||
return NvStatus::Success;
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
|
||||
return NvStatus::BadParameter;
|
||||
auto handleDesc{core.nvMap.GetHandle(id)};
|
||||
if (!handleDesc) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
return handleDesc->Duplicate(ctx.internalSession);
|
||||
}
|
||||
|
||||
PosixResult NvMap::Alloc(In<NvMapCore::Handle::Id> handle, In<u32> heapMask, In<NvMapCore::Handle::Flags> flags, InOut<u32> align, In<u8> kind, In<u64> address) {
|
||||
state.logger->Debug("handle: {}, flags: ( mapUncached: {}, keepUncachedAfterFree: {} ), align: 0x{:X}, kind: {}, address: 0x{:X}", handle, flags.mapUncached, flags.keepUncachedAfterFree, align, kind, address);
|
||||
|
||||
if (!handle) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
if (!std::ispow2(align)) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
// Force page size alignment at a minimum
|
||||
if (align < PAGE_SIZE) [[unlikely]]
|
||||
align = PAGE_SIZE;
|
||||
|
||||
auto handleDesc{core.nvMap.GetHandle(handle)};
|
||||
if (!handleDesc) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
return handleDesc->Alloc(flags, align, kind, address);
|
||||
}
|
||||
|
||||
PosixResult NvMap::Free(In<NvMapCore::Handle::Id> handle, Out<u64> address, Out<u32> size, Out<NvMapCore::Handle::Flags> flags) {
|
||||
state.logger->Debug("handle: {}", handle);
|
||||
|
||||
if (!handle) [[unlikely]]
|
||||
return PosixResult::Success;
|
||||
|
||||
if (auto freeInfo{core.nvMap.FreeHandle(handle, ctx.internalSession)}) {
|
||||
address = freeInfo->address;
|
||||
size = static_cast<u32>(freeInfo->size);
|
||||
flags = NvMapCore::Handle::Flags{ .mapUncached = freeInfo->wasUncached };
|
||||
} else {
|
||||
state.logger->Debug("Handle not freed");
|
||||
}
|
||||
|
||||
return PosixResult::Success;
|
||||
}
|
||||
|
||||
PosixResult NvMap::Param(In<NvMapCore::Handle::Id> handle, In<HandleParameterType> param, Out<u32> result) {
|
||||
state.logger->Debug("handle: {}, param: {}", handle, param);
|
||||
|
||||
if (!handle)
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
auto handleDesc{core.nvMap.GetHandle(handle)};
|
||||
if (!handleDesc) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
switch (param) {
|
||||
case HandleParameterType::Size:
|
||||
result = handleDesc->origSize;
|
||||
return PosixResult::Success;
|
||||
case HandleParameterType::Alignment:
|
||||
result = handleDesc->align;
|
||||
return PosixResult::Success;
|
||||
case HandleParameterType::Base:
|
||||
result = -static_cast<i32>(PosixResult::InvalidArgument);
|
||||
return PosixResult::Success;
|
||||
case HandleParameterType::Heap:
|
||||
if (handleDesc->allocated)
|
||||
result = 0x40000000;
|
||||
else
|
||||
result = 0;
|
||||
|
||||
return PosixResult::Success;
|
||||
case HandleParameterType::Kind:
|
||||
result = handleDesc->kind;
|
||||
return PosixResult::Success;
|
||||
case HandleParameterType::IsSharedMemMapped:
|
||||
result = handleDesc->isSharedMemMapped;
|
||||
return PosixResult::Success;
|
||||
default:
|
||||
return PosixResult::InvalidArgument;
|
||||
}
|
||||
}
|
||||
|
||||
NvStatus NvMap::Free(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
u32 _pad0_;
|
||||
u8 *ptr; // Out
|
||||
u32 size; // Out
|
||||
u32 flags; // Out
|
||||
} &data = buffer.as<Data>();
|
||||
PosixResult NvMap::GetId(Out<NvMapCore::Handle::Id> id, In<NvMapCore::Handle::Id> handle) {
|
||||
state.logger->Debug("handle: {}", handle);
|
||||
|
||||
std::unique_lock lock(mapMutex);
|
||||
try {
|
||||
auto &object{maps.at(data.handle - 1)};
|
||||
if (object.use_count() > 1) {
|
||||
data.ptr = object->ptr;
|
||||
data.flags = 0x0;
|
||||
} else {
|
||||
data.ptr = nullptr;
|
||||
data.flags = 0x1; // Not free yet
|
||||
}
|
||||
// See the comment in FromId for extra info on this function
|
||||
if (!handle) [[unlikely]]
|
||||
return PosixResult::InvalidArgument;
|
||||
|
||||
data.size = object->size;
|
||||
object = nullptr;
|
||||
auto handleDesc{core.nvMap.GetHandle(handle)};
|
||||
if (!handleDesc) [[unlikely]]
|
||||
return PosixResult::NotPermitted; // This will always return EPERM irrespective of if the handle exists or not
|
||||
|
||||
state.logger->Debug("Handle: 0x{:X} -> Pointer: 0x{:X}, Size: 0x{:X}, Flags: 0x{:X}", data.handle, data.ptr, data.size, data.flags);
|
||||
return NvStatus::Success;
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
|
||||
return NvStatus::BadParameter;
|
||||
}
|
||||
id = handleDesc->id;
|
||||
return PosixResult::Success;
|
||||
}
|
||||
|
||||
NvStatus NvMap::Param(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
// https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102
|
||||
enum class Parameter : u32 {
|
||||
Size = 1,
|
||||
Alignment = 2,
|
||||
Base = 3,
|
||||
HeapMask = 4,
|
||||
Kind = 5,
|
||||
Compr = 6,
|
||||
};
|
||||
#include "deserialisation/macro_def.inc"
|
||||
static constexpr u32 NvMapMagic{1};
|
||||
|
||||
struct Data {
|
||||
u32 handle; // In
|
||||
Parameter parameter; // In
|
||||
u32 result; // Out
|
||||
} &data = buffer.as<Data>();
|
||||
|
||||
try {
|
||||
auto object{GetObject(data.handle)};
|
||||
|
||||
switch (data.parameter) {
|
||||
case Parameter::Size:
|
||||
data.result = object->size;
|
||||
break;
|
||||
|
||||
case Parameter::Alignment:
|
||||
data.result = object->align;
|
||||
break;
|
||||
|
||||
case Parameter::HeapMask:
|
||||
data.result = object->heapMask;
|
||||
break;
|
||||
|
||||
case Parameter::Kind:
|
||||
data.result = object->kind;
|
||||
break;
|
||||
|
||||
case Parameter::Compr:
|
||||
data.result = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
state.logger->Warn("Parameter not implemented: 0x{:X}", data.parameter);
|
||||
return NvStatus::NotImplemented;
|
||||
}
|
||||
|
||||
state.logger->Debug("Handle: 0x{:X}, Parameter: {} -> Result: 0x{:X}", data.handle, data.parameter, data.result);
|
||||
return NvStatus::Success;
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
|
||||
return NvStatus::BadParameter;
|
||||
}
|
||||
}
|
||||
|
||||
NvStatus NvMap::GetId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
struct Data {
|
||||
u32 id; // Out
|
||||
u32 handle; // In
|
||||
} &data = buffer.as<Data>();
|
||||
|
||||
try {
|
||||
data.id = GetObject(data.handle)->id;
|
||||
state.logger->Debug("Handle: 0x{:X} -> ID: 0x{:X}", data.handle, data.id);
|
||||
return NvStatus::Success;
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Warn("Invalid NvMap handle: 0x{:X}", data.handle);
|
||||
return NvStatus::BadParameter;
|
||||
}
|
||||
}
|
||||
IOCTL_HANDLER_FUNC(NvMap, ({
|
||||
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x1),
|
||||
Create, ARGS(In<u32>, Out<NvMapCore::Handle::Id>))
|
||||
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0x3),
|
||||
FromId, ARGS(In<NvMapCore::Handle::Id>, Out<NvMapCore::Handle::Id>))
|
||||
IOCTL_CASE_ARGS(INOUT, SIZE(0x20), MAGIC(NvMapMagic), FUNC(0x4),
|
||||
Alloc, ARGS(In<NvMapCore::Handle::Id>, In<u32>, In<NvMapCore::Handle::Flags>, InOut<u32>, In<u8>, Pad<u8, 0x7>, In<u64>))
|
||||
IOCTL_CASE_ARGS(INOUT, SIZE(0x18), MAGIC(NvMapMagic), FUNC(0x5),
|
||||
Free, ARGS(In<NvMapCore::Handle::Id>, Pad<u32>, Out<u64>, Out<u32>, Out<NvMapCore::Handle::Flags>))
|
||||
IOCTL_CASE_ARGS(INOUT, SIZE(0xC), MAGIC(NvMapMagic), FUNC(0x9),
|
||||
Param, ARGS(In<NvMapCore::Handle::Id>, In<HandleParameterType>, Out<u32>))
|
||||
IOCTL_CASE_ARGS(INOUT, SIZE(0x8), MAGIC(NvMapMagic), FUNC(0xE),
|
||||
GetId, ARGS(Out<NvMapCore::Handle::Id>, In<NvMapCore::Handle::Id>))
|
||||
}))
|
||||
#include "deserialisation/macro_undef.inc"
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2021 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
|
@ -7,91 +7,60 @@
|
|||
|
||||
namespace skyline::service::nvdrv::device {
|
||||
/**
|
||||
* @brief NvMap (/dev/nvmap) is used to map certain CPU memory as GPU memory (https://switchbrew.org/wiki/NV_services)
|
||||
* @brief NvMap (/dev/nvmap) is used to keep track of buffers and map them onto the SMMU (https://switchbrew.org/wiki/NV_services)
|
||||
* @url https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h
|
||||
*/
|
||||
class NvMap : public NvDevice {
|
||||
public:
|
||||
/**
|
||||
* @brief NvMapObject is used to hold the state of held objects
|
||||
*/
|
||||
struct NvMapObject {
|
||||
u32 id;
|
||||
u32 size;
|
||||
u8 *ptr{};
|
||||
u32 flags{}; //!< The flag of the memory (0 = Read Only, 1 = Read-Write)
|
||||
u32 align{};
|
||||
u32 heapMask{}; //!< This is set during Alloc and returned during Param
|
||||
u8 kind{}; //!< This is same as heapMask
|
||||
using NvMapCore = core::NvMap;
|
||||
|
||||
enum class Status {
|
||||
Created, //!< The object has been created but memory has not been allocated
|
||||
Allocated //!< The object has been allocated
|
||||
} status{Status::Created}; //!< This holds the status of the object
|
||||
|
||||
NvMapObject(u32 id, u32 size);
|
||||
enum class HandleParameterType : u32 {
|
||||
Size = 1,
|
||||
Alignment = 2,
|
||||
Base = 3,
|
||||
Heap = 4,
|
||||
Kind = 5,
|
||||
IsSharedMemMapped = 6
|
||||
};
|
||||
|
||||
std::shared_mutex mapMutex; //!< Synchronizes mutations and accesses of the mappings
|
||||
std::vector<std::shared_ptr<NvMapObject>> maps;
|
||||
|
||||
u32 idIndex{1}; //!< This is used to keep track of the next ID to allocate
|
||||
|
||||
NvMap(const DeviceState &state);
|
||||
|
||||
std::shared_ptr<NvMapObject> GetObject(u32 handle) {
|
||||
if (handle-- == 0)
|
||||
throw std::out_of_range("0 is an invalid nvmap handle");
|
||||
std::shared_lock lock(mapMutex);
|
||||
auto &object{maps.at(handle)};
|
||||
if (!object)
|
||||
throw std::out_of_range("A freed nvmap handle was requested");
|
||||
return object;
|
||||
}
|
||||
NvMap(const DeviceState &state, Core &core, const SessionContext &ctx);
|
||||
|
||||
/**
|
||||
* @brief Creates an NvMapObject and returns an handle to it
|
||||
* @brief Creates an nvmap handle for the given size
|
||||
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_CREATE
|
||||
*/
|
||||
NvStatus Create(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
PosixResult Create(In<u32> size, Out<NvMapCore::Handle::Id> handle);
|
||||
|
||||
/**
|
||||
* @brief Returns the handle of an NvMapObject from its ID
|
||||
* @brief Creates a new ref to the handle of the given ID
|
||||
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID
|
||||
*/
|
||||
NvStatus FromId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
PosixResult FromId(In<NvMapCore::Handle::Id> id, Out<NvMapCore::Handle::Id> handle);
|
||||
|
||||
/**
|
||||
* @brief Allocates memory for an NvMapObject
|
||||
* @brief Adds the given backing memory to the nvmap handle
|
||||
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_ALLOC
|
||||
*/
|
||||
NvStatus Alloc(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
PosixResult Alloc(In<NvMapCore::Handle::Id> handle, In<u32> heapMask, In<NvMapCore::Handle::Flags> flags, InOut<u32> align, In<u8> kind, In<u64> address);
|
||||
|
||||
/**
|
||||
* @brief Frees previously allocated memory
|
||||
* @brief Attempts to free a handle and unpin it from SMMU memory
|
||||
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FREE
|
||||
*/
|
||||
NvStatus Free(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
PosixResult Free(In<NvMapCore::Handle::Id> handle, Out<u64> address, Out<u32> size, Out<NvMapCore::Handle::Flags> flags);
|
||||
|
||||
/**
|
||||
* @brief Returns a particular parameter from an NvMapObject
|
||||
* @brief Returns info about a property of the nvmap handle
|
||||
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_PARAM
|
||||
*/
|
||||
NvStatus Param(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
PosixResult Param(In<NvMapCore::Handle::Id> handle, In<HandleParameterType> param, Out<u32> result);
|
||||
|
||||
/**
|
||||
* @brief Returns the ID of an NvMapObject from its handle
|
||||
* @brief Returns a global ID for the given nvmap handle
|
||||
* @url https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID
|
||||
*/
|
||||
NvStatus GetId(IoctlType type, span<u8> buffer, span<u8> inlineBuffer);
|
||||
PosixResult GetId(Out<NvMapCore::Handle::Id> id, In<NvMapCore::Handle::Id> handle);
|
||||
|
||||
NVDEVICE_DECL(
|
||||
NVFUNC(0x0101, NvMap, Create),
|
||||
NVFUNC(0x0103, NvMap, FromId),
|
||||
NVFUNC(0x0104, NvMap, Alloc),
|
||||
NVFUNC(0x0105, NvMap, Free),
|
||||
NVFUNC(0x0109, NvMap, Param),
|
||||
NVFUNC(0x010E, NvMap, GetId)
|
||||
)
|
||||
PosixResult Ioctl(IoctlDescriptor cmd, span<u8> buffer) override;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,61 +1,122 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include "driver.h"
|
||||
#include "devices/nvhost_ctrl.h"
|
||||
#include "devices/nvhost_ctrl_gpu.h"
|
||||
#include "devices/nvmap.h"
|
||||
#include "devices/nvhost_channel.h"
|
||||
#include "devices/nvhost_as_gpu.h"
|
||||
#include "devices/nvhost/ctrl.h"
|
||||
#include "devices/nvhost/ctrl_gpu.h"
|
||||
#include "devices/nvhost/gpu_channel.h"
|
||||
#include "devices/nvhost/as_gpu.h"
|
||||
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
Driver::Driver(const DeviceState &state) : state(state), hostSyncpoint(state) {}
|
||||
Driver::Driver(const DeviceState &state) : state(state), core(state) {}
|
||||
|
||||
u32 Driver::OpenDevice(std::string_view path) {
|
||||
state.logger->Debug("Opening NVDRV device ({}): {}", fdIndex, path);
|
||||
NvResult Driver::OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx) {
|
||||
state.logger->Debug("Opening NvDrv device ({}): {}", fd, path);
|
||||
auto pathHash{util::Hash(path)};
|
||||
|
||||
switch (util::Hash(path)) {
|
||||
#define NVDEVICE(type, name, devicePath) \
|
||||
case util::Hash(devicePath): { \
|
||||
std::shared_ptr<device::type> device{}; \
|
||||
if (name.expired()) { \
|
||||
device = std::make_shared<device::type>(state); \
|
||||
name = device; \
|
||||
} else { \
|
||||
device = name.lock(); \
|
||||
} \
|
||||
devices.push_back(device); \
|
||||
break; \
|
||||
}
|
||||
NVDEVICE_LIST
|
||||
#undef NVDEVICE
|
||||
#define DEVICE_SWITCH(cases) \
|
||||
switch (pathHash) { \
|
||||
cases; \
|
||||
default: \
|
||||
break; \
|
||||
}
|
||||
|
||||
default:
|
||||
throw exception("Cannot find NVDRV device");
|
||||
#define DEVICE_CASE(path, object) \
|
||||
case util::Hash(path): \
|
||||
devices.emplace(fd, std::make_unique<device::object>(state, core, ctx)); \
|
||||
return NvResult::Success;
|
||||
|
||||
DEVICE_SWITCH(
|
||||
DEVICE_CASE("/dev/nvmap", NvMap)
|
||||
DEVICE_CASE("/dev/nvhost-ctrl", nvhost::Ctrl)
|
||||
);
|
||||
|
||||
if (ctx.perms.AccessGpu) {
|
||||
DEVICE_SWITCH(
|
||||
DEVICE_CASE("/dev/nvhost-as-gpu", nvhost::AsGpu)
|
||||
DEVICE_CASE("/dev/nvhost-ctrl-gpu", nvhost::CtrlGpu)
|
||||
DEVICE_CASE("/dev/nvhost-gpu", nvhost::GpuChannel)
|
||||
);
|
||||
}
|
||||
|
||||
return fdIndex++;
|
||||
#undef DEVICE_CASE
|
||||
#undef DEVICE_SWITCH
|
||||
|
||||
// Device doesn't exist/no permissions
|
||||
return NvResult::FileOperationFailed;
|
||||
}
|
||||
|
||||
std::shared_ptr<device::NvDevice> Driver::GetDevice(u32 fd) {
|
||||
static NvResult ConvertResult(PosixResult result) {
|
||||
switch (result) {
|
||||
case PosixResult::Success:
|
||||
return NvResult::Success;
|
||||
case PosixResult::NotPermitted:
|
||||
return NvResult::AccessDenied;
|
||||
case PosixResult::TryAgain:
|
||||
return NvResult::Timeout;
|
||||
case PosixResult::Busy:
|
||||
return NvResult::Busy;
|
||||
case PosixResult::InvalidArgument:
|
||||
return NvResult::BadValue;
|
||||
case PosixResult::InappropriateIoctlForDevice:
|
||||
return NvResult::IoctlFailed;
|
||||
case PosixResult::NotSupported:
|
||||
return NvResult::NotSupported;
|
||||
case PosixResult::TimedOut:
|
||||
return NvResult::Timeout;
|
||||
default:
|
||||
throw exception("Unhandled POSIX result: {}!", static_cast<i32>(result));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
NvResult Driver::Ioctl(u32 fd, IoctlDescriptor cmd, span<u8> buffer) {
|
||||
state.logger->Debug("fd: {}, cmd: 0x{:X}, device: {}", fd, cmd.raw, devices.at(fd)->GetName());
|
||||
|
||||
try {
|
||||
auto item{devices.at(fd)};
|
||||
if (!item)
|
||||
throw exception("GetDevice was called with a closed file descriptor: 0x{:X}", fd);
|
||||
return item;
|
||||
} catch (std::out_of_range) {
|
||||
throw exception("GetDevice was called with invalid file descriptor: 0x{:X}", fd);
|
||||
return ConvertResult(devices.at(fd)->Ioctl(cmd, buffer));
|
||||
} catch (const std::out_of_range &) {
|
||||
throw exception("Ioctl was called with invalid file descriptor: 0x{:X}", fd);
|
||||
}
|
||||
}
|
||||
|
||||
NvResult Driver::Ioctl2(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
state.logger->Debug("fd: {}, cmd: 0x{:X}, device: {}", fd, cmd.raw, devices.at(fd)->GetName());
|
||||
|
||||
try {
|
||||
return ConvertResult(devices.at(fd)->Ioctl2(cmd, buffer, inlineBuffer));
|
||||
} catch (const std::out_of_range &) {
|
||||
throw exception("Ioctl2 was called with invalid file descriptor: 0x{:X}", fd);
|
||||
}
|
||||
}
|
||||
|
||||
NvResult Driver::Ioctl3(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer) {
|
||||
state.logger->Debug("fd: {}, cmd: 0x{:X}, device: {}", fd, cmd.raw, devices.at(fd)->GetName());
|
||||
|
||||
try {
|
||||
return ConvertResult(devices.at(fd)->Ioctl3(cmd, buffer, inlineBuffer));
|
||||
} catch (const std::out_of_range &) {
|
||||
throw exception("Ioctl3 was called with invalid file descriptor: 0x{:X}", fd);
|
||||
}
|
||||
}
|
||||
|
||||
void Driver::CloseDevice(u32 fd) {
|
||||
try {
|
||||
auto &device{devices.at(fd)};
|
||||
device.reset();
|
||||
devices.at(fd).reset();
|
||||
} catch (const std::out_of_range &) {
|
||||
state.logger->Warn("Trying to close non-existent FD");
|
||||
}
|
||||
}
|
||||
|
||||
std::weak_ptr<Driver> driver{};
|
||||
std::shared_ptr<kernel::type::KEvent> Driver::QueryEvent(u32 fd, u32 eventId) {
|
||||
state.logger->Debug("fd: {}, eventId: 0x{:X}, device: {}", fd, eventId, devices.at(fd)->GetName());
|
||||
|
||||
try {
|
||||
return devices.at(fd)->QueryEvent(eventId);
|
||||
} catch (const std::exception &) {
|
||||
throw exception("QueryEvent was called with invalid file descriptor: 0x{:X}", fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,76 +1,55 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// SPDX-License-Identifier: MIT OR MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "devices/nvhost_syncpoint.h"
|
||||
|
||||
#define NVDEVICE_LIST \
|
||||
NVDEVICE(NvHostCtrl, nvHostCtrl, "/dev/nvhost-ctrl") \
|
||||
NVDEVICE(NvHostChannel, nvHostGpu, "/dev/nvhost-gpu") \
|
||||
NVDEVICE(NvHostChannel, nvHostNvdec, "/dev/nvhost-nvdec") \
|
||||
NVDEVICE(NvHostChannel, nvHostVic, "/dev/nvhost-vic") \
|
||||
NVDEVICE(NvMap, nvMap, "/dev/nvmap") \
|
||||
NVDEVICE(NvHostAsGpu, nvHostAsGpu, "/dev/nvhost-as-gpu") \
|
||||
NVDEVICE(NvHostCtrlGpu, nvHostCtrlGpu, "/dev/nvhost-ctrl-gpu")
|
||||
#include <common.h>
|
||||
#include "types.h"
|
||||
#include "devices/nvdevice.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace skyline::service::nvdrv {
|
||||
namespace device {
|
||||
class NvDevice;
|
||||
|
||||
#define NVDEVICE(type, name, path) class type;
|
||||
NVDEVICE_LIST
|
||||
#undef NVDEVICE
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief nvnflinger:dispdrv or nns::hosbinder::IHOSBinderDriver is responsible for writing buffers to the display
|
||||
*/
|
||||
class Driver {
|
||||
private:
|
||||
const DeviceState &state;
|
||||
std::vector<std::shared_ptr<device::NvDevice>> devices; //!< A vector of shared pointers to NvDevice object that correspond to FDs
|
||||
u32 fdIndex{}; //!< The next file descriptor to assign
|
||||
std::unordered_map<FileDescriptor, std::unique_ptr<device::NvDevice>> devices;
|
||||
|
||||
public:
|
||||
NvHostSyncpoint hostSyncpoint;
|
||||
|
||||
#define NVDEVICE(type, name, path) std::weak_ptr<device::type> name;
|
||||
NVDEVICE_LIST
|
||||
#undef NVDEVICE
|
||||
Core core; //!< The core global state object of nvdrv that is accessed by devices
|
||||
|
||||
Driver(const DeviceState &state);
|
||||
|
||||
/**
|
||||
* @brief Open a specific device and return a FD
|
||||
* @param path The path of the device to open an FD to
|
||||
* @return The file descriptor to the device
|
||||
* @brief Creates a new device as specified by path
|
||||
* @param path The /dev path that corresponds to the device
|
||||
* @param fd The fd that will be used to refer to the device
|
||||
* @param ctx The context to be attached to the device
|
||||
*/
|
||||
u32 OpenDevice(std::string_view path);
|
||||
NvResult OpenDevice(std::string_view path, FileDescriptor fd, const SessionContext &ctx);
|
||||
|
||||
/**
|
||||
* @brief Returns a particular device with a specific FD
|
||||
* @param fd The file descriptor to retrieve
|
||||
* @return A shared pointer to the device
|
||||
* @brief Calls an IOCTL on the device specified by `fd`
|
||||
*/
|
||||
std::shared_ptr<device::NvDevice> GetDevice(u32 fd);
|
||||
NvResult Ioctl(u32 fd, IoctlDescriptor cmd, span<u8> buffer);
|
||||
|
||||
/**
|
||||
* @brief Returns a particular device with a specific FD
|
||||
* @tparam objectClass The class of the device to return
|
||||
* @param fd The file descriptor to retrieve
|
||||
* @return A shared pointer to the device
|
||||
* @brief Calls an IOCTL on the device specified by `fd` using the given inline input buffer
|
||||
*/
|
||||
template<typename objectClass>
|
||||
std::shared_ptr<objectClass> GetDevice(u32 fd) {
|
||||
return std::static_pointer_cast<objectClass>(GetDevice(fd));
|
||||
}
|
||||
NvResult Ioctl2(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer);
|
||||
|
||||
/**
|
||||
* @brief Closes the specified device with its file descriptor
|
||||
* @brief Calls an IOCTL on the device specified by `fd` using the given inline output buffer
|
||||
*/
|
||||
NvResult Ioctl3(u32 fd, IoctlDescriptor cmd, span<u8> buffer, span<u8> inlineBuffer);
|
||||
|
||||
/**
|
||||
* @brief Queries a KEvent for the given `eventId` for the device specified by `fd`
|
||||
*/
|
||||
std::shared_ptr<kernel::type::KEvent> QueryEvent(u32 fd, u32 eventId);
|
||||
|
||||
/**
|
||||
* @brief Closes the device specified by `fd`
|
||||
*/
|
||||
void CloseDevice(u32 fd);
|
||||
};
|
||||
|
||||
extern std::weak_ptr<Driver> driver; //!< A globally shared instance of the Driver
|
||||
}
|
||||
|
|
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 "services/timesrv/core.h"
|
||||
#include "fssrv/IFileSystemProxy.h"
|
||||
#include "services/nvdrv/INvDrvServices.h"
|
||||
#include "nvdrv/INvDrvServices.h"
|
||||
#include "nvdrv/driver.h"
|
||||
#include "hosbinder/IHOSBinderDriver.h"
|
||||
#include "visrv/IApplicationRootService.h"
|
||||
#include "visrv/ISystemRootService.h"
|
||||
|
@ -46,8 +47,9 @@
|
|||
namespace skyline::service {
|
||||
struct GlobalServiceState {
|
||||
timesrv::core::TimeServiceObject timesrv;
|
||||
nvdrv::Driver nvdrv;
|
||||
|
||||
explicit GlobalServiceState(const DeviceState &state) : timesrv(state) {}
|
||||
explicit GlobalServiceState(const DeviceState &state) : timesrv(state), nvdrv(state) {}
|
||||
};
|
||||
|
||||
ServiceManager::ServiceManager(const DeviceState &state) : state(state), smUserInterface(std::make_shared<sm::IUserInterface>(state, *this)), globalServiceState(std::make_shared<GlobalServiceState>(state)) {}
|
||||
|
@ -73,11 +75,8 @@ namespace skyline::service {
|
|||
SERVICE_CASE(glue::IStaticService, "time:r", globalServiceState->timesrv.managerServer.GetStaticServiceAsRepair(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceRepairPermissions)
|
||||
SERVICE_CASE(glue::IStaticService, "time:u", globalServiceState->timesrv.managerServer.GetStaticServiceAsUser(state, *this), globalServiceState->timesrv, timesrv::constant::StaticServiceUserPermissions)
|
||||
SERVICE_CASE(fssrv::IFileSystemProxy, "fsp-srv")
|
||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv")
|
||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:a")
|
||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:s")
|
||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv:t")
|
||||
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv")
|
||||
SERVICE_CASE(nvdrv::INvDrvServices, "nvdrv", globalServiceState->nvdrv, nvdrv::ApplicationSessionPermissions)
|
||||
SERVICE_CASE(hosbinder::IHOSBinderDriver, "dispdrv", globalServiceState->nvdrv.core.nvMap)
|
||||
SERVICE_CASE(visrv::IApplicationRootService, "vi:u")
|
||||
SERVICE_CASE(visrv::ISystemRootService, "vi:s")
|
||||
SERVICE_CASE(visrv::IManagerRootService, "vi:m")
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "soc/gmmu.h"
|
||||
#include "soc/host1x.h"
|
||||
#include "soc/gm20b.h"
|
||||
|
||||
|
@ -14,10 +13,9 @@ namespace skyline::soc {
|
|||
*/
|
||||
class SOC {
|
||||
public:
|
||||
gmmu::GraphicsMemoryManager gmmu;
|
||||
host1x::Host1X host1x;
|
||||
gm20b::GM20B gm20b;
|
||||
|
||||
SOC(const DeviceState &state) : gmmu(state), gm20b(state) {}
|
||||
SOC(const DeviceState &state) : gm20b(state) {}
|
||||
};
|
||||
}
|
||||
|
|
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
|
||||
|
||||
#include <common/address_space.h>
|
||||
#include "gm20b/engines/maxwell_3d.h"
|
||||
#include "gm20b/gpfifo.h"
|
||||
|
||||
namespace skyline::soc::gm20b {
|
||||
/**
|
||||
* @brief The GPU block in the X1, it contains all GPU engines required for accelerating graphics operations
|
||||
* @note We omit parts of components related to external access such as the GM20B Host, all accesses to the external components are done directly
|
||||
* @note We omit parts of components related to external access such as the grhost, all accesses to the external components are done directly
|
||||
*/
|
||||
class GM20B {
|
||||
public:
|
||||
static constexpr u8 AddressSpaceBits{40}; //!< The width of the GMMU AS
|
||||
using GMMU = FlatMemoryManager<u64, 0, AddressSpaceBits>;
|
||||
|
||||
engine::Engine fermi2D;
|
||||
engine::maxwell3d::Maxwell3D maxwell3D;
|
||||
engine::Engine maxwellCompute;
|
||||
engine::Engine maxwellDma;
|
||||
engine::Engine keplerMemory;
|
||||
GPFIFO gpfifo;
|
||||
GMMU gmmu;
|
||||
|
||||
GM20B(const DeviceState &state) : fermi2D(state), keplerMemory(state), maxwell3D(state), maxwellCompute(state), maxwellDma(state), gpfifo(state) {}
|
||||
GM20B(const DeviceState &state);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,16 +16,6 @@ namespace skyline::soc::gm20b {
|
|||
MaxwellDma = 0xB0B5,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief The parameters of a GPU engine method call
|
||||
*/
|
||||
struct MethodParams {
|
||||
u16 method;
|
||||
u32 argument;
|
||||
u32 subChannel;
|
||||
bool lastCall; //!< If this is the last call in the pushbuffer entry to this specific macro
|
||||
};
|
||||
|
||||
namespace engine {
|
||||
/**
|
||||
* @brief The Engine class provides an interface that can be used to communicate with the GPU's internal engines
|
||||
|
@ -37,13 +27,11 @@ namespace skyline::soc::gm20b {
|
|||
public:
|
||||
Engine(const DeviceState &state) : state(state) {}
|
||||
|
||||
virtual ~Engine() = default;
|
||||
|
||||
/**
|
||||
* @brief Calls an engine method with the given parameters
|
||||
*/
|
||||
virtual void CallMethod(MethodParams params) {
|
||||
state.logger->Warn("Called method in unimplemented engine: 0x{:X} args: 0x{:X}", params.method, params.argument);
|
||||
void CallMethod(u32 method, u32 argument, bool lastCall) {
|
||||
state.logger->Warn("Called method in unimplemented engine: 0x{:X} args: 0x{:X}", method, argument);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -167,10 +167,10 @@ namespace skyline::soc::gm20b::engine {
|
|||
public:
|
||||
GPFIFO(const DeviceState &state) : Engine(state) {}
|
||||
|
||||
void CallMethod(MethodParams params) override {
|
||||
state.logger->Debug("Called method in GPFIFO: 0x{:X} args: 0x{:X}", params.method, params.argument);
|
||||
void CallMethod(u32 method, u32 argument, bool lastCall) {
|
||||
state.logger->Debug("Called method in GPFIFO: 0x{:X} args: 0x{:X}", method, argument);
|
||||
|
||||
registers.raw[params.method] = params.argument;
|
||||
registers.raw[method] = argument;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
// Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
|
||||
|
||||
#include <soc/gmmu.h>
|
||||
#include <common/address_space.h>
|
||||
#include <soc/gm20b/engines/maxwell_3d.h>
|
||||
|
||||
namespace skyline::soc::gm20b::engine::maxwell3d {
|
||||
|
@ -193,7 +193,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
}
|
||||
|
||||
FORCE_INLINE void MacroInterpreter::Send(u32 pArgument) {
|
||||
maxwell3D.CallMethod(MethodParams{methodAddress.address, pArgument, 0, true});
|
||||
maxwell3D.CallMethod(methodAddress.address, pArgument, true);
|
||||
methodAddress.address += methodAddress.increment;
|
||||
}
|
||||
|
||||
|
|
|
@ -72,48 +72,62 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
registers.viewportTransformEnable = true;
|
||||
}
|
||||
|
||||
void Maxwell3D::CallMethod(MethodParams params) {
|
||||
state.logger->Debug("Called method in Maxwell 3D: 0x{:X} args: 0x{:X}", params.method, params.argument);
|
||||
void Maxwell3D::CallMethod(u32 method, u32 argument, bool lastCall) {
|
||||
state.logger->Debug("Called method in Maxwell 3D: 0x{:X} args: 0x{:X}", method, argument);
|
||||
|
||||
// Methods that are greater than the register size are for macro control
|
||||
if (params.method > RegisterCount) {
|
||||
if (!(params.method & 1))
|
||||
macroInvocation.index = ((params.method - RegisterCount) >> 1) % macroPositions.size();
|
||||
if (method > RegisterCount) [[unlikely]] {
|
||||
// Starting a new macro at index 'method - RegisterCount'
|
||||
if (!(method & 1)) {
|
||||
if (macroInvocation.index != -1) {
|
||||
// Flush the current macro as we are switching to another one
|
||||
macroInterpreter.Execute(macroPositions[macroInvocation.index], macroInvocation.arguments);
|
||||
macroInvocation.arguments.clear();
|
||||
}
|
||||
|
||||
macroInvocation.arguments.push_back(params.argument);
|
||||
|
||||
// Macros are always executed on the last method call in a pushbuffer entry
|
||||
if (params.lastCall) {
|
||||
macroInterpreter.Execute(macroPositions[macroInvocation.index], macroInvocation.arguments);
|
||||
|
||||
macroInvocation.arguments.clear();
|
||||
macroInvocation.index = 0;
|
||||
// Setup for the new macro index
|
||||
macroInvocation.index = ((method - RegisterCount) >> 1) % macroPositions.size();
|
||||
}
|
||||
|
||||
macroInvocation.arguments.emplace_back(argument);
|
||||
|
||||
// Flush macro after all of the data in the method call has been sent
|
||||
if (lastCall && macroInvocation.index != -1) {
|
||||
macroInterpreter.Execute(macroPositions[macroInvocation.index], macroInvocation.arguments);
|
||||
macroInvocation.arguments.clear();
|
||||
macroInvocation.index = -1;
|
||||
}
|
||||
|
||||
// Bail out early
|
||||
return;
|
||||
}
|
||||
|
||||
registers.raw[params.method] = params.argument;
|
||||
registers.raw[method] = argument;
|
||||
|
||||
if (shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodTrack || shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodTrackWithFilter)
|
||||
shadowRegisters.raw[params.method] = params.argument;
|
||||
shadowRegisters.raw[method] = argument;
|
||||
else if (shadowRegisters.mme.shadowRamControl == Registers::MmeShadowRamControl::MethodReplay)
|
||||
params.argument = shadowRegisters.raw[params.method];
|
||||
argument = shadowRegisters.raw[method];
|
||||
|
||||
switch (params.method) {
|
||||
switch (method) {
|
||||
case MAXWELL3D_OFFSET(mme.instructionRamLoad):
|
||||
if (registers.mme.instructionRamPointer >= macroCode.size())
|
||||
throw exception("Macro memory is full!");
|
||||
|
||||
macroCode[registers.mme.instructionRamPointer++] = params.argument;
|
||||
macroCode[registers.mme.instructionRamPointer++] = argument;
|
||||
|
||||
// Wraparound writes
|
||||
registers.mme.instructionRamPointer %= macroCode.size();
|
||||
|
||||
break;
|
||||
case MAXWELL3D_OFFSET(mme.startAddressRamLoad):
|
||||
if (registers.mme.startAddressRamPointer >= macroPositions.size())
|
||||
throw exception("Maximum amount of macros reached!");
|
||||
|
||||
macroPositions[registers.mme.startAddressRamPointer++] = params.argument;
|
||||
macroPositions[registers.mme.startAddressRamPointer++] = argument;
|
||||
break;
|
||||
case MAXWELL3D_OFFSET(mme.shadowRamControl):
|
||||
shadowRegisters.mme.shadowRamControl = static_cast<Registers::MmeShadowRamControl>(params.argument);
|
||||
shadowRegisters.mme.shadowRamControl = static_cast<Registers::MmeShadowRamControl>(argument);
|
||||
break;
|
||||
case MAXWELL3D_OFFSET(syncpointAction):
|
||||
state.logger->Debug("Increment syncpoint: {}", static_cast<u16>(registers.syncpointAction.id));
|
||||
|
@ -135,6 +149,8 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
case MAXWELL3D_OFFSET(firmwareCall[4]):
|
||||
registers.raw[0xD00] = 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,7 +173,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
|
||||
switch (registers.semaphore.info.structureSize) {
|
||||
case Registers::SemaphoreInfo::StructureSize::OneWord:
|
||||
state.soc->gmmu.Write<u32>(static_cast<u32>(result), registers.semaphore.address.Pack());
|
||||
state.soc->gm20b.gmmu.Write<u32>(registers.semaphore.address.Pack(), static_cast<u32>(result));
|
||||
break;
|
||||
case Registers::SemaphoreInfo::StructureSize::FourWords: {
|
||||
// Convert the current nanosecond time to GPU ticks
|
||||
|
@ -167,7 +183,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
u64 nsTime{util::GetTimeNs()};
|
||||
u64 timestamp{(nsTime / NsToTickDenominator) * NsToTickNumerator + ((nsTime % NsToTickDenominator) * NsToTickNumerator) / NsToTickDenominator};
|
||||
|
||||
state.soc->gmmu.Write<FourWordResult>(FourWordResult{result, timestamp}, registers.semaphore.address.Pack());
|
||||
state.soc->gm20b.gmmu.Write<FourWordResult>(registers.semaphore.address.Pack(), FourWordResult{result, timestamp});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
std::array<size_t, 0x80> macroPositions{}; //!< The positions of each individual macro in macro memory, there can be a maximum of 0x80 macros at any one time
|
||||
|
||||
struct {
|
||||
u32 index;
|
||||
i32 index{-1};
|
||||
std::vector<u32> arguments;
|
||||
} macroInvocation{}; //!< Data for a macro that is pending execution
|
||||
|
||||
|
@ -557,7 +557,7 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
Registers registers{};
|
||||
Registers shadowRegisters{}; //!< The shadow registers, their function is controlled by the 'shadowRamControl' register
|
||||
|
||||
std::array<u32, 0x10000> macroCode{}; //!< This stores GPU macros, the 256KiB size is from Ryujinx
|
||||
std::array<u32, 0x2000> macroCode{}; //!< This stores GPU macros, writes to it will wraparound on overflow
|
||||
|
||||
Maxwell3D(const DeviceState &state);
|
||||
|
||||
|
@ -566,6 +566,6 @@ namespace skyline::soc::gm20b::engine::maxwell3d {
|
|||
*/
|
||||
void ResetRegs();
|
||||
|
||||
void CallMethod(MethodParams params) override;
|
||||
void CallMethod(u32 method, u32 argument, bool lastCall);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,41 +5,90 @@
|
|||
#include <loader/loader.h>
|
||||
#include <kernel/types/KProcess.h>
|
||||
#include <soc.h>
|
||||
#include <os.h>
|
||||
|
||||
namespace skyline::soc::gm20b {
|
||||
void GPFIFO::Send(MethodParams params) {
|
||||
state.logger->Debug("Called GPU method - method: 0x{:X} argument: 0x{:X} subchannel: 0x{:X} last: {}", params.method, params.argument, params.subChannel, params.lastCall);
|
||||
/**
|
||||
* @brief A single pushbuffer method header that describes a compressed method sequence
|
||||
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_ram.ref.txt#L850
|
||||
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/clb06f.h#L179
|
||||
*/
|
||||
union PushBufferMethodHeader {
|
||||
u32 raw;
|
||||
|
||||
if (params.method == 0) {
|
||||
switch (static_cast<EngineID>(params.argument)) {
|
||||
case EngineID::Fermi2D:
|
||||
subchannels.at(params.subChannel) = &state.soc->gm20b.fermi2D;
|
||||
enum class TertOp : u8 {
|
||||
Grp0IncMethod = 0,
|
||||
Grp0SetSubDevMask = 1,
|
||||
Grp0StoreSubDevMask = 2,
|
||||
Grp0UseSubDevMask = 3,
|
||||
Grp2NonIncMethod = 0,
|
||||
};
|
||||
|
||||
enum class SecOp : u8 {
|
||||
Grp0UseTert = 0,
|
||||
IncMethod = 1,
|
||||
Grp2UseTert = 2,
|
||||
NonIncMethod = 3,
|
||||
ImmdDataMethod = 4,
|
||||
OneInc = 5,
|
||||
Reserved6 = 6,
|
||||
EndPbSegment = 7,
|
||||
};
|
||||
|
||||
u16 methodAddress : 12;
|
||||
struct {
|
||||
u8 _pad0_ : 4;
|
||||
u16 subDeviceMask : 12;
|
||||
};
|
||||
|
||||
struct {
|
||||
u16 _pad1_ : 13;
|
||||
u8 methodSubChannel : 3;
|
||||
union {
|
||||
TertOp tertOp : 3;
|
||||
u16 methodCount : 13;
|
||||
u16 immdData : 13;
|
||||
};
|
||||
};
|
||||
|
||||
struct {
|
||||
u32 _pad2_ : 29;
|
||||
SecOp secOp : 3;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(PushBufferMethodHeader) == sizeof(u32));
|
||||
|
||||
void GPFIFO::Send(u32 method, u32 argument, u32 subChannel, bool lastCall) {
|
||||
constexpr u32 ThreeDSubChannel{0};
|
||||
constexpr u32 ComputeSubChannel{1};
|
||||
constexpr u32 Inline2MemorySubChannel{2};
|
||||
constexpr u32 TwoDSubChannel{3};
|
||||
constexpr u32 CopySubChannel{4}; // HW forces a memory flush on a switch from this subchannel to others
|
||||
|
||||
state.logger->Debug("Called GPU method - method: 0x{:X} argument: 0x{:X} subchannel: 0x{:X} last: {}", method, argument, subChannel, lastCall);
|
||||
|
||||
if (method < engine::GPFIFO::RegisterCount) {
|
||||
gpfifoEngine.CallMethod(method, argument, lastCall);
|
||||
} else {
|
||||
switch (subChannel) {
|
||||
case ThreeDSubChannel:
|
||||
state.soc->gm20b.maxwell3D.CallMethod(method, argument, lastCall);
|
||||
break;
|
||||
case EngineID::KeplerMemory:
|
||||
subchannels.at(params.subChannel) = &state.soc->gm20b.keplerMemory;
|
||||
case ComputeSubChannel:
|
||||
state.soc->gm20b.maxwellCompute.CallMethod(method, argument, lastCall);
|
||||
break;
|
||||
case EngineID::Maxwell3D:
|
||||
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwell3D;
|
||||
case Inline2MemorySubChannel:
|
||||
state.soc->gm20b.keplerMemory.CallMethod(method, argument, lastCall);
|
||||
break;
|
||||
case EngineID::MaxwellCompute:
|
||||
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwellCompute;
|
||||
case TwoDSubChannel:
|
||||
state.soc->gm20b.fermi2D.CallMethod(method, argument, lastCall);
|
||||
break;
|
||||
case EngineID::MaxwellDma:
|
||||
subchannels.at(params.subChannel) = &state.soc->gm20b.maxwellDma;
|
||||
case CopySubChannel:
|
||||
state.soc->gm20b.maxwellDma.CallMethod(method, argument, lastCall);
|
||||
break;
|
||||
default:
|
||||
throw exception("Unknown engine 0x{:X} cannot be bound to subchannel {}", params.argument, params.subChannel);
|
||||
throw exception("Tried to call into a software subchannel: {}!", subChannel);
|
||||
}
|
||||
|
||||
state.logger->Info("Bound GPU engine 0x{:X} to subchannel {}", params.argument, params.subChannel);
|
||||
return;
|
||||
} else if (params.method < engine::GPFIFO::RegisterCount) {
|
||||
gpfifoEngine.CallMethod(params);
|
||||
} else {
|
||||
if (subchannels.at(params.subChannel) == nullptr)
|
||||
throw exception("Calling method on unbound channel");
|
||||
|
||||
subchannels.at(params.subChannel)->CallMethod(params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +105,7 @@ namespace skyline::soc::gm20b {
|
|||
}
|
||||
|
||||
pushBufferData.resize(gpEntry.size);
|
||||
state.soc->gmmu.Read<u32>(pushBufferData, gpEntry.Address());
|
||||
state.soc->gm20b.gmmu.Read<u32>(pushBufferData, gpEntry.Address());
|
||||
|
||||
for (auto entry{pushBufferData.begin()}; entry != pushBufferData.end(); entry++) {
|
||||
// An entry containing all zeroes is a NOP, skip over it
|
||||
|
@ -66,30 +115,29 @@ namespace skyline::soc::gm20b {
|
|||
PushBufferMethodHeader methodHeader{.raw = *entry};
|
||||
switch (methodHeader.secOp) {
|
||||
case PushBufferMethodHeader::SecOp::IncMethod:
|
||||
for (u16 i{}; i < methodHeader.methodCount; i++)
|
||||
Send(MethodParams{static_cast<u16>(methodHeader.methodAddress + i), *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
|
||||
for (u32 i{}; i < methodHeader.methodCount; i++)
|
||||
Send(methodHeader.methodAddress + i, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
|
||||
break;
|
||||
|
||||
case PushBufferMethodHeader::SecOp::NonIncMethod:
|
||||
for (u16 i{}; i < methodHeader.methodCount; i++)
|
||||
Send(MethodParams{methodHeader.methodAddress, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
|
||||
for (u32 i{}; i < methodHeader.methodCount; i++)
|
||||
Send(methodHeader.methodAddress, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
|
||||
break;
|
||||
|
||||
case PushBufferMethodHeader::SecOp::OneInc:
|
||||
for (u16 i{}; i < methodHeader.methodCount; i++)
|
||||
Send(MethodParams{static_cast<u16>(methodHeader.methodAddress + static_cast<bool>(i)), *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1});
|
||||
for (u32 i{}; i < methodHeader.methodCount; i++)
|
||||
Send(methodHeader.methodAddress + !!i, *++entry, methodHeader.methodSubChannel, i == methodHeader.methodCount - 1);
|
||||
break;
|
||||
|
||||
case PushBufferMethodHeader::SecOp::ImmdDataMethod:
|
||||
Send(MethodParams{methodHeader.methodAddress, methodHeader.immdData, methodHeader.methodSubChannel, true});
|
||||
Send(methodHeader.methodAddress, methodHeader.immdData, methodHeader.methodSubChannel, true);
|
||||
break;
|
||||
|
||||
case PushBufferMethodHeader::SecOp::EndPbSegment:
|
||||
return;
|
||||
|
||||
default:
|
||||
state.logger->Warn("Unsupported pushbuffer method SecOp: {}", static_cast<u8>(methodHeader.secOp));
|
||||
break;
|
||||
throw exception("Unsupported pushbuffer method SecOp: {}", static_cast<u8>(methodHeader.secOp));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +154,7 @@ namespace skyline::soc::gm20b {
|
|||
try {
|
||||
signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler);
|
||||
pushBuffers->Process([this](GpEntry gpEntry) {
|
||||
state.logger->Debug("Processing pushbuffer: 0x{:X}", gpEntry.Address());
|
||||
state.logger->Warn("Processing pushbuffer: 0x{:X}", gpEntry.Address());
|
||||
Process(gpEntry);
|
||||
});
|
||||
} catch (const signal::SignalException &e) {
|
||||
|
|
|
@ -72,56 +72,6 @@ namespace skyline::soc::gm20b {
|
|||
};
|
||||
static_assert(sizeof(GpEntry) == sizeof(u64));
|
||||
|
||||
/**
|
||||
* @brief A single pushbuffer method header that describes a compressed method sequence
|
||||
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_ram.ref.txt#L850
|
||||
* @url https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/clb06f.h#L179
|
||||
*/
|
||||
union PushBufferMethodHeader {
|
||||
u32 raw;
|
||||
|
||||
enum class TertOp : u8 {
|
||||
Grp0IncMethod = 0,
|
||||
Grp0SetSubDevMask = 1,
|
||||
Grp0StoreSubDevMask = 2,
|
||||
Grp0UseSubDevMask = 3,
|
||||
Grp2NonIncMethod = 0,
|
||||
};
|
||||
|
||||
enum class SecOp : u8 {
|
||||
Grp0UseTert = 0,
|
||||
IncMethod = 1,
|
||||
Grp2UseTert = 2,
|
||||
NonIncMethod = 3,
|
||||
ImmdDataMethod = 4,
|
||||
OneInc = 5,
|
||||
Reserved6 = 6,
|
||||
EndPbSegment = 7,
|
||||
};
|
||||
|
||||
u16 methodAddress : 12;
|
||||
struct {
|
||||
u8 _pad0_ : 4;
|
||||
u16 subDeviceMask : 12;
|
||||
};
|
||||
|
||||
struct {
|
||||
u16 _pad1_ : 13;
|
||||
u8 methodSubChannel : 3;
|
||||
union {
|
||||
TertOp tertOp : 3;
|
||||
u16 methodCount : 13;
|
||||
u16 immdData : 13;
|
||||
};
|
||||
};
|
||||
|
||||
struct {
|
||||
u32 _pad2_ : 29;
|
||||
SecOp secOp : 3;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(PushBufferMethodHeader) == sizeof(u32));
|
||||
|
||||
/**
|
||||
* @brief The GPFIFO class handles creating pushbuffers from GP entries and then processing them
|
||||
* @note This class doesn't perfectly map to any particular hardware component on the X1, it does a mix of the GPU Host PBDMA (With and handling the GPFIFO entries
|
||||
|
@ -138,7 +88,7 @@ namespace skyline::soc::gm20b {
|
|||
/**
|
||||
* @brief Sends a method call to the GPU hardware
|
||||
*/
|
||||
void Send(MethodParams params);
|
||||
void Send(u32 method, u32 argument, u32 subchannel, bool lastCall);
|
||||
|
||||
/**
|
||||
* @brief Processes the pushbuffer contained within the given GpEntry, calling methods as needed
|
||||
|
|
|
@ -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
|
||||
|
||||
#include <concepts>
|
||||
#include <common.h>
|
||||
|
||||
namespace skyline::vfs {
|
||||
|
@ -82,7 +83,7 @@ namespace skyline::vfs {
|
|||
/**
|
||||
* @brief Implicit casting for reading into spans of different types
|
||||
*/
|
||||
template<typename T, typename std::enable_if<!std::is_same_v<T, u8>, bool>::type = true>
|
||||
template<typename T> requires (!std::same_as<T, u8>)
|
||||
size_t Read(span <T> output, size_t offset = 0) {
|
||||
return Read(output.template cast<u8>(), offset);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue