code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "video_core/dma_pusher.h"

namespace Tegra {
namespace Control {
struct ChannelState;
}
} // namespace Tegra

namespace Service::Nvidia {

namespace NvCore {
class Container;
class NvMap;
class SyncpointManager;
} // namespace NvCore

class EventInterface;
} // namespace Service::Nvidia

namespace Service::Nvidia::Devices {

class nvhost_as_gpu;
class nvmap;
class nvhost_gpu final : public nvdevice {
public:
    explicit nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
                        NvCore::Container& core);
    ~nvhost_gpu() override;

    NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<u8> output) override;
    NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<const u8> inline_input, std::span<u8> output) override;
    NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                    std::span<u8> inline_output) override;

    void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
    void OnClose(DeviceFD fd) override;

    Kernel::KEvent* QueryEvent(u32 event_id) override;

private:
    friend class nvhost_as_gpu;
    enum class CtxObjects : u32_le {
        Ctx2D = 0x902D,
        Ctx3D = 0xB197,
        CtxCompute = 0xB1C0,
        CtxKepler = 0xA140,
        CtxDMA = 0xB0B5,
        CtxChannelGPFIFO = 0xB06F,
    };

    struct IoctlSetNvmapFD {
        s32_le nvmap_fd{};
    };
    static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");

    struct IoctlChannelSetTimeout {
        u32_le timeout{};
    };
    static_assert(sizeof(IoctlChannelSetTimeout) == 4, "IoctlChannelSetTimeout is incorrect size");

    struct IoctlAllocGPFIFO {
        u32_le num_entries{};
        u32_le flags{};
    };
    static_assert(sizeof(IoctlAllocGPFIFO) == 8, "IoctlAllocGPFIFO is incorrect size");

    struct IoctlClientData {
        u64_le data{};
    };
    static_assert(sizeof(IoctlClientData) == 8, "IoctlClientData is incorrect size");

    struct IoctlZCullBind {
        u64_le gpu_va{};
        u32_le mode{}; // 0=global, 1=no_ctxsw, 2=separate_buffer, 3=part_of_regular_buf
        INSERT_PADDING_WORDS(1);
    };
    static_assert(sizeof(IoctlZCullBind) == 16, "IoctlZCullBind is incorrect size");

    struct IoctlSetErrorNotifier {
        u64_le offset{};
        u64_le size{};
        u32_le mem{}; // nvmap object handle
        INSERT_PADDING_WORDS(1);
    };
    static_assert(sizeof(IoctlSetErrorNotifier) == 24, "IoctlSetErrorNotifier is incorrect size");

    struct IoctlChannelSetPriority {
        u32_le priority{};
    };
    static_assert(sizeof(IoctlChannelSetPriority) == 4,
                  "IoctlChannelSetPriority is incorrect size");

    struct IoctlSetTimeslice {
        u32_le timeslice{};
    };
    static_assert(sizeof(IoctlSetTimeslice) == 4, "IoctlSetTimeslice is incorrect size");

    struct IoctlEventIdControl {
        u32_le cmd{}; // 0=disable, 1=enable, 2=clear
        u32_le id{};
    };
    static_assert(sizeof(IoctlEventIdControl) == 8, "IoctlEventIdControl is incorrect size");

    struct IoctlGetErrorNotification {
        u64_le timestamp{};
        u32_le info32{};
        u16_le info16{};
        u16_le status{}; // always 0xFFFF
    };
    static_assert(sizeof(IoctlGetErrorNotification) == 16,
                  "IoctlGetErrorNotification is incorrect size");

    static_assert(sizeof(NvFence) == 8, "Fence is incorrect size");

    struct IoctlAllocGpfifoEx {
        u32_le num_entries{};
        u32_le flags{};
        u32_le unk0{};
        u32_le unk1{};
        u32_le unk2{};
        u32_le unk3{};
        u32_le unk4{};
        u32_le unk5{};
    };
    static_assert(sizeof(IoctlAllocGpfifoEx) == 32, "IoctlAllocGpfifoEx is incorrect size");

    struct IoctlAllocGpfifoEx2 {
        u32_le num_entries{}; // in
        u32_le flags{};       // in
        u32_le unk0{};        // in (1 works)
        NvFence fence_out{};  // out
        u32_le unk1{};        // in
        u32_le unk2{};        // in
        u32_le unk3{};        // in
    };
    static_assert(sizeof(IoctlAllocGpfifoEx2) == 32, "IoctlAllocGpfifoEx2 is incorrect size");

    struct IoctlAllocObjCtx {
        u32_le class_num{}; // 0x902D=2d, 0xB197=3d, 0xB1C0=compute, 0xA140=kepler, 0xB0B5=DMA,
                            // 0xB06F=channel_gpfifo
        u32_le flags{};
        u64_le obj_id{}; // (ignored) used for FREE_OBJ_CTX ioctl, which is not supported
    };
    static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size");

    struct IoctlSubmitGpfifo {
        u64_le address{};     // pointer to gpfifo entry structs
        u32_le num_entries{}; // number of fence objects being submitted
        union {
            u32_le raw;
            BitField<0, 1, u32_le> fence_wait;      // append a wait sync_point to the list
            BitField<1, 1, u32_le> fence_increment; // append an increment to the list
            BitField<2, 1, u32_le> new_hw_format;   // mostly ignored
            BitField<4, 1, u32_le> suppress_wfi;    // suppress wait for interrupt
            BitField<8, 1, u32_le> increment_value; // increment the returned fence
        } flags;
        NvFence fence{}; // returned new fence object for others to wait on
    };
    static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
                  "IoctlSubmitGpfifo is incorrect size");

    struct IoctlGetWaitbase {
        u32 unknown{}; // seems to be ignored? Nintendo added this
        u32 value{};
    };
    static_assert(sizeof(IoctlGetWaitbase) == 8, "IoctlGetWaitbase is incorrect size");

    s32_le nvmap_fd{};
    u64_le user_data{};
    IoctlZCullBind zcull_params{};
    u32_le channel_priority{};
    u32_le channel_timeslice{};

    NvResult SetNVMAPfd(IoctlSetNvmapFD& params);
    NvResult SetClientData(IoctlClientData& params);
    NvResult GetClientData(IoctlClientData& params);
    NvResult ZCullBind(IoctlZCullBind& params);
    NvResult SetErrorNotifier(IoctlSetErrorNotifier& params);
    NvResult SetChannelPriority(IoctlChannelSetPriority& params);
    NvResult AllocGPFIFOEx2(IoctlAllocGpfifoEx2& params, DeviceFD fd);
    NvResult AllocateObjectContext(IoctlAllocObjCtx& params);

    NvResult SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, Tegra::CommandList&& entries);
    NvResult SubmitGPFIFOBase1(IoctlSubmitGpfifo& params,
                               std::span<Tegra::CommandListHeader> commands, bool kickoff = false);
    NvResult SubmitGPFIFOBase2(IoctlSubmitGpfifo& params,
                               std::span<const Tegra::CommandListHeader> commands);

    NvResult GetWaitbase(IoctlGetWaitbase& params);
    NvResult ChannelSetTimeout(IoctlChannelSetTimeout& params);
    NvResult ChannelSetTimeslice(IoctlSetTimeslice& params);

    EventInterface& events_interface;
    NvCore::Container& core;
    NvCore::SyncpointManager& syncpoint_manager;
    NvCore::NvMap& nvmap;
    std::shared_ptr<Tegra::Control::ChannelState> channel_state;
    std::unordered_map<DeviceFD, NvCore::SessionId> sessions;
    u32 channel_syncpoint;
    std::mutex channel_mutex;

    // Events
    Kernel::KEvent* sm_exception_breakpoint_int_report_event;
    Kernel::KEvent* sm_exception_breakpoint_pause_report_event;
    Kernel::KEvent* error_notifier_event;
};

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "audio_core/audio_core.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
#include "video_core/renderer_base.h"

namespace Service::Nvidia::Devices {

nvhost_nvdec::nvhost_nvdec(Core::System& system_, NvCore::Container& core_)
    : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::NvDec} {}
nvhost_nvdec::~nvhost_nvdec() = default;

NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                              std::span<u8> output) {
    switch (command.group) {
    case 0x0:
        switch (command.cmd) {
        case 0x1: {
            auto& host1x_file = core.Host1xDeviceFile();
            if (!host1x_file.fd_to_id.contains(fd)) {
                host1x_file.fd_to_id[fd] = host1x_file.nvdec_next_id++;
            }
            return WrapFixedVariable(this, &nvhost_nvdec::Submit, input, output, fd);
        }
        case 0x2:
            return WrapFixed(this, &nvhost_nvdec::GetSyncpoint, input, output);
        case 0x3:
            return WrapFixed(this, &nvhost_nvdec::GetWaitbase, input, output);
        case 0x7:
            return WrapFixed(this, &nvhost_nvdec::SetSubmitTimeout, input, output);
        case 0x9:
            return WrapFixedVariable(this, &nvhost_nvdec::MapBuffer, input, output, fd);
        case 0xa:
            return WrapFixedVariable(this, &nvhost_nvdec::UnmapBuffer, input, output);
        default:
            break;
        }
        break;
    case 'H':
        switch (command.cmd) {
        case 0x1:
            return WrapFixed(this, &nvhost_nvdec::SetNVMAPfd, input, output);
        default:
            break;
        }
        break;
    }

    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvhost_nvdec::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                              std::span<const u8> inline_input, std::span<u8> output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input,
                              std::span<u8> output, std::span<u8> inline_output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
    LOG_INFO(Service_NVDRV, "NVDEC video stream started");
    system.SetNVDECActive(true);
    sessions[fd] = session_id;
}

void nvhost_nvdec::OnClose(DeviceFD fd) {
    LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
    auto& host1x_file = core.Host1xDeviceFile();
    const auto iter = host1x_file.fd_to_id.find(fd);
    if (iter != host1x_file.fd_to_id.end()) {
        system.GPU().ClearCdmaInstance(iter->second);
    }
    system.SetNVDECActive(false);
    auto it = sessions.find(fd);
    if (it != sessions.end()) {
        sessions.erase(it);
    }
}

} // namespace Service::Nvidia::Devices

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"

namespace Service::Nvidia::Devices {

class nvhost_nvdec final : public nvhost_nvdec_common {
public:
    explicit nvhost_nvdec(Core::System& system_, NvCore::Container& core);
    ~nvhost_nvdec() override;

    NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<u8> output) override;
    NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<const u8> inline_input, std::span<u8> output) override;
    NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                    std::span<u8> inline_output) override;

    void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
    void OnClose(DeviceFD fd) override;
};

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <cstring>

#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
#include "core/memory.h"
#include "video_core/host1x/host1x.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"

namespace Service::Nvidia::Devices {

namespace {
// Copies count amount of type T from the input vector into the dst vector.
// Returns the number of bytes written into dst.
template <typename T>
std::size_t SliceVectors(std::span<const u8> input, std::vector<T>& dst, std::size_t count,
                         std::size_t offset) {
    if (dst.empty()) {
        return 0;
    }
    const size_t bytes_copied = count * sizeof(T);
    if (input.size() < offset + bytes_copied) {
        return 0;
    }
    std::memcpy(dst.data(), input.data() + offset, bytes_copied);
    return bytes_copied;
}

// Writes the data in src to an offset into the dst vector. The offset is specified in bytes
// Returns the number of bytes written into dst.
template <typename T>
std::size_t WriteVectors(std::span<u8> dst, const std::vector<T>& src, std::size_t offset) {
    if (src.empty()) {
        return 0;
    }
    const size_t bytes_copied = src.size() * sizeof(T);
    if (dst.size() < offset + bytes_copied) {
        return 0;
    }
    std::memcpy(dst.data() + offset, src.data(), bytes_copied);
    return bytes_copied;
}
} // Anonymous namespace

nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, NvCore::Container& core_,
                                         NvCore::ChannelType channel_type_)
    : nvdevice{system_}, core{core_}, syncpoint_manager{core.GetSyncpointManager()},
      nvmap{core.GetNvMapFile()}, channel_type{channel_type_} {
    auto& syncpts_accumulated = core.Host1xDeviceFile().syncpts_accumulated;
    if (syncpts_accumulated.empty()) {
        channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
    } else {
        channel_syncpoint = syncpts_accumulated.front();
        syncpts_accumulated.pop_front();
    }
}

nvhost_nvdec_common::~nvhost_nvdec_common() {
    core.Host1xDeviceFile().syncpts_accumulated.push_back(channel_syncpoint);
}

NvResult nvhost_nvdec_common::SetNVMAPfd(IoctlSetNvmapFD& params) {
    LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);

    nvmap_fd = params.nvmap_fd;
    return NvResult::Success;
}

NvResult nvhost_nvdec_common::Submit(IoctlSubmit& params, std::span<u8> data, DeviceFD fd) {
    LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count);

    // Instantiate param buffers
    std::vector<CommandBuffer> command_buffers(params.cmd_buffer_count);
    std::vector<Reloc> relocs(params.relocation_count);
    std::vector<u32> reloc_shifts(params.relocation_count);
    std::vector<SyncptIncr> syncpt_increments(params.syncpoint_count);
    std::vector<u32> fence_thresholds(params.fence_count);

    // Slice input into their respective buffers
    std::size_t offset = 0;
    offset += SliceVectors(data, command_buffers, params.cmd_buffer_count, offset);
    offset += SliceVectors(data, relocs, params.relocation_count, offset);
    offset += SliceVectors(data, reloc_shifts, params.relocation_count, offset);
    offset += SliceVectors(data, syncpt_increments, params.syncpoint_count, offset);
    offset += SliceVectors(data, fence_thresholds, params.fence_count, offset);

    auto& gpu = system.GPU();
    auto* session = core.GetSession(sessions[fd]);

    if (gpu.UseNvdec()) {
        for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
            const SyncptIncr& syncpt_incr = syncpt_increments[i];
            fence_thresholds[i] =
                syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
        }
    }
    for (const auto& cmd_buffer : command_buffers) {
        const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
        ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
        Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
        session->process->GetMemory().ReadBlock(object->address + cmd_buffer.offset, cmdlist.data(),
                                                cmdlist.size() * sizeof(u32));
        gpu.PushCommandBuffer(core.Host1xDeviceFile().fd_to_id[fd], cmdlist);
    }
    // Some games expect command_buffers to be written back
    offset = 0;
    offset += WriteVectors(data, command_buffers, offset);
    offset += WriteVectors(data, relocs, offset);
    offset += WriteVectors(data, reloc_shifts, offset);
    offset += WriteVectors(data, syncpt_increments, offset);
    offset += WriteVectors(data, fence_thresholds, offset);

    return NvResult::Success;
}

NvResult nvhost_nvdec_common::GetSyncpoint(IoctlGetSyncpoint& params) {
    LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
    params.value = channel_syncpoint;
    return NvResult::Success;
}

NvResult nvhost_nvdec_common::GetWaitbase(IoctlGetWaitbase& params) {
    LOG_CRITICAL(Service_NVDRV, "called WAITBASE");
    params.value = 0; // Seems to be hard coded at 0
    return NvResult::Success;
}

NvResult nvhost_nvdec_common::MapBuffer(IoctlMapBuffer& params, std::span<MapBufferEntry> entries,
                                        DeviceFD fd) {
    const size_t num_entries = std::min(params.num_entries, static_cast<u32>(entries.size()));
    for (size_t i = 0; i < num_entries; i++) {
        DAddr pin_address = nvmap.PinHandle(entries[i].map_handle, true);
        entries[i].map_address = static_cast<u32>(pin_address);
    }

    return NvResult::Success;
}

NvResult nvhost_nvdec_common::UnmapBuffer(IoctlMapBuffer& params,
                                          std::span<MapBufferEntry> entries) {
    const size_t num_entries = std::min(params.num_entries, static_cast<u32>(entries.size()));
    for (size_t i = 0; i < num_entries; i++) {
        nvmap.UnpinHandle(entries[i].map_handle);
        entries[i] = {};
    }

    params = {};
    return NvResult::Success;
}

NvResult nvhost_nvdec_common::SetSubmitTimeout(u32 timeout) {
    LOG_WARNING(Service_NVDRV, "(STUBBED) called");
    return NvResult::Success;
}

Kernel::KEvent* nvhost_nvdec_common::QueryEvent(u32 event_id) {
    LOG_CRITICAL(Service_NVDRV, "Unknown HOSTX1 Event {}", event_id);
    return nullptr;
}

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <deque>
#include <unordered_map>
#include <vector>

#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"

namespace Service::Nvidia {

namespace NvCore {
class Container;
class NvMap;
} // namespace NvCore

namespace Devices {

class nvhost_nvdec_common : public nvdevice {
public:
    explicit nvhost_nvdec_common(Core::System& system_, NvCore::Container& core,
                                 NvCore::ChannelType channel_type);
    ~nvhost_nvdec_common() override;

protected:
    struct IoctlSetNvmapFD {
        s32_le nvmap_fd{};
    };
    static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");

    struct IoctlSubmitCommandBuffer {
        u32_le id{};
        u32_le offset{};
        u32_le count{};
    };
    static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC,
                  "IoctlSubmitCommandBuffer is incorrect size");
    struct IoctlSubmit {
        u32_le cmd_buffer_count{};
        u32_le relocation_count{};
        u32_le syncpoint_count{};
        u32_le fence_count{};
    };
    static_assert(sizeof(IoctlSubmit) == 0x10, "IoctlSubmit has incorrect size");

    struct CommandBuffer {
        s32 memory_id{};
        u32 offset{};
        s32 word_count{};
    };
    static_assert(sizeof(CommandBuffer) == 0xC, "CommandBuffer has incorrect size");

    struct Reloc {
        s32 cmdbuffer_memory{};
        s32 cmdbuffer_offset{};
        s32 target{};
        s32 target_offset{};
    };
    static_assert(sizeof(Reloc) == 0x10, "Reloc has incorrect size");

    struct SyncptIncr {
        u32 id{};
        u32 increments{};
        u32 unk0{};
        u32 unk1{};
        u32 unk2{};
    };
    static_assert(sizeof(SyncptIncr) == 0x14, "SyncptIncr has incorrect size");

    struct IoctlGetSyncpoint {
        // Input
        u32_le param{};
        // Output
        u32_le value{};
    };
    static_assert(sizeof(IoctlGetSyncpoint) == 8, "IocGetIdParams has wrong size");

    struct IoctlGetWaitbase {
        u32_le unknown{}; // seems to be ignored? Nintendo added this
        u32_le value{};
    };
    static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size");

    struct IoctlMapBuffer {
        u32_le num_entries{};
        u32_le data_address{}; // Ignored by the driver.
        u32_le attach_host_ch_das{};
    };
    static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");

    struct IocGetIdParams {
        // Input
        u32_le param{};
        // Output
        u32_le value{};
    };
    static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");

    // Used for mapping and unmapping command buffers
    struct MapBufferEntry {
        u32_le map_handle{};
        u32_le map_address{};
    };
    static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size");

    /// Ioctl command implementations
    NvResult SetNVMAPfd(IoctlSetNvmapFD&);
    NvResult Submit(IoctlSubmit& params, std::span<u8> input, DeviceFD fd);
    NvResult GetSyncpoint(IoctlGetSyncpoint& params);
    NvResult GetWaitbase(IoctlGetWaitbase& params);
    NvResult MapBuffer(IoctlMapBuffer& params, std::span<MapBufferEntry> entries, DeviceFD fd);
    NvResult UnmapBuffer(IoctlMapBuffer& params, std::span<MapBufferEntry> entries);
    NvResult SetSubmitTimeout(u32 timeout);

    Kernel::KEvent* QueryEvent(u32 event_id) override;

    u32 channel_syncpoint;
    s32_le nvmap_fd{};
    u32_le submit_timeout{};
    NvCore::Container& core;
    NvCore::SyncpointManager& syncpoint_manager;
    NvCore::NvMap& nvmap;
    NvCore::ChannelType channel_type;
    std::array<u32, MaxSyncPoints> device_syncpoints{};
    std::unordered_map<DeviceFD, NvCore::SessionId> sessions;
};
}; // namespace Devices
} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <cstring>

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"

namespace Service::Nvidia::Devices {

nvhost_nvjpg::nvhost_nvjpg(Core::System& system_) : nvdevice{system_} {}
nvhost_nvjpg::~nvhost_nvjpg() = default;

NvResult nvhost_nvjpg::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                              std::span<u8> output) {
    switch (command.group) {
    case 'H':
        switch (command.cmd) {
        case 0x1:
            return WrapFixed(this, &nvhost_nvjpg::SetNVMAPfd, input, output);
        default:
            break;
        }
        break;
    default:
        break;
    }

    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvhost_nvjpg::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                              std::span<const u8> inline_input, std::span<u8> output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvhost_nvjpg::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input,
                              std::span<u8> output, std::span<u8> inline_output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

void nvhost_nvjpg::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {}
void nvhost_nvjpg::OnClose(DeviceFD fd) {}

NvResult nvhost_nvjpg::SetNVMAPfd(IoctlSetNvmapFD& params) {
    LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);

    nvmap_fd = params.nvmap_fd;
    return NvResult::Success;
}

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"

namespace Service::Nvidia::Devices {

class nvhost_nvjpg final : public nvdevice {
public:
    explicit nvhost_nvjpg(Core::System& system_);
    ~nvhost_nvjpg() override;

    NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<u8> output) override;
    NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<const u8> inline_input, std::span<u8> output) override;
    NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                    std::span<u8> inline_output) override;

    void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
    void OnClose(DeviceFD fd) override;

private:
    struct IoctlSetNvmapFD {
        s32_le nvmap_fd{};
    };
    static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");

    s32_le nvmap_fd{};

    NvResult SetNVMAPfd(IoctlSetNvmapFD& params);
};

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
#include "video_core/renderer_base.h"

namespace Service::Nvidia::Devices {

nvhost_vic::nvhost_vic(Core::System& system_, NvCore::Container& core_)
    : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::VIC} {}

nvhost_vic::~nvhost_vic() = default;

NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                            std::span<u8> output) {
    switch (command.group) {
    case 0x0:
        switch (command.cmd) {
        case 0x1: {
            auto& host1x_file = core.Host1xDeviceFile();
            if (!host1x_file.fd_to_id.contains(fd)) {
                host1x_file.fd_to_id[fd] = host1x_file.vic_next_id++;
            }
            return WrapFixedVariable(this, &nvhost_vic::Submit, input, output, fd);
        }
        case 0x2:
            return WrapFixed(this, &nvhost_vic::GetSyncpoint, input, output);
        case 0x3:
            return WrapFixed(this, &nvhost_vic::GetWaitbase, input, output);
        case 0x9:
            return WrapFixedVariable(this, &nvhost_vic::MapBuffer, input, output, fd);
        case 0xa:
            return WrapFixedVariable(this, &nvhost_vic::UnmapBuffer, input, output);
        default:
            break;
        }
        break;
    case 'H':
        switch (command.cmd) {
        case 0x1:
            return WrapFixed(this, &nvhost_vic::SetNVMAPfd, input, output);
        default:
            break;
        }
        break;
    default:
        break;
    }

    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvhost_vic::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                            std::span<const u8> inline_input, std::span<u8> output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input,
                            std::span<u8> output, std::span<u8> inline_output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

void nvhost_vic::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
    sessions[fd] = session_id;
}

void nvhost_vic::OnClose(DeviceFD fd) {
    auto& host1x_file = core.Host1xDeviceFile();
    const auto iter = host1x_file.fd_to_id.find(fd);
    if (iter != host1x_file.fd_to_id.end()) {
        system.GPU().ClearCdmaInstance(iter->second);
    }
    sessions.erase(fd);
}

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"

namespace Service::Nvidia::Devices {

class nvhost_vic final : public nvhost_nvdec_common {
public:
    explicit nvhost_vic(Core::System& system_, NvCore::Container& core);
    ~nvhost_vic();

    NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<u8> output) override;
    NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<const u8> inline_input, std::span<u8> output) override;
    NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                    std::span<u8> inline_output) override;

    void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
    void OnClose(DeviceFD fd) override;
};
} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <bit>
#include <cstring>

#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/memory.h"

using Core::Memory::YUZU_PAGESIZE;

namespace Service::Nvidia::Devices {

nvmap::nvmap(Core::System& system_, NvCore::Container& container_)
    : nvdevice{system_}, container{container_}, file{container.GetNvMapFile()} {}

nvmap::~nvmap() = default;

NvResult nvmap::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                       std::span<u8> output) {
    switch (command.group) {
    case 0x1:
        switch (command.cmd) {
        case 0x1:
            return WrapFixed(this, &nvmap::IocCreate, input, output);
        case 0x3:
            return WrapFixed(this, &nvmap::IocFromId, input, output);
        case 0x4:
            return WrapFixed(this, &nvmap::IocAlloc, input, output, fd);
        case 0x5:
            return WrapFixed(this, &nvmap::IocFree, input, output, fd);
        case 0x9:
            return WrapFixed(this, &nvmap::IocParam, input, output);
        case 0xe:
            return WrapFixed(this, &nvmap::IocGetId, input, output);
        default:
            break;
        }
        break;
    default:
        break;
    }

    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvmap::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                       std::span<const u8> inline_input, std::span<u8> output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                       std::span<u8> inline_output) {
    UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
    return NvResult::NotImplemented;
}

void nvmap::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
    sessions[fd] = session_id;
}
void nvmap::OnClose(DeviceFD fd) {
    auto it = sessions.find(fd);
    if (it != sessions.end()) {
        sessions.erase(it);
    }
}

NvResult nvmap::IocCreate(IocCreateParams& params) {
    LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size);

    std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
    auto result =
        file.CreateHandle(Common::AlignUp(params.size, YUZU_PAGESIZE), handle_description);
    if (result != NvResult::Success) {
        LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
        return result;
    }
    handle_description->orig_size = params.size; // Orig size is the unaligned size
    params.handle = handle_description->id;
    LOG_DEBUG(Service_NVDRV, "handle: {}, size: 0x{:X}", handle_description->id, params.size);

    return NvResult::Success;
}

NvResult nvmap::IocAlloc(IocAllocParams& params, DeviceFD fd) {
    LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address);

    if (!params.handle) {
        LOG_CRITICAL(Service_NVDRV, "Handle is 0");
        return NvResult::BadValue;
    }

    if ((params.align - 1) & params.align) {
        LOG_CRITICAL(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
        return NvResult::BadValue;
    }

    // Force page size alignment at a minimum
    if (params.align < YUZU_PAGESIZE) {
        params.align = YUZU_PAGESIZE;
    }

    auto handle_description{file.GetHandle(params.handle)};
    if (!handle_description) {
        LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
        return NvResult::BadValue;
    }

    if (handle_description->allocated) {
        LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
        return NvResult::InsufficientMemory;
    }

    const auto result = handle_description->Alloc(params.flags, params.align, params.kind,
                                                  params.address, sessions[fd]);
    if (result != NvResult::Success) {
        LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
        return result;
    }
    bool is_out_io{};
    auto process = container.GetSession(sessions[fd])->process;
    ASSERT(process->GetPageTable()
               .LockForMapDeviceAddressSpace(&is_out_io, handle_description->address,
                                             handle_description->size,
                                             Kernel::KMemoryPermission::None, true, false)
               .IsSuccess());
    return result;
}

NvResult nvmap::IocGetId(IocGetIdParams& params) {
    LOG_DEBUG(Service_NVDRV, "called");

    // See the comment in FromId for extra info on this function
    if (!params.handle) {
        LOG_CRITICAL(Service_NVDRV, "Error!");
        return NvResult::BadValue;
    }

    auto handle_description{file.GetHandle(params.handle)};
    if (!handle_description) {
        LOG_CRITICAL(Service_NVDRV, "Error!");
        return NvResult::AccessDenied; // This will always return EPERM irrespective of if the
                                       // handle exists or not
    }

    params.id = handle_description->id;
    return NvResult::Success;
}

NvResult nvmap::IocFromId(IocFromIdParams& params) {
    LOG_DEBUG(Service_NVDRV, "called, id:{}", params.id);

    // 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 (!params.id) {
        LOG_CRITICAL(Service_NVDRV, "Zero Id is invalid!");
        return NvResult::BadValue;
    }

    auto handle_description{file.GetHandle(params.id)};
    if (!handle_description) {
        LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
        return NvResult::BadValue;
    }

    auto result = handle_description->Duplicate(false);
    if (result != NvResult::Success) {
        LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
        return result;
    }
    params.handle = handle_description->id;
    return NvResult::Success;
}

NvResult nvmap::IocParam(IocParamParams& params) {
    enum class ParamTypes { Size = 1, Alignment = 2, Base = 3, Heap = 4, Kind = 5, Compr = 6 };

    LOG_DEBUG(Service_NVDRV, "called type={}", params.param);

    if (!params.handle) {
        LOG_CRITICAL(Service_NVDRV, "Invalid handle!");
        return NvResult::BadValue;
    }

    auto handle_description{file.GetHandle(params.handle)};
    if (!handle_description) {
        LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
        return NvResult::BadValue;
    }

    switch (params.param) {
    case HandleParameterType::Size:
        params.result = static_cast<u32_le>(handle_description->orig_size);
        break;
    case HandleParameterType::Alignment:
        params.result = static_cast<u32_le>(handle_description->align);
        break;
    case HandleParameterType::Base:
        params.result = static_cast<u32_le>(-22); // posix EINVAL
        break;
    case HandleParameterType::Heap:
        if (handle_description->allocated)
            params.result = 0x40000000;
        else
            params.result = 0;
        break;
    case HandleParameterType::Kind:
        params.result = handle_description->kind;
        break;
    case HandleParameterType::IsSharedMemMapped:
        params.result = handle_description->is_shared_mem_mapped;
        break;
    default:
        return NvResult::BadValue;
    }

    return NvResult::Success;
}

NvResult nvmap::IocFree(IocFreeParams& params, DeviceFD fd) {
    LOG_DEBUG(Service_NVDRV, "called");

    if (!params.handle) {
        LOG_CRITICAL(Service_NVDRV, "Handle null freed?");
        return NvResult::Success;
    }

    if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
        auto process = container.GetSession(sessions[fd])->process;
        if (freeInfo->can_unlock) {
            ASSERT(process->GetPageTable()
                       .UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
                       .IsSuccess());
        }
        params.address = freeInfo->address;
        params.size = static_cast<u32>(freeInfo->size);
        params.flags.raw = 0;
        params.flags.map_uncached.Assign(freeInfo->was_uncached);
    } else {
        // This is possible when there's internal dups or other duplicates.
    }

    return NvResult::Success;
}

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>
#include <unordered_map>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"

namespace Service::Nvidia::NvCore {
class Container;
} // namespace Service::Nvidia::NvCore

namespace Service::Nvidia::Devices {

class nvmap final : public nvdevice {
public:
    explicit nvmap(Core::System& system_, NvCore::Container& container);
    ~nvmap() override;

    nvmap(const nvmap&) = delete;
    nvmap& operator=(const nvmap&) = delete;

    NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<u8> output) override;
    NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<const u8> inline_input, std::span<u8> output) override;
    NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                    std::span<u8> inline_output) override;

    void OnOpen(NvCore::SessionId session_id, DeviceFD fd) override;
    void OnClose(DeviceFD fd) override;

    enum class HandleParameterType : u32_le {
        Size = 1,
        Alignment = 2,
        Base = 3,
        Heap = 4,
        Kind = 5,
        IsSharedMemMapped = 6
    };

    struct IocCreateParams {
        // Input
        u32_le size{};
        // Output
        u32_le handle{};
    };
    static_assert(sizeof(IocCreateParams) == 8, "IocCreateParams has wrong size");

    struct IocFromIdParams {
        // Input
        u32_le id{};
        // Output
        u32_le handle{};
    };
    static_assert(sizeof(IocFromIdParams) == 8, "IocFromIdParams has wrong size");

    struct IocAllocParams {
        // Input
        u32_le handle{};
        u32_le heap_mask{};
        NvCore::NvMap::Handle::Flags flags{};
        u32_le align{};
        u8 kind{};
        INSERT_PADDING_BYTES(7);
        u64_le address{};
    };
    static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");

    struct IocFreeParams {
        u32_le handle{};
        INSERT_PADDING_BYTES(4);
        u64_le address{};
        u32_le size{};
        NvCore::NvMap::Handle::Flags flags{};
    };
    static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");

    struct IocParamParams {
        // Input
        u32_le handle{};
        HandleParameterType param{};
        // Output
        u32_le result{};
    };
    static_assert(sizeof(IocParamParams) == 12, "IocParamParams has wrong size");

    struct IocGetIdParams {
        // Output
        u32_le id{};
        // Input
        u32_le handle{};
    };
    static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");

    NvResult IocCreate(IocCreateParams& params);
    NvResult IocAlloc(IocAllocParams& params, DeviceFD fd);
    NvResult IocGetId(IocGetIdParams& params);
    NvResult IocFromId(IocFromIdParams& params);
    NvResult IocParam(IocParamParams& params);
    NvResult IocFree(IocFreeParams& params, DeviceFD fd);

private:
    /// Id to use for the next handle that is created.
    u32 next_handle = 0;

    /// Id to use for the next object that is created.
    u32 next_id = 0;

    NvCore::Container& container;
    NvCore::NvMap& file;
    std::unordered_map<DeviceFD, NvCore::SessionId> sessions;
};

} // namespace Service::Nvidia::Devices


code:
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"

namespace Service::Nvidia {

constexpr u32 MaxSyncPoints = 192;
constexpr u32 MaxNvEvents = 64;
using DeviceFD = s32;

constexpr DeviceFD INVALID_NVDRV_FD = -1;

struct NvFence {
    s32 id;
    u32 value;
};
static_assert(sizeof(NvFence) == 8, "NvFence has wrong size");

enum class NvResult : u32 {
    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,
    InsufficientTransferMemory = 0x1000,
    InsufficientVideoMemory = 0x10000,
    BadSurfaceColorScheme = 0x10001,
    InvalidSurface = 0x10002,
    SurfaceNotSupported = 0x10003,
    DispInitFailed = 0x20000,
    DispAlreadyAttached = 0x20001,
    DispTooManyDisplays = 0x20002,
    DispNoDisplaysAttached = 0x20003,
    DispModeNotSupported = 0x20004,
    DispNotFound = 0x20005,
    DispAttachDisallowed = 0x20006,
    DispTypeNotSupported = 0x20007,
    DispAuthenticationFailed = 0x20008,
    DispNotAttached = 0x20009,
    DispSamePwrState = 0x2000A,
    DispEdidFailure = 0x2000B,
    DispDsiReadAckError = 0x2000C,
    DispDsiReadInvalidResp = 0x2000D,
    FileWriteFailed = 0x30000,
    FileReadFailed = 0x30001,
    EndOfFile = 0x30002,
    FileOperationFailed = 0x30003,
    DirOperationFailed = 0x30004,
    EndOfDirList = 0x30005,
    ConfigVarNotFound = 0x30006,
    InvalidConfigVar = 0x30007,
    LibraryNotFound = 0x30008,
    SymbolNotFound = 0x30009,
    MemoryMapFailed = 0x3000A,
    IoctlFailed = 0x3000F,
    AccessDenied = 0x30010,
    DeviceNotFound = 0x30011,
    KernelDriverNotFound = 0x30012,
    FileNotFound = 0x30013,
    PathAlreadyExists = 0x30014,
    ModuleNotPresent = 0xA000E,
};

// obtained from
// https://github.com/skyline-emu/skyline/blob/nvdec-dev/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h#L47
enum class EventState {
    Available = 0,
    Waiting = 1,
    Cancelling = 2,
    Signalling = 3,
    Signalled = 4,
    Cancelled = 5,
};

union Ioctl {
    u32_le raw;
    BitField<0, 8, u32> cmd;
    BitField<8, 8, u32> group;
    BitField<16, 14, u32> length;
    BitField<30, 1, u32> is_in;
    BitField<31, 1, u32> is_out;
};

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

#include <utility>

#include <fmt/format.h>
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
#include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"
#include "core/hle/service/nvdrv/nvmemp.h"
#include "core/hle/service/nvnflinger/nvnflinger.h"
#include "core/hle/service/server_manager.h"
#include "video_core/gpu.h"

namespace Service::Nvidia {

EventInterface::EventInterface(Module& module_) : module{module_}, guard{}, on_signal{} {}

EventInterface::~EventInterface() = default;

Kernel::KEvent* EventInterface::CreateEvent(std::string name) {
    Kernel::KEvent* new_event = module.service_context.CreateEvent(std::move(name));
    return new_event;
}

void EventInterface::FreeEvent(Kernel::KEvent* event) {
    module.service_context.CloseEvent(event);
}

void LoopProcess(Core::System& system) {
    auto server_manager = std::make_unique<ServerManager>(system);
    auto module = std::make_shared<Module>(system);
    const auto NvdrvInterfaceFactoryForApplication = [&, module] {
        return std::make_shared<NVDRV>(system, module, "nvdrv");
    };
    const auto NvdrvInterfaceFactoryForApplets = [&, module] {
        return std::make_shared<NVDRV>(system, module, "nvdrv:a");
    };
    const auto NvdrvInterfaceFactoryForSysmodules = [&, module] {
        return std::make_shared<NVDRV>(system, module, "nvdrv:s");
    };
    const auto NvdrvInterfaceFactoryForTesting = [&, module] {
        return std::make_shared<NVDRV>(system, module, "nvdrv:t");
    };
    server_manager->RegisterNamedService("nvdrv", NvdrvInterfaceFactoryForApplication);
    server_manager->RegisterNamedService("nvdrv:a", NvdrvInterfaceFactoryForApplets);
    server_manager->RegisterNamedService("nvdrv:s", NvdrvInterfaceFactoryForSysmodules);
    server_manager->RegisterNamedService("nvdrv:t", NvdrvInterfaceFactoryForTesting);
    server_manager->RegisterNamedService("nvmemp", std::make_shared<NVMEMP>(system));
    ServerManager::RunServer(std::move(server_manager));
}

Module::Module(Core::System& system)
    : container{system.Host1x()}, service_context{system, "nvdrv"}, events_interface{*this} {
    builders["/dev/nvhost-as-gpu"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_as_gpu>(system, *this, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvhost-gpu"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_gpu>(system, events_interface, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvhost-ctrl-gpu"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_ctrl_gpu>(system, events_interface);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvmap"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvmap>(system, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvdisp_disp0"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvdisp_disp0>(system, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvhost-ctrl"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_ctrl>(system, events_interface, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvhost-nvdec"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_nvdec>(system, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvhost-nvjpg"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_nvjpg>(system);
        return open_files.emplace(fd, std::move(device)).first;
    };
    builders["/dev/nvhost-vic"] = [this, &system](DeviceFD fd) {
        auto device = std::make_shared<Devices::nvhost_vic>(system, container);
        return open_files.emplace(fd, std::move(device)).first;
    };
}

Module::~Module() {}

NvResult Module::VerifyFD(DeviceFD fd) const {
    if (fd < 0) {
        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
        return NvResult::InvalidState;
    }

    if (open_files.find(fd) == open_files.end()) {
        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
        return NvResult::NotImplemented;
    }

    return NvResult::Success;
}

DeviceFD Module::Open(const std::string& device_name, NvCore::SessionId session_id) {
    auto it = builders.find(device_name);
    if (it == builders.end()) {
        LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
        return INVALID_NVDRV_FD;
    }

    const DeviceFD fd = next_fd++;
    auto& builder = it->second;
    auto device = builder(fd)->second;

    device->OnOpen(session_id, fd);

    return fd;
}

NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input,
                        std::span<u8> output) {
    if (fd < 0) {
        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
        return NvResult::InvalidState;
    }

    const auto itr = open_files.find(fd);

    if (itr == open_files.end()) {
        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
        return NvResult::NotImplemented;
    }

    return itr->second->Ioctl1(fd, command, input, output);
}

NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                        std::span<const u8> inline_input, std::span<u8> output) {
    if (fd < 0) {
        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
        return NvResult::InvalidState;
    }

    const auto itr = open_files.find(fd);

    if (itr == open_files.end()) {
        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
        return NvResult::NotImplemented;
    }

    return itr->second->Ioctl2(fd, command, input, inline_input, output);
}

NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                        std::span<u8> inline_output) {
    if (fd < 0) {
        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
        return NvResult::InvalidState;
    }

    const auto itr = open_files.find(fd);

    if (itr == open_files.end()) {
        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
        return NvResult::NotImplemented;
    }

    return itr->second->Ioctl3(fd, command, input, output, inline_output);
}

NvResult Module::Close(DeviceFD fd) {
    if (fd < 0) {
        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
        return NvResult::InvalidState;
    }

    const auto itr = open_files.find(fd);

    if (itr == open_files.end()) {
        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
        return NvResult::NotImplemented;
    }

    itr->second->OnClose(fd);

    open_files.erase(itr);

    return NvResult::Success;
}

NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
    if (fd < 0) {
        LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
        return NvResult::InvalidState;
    }

    const auto itr = open_files.find(fd);

    if (itr == open_files.end()) {
        LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
        return NvResult::NotImplemented;
    }

    event = itr->second->QueryEvent(event_id);
    if (!event) {
        return NvResult::BadParameter;
    }
    return NvResult::Success;
}

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <functional>
#include <list>
#include <memory>
#include <span>
#include <string>
#include <unordered_map>

#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/service.h"

namespace Core {
class System;
}

namespace Kernel {
class KEvent;
}

namespace Service::Nvidia {

namespace NvCore {
class Container;
class SyncpointManager;
} // namespace NvCore

namespace Devices {
class nvdevice;
class nvhost_ctrl;
} // namespace Devices

class Module;

class EventInterface {
public:
    explicit EventInterface(Module& module_);
    ~EventInterface();

    Kernel::KEvent* CreateEvent(std::string name);

    void FreeEvent(Kernel::KEvent* event);

private:
    Module& module;
    std::mutex guard;
    std::list<Devices::nvhost_ctrl*> on_signal;
};

class Module final {
public:
    explicit Module(Core::System& system_);
    ~Module();

    /// Returns a pointer to one of the available devices, identified by its name.
    template <typename T>
    std::shared_ptr<T> GetDevice(DeviceFD fd) {
        auto itr = open_files.find(fd);
        if (itr == open_files.end())
            return nullptr;
        return std::static_pointer_cast<T>(itr->second);
    }

    NvResult VerifyFD(DeviceFD fd) const;

    /// Opens a device node and returns a file descriptor to it.
    DeviceFD Open(const std::string& device_name, NvCore::SessionId session_id);

    /// Sends an ioctl command to the specified file descriptor.
    NvResult Ioctl1(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output);

    NvResult Ioctl2(DeviceFD fd, Ioctl command, std::span<const u8> input,
                    std::span<const u8> inline_input, std::span<u8> output);

    NvResult Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> input, std::span<u8> output,
                    std::span<u8> inline_output);

    /// Closes a device file descriptor and returns operation success.
    NvResult Close(DeviceFD fd);

    NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);

    NvCore::Container& GetContainer() {
        return container;
    }

private:
    friend class EventInterface;

    /// Manages syncpoints on the host
    NvCore::Container container;

    /// Id to use for the next open file descriptor.
    DeviceFD next_fd = 1;

    using FilesContainerType = std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
    /// Mapping of file descriptors to the devices they reference.
    FilesContainerType open_files;

    KernelHelpers::ServiceContext service_context;

    EventInterface events_interface;

    std::unordered_map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
};

void LoopProcess(Core::System& system);

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
// SPDX-License-Identifier: GPL-3.0-or-later

#include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvdrv/nvdrv_interface.h"

namespace Service::Nvidia {

void NVDRV::Open(HLERequestContext& ctx) {
    LOG_DEBUG(Service_NVDRV, "called");
    IPC::ResponseBuilder rb{ctx, 4};
    rb.Push(ResultSuccess);

    if (!is_initialized) {
        rb.Push<DeviceFD>(0);
        rb.PushEnum(NvResult::NotInitialized);

        LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
        return;
    }

    const auto& buffer = ctx.ReadBuffer();
    const std::string device_name(Common::StringFromBuffer(buffer));

    if (device_name == "/dev/nvhost-prof-gpu") {
        rb.Push<DeviceFD>(0);
        rb.PushEnum(NvResult::NotSupported);

        LOG_WARNING(Service_NVDRV, "/dev/nvhost-prof-gpu cannot be opened in production");
        return;
    }

    DeviceFD fd = nvdrv->Open(device_name, session_id);

    rb.Push<DeviceFD>(fd);
    rb.PushEnum(fd != INVALID_NVDRV_FD ? NvResult::Success : NvResult::FileOperationFailed);
}

void NVDRV::ServiceError(HLERequestContext& ctx, NvResult result) {
    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(result);
}

void NVDRV::Ioctl1(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto fd = rp.Pop<DeviceFD>();
    const auto command = rp.PopRaw<Ioctl>();
    LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);

    if (!is_initialized) {
        ServiceError(ctx, NvResult::NotInitialized);
        LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
        return;
    }

    // Check device
    output_buffer.resize_destructive(ctx.GetWriteBufferSize(0));
    const auto input_buffer = ctx.ReadBuffer(0);

    const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer);
    if (command.is_out != 0) {
        ctx.WriteBuffer(output_buffer);
    }

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(nv_result);
}

void NVDRV::Ioctl2(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto fd = rp.Pop<DeviceFD>();
    const auto command = rp.PopRaw<Ioctl>();
    LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);

    if (!is_initialized) {
        ServiceError(ctx, NvResult::NotInitialized);
        LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
        return;
    }

    const auto input_buffer = ctx.ReadBuffer(0);
    const auto input_inlined_buffer = ctx.ReadBuffer(1);
    output_buffer.resize_destructive(ctx.GetWriteBufferSize(0));

    const auto nv_result =
        nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer);
    if (command.is_out != 0) {
        ctx.WriteBuffer(output_buffer);
    }

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(nv_result);
}

void NVDRV::Ioctl3(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto fd = rp.Pop<DeviceFD>();
    const auto command = rp.PopRaw<Ioctl>();
    LOG_DEBUG(Service_NVDRV, "called fd={}, ioctl=0x{:08X}", fd, command.raw);

    if (!is_initialized) {
        ServiceError(ctx, NvResult::NotInitialized);
        LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
        return;
    }

    const auto input_buffer = ctx.ReadBuffer(0);
    output_buffer.resize_destructive(ctx.GetWriteBufferSize(0));
    inline_output_buffer.resize_destructive(ctx.GetWriteBufferSize(1));

    const auto nv_result =
        nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, inline_output_buffer);
    if (command.is_out != 0) {
        ctx.WriteBuffer(output_buffer, 0);
        ctx.WriteBuffer(inline_output_buffer, 1);
    }

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(nv_result);
}

void NVDRV::Close(HLERequestContext& ctx) {
    LOG_DEBUG(Service_NVDRV, "called");

    if (!is_initialized) {
        ServiceError(ctx, NvResult::NotInitialized);
        LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
        return;
    }

    IPC::RequestParser rp{ctx};
    const auto fd = rp.Pop<DeviceFD>();
    const auto result = nvdrv->Close(fd);

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(result);
}

void NVDRV::Initialize(HLERequestContext& ctx) {
    LOG_WARNING(Service_NVDRV, "(STUBBED) called");
    IPC::ResponseBuilder rb{ctx, 3};
    SCOPE_EXIT {
        rb.Push(ResultSuccess);
        rb.PushEnum(NvResult::Success);
    };

    if (is_initialized) {
        // No need to initialize again
        return;
    }

    IPC::RequestParser rp{ctx};
    const auto process_handle{ctx.GetCopyHandle(0)};
    // The transfer memory is lent to nvdrv as a work buffer since nvdrv is
    // unable to allocate as much memory on its own. For HLE it's unnecessary to handle it
    [[maybe_unused]] const auto transfer_memory_handle{ctx.GetCopyHandle(1)};
    [[maybe_unused]] const auto transfer_memory_size = rp.Pop<u32>();

    auto& container = nvdrv->GetContainer();
    auto process = ctx.GetObjectFromHandle<Kernel::KProcess>(process_handle);
    session_id = container.OpenSession(process.GetPointerUnsafe());

    is_initialized = true;
}

void NVDRV::QueryEvent(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    const auto fd = rp.Pop<DeviceFD>();
    const auto event_id = rp.Pop<u32>();

    if (!is_initialized) {
        ServiceError(ctx, NvResult::NotInitialized);
        LOG_ERROR(Service_NVDRV, "NvServices is not initialized!");
        return;
    }

    Kernel::KEvent* event = nullptr;
    NvResult result = nvdrv->QueryEvent(fd, event_id, event);

    if (result == NvResult::Success) {
        IPC::ResponseBuilder rb{ctx, 3, 1};
        rb.Push(ResultSuccess);
        auto& readable_event = event->GetReadableEvent();
        rb.PushCopyObjects(readable_event);
        rb.PushEnum(NvResult::Success);
    } else {
        LOG_ERROR(Service_NVDRV, "Invalid event request!");
        IPC::ResponseBuilder rb{ctx, 3};
        rb.Push(ResultSuccess);
        rb.PushEnum(result);
    }
}

void NVDRV::SetAruid(HLERequestContext& ctx) {
    IPC::RequestParser rp{ctx};
    pid = rp.Pop<u64>();
    LOG_WARNING(Service_NVDRV, "(STUBBED) called, pid=0x{:X}", pid);

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(NvResult::Success);
}

void NVDRV::SetGraphicsFirmwareMemoryMarginEnabled(HLERequestContext& ctx) {
    LOG_WARNING(Service_NVDRV, "(STUBBED) called");

    IPC::ResponseBuilder rb{ctx, 2};
    rb.Push(ResultSuccess);
}

void NVDRV::GetStatus(HLERequestContext& ctx) {
    LOG_WARNING(Service_NVDRV, "(STUBBED) called");

    IPC::ResponseBuilder rb{ctx, 3};
    rb.Push(ResultSuccess);
    rb.PushEnum(NvResult::Success);
}

void NVDRV::DumpGraphicsMemoryInfo(HLERequestContext& ctx) {
    // According to SwitchBrew, this has no inputs and no outputs, so effectively does nothing on
    // retail hardware.
    LOG_DEBUG(Service_NVDRV, "called");

    IPC::ResponseBuilder rb{ctx, 2};
    rb.Push(ResultSuccess);
}

NVDRV::NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name)
    : ServiceFramework{system_, name}, nvdrv{std::move(nvdrv_)} {
    static const FunctionInfo functions[] = {
        {0, &NVDRV::Open, "Open"},
        {1, &NVDRV::Ioctl1, "Ioctl"},
        {2, &NVDRV::Close, "Close"},
        {3, &NVDRV::Initialize, "Initialize"},
        {4, &NVDRV::QueryEvent, "QueryEvent"},
        {5, nullptr, "MapSharedMem"},
        {6, &NVDRV::GetStatus, "GetStatus"},
        {7, nullptr, "SetAruidForTest"},
        {8, &NVDRV::SetAruid, "SetAruid"},
        {9, &NVDRV::DumpGraphicsMemoryInfo, "DumpGraphicsMemoryInfo"},
        {10, nullptr, "InitializeDevtools"},
        {11, &NVDRV::Ioctl2, "Ioctl2"},
        {12, &NVDRV::Ioctl3, "Ioctl3"},
        {13, &NVDRV::SetGraphicsFirmwareMemoryMarginEnabled,
         "SetGraphicsFirmwareMemoryMarginEnabled"},
    };
    RegisterHandlers(functions);
}

NVDRV::~NVDRV() {
    if (is_initialized) {
        auto& container = nvdrv->GetContainer();
        container.CloseSession(session_id);
    }
}

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <memory>

#include "common/scratch_buffer.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/service.h"

namespace Service::Nvidia {

class NVDRV final : public ServiceFramework<NVDRV> {
public:
    explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
    ~NVDRV() override;

    std::shared_ptr<Module> GetModule() const {
        return nvdrv;
    }

private:
    void Open(HLERequestContext& ctx);
    void Ioctl1(HLERequestContext& ctx);
    void Ioctl2(HLERequestContext& ctx);
    void Ioctl3(HLERequestContext& ctx);
    void Close(HLERequestContext& ctx);
    void Initialize(HLERequestContext& ctx);
    void QueryEvent(HLERequestContext& ctx);
    void SetAruid(HLERequestContext& ctx);
    void SetGraphicsFirmwareMemoryMarginEnabled(HLERequestContext& ctx);
    void GetStatus(HLERequestContext& ctx);
    void DumpGraphicsMemoryInfo(HLERequestContext& ctx);

    void ServiceError(HLERequestContext& ctx, NvResult result);

    std::shared_ptr<Module> nvdrv;

    u64 pid{};
    bool is_initialized{};
    NvCore::SessionId session_id{};
    Common::ScratchBuffer<u8> output_buffer;
    Common::ScratchBuffer<u8> inline_output_buffer;
};

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/service/nvdrv/nvmemp.h"

namespace Service::Nvidia {

NVMEMP::NVMEMP(Core::System& system_) : ServiceFramework{system_, "nvmemp"} {
    static const FunctionInfo functions[] = {
        {0, &NVMEMP::Open, "Open"},
        {1, &NVMEMP::GetAruid, "GetAruid"},
    };
    RegisterHandlers(functions);
}

NVMEMP::~NVMEMP() = default;

void NVMEMP::Open(HLERequestContext& ctx) {
    UNIMPLEMENTED();
}

void NVMEMP::GetAruid(HLERequestContext& ctx) {
    UNIMPLEMENTED();
}

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/hle/service/service.h"

namespace Core {
class System;
}

namespace Service::Nvidia {

class NVMEMP final : public ServiceFramework<NVMEMP> {
public:
    explicit NVMEMP(Core::System& system_);
    ~NVMEMP() override;

private:
    void Open(HLERequestContext& ctx);
    void GetAruid(HLERequestContext& ctx);
};

} // namespace Service::Nvidia


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/binder/IBinder.h

#pragma once

#include <span>

#include "common/common_types.h"

namespace Kernel {
class KReadableEvent;
} // namespace Kernel

namespace Service {
class HLERequestContext;
}

namespace Service::android {

class IBinder {
public:
    virtual ~IBinder() = default;
    virtual void Transact(u32 code, std::span<const u8> parcel_data, std::span<u8> parcel_reply,
                          u32 flags) = 0;
    virtual Kernel::KReadableEvent* GetNativeHandle(u32 type_id) = 0;
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItem.h

#pragma once

#include <memory>

#include "common/common_types.h"
#include "common/math_util.h"
#include "core/hle/service/nvnflinger/ui/fence.h"
#include "core/hle/service/nvnflinger/window.h"

namespace Service::android {

class GraphicBuffer;

class BufferItem final {
public:
    constexpr BufferItem() = default;

    std::shared_ptr<GraphicBuffer> graphic_buffer;
    Fence fence;
    Common::Rectangle<s32> crop;
    NativeWindowTransform transform{};
    u32 scaling_mode{};
    s64 timestamp{};
    bool is_auto_timestamp{};
    u64 frame_number{};

    // The default value for buf, used to indicate this doesn't correspond to a slot.
    static constexpr s32 INVALID_BUFFER_SLOT = -1;
    union {
        s32 slot{INVALID_BUFFER_SLOT};
        s32 buf;
    };

    bool is_droppable{};
    bool acquire_called{};
    bool transform_to_display_inverse{};
    s32 swap_interval{};
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferItemConsumer.cpp

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_item_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"

namespace Service::android {

BufferItemConsumer::BufferItemConsumer(std::shared_ptr<BufferQueueConsumer> consumer_)
    : ConsumerBase{std::move(consumer_)} {}

Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
                                         bool wait_for_fence) {
    if (!item) {
        return Status::BadValue;
    }

    std::scoped_lock lock{mutex};

    if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) {
        if (status != Status::NoBufferAvailable) {
            LOG_ERROR(Service_Nvnflinger, "Failed to acquire buffer: {}", status);
        }
        return status;
    }

    if (wait_for_fence) {
        UNIMPLEMENTED();
    }

    item->graphic_buffer = slots[item->slot].graphic_buffer;

    return Status::NoError;
}

Status BufferItemConsumer::ReleaseBuffer(const BufferItem& item, const Fence& release_fence) {
    std::scoped_lock lock{mutex};

    if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence);
        status != Status::NoError) {
        LOG_ERROR(Service_Nvnflinger, "Failed to add fence: {}", status);
    }

    if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer);
        status != Status::NoError) {
        LOG_WARNING(Service_Nvnflinger, "Failed to release buffer: {}", status);
        return status;
    }

    return Status::NoError;
}

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItemConsumer.h

#pragma once

#include <chrono>
#include <memory>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/consumer_base.h"
#include "core/hle/service/nvnflinger/status.h"

namespace Service::android {

class BufferItem;

class BufferItemConsumer final : public ConsumerBase {
public:
    explicit BufferItemConsumer(std::shared_ptr<BufferQueueConsumer> consumer);
    Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
                         bool wait_for_fence = true);
    Status ReleaseBuffer(const BufferItem& item, const Fence& release_fence);
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp

#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvnflinger/buffer_queue_core.h"
#include "core/hle/service/nvnflinger/parcel.h"
#include "core/hle/service/nvnflinger/producer_listener.h"

namespace Service::android {

BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
    : core{std::move(core_)}, slots{core->slots} {}

BufferQueueConsumer::~BufferQueueConsumer() = default;

Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
                                          std::chrono::nanoseconds expected_present) {
    std::scoped_lock lock{core->mutex};

    // Check that the consumer doesn't currently have the maximum number of buffers acquired.
    const s32 num_acquired_buffers{
        static_cast<s32>(std::count_if(slots.begin(), slots.end(), [](const auto& slot) {
            return slot.buffer_state == BufferState::Acquired;
        }))};

    if (num_acquired_buffers >= core->max_acquired_buffer_count + 1) {
        LOG_ERROR(Service_Nvnflinger, "max acquired buffer count reached: {} (max {})",
                  num_acquired_buffers, core->max_acquired_buffer_count);
        return Status::InvalidOperation;
    }

    // Check if the queue is empty.
    if (core->queue.empty()) {
        return Status::NoBufferAvailable;
    }

    auto front(core->queue.begin());

    // If expected_present is specified, we may not want to return a buffer yet.
    if (expected_present.count() != 0) {
        constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second

        // The expected_present argument indicates when the buffer is expected to be presented
        // on-screen.
        while (core->queue.size() > 1 && !core->queue[0].is_auto_timestamp) {
            const auto& buffer_item{core->queue[1]};

            // If entry[1] is timely, drop entry[0] (and repeat).
            const auto desired_present = buffer_item.timestamp;
            if (desired_present < expected_present.count() - MAX_REASONABLE_NSEC ||
                desired_present > expected_present.count()) {
                // This buffer is set to display in the near future, or desired_present is garbage.
                LOG_DEBUG(Service_Nvnflinger, "nodrop desire={} expect={}", desired_present,
                          expected_present.count());
                break;
            }

            LOG_DEBUG(Service_Nvnflinger, "drop desire={} expect={} size={}", desired_present,
                      expected_present.count(), core->queue.size());

            if (core->StillTracking(*front)) {
                // Front buffer is still in mSlots, so mark the slot as free
                slots[front->slot].buffer_state = BufferState::Free;
            }

            core->queue.erase(front);
            front = core->queue.begin();
        }

        // See if the front buffer is ready to be acquired.
        const auto desired_present = front->timestamp;
        if (desired_present > expected_present.count() &&
            desired_present < expected_present.count() + MAX_REASONABLE_NSEC) {
            LOG_DEBUG(Service_Nvnflinger, "defer desire={} expect={}", desired_present,
                      expected_present.count());
            return Status::PresentLater;
        }

        LOG_DEBUG(Service_Nvnflinger, "accept desire={} expect={}", desired_present,
                  expected_present.count());
    }

    const auto slot = front->slot;
    *out_buffer = *front;

    LOG_DEBUG(Service_Nvnflinger, "acquiring slot={}", slot);

    // If the front buffer is still being tracked, update its slot state
    if (core->StillTracking(*front)) {
        slots[slot].acquire_called = true;
        slots[slot].needs_cleanup_on_release = false;
        slots[slot].buffer_state = BufferState::Acquired;

        // TODO: for now, avoid resetting the fence, so that when we next return this
        // slot to the producer, it will wait for the fence to pass. We should fix this
        // by properly waiting for the fence in the BufferItemConsumer.
        // slots[slot].fence = Fence::NoFence();
    }

    // If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
    // avoid unnecessarily remapping this buffer on the consumer side.
    if (out_buffer->acquire_called) {
        out_buffer->graphic_buffer = nullptr;
    }

    core->queue.erase(front);

    // We might have freed a slot while dropping old buffers, or the producer  may be blocked
    // waiting for the number of buffers in the queue to decrease.
    core->SignalDequeueCondition();

    return Status::NoError;
}

Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence) {
    if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
        LOG_ERROR(Service_Nvnflinger, "slot {} out of range", slot);
        return Status::BadValue;
    }

    std::shared_ptr<IProducerListener> listener;
    {
        std::scoped_lock lock{core->mutex};

        // If the frame number has changed because the buffer has been reallocated, we can ignore
        // this ReleaseBuffer for the old buffer.
        if (frame_number != slots[slot].frame_number) {
            return Status::StaleBufferSlot;
        }

        // Make sure this buffer hasn't been queued while acquired by the consumer.
        auto current(core->queue.begin());
        while (current != core->queue.end()) {
            if (current->slot == slot) {
                LOG_ERROR(Service_Nvnflinger, "buffer slot {} pending release is currently queued",
                          slot);
                return Status::BadValue;
            }
            ++current;
        }

        if (slots[slot].buffer_state == BufferState::Acquired) {
            // TODO: for now, avoid resetting the fence, so that when we next return this
            // slot to the producer, it can wait for its own fence to pass. We should fix this
            // by properly waiting for the fence in the BufferItemConsumer.
            // slots[slot].fence = release_fence;
            slots[slot].buffer_state = BufferState::Free;

            listener = core->connected_producer_listener;

            LOG_DEBUG(Service_Nvnflinger, "releasing slot {}", slot);
        } else if (slots[slot].needs_cleanup_on_release) {
            LOG_DEBUG(Service_Nvnflinger, "releasing a stale buffer slot {} (state = {})", slot,
                      slots[slot].buffer_state);
            slots[slot].needs_cleanup_on_release = false;
            return Status::StaleBufferSlot;
        } else {
            LOG_ERROR(Service_Nvnflinger,
                      "attempted to release buffer slot {} but its state was {}", slot,
                      slots[slot].buffer_state);

            return Status::BadValue;
        }

        core->SignalDequeueCondition();
    }

    // Call back without lock held
    if (listener != nullptr) {
        listener->OnBufferReleased();
    }

    return Status::NoError;
}

Status BufferQueueConsumer::Connect(std::shared_ptr<IConsumerListener> consumer_listener,
                                    bool controlled_by_app) {
    if (consumer_listener == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "consumer_listener may not be nullptr");
        return Status::BadValue;
    }

    LOG_DEBUG(Service_Nvnflinger, "controlled_by_app={}", controlled_by_app);

    std::scoped_lock lock{core->mutex};

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }

    core->consumer_listener = std::move(consumer_listener);
    core->consumer_controlled_by_app = controlled_by_app;

    return Status::NoError;
}

Status BufferQueueConsumer::Disconnect() {
    LOG_DEBUG(Service_Nvnflinger, "called");

    std::scoped_lock lock{core->mutex};

    if (core->consumer_listener == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "no consumer is connected");
        return Status::BadValue;
    }

    core->is_abandoned = true;
    core->consumer_listener = nullptr;
    core->queue.clear();
    core->FreeAllBuffersLocked();
    core->SignalDequeueCondition();

    return Status::NoError;
}

Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) {
    if (out_slot_mask == nullptr) {
        LOG_ERROR(Service_Nvnflinger, "out_slot_mask may not be nullptr");
        return Status::BadValue;
    }

    std::scoped_lock lock{core->mutex};

    if (core->is_abandoned) {
        LOG_ERROR(Service_Nvnflinger, "BufferQueue has been abandoned");
        return Status::NoInit;
    }

    u64 mask = 0;
    for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
        if (!slots[s].acquire_called) {
            mask |= (1ULL << s);
        }
    }

    // Remove from the mask queued buffers for which acquire has been called, since the consumer
    // will not receive their buffer addresses and so must retain their cached information
    auto current(core->queue.begin());
    while (current != core->queue.end()) {
        if (current->acquire_called) {
            mask &= ~(1ULL << current->slot);
        }
        ++current;
    }

    LOG_DEBUG(Service_Nvnflinger, "returning mask {}", mask);
    *out_slot_mask = mask;
    return Status::NoError;
}

void BufferQueueConsumer::Transact(u32 code, std::span<const u8> parcel_data,
                                   std::span<u8> parcel_reply, u32 flags) {
    // Values used by BnGraphicBufferConsumer onTransact
    enum class TransactionId {
        AcquireBuffer = 1,
        DetachBuffer = 2,
        AttachBuffer = 3,
        ReleaseBuffer = 4,
        ConsumerConnect = 5,
        ConsumerDisconnect = 6,
        GetReleasedBuffers = 7,
        SetDefaultBufferSize = 8,
        SetDefaultMaxBufferCount = 9,
        DisableAsyncBuffer = 10,
        SetMaxAcquiredBufferCount = 11,
        SetConsumerName = 12,
        SetDefaultBufferFormat = 13,
        SetConsumerUsageBits = 14,
        SetTransformHint = 15,
        GetSidebandStream = 16,
        Unknown18 = 18,
        Unknown20 = 20,
    };

    Status status{Status::NoError};
    InputParcel parcel_in{parcel_data};
    OutputParcel parcel_out{};

    switch (static_cast<TransactionId>(code)) {
    case TransactionId::AcquireBuffer: {
        BufferItem item;
        const s64 present_when = parcel_in.Read<s64>();

        status = AcquireBuffer(&item, std::chrono::nanoseconds{present_when});

        // TODO: can't write this directly, needs a flattener for the sp<GraphicBuffer>
        // parcel_out.WriteFlattened(item);
        UNREACHABLE();
    }
    case TransactionId::ReleaseBuffer: {
        const s32 slot = parcel_in.Read<s32>();
        const u64 frame_number = parcel_in.Read<u64>();
        const auto release_fence = parcel_in.ReadFlattened<Fence>();

        status = ReleaseBuffer(slot, frame_number, release_fence);

        break;
    }
    case TransactionId::GetReleasedBuffers: {
        u64 slot_mask = 0;

        status = GetReleasedBuffers(&slot_mask);

        parcel_out.Write(slot_mask);
        break;
    }
    default:
        ASSERT_MSG(false, "called, code={} flags={}", code, flags);
        break;
    }

    parcel_out.Write(status);

    const auto serialized = parcel_out.Serialize();
    std::memcpy(parcel_reply.data(), serialized.data(),
                std::min(parcel_reply.size(), serialized.size()));
}

Kernel::KReadableEvent* BufferQueueConsumer::GetNativeHandle(u32 type_id) {
    ASSERT_MSG(false, "called, type_id={}", type_id);
    return nullptr;
}

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueConsumer.h

#pragma once

#include <chrono>
#include <memory>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/binder.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/status.h"

namespace Service::android {

class BufferItem;
class BufferQueueCore;
class IConsumerListener;

class BufferQueueConsumer final : public IBinder {
public:
    explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
    ~BufferQueueConsumer() override;

    Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
    Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence);
    Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app);
    Status Disconnect();
    Status GetReleasedBuffers(u64* out_slot_mask);

    void Transact(u32 code, std::span<const u8> parcel_data, std::span<u8> parcel_reply,
                  u32 flags) override;

    Kernel::KReadableEvent* GetNativeHandle(u32 type_id) override;

private:
    std::shared_ptr<BufferQueueCore> core;
    BufferQueueDefs::SlotsType& slots;
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp

#include "common/assert.h"

#include "core/hle/service/nvnflinger/buffer_queue_core.h"

namespace Service::android {

BufferQueueCore::BufferQueueCore() = default;

BufferQueueCore::~BufferQueueCore() = default;

void BufferQueueCore::SignalDequeueCondition() {
    dequeue_possible.store(true);
    dequeue_condition.notify_all();
}

bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) {
    dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); });
    dequeue_possible.store(false);

    return true;
}

s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
    // If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
    if (!use_async_buffer) {
        return 0;
    }

    if (dequeue_buffer_cannot_block || async) {
        return max_acquired_buffer_count + 1;
    }

    return max_acquired_buffer_count;
}

s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
    return GetMinUndequeuedBufferCountLocked(async);
}

s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
    const auto min_buffer_count = GetMinMaxBufferCountLocked(async);
    auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count);

    if (override_max_buffer_count != 0) {
        ASSERT(override_max_buffer_count >= min_buffer_count);
        return override_max_buffer_count;
    }

    // Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
    // need to have their slots preserved.
    for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
        const auto state = slots[slot].buffer_state;
        if (state == BufferState::Queued || state == BufferState::Dequeued) {
            max_buffer_count = slot + 1;
        }
    }

    return max_buffer_count;
}

s32 BufferQueueCore::GetPreallocatedBufferCountLocked() const {
    return static_cast<s32>(std::count_if(slots.begin(), slots.end(),
                                          [](const auto& slot) { return slot.is_preallocated; }));
}

void BufferQueueCore::FreeBufferLocked(s32 slot) {
    LOG_DEBUG(Service_Nvnflinger, "slot {}", slot);

    slots[slot].graphic_buffer.reset();

    if (slots[slot].buffer_state == BufferState::Acquired) {
        slots[slot].needs_cleanup_on_release = true;
    }

    slots[slot].buffer_state = BufferState::Free;
    slots[slot].frame_number = UINT32_MAX;
    slots[slot].acquire_called = false;
    slots[slot].fence = Fence::NoFence();
}

void BufferQueueCore::FreeAllBuffersLocked() {
    buffer_has_been_queued = false;

    for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
        FreeBufferLocked(slot);
    }
}

bool BufferQueueCore::StillTracking(const BufferItem& item) const {
    const BufferSlot& slot = slots[item.slot];

    return (slot.graphic_buffer != nullptr) && (item.graphic_buffer == slot.graphic_buffer);
}

void BufferQueueCore::WaitWhileAllocatingLocked() const {
    while (is_allocating) {
        is_allocating_condition.wait(mutex);
    }
}

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h

#pragma once

#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
#include <set>
#include <vector>

#include "core/hle/service/nvnflinger/buffer_item.h"
#include "core/hle/service/nvnflinger/buffer_queue_defs.h"
#include "core/hle/service/nvnflinger/pixel_format.h"
#include "core/hle/service/nvnflinger/status.h"
#include "core/hle/service/nvnflinger/window.h"

namespace Service::android {

class IConsumerListener;
class IProducerListener;

class BufferQueueCore final {
    friend class BufferQueueProducer;
    friend class BufferQueueConsumer;

public:
    static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;

    BufferQueueCore();
    ~BufferQueueCore();

private:
    void SignalDequeueCondition();
    bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);

    s32 GetMinUndequeuedBufferCountLocked(bool async) const;
    s32 GetMinMaxBufferCountLocked(bool async) const;
    s32 GetMaxBufferCountLocked(bool async) const;
    s32 GetPreallocatedBufferCountLocked() const;
    void FreeBufferLocked(s32 slot);
    void FreeAllBuffersLocked();
    bool StillTracking(const BufferItem& item) const;
    void WaitWhileAllocatingLocked() const;

private:
    mutable std::mutex mutex;
    bool is_abandoned{};
    bool consumer_controlled_by_app{};
    std::shared_ptr<IConsumerListener> consumer_listener;
    u32 consumer_usage_bit{};
    NativeWindowApi connected_api{NativeWindowApi::NoConnectedApi};
    std::shared_ptr<IProducerListener> connected_producer_listener;
    BufferQueueDefs::SlotsType slots{};
    std::vector<BufferItem> queue;
    s32 override_max_buffer_count{};
    std::condition_variable dequeue_condition;
    std::atomic<bool> dequeue_possible{};
    const bool use_async_buffer{}; // This is always disabled on HOS
    bool dequeue_buffer_cannot_block{};
    PixelFormat default_buffer_format{PixelFormat::Rgba8888};
    u32 default_width{1};
    u32 default_height{1};
    s32 default_max_buffer_count{2};
    const s32 max_acquired_buffer_count{}; // This is always zero on HOS
    bool buffer_has_been_queued{};
    u64 frame_counter{};
    u32 transform_hint{};
    bool is_allocating{};
    mutable std::condition_variable_any is_allocating_condition;
};

} // namespace Service::android


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Parts of this implementation were based on:
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueDefs.h

#pragma once

#include <array>

#include "common/common_types.h"
#include "core/hle/service/nvnflinger/buffer_slot.h"

namespace Service::android::BufferQueueDefs {

// BufferQueue will keep track of at most this value of buffers.
constexpr s32 NUM_BUFFER_SLOTS = 64;

using SlotsType = std::array<BufferSlot, NUM_BUFFER_SLOTS>;

} // namespace Service::android::BufferQueueDefs