summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service')
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp14
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h1
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp229
-rw-r--r--src/core/hle/service/hid/controllers/palma.h163
-rw-r--r--src/core/hle/service/hid/errors.h2
-rw-r--r--src/core/hle/service/hid/hid.cpp447
-rw-r--r--src/core/hle/service/hid/hid.h29
-rw-r--r--src/core/hle/service/hid/irs.cpp3
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp633
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h134
-rw-r--r--src/core/hle/service/ldn/ldn.cpp227
-rw-r--r--src/core/hle/service/ldn/ldn_types.h48
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp161
-rw-r--r--src/core/hle/service/mii/mii_manager.h4
-rw-r--r--src/core/hle/service/nfc/nfc.cpp8
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.cpp79
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.h10
-rw-r--r--src/core/hle/service/nfp/nfp.cpp1093
-rw-r--r--src/core/hle/service/nfp/nfp.h161
-rw-r--r--src/core/hle/service/nfp/nfp_device.cpp681
-rw-r--r--src/core/hle/service/nfp/nfp_device.h101
-rw-r--r--src/core/hle/service/nfp/nfp_result.h24
-rw-r--r--src/core/hle/service/nfp/nfp_types.h (renamed from src/core/hle/service/nfp/amiibo_types.h)149
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp664
-rw-r--r--src/core/hle/service/nfp/nfp_user.h44
-rw-r--r--src/core/hle/service/nvdrv/core/container.cpp50
-rw-r--r--src/core/hle/service/nvdrv/core/container.h52
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.cpp272
-rw-r--r--src/core/hle/service/nvdrv/core/nvmap.h175
-rw-r--r--src/core/hle/service/nvdrv/core/syncpoint_manager.cpp121
-rw-r--r--src/core/hle/service/nvdrv/core/syncpoint_manager.h134
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h8
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp19
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdisp_disp0.h15
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp492
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h191
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp363
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl.h114
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp25
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h14
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp129
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.h54
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp16
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec.h6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp81
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h23
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.cpp20
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_vic.h6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp230
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.h56
-rw-r--r--src/core/hle/service/nvdrv/nvdata.h17
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.cpp130
-rw-r--r--src/core/hle/service/nvdrv/nvdrv.h125
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.cpp31
-rw-r--r--src/core/hle/service/nvdrv/nvdrv_interface.h2
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.cpp38
-rw-r--r--src/core/hle/service/nvdrv/syncpoint_manager.h84
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.cpp9
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_consumer.h8
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.cpp10
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue_producer.h9
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp38
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h7
-rw-r--r--src/core/hle/service/sockets/bsd.cpp2
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp31
-rw-r--r--src/core/hle/service/vi/display/vi_display.h21
-rw-r--r--src/core/hle/service/vi/vi.cpp41
-rw-r--r--src/core/hle/service/vi/vi_results.h13
69 files changed, 5947 insertions, 2450 deletions
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index e23eae36a..c08274ef9 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -707,7 +707,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{31, nullptr, "OpenGameCardFileSystem"},
{32, nullptr, "ExtendSaveDataFileSystem"},
{33, nullptr, "DeleteCacheStorage"},
- {34, nullptr, "GetCacheStorageSize"},
+ {34, &FSP_SRV::GetCacheStorageSize, "GetCacheStorageSize"},
{35, nullptr, "CreateSaveDataFileSystemByHashSalt"},
{36, nullptr, "OpenHostFileSystemWithOption"},
{51, &FSP_SRV::OpenSaveDataFileSystem, "OpenSaveDataFileSystem"},
@@ -1107,6 +1107,18 @@ void FSP_SRV::GetProgramIndexForAccessLog(Kernel::HLERequestContext& ctx) {
rb.Push(access_log_program_index);
}
+void FSP_SRV::GetCacheStorageSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto index{rp.Pop<s32>()};
+
+ LOG_WARNING(Service_FS, "(STUBBED) called with index={}", index);
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.Push(s64{0});
+ rb.Push(s64{0});
+}
+
class IMultiCommitManager final : public ServiceFramework<IMultiCommitManager> {
public:
explicit IMultiCommitManager(Core::System& system_)
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 36f552e05..3d88b97f9 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -54,6 +54,7 @@ private:
void OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx);
void GetProgramIndexForAccessLog(Kernel::HLERequestContext& ctx);
void OpenMultiCommitManager(Kernel::HLERequestContext& ctx);
+ void GetCacheStorageSize(Kernel::HLERequestContext& ctx);
FileSystemController& fsc;
const FileSys::ContentProvider& content_provider;
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index cb29004e8..f8972ec7a 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -660,7 +660,6 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
ASSERT(false);
break;
case Core::HID::NpadStyleIndex::ProController:
- case Core::HID::NpadStyleIndex::Pokeball:
set_motion_state(sixaxis_fullkey_state, motion_state[0]);
break;
case Core::HID::NpadStyleIndex::Handheld:
@@ -676,6 +675,11 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
case Core::HID::NpadStyleIndex::JoyconRight:
set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
break;
+ case Core::HID::NpadStyleIndex::Pokeball:
+ using namespace std::literals::chrono_literals;
+ set_motion_state(sixaxis_fullkey_state, motion_state[0]);
+ sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
+ break;
default:
break;
}
diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
new file mode 100644
index 000000000..575d4e626
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -0,0 +1,229 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core_timing.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/controllers/palma.h"
+#include "core/hle/service/kernel_helpers.h"
+
+namespace Service::HID {
+
+Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_)
+ : ControllerBase{hid_core_}, service_context{service_context_} {
+ controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
+ operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
+}
+
+Controller_Palma::~Controller_Palma() = default;
+
+void Controller_Palma::OnInit() {}
+
+void Controller_Palma::OnRelease() {}
+
+void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
+ if (!IsControllerActivated()) {
+ return;
+ }
+}
+
+Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
+ PalmaConnectionHandle& handle) {
+ active_handle.npad_id = npad_id;
+ handle = active_handle;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ ActivateController();
+ return ResultSuccess;
+}
+
+Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
+ const PalmaConnectionHandle& handle) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
+ }
+ return operation_complete_event->GetReadableEvent();
+}
+
+Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation_type = operation.operation;
+ data = operation.data;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
+ u64 palma_activity) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::PlayActivity;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
+ PalmaFrModeType fr_mode_) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ fr_mode = fr_mode_;
+ return ResultSuccess;
+}
+
+Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadStep;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return ResultSuccess;
+}
+
+Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return ResultSuccess;
+}
+
+void Controller_Palma::ReadPalmaApplicationSection() {}
+
+void Controller_Palma::WritePalmaApplicationSection() {}
+
+Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadUniqueCode;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+void Controller_Palma::WritePalmaActivityEntry() {}
+
+Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
+ u64 unknown) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
+ u8* t_mem, u64 size) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::WriteWaveEntry;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ database_id_version = database_id_version_;
+ operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
+ operation.result = PalmaResultSuccess;
+ operation.data[0] = {};
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
+ const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
+ operation.result = PalmaResultSuccess;
+ operation.data = {};
+ operation.data[0] = static_cast<u8>(database_id_version);
+ operation_complete_event->GetWritableEvent().Signal();
+ return ResultSuccess;
+}
+
+void Controller_Palma::SuspendPalmaFeature() {}
+
+Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ return operation.result;
+}
+void Controller_Palma::ReadPalmaPlayLog() {}
+
+void Controller_Palma::ResetPalmaPlayLog() {}
+
+void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
+ // If true controllers are able to be paired
+ is_connectable = is_all_connectable;
+}
+
+void Controller_Palma::SetIsPalmaPairedConnectable() {}
+
+Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
+ if (handle.npad_id != active_handle.npad_id) {
+ return InvalidPalmaHandle;
+ }
+ // TODO: Do something
+ return ResultSuccess;
+}
+
+void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
+
+void Controller_Palma::CancelWritePalmaWaveEntry() {}
+
+void Controller_Palma::EnablePalmaBoostMode() {}
+
+void Controller_Palma::GetPalmaBluetoothAddress() {}
+
+void Controller_Palma::SetDisallowedPalmaConnection() {}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h
new file mode 100644
index 000000000..1d7fc94e1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.h
@@ -0,0 +1,163 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/errors.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+}
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+class Controller_Palma final : public ControllerBase {
+public:
+ using PalmaOperationData = std::array<u8, 0x140>;
+
+ // This is nn::hid::PalmaOperationType
+ enum class PalmaOperationType {
+ PlayActivity,
+ SetFrModeType,
+ ReadStep,
+ EnableStep,
+ ResetStep,
+ ReadApplicationSection,
+ WriteApplicationSection,
+ ReadUniqueCode,
+ SetUniqueCodeInvalid,
+ WriteActivityEntry,
+ WriteRgbLedPatternEntry,
+ WriteWaveEntry,
+ ReadDataBaseIdentificationVersion,
+ WriteDataBaseIdentificationVersion,
+ SuspendFeature,
+ ReadPlayLog,
+ ResetPlayLog,
+ };
+
+ // This is nn::hid::PalmaWaveSet
+ enum class PalmaWaveSet : u64 {
+ Small,
+ Medium,
+ Large,
+ };
+
+ // This is nn::hid::PalmaFrModeType
+ enum class PalmaFrModeType : u64 {
+ Off,
+ B01,
+ B02,
+ B03,
+ Downloaded,
+ };
+
+ // This is nn::hid::PalmaFeature
+ enum class PalmaFeature : u64 {
+ FrMode,
+ RumbleFeedback,
+ Step,
+ MuteSwitch,
+ };
+
+ // This is nn::hid::PalmaOperationInfo
+ struct PalmaOperationInfo {
+ PalmaOperationType operation{};
+ Result result{PalmaResultSuccess};
+ PalmaOperationData data{};
+ };
+ static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
+
+ // This is nn::hid::PalmaActivityEntry
+ struct PalmaActivityEntry {
+ u32 rgb_led_pattern_index;
+ INSERT_PADDING_BYTES(2);
+ PalmaWaveSet wave_set;
+ u32 wave_index;
+ INSERT_PADDING_BYTES(12);
+ };
+ static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
+
+ struct PalmaConnectionHandle {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_BYTES(4); // Unknown
+ };
+ static_assert(sizeof(PalmaConnectionHandle) == 0x8,
+ "PalmaConnectionHandle has incorrect size.");
+
+ explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~Controller_Palma() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
+
+ Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
+ Result InitializePalma(const PalmaConnectionHandle& handle);
+ Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
+ const PalmaConnectionHandle& handle) const;
+ Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
+ PalmaOperationType& operation_type,
+ PalmaOperationData& data) const;
+ Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
+ Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
+ Result ReadPalmaStep(const PalmaConnectionHandle& handle);
+ Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
+ Result ResetPalmaStep(const PalmaConnectionHandle& handle);
+ Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
+ Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
+ Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
+ Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave, u8* t_mem,
+ u64 size);
+ Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
+ s32 database_id_version_);
+ Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
+ Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
+ void SetIsPalmaAllConnectable(bool is_all_connectable);
+ Result PairPalma(const PalmaConnectionHandle& handle);
+ void SetPalmaBoostMode(bool boost_mode);
+
+private:
+ void ReadPalmaApplicationSection();
+ void WritePalmaApplicationSection();
+ void WritePalmaActivityEntry();
+ void SuspendPalmaFeature();
+ void ReadPalmaPlayLog();
+ void ResetPalmaPlayLog();
+ void SetIsPalmaPairedConnectable();
+ void CancelWritePalmaWaveEntry();
+ void EnablePalmaBoostMode();
+ void GetPalmaBluetoothAddress();
+ void SetDisallowedPalmaConnection();
+
+ bool is_connectable{};
+ s32 database_id_version{};
+ PalmaOperationInfo operation{};
+ PalmaFrModeType fr_mode{};
+ PalmaConnectionHandle active_handle{};
+
+ Core::HID::EmulatedController* controller;
+
+ Kernel::KEvent* operation_complete_event;
+ KernelHelpers::ServiceContext& service_context;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 4613a4e60..76208e9a4 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -7,6 +7,7 @@
namespace Service::HID {
+constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
@@ -17,6 +18,7 @@ constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
constexpr Result NpadIsSameType{ErrorModule::HID, 602};
constexpr Result InvalidNpadId{ErrorModule::HID, 709};
constexpr Result NpadNotConnected{ErrorModule::HID, 710};
+constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 3d3457160..46bad7871 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -27,6 +27,7 @@
#include "core/hle/service/hid/controllers/keyboard.h"
#include "core/hle/service/hid/controllers/mouse.h"
#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/palma.h"
#include "core/hle/service/hid/controllers/stubbed.h"
#include "core/hle/service/hid/controllers/touchscreen.h"
#include "core/hle/service/hid/controllers/xpad.h"
@@ -35,7 +36,8 @@ namespace Service::HID {
// Updating period for each HID device.
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
-constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz)
+// Correct pad_update_ns is 4ms this is overclocked to lower input lag
+constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
@@ -60,6 +62,7 @@ IAppletResource::IAppletResource(Core::System& system_,
MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
+ MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
// Homebrew doesn't try to activate some controllers, so we activate them by default
GetController<Controller_NPad>(HidController::NPad).ActivateController();
@@ -310,36 +313,36 @@ Hid::Hid(Core::System& system_)
{406, nullptr, "GetNpadLeftRightInterfaceType"},
{407, nullptr, "GetNpadOfHighestBatteryLevel"},
{408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
- {500, nullptr, "GetPalmaConnectionHandle"},
- {501, nullptr, "InitializePalma"},
- {502, nullptr, "AcquirePalmaOperationCompleteEvent"},
- {503, nullptr, "GetPalmaOperationInfo"},
- {504, nullptr, "PlayPalmaActivity"},
- {505, nullptr, "SetPalmaFrModeType"},
- {506, nullptr, "ReadPalmaStep"},
- {507, nullptr, "EnablePalmaStep"},
- {508, nullptr, "ResetPalmaStep"},
- {509, nullptr, "ReadPalmaApplicationSection"},
- {510, nullptr, "WritePalmaApplicationSection"},
- {511, nullptr, "ReadPalmaUniqueCode"},
- {512, nullptr, "SetPalmaUniqueCodeInvalid"},
- {513, nullptr, "WritePalmaActivityEntry"},
- {514, nullptr, "WritePalmaRgbLedPatternEntry"},
- {515, nullptr, "WritePalmaWaveEntry"},
- {516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
- {517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
- {518, nullptr, "SuspendPalmaFeature"},
- {519, nullptr, "GetPalmaOperationResult"},
- {520, nullptr, "ReadPalmaPlayLog"},
- {521, nullptr, "ResetPalmaPlayLog"},
+ {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
+ {501, &Hid::InitializePalma, "InitializePalma"},
+ {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
+ {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
+ {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
+ {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
+ {506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
+ {507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
+ {508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
+ {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
+ {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
+ {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
+ {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
+ {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
+ {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
+ {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
+ {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
+ {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
+ {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
+ {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
+ {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
+ {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
{522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
- {523, nullptr, "SetIsPalmaPairedConnectable"},
- {524, nullptr, "PairPalma"},
+ {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
+ {524, &Hid::PairPalma, "PairPalma"},
{525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
- {526, nullptr, "CancelWritePalmaWaveEntry"},
- {527, nullptr, "EnablePalmaBoostMode"},
- {528, nullptr, "GetPalmaBluetoothAddress"},
- {529, nullptr, "SetDisallowedPalmaConnection"},
+ {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
+ {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
+ {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
+ {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
{1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
{1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
{1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
@@ -1878,14 +1881,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) {
rb.Push(false);
}
+void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.applet_resource_user_id);
+
+ Controller_Palma::PalmaConnectionHandle handle;
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.PushRaw(handle);
+}
+
+void Hid::InitializePalma(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.InitializePalma(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
+}
+
+void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ Controller_Palma::PalmaOperationType operation_type;
+ Controller_Palma::PalmaOperationData data;
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ }
+
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(result);
+ rb.Push(static_cast<u64>(operation_type));
+}
+
+void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto palma_activity{rp.Pop<u64>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
+ connection_handle.npad_id, palma_activity);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
+ connection_handle.npad_id, fr_mode);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.ReadPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool is_enabled;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Controller_Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
+ parameters.connection_handle.npad_id, parameters.is_enabled);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result =
+ controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
+ const auto result = controller.ResetPalmaStep(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .ReadPalmaUniqueCode(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaUniqueCodeInvalid(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) {
+ LOG_CRITICAL(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto unknown{rp.Pop<u64>()};
+
+ const auto buffer = ctx.ReadBuffer();
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
+ connection_handle.npad_id, unknown);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .WritePalmaRgbLedPatternEntry(connection_handle, unknown);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+ const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
+ const auto unknown{rp.Pop<u64>()};
+ const auto t_mem_size{rp.Pop<u64>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+ const auto size{rp.Pop<u64>()};
+
+ ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
+
+ auto t_mem =
+ system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
+
+ if (t_mem.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
+
+ LOG_WARNING(Service_HID,
+ "(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
+ "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
+ connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .WritePalmaWaveEntry(connection_handle, wave_set,
+ system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ s32 database_id_version;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ Controller_Palma::PalmaConnectionHandle connection_handle;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
+ parameters.connection_handle.npad_id, parameters.database_id_version);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
+ parameters.database_id_version);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .GetPalmaDataBaseIdentificationVersion(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .GetPalmaOperationResult(connection_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
- const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto is_palma_all_connectable{rp.Pop<bool>()};
+ struct Parameters {
+ bool is_palma_all_connectable;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
LOG_WARNING(Service_HID,
- "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}",
- applet_resource_user_id, is_palma_all_connectable);
+ "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
+ parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::PairPalma(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
+
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .PairPalma(connection_handle);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -1897,6 +2247,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
+ applet_resource->GetController<Controller_Palma>(HidController::Palma)
+ .SetPalmaBoostMode(palma_boost_mode);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index ac4333022..340d26fdc 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -33,6 +33,7 @@ enum class HidController : std::size_t {
NPad,
Gesture,
ConsoleSixAxisSensor,
+ Palma,
MaxControllers,
};
@@ -166,8 +167,36 @@ private:
void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx);
+ void GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx);
+ void InitializePalma(Kernel::HLERequestContext& ctx);
+ void AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx);
+ void GetPalmaOperationInfo(Kernel::HLERequestContext& ctx);
+ void PlayPalmaActivity(Kernel::HLERequestContext& ctx);
+ void SetPalmaFrModeType(Kernel::HLERequestContext& ctx);
+ void ReadPalmaStep(Kernel::HLERequestContext& ctx);
+ void EnablePalmaStep(Kernel::HLERequestContext& ctx);
+ void ResetPalmaStep(Kernel::HLERequestContext& ctx);
+ void ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx);
+ void WritePalmaApplicationSection(Kernel::HLERequestContext& ctx);
+ void ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx);
+ void SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx);
+ void WritePalmaActivityEntry(Kernel::HLERequestContext& ctx);
+ void WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx);
+ void WritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
+ void SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
+ void GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
+ void SuspendPalmaFeature(Kernel::HLERequestContext& ctx);
+ void GetPalmaOperationResult(Kernel::HLERequestContext& ctx);
+ void ReadPalmaPlayLog(Kernel::HLERequestContext& ctx);
+ void ResetPalmaPlayLog(Kernel::HLERequestContext& ctx);
void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
+ void SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx);
+ void PairPalma(Kernel::HLERequestContext& ctx);
void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
+ void EnablePalmaBoostMode(Kernel::HLERequestContext& ctx);
+ void GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx);
+ void SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx);
void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index c4b44cbf9..6a3453457 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -542,7 +542,8 @@ Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_h
Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
const Core::IrSensor::IrCameraHandle& camera_handle) {
- ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id");
+ const auto npad_id_max_index = static_cast<u8>(sizeof(StatusManager::device));
+ ASSERT_MSG(camera_handle.npad_id < npad_id_max_index, "invalid npad_id");
return shared_memory->device[camera_handle.npad_id];
}
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
new file mode 100644
index 000000000..8f3c04550
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -0,0 +1,633 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/service/ldn/lan_discovery.h"
+#include "core/internal_network/network.h"
+#include "core/internal_network/network_interface.h"
+
+namespace Service::LDN {
+
+LanStation::LanStation(s8 node_id_, LANDiscovery* discovery_)
+ : node_info(nullptr), status(NodeStatus::Disconnected), node_id(node_id_),
+ discovery(discovery_) {}
+
+LanStation::~LanStation() = default;
+
+NodeStatus LanStation::GetStatus() const {
+ return status;
+}
+
+void LanStation::OnClose() {
+ LOG_INFO(Service_LDN, "OnClose {}", node_id);
+ Reset();
+ discovery->UpdateNodes();
+}
+
+void LanStation::Reset() {
+ status = NodeStatus::Disconnected;
+};
+
+void LanStation::OverrideInfo() {
+ bool connected = GetStatus() == NodeStatus::Connected;
+ node_info->node_id = node_id;
+ node_info->is_connected = connected ? 1 : 0;
+}
+
+LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_)
+ : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}),
+ room_network{room_network_} {}
+
+LANDiscovery::~LANDiscovery() {
+ if (inited) {
+ Result rc = Finalize();
+ LOG_INFO(Service_LDN, "Finalize: {}", rc.raw);
+ }
+}
+
+void LANDiscovery::InitNetworkInfo() {
+ network_info.common.bssid = GetFakeMac();
+ network_info.common.channel = WifiChannel::Wifi24_6;
+ network_info.common.link_level = LinkLevel::Good;
+ network_info.common.network_type = PackedNetworkType::Ldn;
+ network_info.common.ssid = fake_ssid;
+
+ auto& nodes = network_info.ldn.nodes;
+ for (std::size_t i = 0; i < NodeCountMax; i++) {
+ nodes[i].node_id = static_cast<s8>(i);
+ nodes[i].is_connected = 0;
+ }
+}
+
+void LANDiscovery::InitNodeStateChange() {
+ for (auto& node_update : node_changes) {
+ node_update.state_change = NodeStateChange::None;
+ }
+ for (auto& node_state : node_last_states) {
+ node_state = 0;
+ }
+}
+
+State LANDiscovery::GetState() const {
+ return state;
+}
+
+void LANDiscovery::SetState(State new_state) {
+ state = new_state;
+}
+
+Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
+ if (state == State::AccessPointCreated || state == State::StationConnected) {
+ std::memcpy(&out_network, &network_info, sizeof(network_info));
+ return ResultSuccess;
+ }
+
+ return ResultBadState;
+}
+
+Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
+ std::vector<NodeLatestUpdate>& out_updates,
+ std::size_t buffer_count) {
+ if (buffer_count > NodeCountMax) {
+ return ResultInvalidBufferCount;
+ }
+
+ if (state == State::AccessPointCreated || state == State::StationConnected) {
+ std::memcpy(&out_network, &network_info, sizeof(network_info));
+ for (std::size_t i = 0; i < buffer_count; i++) {
+ out_updates[i].state_change = node_changes[i].state_change;
+ node_changes[i].state_change = NodeStateChange::None;
+ }
+ return ResultSuccess;
+ }
+
+ return ResultBadState;
+}
+
+DisconnectReason LANDiscovery::GetDisconnectReason() const {
+ return disconnect_reason;
+}
+
+Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
+ const ScanFilter& filter) {
+ if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
+ filter.network_type <= NetworkType::All) {
+ if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
+ return ResultBadInput;
+ }
+ }
+
+ {
+ std::scoped_lock lock{packet_mutex};
+ scan_results.clear();
+
+ SendBroadcast(Network::LDNPacketType::Scan);
+ }
+
+ LOG_INFO(Service_LDN, "Waiting for scan replies");
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ std::scoped_lock lock{packet_mutex};
+ for (const auto& [key, info] : scan_results) {
+ if (count >= networks.size()) {
+ break;
+ }
+
+ if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) {
+ if (filter.network_id.intent_id.local_communication_id !=
+ info.network_id.intent_id.local_communication_id) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) {
+ if (filter.network_id.session_id != info.network_id.session_id) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) {
+ if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) {
+ if (filter.ssid != info.common.ssid) {
+ continue;
+ }
+ }
+ if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) {
+ if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) {
+ continue;
+ }
+ }
+
+ networks[count++] = info;
+ }
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::SetAdvertiseData(std::span<const u8> data) {
+ std::scoped_lock lock{packet_mutex};
+ const std::size_t size = data.size();
+ if (size > AdvertiseDataSizeMax) {
+ return ResultAdvertiseDataTooLarge;
+ }
+
+ std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size);
+ network_info.ldn.advertise_data_size = static_cast<u16>(size);
+
+ UpdateNodes();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::OpenAccessPoint() {
+ std::scoped_lock lock{packet_mutex};
+ disconnect_reason = DisconnectReason::None;
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ ResetStations();
+ SetState(State::AccessPointOpened);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CloseAccessPoint() {
+ std::scoped_lock lock{packet_mutex};
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ if (state == State::AccessPointCreated) {
+ DestroyNetwork();
+ }
+
+ ResetStations();
+ SetState(State::Initialized);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::OpenStation() {
+ std::scoped_lock lock{packet_mutex};
+ disconnect_reason = DisconnectReason::None;
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ ResetStations();
+ SetState(State::StationOpened);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CloseStation() {
+ std::scoped_lock lock{packet_mutex};
+ if (state == State::None) {
+ return ResultBadState;
+ }
+
+ if (state == State::StationConnected) {
+ Disconnect();
+ }
+
+ ResetStations();
+ SetState(State::Initialized);
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
+ const UserConfig& user_config,
+ const NetworkConfig& network_config) {
+ std::scoped_lock lock{packet_mutex};
+
+ if (state != State::AccessPointOpened) {
+ return ResultBadState;
+ }
+
+ InitNetworkInfo();
+ network_info.ldn.node_count_max = network_config.node_count_max;
+ network_info.ldn.security_mode = security_config.security_mode;
+
+ if (network_config.channel == WifiChannel::Default) {
+ network_info.common.channel = WifiChannel::Wifi24_6;
+ } else {
+ network_info.common.channel = network_config.channel;
+ }
+
+ std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
+ network_info.network_id.session_id.high = bits_engine();
+ network_info.network_id.session_id.low = bits_engine();
+ network_info.network_id.intent_id = network_config.intent_id;
+
+ NodeInfo& node0 = network_info.ldn.nodes[0];
+ const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version);
+ if (rc2.IsError()) {
+ return ResultAccessPointConnectionFailed;
+ }
+
+ SetState(State::AccessPointCreated);
+
+ InitNodeStateChange();
+ node0.is_connected = 1;
+ UpdateNodes();
+
+ return rc2;
+}
+
+Result LANDiscovery::DestroyNetwork() {
+ for (auto local_ip : connected_clients) {
+ SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
+ }
+
+ ResetStations();
+
+ SetState(State::AccessPointOpened);
+ lan_event();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
+ u16 local_communication_version) {
+ std::scoped_lock lock{packet_mutex};
+ if (network_info_.ldn.node_count == 0) {
+ return ResultInvalidNodeCount;
+ }
+
+ Result rc = GetNodeInfo(node_info, user_config, local_communication_version);
+ if (rc.IsError()) {
+ return ResultConnectionFailed;
+ }
+
+ Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
+ std::reverse(std::begin(node_host), std::end(node_host)); // htonl
+ host_ip = node_host;
+ SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
+
+ InitNodeStateChange();
+
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Disconnect() {
+ if (host_ip) {
+ SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
+ }
+
+ SetState(State::StationOpened);
+ lan_event();
+
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) {
+ std::scoped_lock lock{packet_mutex};
+ if (inited) {
+ return ResultSuccess;
+ }
+
+ for (auto& station : stations) {
+ station.discovery = this;
+ station.node_info = &network_info.ldn.nodes[station.node_id];
+ station.Reset();
+ }
+
+ connected_clients.clear();
+ lan_event = lan_event_;
+
+ SetState(State::Initialized);
+
+ inited = true;
+ return ResultSuccess;
+}
+
+Result LANDiscovery::Finalize() {
+ std::scoped_lock lock{packet_mutex};
+ Result rc = ResultSuccess;
+
+ if (inited) {
+ if (state == State::AccessPointCreated) {
+ DestroyNetwork();
+ }
+ if (state == State::StationConnected) {
+ Disconnect();
+ }
+
+ ResetStations();
+ inited = false;
+ }
+
+ SetState(State::None);
+
+ return rc;
+}
+
+void LANDiscovery::ResetStations() {
+ for (auto& station : stations) {
+ station.Reset();
+ }
+ connected_clients.clear();
+}
+
+void LANDiscovery::UpdateNodes() {
+ u8 count = 0;
+ for (auto& station : stations) {
+ bool connected = station.GetStatus() == NodeStatus::Connected;
+ if (connected) {
+ count++;
+ }
+ station.OverrideInfo();
+ }
+ network_info.ldn.node_count = count + 1;
+
+ for (auto local_ip : connected_clients) {
+ SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip);
+ }
+
+ OnNetworkInfoChanged();
+}
+
+void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
+ network_info = info;
+ if (state == State::StationOpened) {
+ SetState(State::StationConnected);
+ }
+ OnNetworkInfoChanged();
+}
+
+void LANDiscovery::OnDisconnectFromHost() {
+ LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state));
+ host_ip = std::nullopt;
+ if (state == State::StationConnected) {
+ SetState(State::StationOpened);
+ lan_event();
+ }
+}
+
+void LANDiscovery::OnNetworkInfoChanged() {
+ if (IsNodeStateChanged()) {
+ lan_event();
+ }
+ return;
+}
+
+Network::IPv4Address LANDiscovery::GetLocalIp() const {
+ Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ local_ip = room_member->GetFakeIpAddress();
+ }
+ }
+ return local_ip;
+}
+
+template <typename Data>
+void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data,
+ Ipv4Address remote_ip) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = false;
+ packet.local_ip = GetLocalIp();
+ packet.remote_ip = remote_ip;
+
+ packet.data.resize(sizeof(data));
+ std::memcpy(packet.data.data(), &data, sizeof(data));
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = false;
+ packet.local_ip = GetLocalIp();
+ packet.remote_ip = remote_ip;
+
+ SendPacket(packet);
+}
+
+template <typename Data>
+void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = true;
+ packet.local_ip = GetLocalIp();
+
+ packet.data.resize(sizeof(data));
+ std::memcpy(packet.data.data(), &data, sizeof(data));
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
+ Network::LDNPacket packet;
+ packet.type = type;
+
+ packet.broadcast = true;
+ packet.local_ip = GetLocalIp();
+
+ SendPacket(packet);
+}
+
+void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ room_member->SendLdnPacket(packet);
+ }
+ }
+}
+
+void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
+ std::scoped_lock lock{packet_mutex};
+ switch (packet.type) {
+ case Network::LDNPacketType::Scan: {
+ LOG_INFO(Frontend, "Scan packet received!");
+ if (state == State::AccessPointCreated) {
+ // Reply to the sender
+ SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip);
+ }
+ break;
+ }
+ case Network::LDNPacketType::ScanResp: {
+ LOG_INFO(Frontend, "ScanResp packet received!");
+
+ NetworkInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
+ scan_results.insert({info.common.bssid, info});
+
+ break;
+ }
+ case Network::LDNPacketType::Connect: {
+ LOG_INFO(Frontend, "Connect packet received!");
+
+ NodeInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
+
+ connected_clients.push_back(packet.local_ip);
+
+ for (LanStation& station : stations) {
+ if (station.status != NodeStatus::Connected) {
+ *station.node_info = info;
+ station.status = NodeStatus::Connected;
+ break;
+ }
+ }
+
+ UpdateNodes();
+
+ break;
+ }
+ case Network::LDNPacketType::Disconnect: {
+ LOG_INFO(Frontend, "Disconnect packet received!");
+
+ connected_clients.erase(
+ std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip),
+ connected_clients.end());
+
+ NodeInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
+
+ for (LanStation& station : stations) {
+ if (station.status == NodeStatus::Connected &&
+ station.node_info->mac_address == info.mac_address) {
+ station.OnClose();
+ break;
+ }
+ }
+
+ break;
+ }
+ case Network::LDNPacketType::DestroyNetwork: {
+ ResetStations();
+ OnDisconnectFromHost();
+ break;
+ }
+ case Network::LDNPacketType::SyncNetwork: {
+ if (state == State::StationOpened || state == State::StationConnected) {
+ LOG_INFO(Frontend, "SyncNetwork packet received!");
+ NetworkInfo info{};
+ std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
+
+ OnSyncNetwork(info);
+ } else {
+ LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
+ }
+
+ break;
+ }
+ default: {
+ LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type));
+ break;
+ }
+ }
+}
+
+bool LANDiscovery::IsNodeStateChanged() {
+ bool changed = false;
+ const auto& nodes = network_info.ldn.nodes;
+ for (int i = 0; i < NodeCountMax; i++) {
+ if (nodes[i].is_connected != node_last_states[i]) {
+ if (nodes[i].is_connected) {
+ node_changes[i].state_change |= NodeStateChange::Connect;
+ } else {
+ node_changes[i].state_change |= NodeStateChange::Disconnect;
+ }
+ node_last_states[i] = nodes[i].is_connected;
+ changed = true;
+ }
+ }
+ return changed;
+}
+
+bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const {
+ const auto flag_value = static_cast<u32>(flag);
+ const auto search_flag_value = static_cast<u32>(search_flag);
+ return (flag_value & search_flag_value) == search_flag_value;
+}
+
+int LANDiscovery::GetStationCount() const {
+ return static_cast<int>(
+ std::count_if(stations.begin(), stations.end(), [](const auto& station) {
+ return station.GetStatus() != NodeStatus::Disconnected;
+ }));
+}
+
+MacAddress LANDiscovery::GetFakeMac() const {
+ MacAddress mac{};
+ mac.raw[0] = 0x02;
+ mac.raw[1] = 0x00;
+
+ const auto ip = GetLocalIp();
+ memcpy(mac.raw.data() + 2, &ip, sizeof(ip));
+
+ return mac;
+}
+
+Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig,
+ u16 localCommunicationVersion) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface available");
+ return ResultNoIpAddress;
+ }
+
+ node.mac_address = GetFakeMac();
+ node.is_connected = 1;
+ std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1);
+ node.local_communication_version = localCommunicationVersion;
+
+ Ipv4Address current_address = GetLocalIp();
+ std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
+ node.ipv4_address = current_address;
+
+ return ResultSuccess;
+}
+
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
new file mode 100644
index 000000000..3833cd764
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <random>
+#include <span>
+#include <thread>
+#include <unordered_map>
+
+#include "common/logging/log.h"
+#include "common/socket_types.h"
+#include "core/hle/result.h"
+#include "core/hle/service/ldn/ldn_results.h"
+#include "core/hle/service/ldn/ldn_types.h"
+#include "network/network.h"
+
+namespace Service::LDN {
+
+class LANDiscovery;
+
+class LanStation {
+public:
+ LanStation(s8 node_id_, LANDiscovery* discovery_);
+ ~LanStation();
+
+ void OnClose();
+ NodeStatus GetStatus() const;
+ void Reset();
+ void OverrideInfo();
+
+protected:
+ friend class LANDiscovery;
+ NodeInfo* node_info;
+ NodeStatus status;
+ s8 node_id;
+ LANDiscovery* discovery;
+};
+
+class LANDiscovery {
+public:
+ using LanEventFunc = std::function<void()>;
+
+ LANDiscovery(Network::RoomNetwork& room_network_);
+ ~LANDiscovery();
+
+ State GetState() const;
+ void SetState(State new_state);
+
+ Result GetNetworkInfo(NetworkInfo& out_network) const;
+ Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
+ std::size_t buffer_count);
+
+ DisconnectReason GetDisconnectReason() const;
+ Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
+ Result SetAdvertiseData(std::span<const u8> data);
+
+ Result OpenAccessPoint();
+ Result CloseAccessPoint();
+
+ Result OpenStation();
+ Result CloseStation();
+
+ Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config,
+ const NetworkConfig& network_config);
+ Result DestroyNetwork();
+
+ Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
+ u16 local_communication_version);
+ Result Disconnect();
+
+ Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true);
+ Result Finalize();
+
+ void ReceivePacket(const Network::LDNPacket& packet);
+
+protected:
+ friend class LanStation;
+
+ void InitNetworkInfo();
+ void InitNodeStateChange();
+
+ void ResetStations();
+ void UpdateNodes();
+
+ void OnSyncNetwork(const NetworkInfo& info);
+ void OnDisconnectFromHost();
+ void OnNetworkInfoChanged();
+
+ bool IsNodeStateChanged();
+ bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const;
+ int GetStationCount() const;
+ MacAddress GetFakeMac() const;
+ Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config,
+ u16 local_communication_version);
+
+ Network::IPv4Address GetLocalIp() const;
+ template <typename Data>
+ void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip);
+ void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip);
+ template <typename Data>
+ void SendBroadcast(Network::LDNPacketType type, const Data& data);
+ void SendBroadcast(Network::LDNPacketType type);
+ void SendPacket(const Network::LDNPacket& packet);
+
+ static const LanEventFunc empty_func;
+ static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"};
+
+ bool inited{};
+ std::mutex packet_mutex;
+ std::array<LanStation, StationCountMax> stations;
+ std::array<NodeLatestUpdate, NodeCountMax> node_changes{};
+ std::array<u8, NodeCountMax> node_last_states{};
+ std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{};
+ NodeInfo node_info{};
+ NetworkInfo network_info{};
+ State state{State::None};
+ DisconnectReason disconnect_reason{DisconnectReason::None};
+
+ // TODO (flTobi): Should this be an std::set?
+ std::vector<Ipv4Address> connected_clients;
+ std::optional<Ipv4Address> host_ip;
+
+ LanEventFunc lan_event;
+
+ Network::RoomNetwork& room_network;
+};
+} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index c11daff54..ea3e7e55a 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -4,11 +4,13 @@
#include <memory>
#include "core/core.h"
+#include "core/hle/service/ldn/lan_discovery.h"
#include "core/hle/service/ldn/ldn.h"
#include "core/hle/service/ldn/ldn_results.h"
#include "core/hle/service/ldn/ldn_types.h"
#include "core/internal_network/network.h"
#include "core/internal_network/network_interface.h"
+#include "network/network.h"
// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
#undef CreateEvent
@@ -105,13 +107,13 @@ class IUserLocalCommunicationService final
public:
explicit IUserLocalCommunicationService(Core::System& system_)
: ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew},
- service_context{system, "IUserLocalCommunicationService"}, room_network{
- system_.GetRoomNetwork()} {
+ service_context{system, "IUserLocalCommunicationService"},
+ room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IUserLocalCommunicationService::GetState, "GetState"},
{1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
- {2, nullptr, "GetIpv4Address"},
+ {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
{3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
{4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
{5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
@@ -119,7 +121,7 @@ public:
{101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
{102, &IUserLocalCommunicationService::Scan, "Scan"},
{103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
- {104, nullptr, "SetWirelessControllerRestriction"},
+ {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
{200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
{201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
{202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
@@ -148,16 +150,30 @@ public:
}
~IUserLocalCommunicationService() {
+ if (is_initialized) {
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
+ }
+
service_context.CloseEvent(state_change_event);
}
+ /// Callback to parse and handle a received LDN packet.
+ void OnLDNPacketReceived(const Network::LDNPacket& packet) {
+ lan_discovery.ReceivePacket(packet);
+ }
+
void OnEventFired() {
state_change_event->GetWritableEvent().Signal();
}
void GetState(Kernel::HLERequestContext& ctx) {
State state = State::Error;
- LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state);
+
+ if (is_initialized) {
+ state = lan_discovery.GetState();
+ }
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -175,7 +191,7 @@ public:
}
NetworkInfo network_info{};
- const auto rc = ResultSuccess;
+ const auto rc = lan_discovery.GetNetworkInfo(network_info);
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
IPC::ResponseBuilder rb{ctx, 2};
@@ -183,28 +199,50 @@ public:
return;
}
- LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
- network_info.common.ssid.GetStringValue(), network_info.ldn.node_count);
-
ctx.WriteBuffer<NetworkInfo>(network_info);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(rc);
+ rb.Push(ResultSuccess);
}
- void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
- const auto disconnect_reason = DisconnectReason::None;
+ void GetIpv4Address(Kernel::HLERequestContext& ctx) {
+ const auto network_interface = Network::GetSelectedNetworkInterface();
+
+ if (!network_interface) {
+ LOG_ERROR(Service_LDN, "No network interface available");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultNoIpAddress);
+ return;
+ }
- LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason);
+ Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
+ Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
+
+ // When we're connected to a room, spoof the hosts IP address
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ if (room_member->IsConnected()) {
+ current_address = room_member->GetFakeIpAddress();
+ }
+ }
+
+ std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
+ std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(current_address);
+ rb.PushRaw(subnet_mask);
+ }
+ void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.PushEnum(disconnect_reason);
+ rb.PushEnum(lan_discovery.GetDisconnectReason());
}
void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
SecurityParameter security_parameter{};
NetworkInfo info{};
- const Result rc = ResultSuccess;
+ const Result rc = lan_discovery.GetNetworkInfo(info);
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
@@ -217,8 +255,6 @@ public:
std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
sizeof(SecurityParameter::data));
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(rc);
rb.PushRaw<SecurityParameter>(security_parameter);
@@ -227,7 +263,7 @@ public:
void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
NetworkConfig config{};
NetworkInfo info{};
- const Result rc = ResultSuccess;
+ const Result rc = lan_discovery.GetNetworkInfo(info);
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
@@ -241,12 +277,6 @@ public:
config.node_count_max = info.ldn.node_count_max;
config.local_communication_version = info.ldn.nodes[0].local_communication_version;
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, "
- "local_communication_version={}",
- config.intent_id.local_communication_id, config.intent_id.scene_id,
- config.channel, config.node_count_max, config.local_communication_version);
-
IPC::ResponseBuilder rb{ctx, 10};
rb.Push(rc);
rb.PushRaw<NetworkConfig>(config);
@@ -265,17 +295,17 @@ public:
const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
- LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size,
+ LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
node_buffer_count);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultBadInput);
return;
}
- NetworkInfo info;
+ NetworkInfo info{};
std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
- const auto rc = ResultSuccess;
+ const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
if (rc.IsError()) {
LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
IPC::ResponseBuilder rb{ctx, 2};
@@ -283,9 +313,6 @@ public:
return;
}
- LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
- info.common.ssid.GetStringValue(), info.ldn.node_count);
-
ctx.WriteBuffer(info, 0);
ctx.WriteBuffer(latest_update, 1);
@@ -317,92 +344,78 @@ public:
u16 count = 0;
std::vector<NetworkInfo> network_infos(network_info_size);
+ Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}",
- channel, scan_filter.flag, scan_filter.network_type);
+ LOG_INFO(Service_LDN,
+ "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
+ channel, scan_filter.flag, scan_filter.network_type, is_private);
ctx.WriteBuffer(network_infos);
IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
+ rb.Push(rc);
rb.Push<u32>(count);
}
- void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
+ void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_LDN, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
}
+ void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(lan_discovery.OpenAccessPoint());
+ }
+
void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.CloseAccessPoint());
}
void CreateNetwork(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- struct Parameters {
- SecurityConfig security_config;
- UserConfig user_config;
- INSERT_PADDING_WORDS_NOINIT(1);
- NetworkConfig network_config;
- };
- static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size.");
+ LOG_INFO(Service_LDN, "called");
- const auto parameters{rp.PopRaw<Parameters>()};
+ CreateNetworkImpl(ctx);
+ }
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode,
- parameters.network_config.local_communication_version);
+ void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_LDN, "called");
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ CreateNetworkImpl(ctx, true);
}
- void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
+ void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
IPC::RequestParser rp{ctx};
- struct Parameters {
- SecurityConfig security_config;
- SecurityParameter security_parameter;
- UserConfig user_config;
- NetworkConfig network_config;
- };
- static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size.");
-
- const auto parameters{rp.PopRaw<Parameters>()};
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode,
- parameters.network_config.local_communication_version);
+ const auto security_config{rp.PopRaw<SecurityConfig>()};
+ [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
+ : SecurityParameter{}};
+ const auto user_config{rp.PopRaw<UserConfig>()};
+ rp.Pop<u32>(); // Padding
+ const auto network_Config{rp.PopRaw<NetworkConfig>()};
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
}
void DestroyNetwork(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.DestroyNetwork());
}
void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
std::vector<u8> read_buffer = ctx.ReadBuffer();
- LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size());
-
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
}
void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
@@ -420,17 +433,17 @@ public:
}
void OpenStation(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.OpenStation());
}
void CloseStation(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.CloseStation());
}
void Connect(Kernel::HLERequestContext& ctx) {
@@ -445,16 +458,13 @@ public:
const auto parameters{rp.PopRaw<Parameters>()};
- LOG_WARNING(Service_LDN,
- "(STUBBED) called, passphrase_size={}, security_mode={}, "
- "local_communication_version={}",
- parameters.security_config.passphrase_size,
- parameters.security_config.security_mode,
- parameters.local_communication_version);
+ LOG_INFO(Service_LDN,
+ "called, passphrase_size={}, security_mode={}, "
+ "local_communication_version={}",
+ parameters.security_config.passphrase_size,
+ parameters.security_config.security_mode, parameters.local_communication_version);
const std::vector<u8> read_buffer = ctx.ReadBuffer();
- NetworkInfo network_info{};
-
if (read_buffer.size() != sizeof(NetworkInfo)) {
LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
IPC::ResponseBuilder rb{ctx, 2};
@@ -462,40 +472,47 @@ public:
return;
}
+ NetworkInfo network_info{};
std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
+ static_cast<u16>(parameters.local_communication_version)));
}
void Disconnect(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ LOG_INFO(Service_LDN, "called");
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.Disconnect());
}
- void Initialize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ void Initialize(Kernel::HLERequestContext& ctx) {
const auto rc = InitializeImpl(ctx);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(rc);
}
void Finalize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ room_member->Unbind(ldn_packet_received);
+ }
is_initialized = false;
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ rb.Push(lan_discovery.Finalize());
}
void Initialize2(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_LDN, "(STUBBED) called");
-
const auto rc = InitializeImpl(ctx);
+ if (rc.IsError()) {
+ LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
+ }
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(rc);
@@ -508,14 +525,26 @@ public:
return ResultAirplaneModeEnabled;
}
+ if (auto room_member = room_network.GetRoomMember().lock()) {
+ ldn_packet_received = room_member->BindOnLdnPacketReceived(
+ [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
+ } else {
+ LOG_ERROR(Service_LDN, "Couldn't bind callback!");
+ return ResultAirplaneModeEnabled;
+ }
+
+ lan_discovery.Initialize([&]() { OnEventFired(); });
is_initialized = true;
- // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented
- return ResultAirplaneModeEnabled;
+ return ResultSuccess;
}
KernelHelpers::ServiceContext service_context;
Kernel::KEvent* state_change_event;
Network::RoomNetwork& room_network;
+ LANDiscovery lan_discovery;
+
+ // Callback identifier for the OnLDNPacketReceived event.
+ Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
bool is_initialized{};
};
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
index 6231e936d..44c2c773b 100644
--- a/src/core/hle/service/ldn/ldn_types.h
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -31,6 +31,8 @@ enum class NodeStateChange : u8 {
DisconnectAndConnect,
};
+DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange)
+
enum class ScanFilterFlag : u32 {
None = 0,
LocalCommunicationId = 1 << 0,
@@ -100,13 +102,13 @@ enum class AcceptPolicy : u8 {
enum class WifiChannel : s16 {
Default = 0,
- wifi24_1 = 1,
- wifi24_6 = 6,
- wifi24_11 = 11,
- wifi50_36 = 36,
- wifi50_40 = 40,
- wifi50_44 = 44,
- wifi50_48 = 48,
+ Wifi24_1 = 1,
+ Wifi24_6 = 6,
+ Wifi24_11 = 11,
+ Wifi50_36 = 36,
+ Wifi50_40 = 40,
+ Wifi50_44 = 44,
+ Wifi50_48 = 48,
};
enum class LinkLevel : s8 {
@@ -116,6 +118,11 @@ enum class LinkLevel : s8 {
Excellent,
};
+enum class NodeStatus : u8 {
+ Disconnected,
+ Connected,
+};
+
struct NodeLatestUpdate {
NodeStateChange state_change;
INSERT_PADDING_BYTES(0x7); // Unknown
@@ -150,7 +157,7 @@ struct Ssid {
Ssid() = default;
- explicit Ssid(std::string_view data) {
+ constexpr explicit Ssid(std::string_view data) {
length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
data.copy(raw.data(), length);
raw[length] = 0;
@@ -159,19 +166,18 @@ struct Ssid {
std::string GetStringValue() const {
return std::string(raw.data());
}
-};
-static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
-struct Ipv4Address {
- union {
- u32 raw{};
- std::array<u8, 4> bytes;
- };
+ bool operator==(const Ssid& b) const {
+ return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
+ }
- std::string GetStringValue() const {
- return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
+ bool operator!=(const Ssid& b) const {
+ return !operator==(b);
}
};
+static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
+
+using Ipv4Address = std::array<u8, 4>;
static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
struct MacAddress {
@@ -181,6 +187,14 @@ struct MacAddress {
};
static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
+struct MACAddressHash {
+ size_t operator()(const MacAddress& address) const {
+ u64 value{};
+ std::memcpy(&value, address.raw.data(), sizeof(address.raw));
+ return value;
+ }
+};
+
struct ScanFilter {
NetworkId network_id;
NetworkType network_type;
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index c484a9c8d..3a2fe938f 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -427,12 +427,11 @@ CharInfo MiiManager::BuildDefault(std::size_t index) {
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
}
-CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
+CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
Service::Mii::MiiManager manager;
auto mii = manager.BuildDefault(0);
- // Check if mii data exist
- if (mii_v3.mii_name[0] == 0) {
+ if (!ValidateV3Info(mii_v3)) {
return mii;
}
@@ -443,8 +442,15 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
mii.height = mii_v3.height;
mii.build = mii_v3.build;
- memset(mii.name.data(), 0, sizeof(mii.name));
- memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name));
+ // Copy name until string terminator
+ mii.name = {};
+ for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
+ mii.name[index] = mii_v3.mii_name[index];
+ if (mii.name[index] == 0) {
+ break;
+ }
+ }
+
mii.font_region = mii_v3.region_information.character_set;
mii.faceline_type = mii_v3.appearance_bits1.face_shape;
@@ -504,6 +510,151 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
return mii;
}
+Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
+ Service::Mii::MiiManager manager;
+ Ver3StoreData mii_v3{};
+
+ // TODO: We are ignoring a bunch of data from the mii_v3
+
+ mii_v3.version = 1;
+ mii_v3.mii_information.gender.Assign(mii.gender);
+ mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
+ mii_v3.height = mii.height;
+ mii_v3.build = mii.build;
+
+ // Copy name until string terminator
+ mii_v3.mii_name = {};
+ for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
+ mii_v3.mii_name[index] = mii.name[index];
+ if (mii_v3.mii_name[index] == 0) {
+ break;
+ }
+ }
+
+ mii_v3.region_information.character_set.Assign(mii.font_region);
+
+ mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
+ mii_v3.appearance_bits1.skin_color.Assign(mii.faceline_color);
+ mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
+ mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
+
+ mii_v3.hair_style = mii.hair_type;
+ mii_v3.appearance_bits3.hair_color.Assign(mii.hair_color);
+ mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
+
+ mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
+ mii_v3.appearance_bits4.eye_color.Assign(mii.eye_color);
+ mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
+ mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
+ mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
+ mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
+ mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
+
+ mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
+ mii_v3.appearance_bits5.eyebrow_color.Assign(mii.eyebrow_color);
+ mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
+ mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
+ mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
+ mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
+ mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
+
+ mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
+ mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
+ mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
+
+ mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
+ mii_v3.appearance_bits7.mouth_color.Assign(mii.mouth_color);
+ mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
+ mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
+ mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
+
+ mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
+ mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
+ mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
+
+ mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
+ mii_v3.appearance_bits9.facial_hair_color.Assign(mii.beard_color);
+
+ mii_v3.appearance_bits10.glasses_type.Assign(mii.glasses_type);
+ mii_v3.appearance_bits10.glasses_color.Assign(mii.glasses_color);
+ mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
+ mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
+
+ mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
+ mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
+ mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
+ mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
+
+ // TODO: Validate mii_v3 data
+
+ return mii_v3;
+}
+
+bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
+ bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
+
+ is_valid = is_valid && (mii_v3.mii_name[0] != 0);
+
+ is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
+ is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
+ is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
+ is_valid = is_valid && (mii_v3.height < 128);
+ is_valid = is_valid && (mii_v3.build < 128);
+
+ is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
+
+ is_valid = is_valid && (mii_v3.hair_style < 132);
+ is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
+
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
+ is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
+ is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
+ is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
+
+ is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
+ is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
+
+ is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
+
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
+ is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
+
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
+ is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
+
+ return is_valid;
+}
+
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result;
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index d847de0bd..83ad3d343 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -22,7 +22,9 @@ public:
ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
CharInfo BuildRandom(Age age, Gender gender, Race race);
CharInfo BuildDefault(std::size_t index);
- CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const;
+ CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
+ Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const;
+ bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
Result GetIndex(const CharInfo& info, u32& index);
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 13a843a28..046c5f18f 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -106,10 +106,10 @@ public:
{1, &IUser::FinalizeOld, "FinalizeOld"},
{2, &IUser::GetStateOld, "GetStateOld"},
{3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
- {400, nullptr, "Initialize"},
- {401, nullptr, "Finalize"},
- {402, nullptr, "GetState"},
- {403, nullptr, "IsNfcEnabled"},
+ {400, &IUser::InitializeOld, "Initialize"},
+ {401, &IUser::FinalizeOld, "Finalize"},
+ {402, &IUser::GetStateOld, "GetState"},
+ {403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
{404, nullptr, "ListDevices"},
{405, nullptr, "GetDeviceState"},
{406, nullptr, "GetNpadId"},
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index 31dd3a307..c32a6816b 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -20,14 +20,15 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
const auto& amiibo_data = ntag_file.user_memory;
LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
- LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter);
+ LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
- LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
- LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
- LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
- LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
- LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series);
- LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
+ LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
+ LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
+ LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
+ LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
+ static_cast<u16>(amiibo_data.model_info.model_number));
+ LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
+ LOG_DEBUG(Service_NFP, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type);
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
@@ -35,11 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
// Validate UUID
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
- if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) {
+ if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
+ ntag_file.uuid.uid[3]) {
return false;
}
- if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) !=
- ntag_file.uuid[8]) {
+ if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
+ ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
return false;
}
@@ -53,11 +55,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
if (amiibo_data.constant_value != 0xA5) {
return false;
}
- if (amiibo_data.model_info.constant_value != 0x02) {
+ if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
+ return false;
+ }
+ if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
return false;
}
- // dynamic_lock value apparently is not constant
- // ntag_file.dynamic_lock == 0x0F0001
if (ntag_file.CFG0 != 0x04000000U) {
return false;
}
@@ -70,7 +73,8 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
NTAG215File encoded_data{};
- memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2));
+ encoded_data.uid = nfc_data.uuid.uid;
+ encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
encoded_data.static_lock = nfc_data.static_lock;
encoded_data.compability_container = nfc_data.compability_container;
encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
@@ -82,10 +86,10 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
encoded_data.unknown = nfc_data.user_memory.unknown;
- encoded_data.hash = nfc_data.user_memory.hash;
+ encoded_data.unknown2 = nfc_data.user_memory.unknown2;
encoded_data.application_area = nfc_data.user_memory.application_area;
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
- memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid));
+ encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
encoded_data.model_info = nfc_data.user_memory.model_info;
encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
encoded_data.dynamic_lock = nfc_data.dynamic_lock;
@@ -99,8 +103,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
EncryptedNTAG215File nfc_data{};
- memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2));
- memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid));
+ nfc_data.uuid.uid = encoded_data.uid;
+ nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
+ nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
nfc_data.static_lock = encoded_data.static_lock;
nfc_data.compability_container = encoded_data.compability_container;
nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
@@ -112,7 +117,7 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
nfc_data.user_memory.unknown = encoded_data.unknown;
- nfc_data.user_memory.hash = encoded_data.hash;
+ nfc_data.user_memory.unknown2 = encoded_data.unknown2;
nfc_data.user_memory.application_area = encoded_data.application_area;
nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
nfc_data.user_memory.model_info = encoded_data.model_info;
@@ -127,10 +132,10 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
u32 GetTagPassword(const TagUuid& uuid) {
// Verifiy that the generated password is correct
- u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
- password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
- password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
- password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
+ u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
+ password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
+ password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
+ password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
return password;
}
@@ -138,15 +143,13 @@ HashSeed GetSeed(const NTAG215File& data) {
HashSeed seed{
.magic = data.write_counter,
.padding = {},
- .uuid1 = {},
- .uuid2 = {},
+ .uid_1 = data.uid,
+ .nintendo_id_1 = data.nintendo_id,
+ .uid_2 = data.uid,
+ .nintendo_id_2 = data.nintendo_id,
.keygen_salt = data.keygen_salt,
};
- // Copy the first 8 bytes of uuid
- memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
- memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
-
return seed;
}
@@ -165,8 +168,10 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
output.insert(output.end(), key.magic_bytes.begin(),
key.magic_bytes.begin() + key.magic_length);
- output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end());
- output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end());
+ output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
+ output.emplace_back(seed.nintendo_id_1);
+ output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
+ output.emplace_back(seed.nintendo_id_2);
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
@@ -177,7 +182,6 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed) {
-
// Initialize context
ctx.used = false;
ctx.counter = 0;
@@ -250,14 +254,15 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
reinterpret_cast<unsigned char*>(&out_data.settings));
// Copy the rest of the data directly
- out_data.uuid2 = in_data.uuid2;
+ out_data.uid = in_data.uid;
+ out_data.nintendo_id = in_data.nintendo_id;
+ out_data.lock_bytes = in_data.lock_bytes;
out_data.static_lock = in_data.static_lock;
out_data.compability_container = in_data.compability_container;
out_data.constant_value = in_data.constant_value;
out_data.write_counter = in_data.write_counter;
- out_data.uuid = in_data.uuid;
out_data.model_info = in_data.model_info;
out_data.keygen_salt = in_data.keygen_salt;
out_data.dynamic_lock = in_data.dynamic_lock;
@@ -309,7 +314,7 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t
// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
- sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
+ sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
// Regenerate data HMAC
@@ -350,7 +355,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
- sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
+ sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
// Init mbedtls HMAC context
@@ -364,7 +369,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
input_length2); // Data
mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
sizeof(HashData)); // Tag HMAC
- mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid),
+ mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length);
mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
index af7335912..0175ced91 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.h
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -5,7 +5,7 @@
#include <array>
-#include "core/hle/service/nfp/amiibo_types.h"
+#include "core/hle/service/nfp/nfp_types.h"
struct mbedtls_md_context_t;
@@ -22,10 +22,12 @@ using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed {
- u16 magic;
+ u16_be magic;
std::array<u8, 0xE> padding;
- std::array<u8, 0x8> uuid1;
- std::array<u8, 0x8> uuid2;
+ UniqueSerialNumber uid_1;
+ u8 nintendo_id_1;
+ UniqueSerialNumber uid_2;
+ u8 nintendo_id_2;
std::array<u8, 0x20> keygen_salt;
};
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index e0ed3f771..0cb55ca49 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -1,1098 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <array>
-#include <atomic>
-
-#include "common/fs/file.h"
-#include "common/fs/path_util.h"
#include "common/logging/log.h"
-#include "common/string_util.h"
-#include "core/core.h"
-#include "core/hid/emulated_controller.h"
-#include "core/hid/hid_core.h"
-#include "core/hid/hid_types.h"
#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/mii/mii_manager.h"
-#include "core/hle/service/nfp/amiibo_crypto.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
-namespace ErrCodes {
-constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
-constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
-constexpr Result NfcDisabled(ErrorModule::NFP, 80);
-constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
-constexpr Result TagRemoved(ErrorModule::NFP, 97);
-constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
-constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
-constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
-} // namespace ErrCodes
-
-IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
- : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
- nfp_interface{nfp_interface_} {
- static const FunctionInfo functions[] = {
- {0, &IUser::Initialize, "Initialize"},
- {1, &IUser::Finalize, "Finalize"},
- {2, &IUser::ListDevices, "ListDevices"},
- {3, &IUser::StartDetection, "StartDetection"},
- {4, &IUser::StopDetection, "StopDetection"},
- {5, &IUser::Mount, "Mount"},
- {6, &IUser::Unmount, "Unmount"},
- {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
- {8, &IUser::GetApplicationArea, "GetApplicationArea"},
- {9, &IUser::SetApplicationArea, "SetApplicationArea"},
- {10, &IUser::Flush, "Flush"},
- {11, nullptr, "Restore"},
- {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
- {13, &IUser::GetTagInfo, "GetTagInfo"},
- {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
- {15, &IUser::GetCommonInfo, "GetCommonInfo"},
- {16, &IUser::GetModelInfo, "GetModelInfo"},
- {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
- {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
- {19, &IUser::GetState, "GetState"},
- {20, &IUser::GetDeviceState, "GetDeviceState"},
- {21, &IUser::GetNpadId, "GetNpadId"},
- {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
- {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
- {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
- };
- RegisterHandlers(functions);
-
- availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
-}
-
-void IUser::Initialize(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- state = State::Initialized;
-
- // TODO(german77): Loop through all interfaces
- nfp_interface.Initialize();
-
- IPC::ResponseBuilder rb{ctx, 2, 0};
- rb.Push(ResultSuccess);
-}
-
-void IUser::Finalize(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::NonInitialized;
-
- // TODO(german77): Loop through all interfaces
- nfp_interface.Finalize();
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- std::vector<u64> devices;
-
- // TODO(german77): Loop through all interfaces
- devices.push_back(nfp_interface.GetHandle());
-
- if (devices.size() == 0) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
- return;
- }
-
- ctx.WriteBuffer(devices);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<s32>(devices.size()));
-}
-
-void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto nfp_protocol{rp.Pop<s32>()};
- LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.StartDetection(nfp_protocol);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.StopDetection();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Mount(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto model_type{rp.PopEnum<ModelType>()};
- const auto mount_target{rp.PopEnum<MountTarget>()};
- LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
- model_type, mount_target);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.Mount();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Unmount(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.Unmount();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto access_id{rp.Pop<u32>()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
- access_id);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.OpenApplicationArea(access_id);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- ApplicationArea data{};
- const auto result = nfp_interface.GetApplicationArea(data);
- ctx.WriteBuffer(data);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.Push(static_cast<u32>(data.size()));
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto data{ctx.ReadBuffer()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
- data.size());
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.SetApplicationArea(data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Flush(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.Flush();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto access_id{rp.Pop<u32>()};
- const auto data{ctx.ReadBuffer()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
- device_handle, access_id, data.size());
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.CreateApplicationArea(access_id, data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- TagInfo tag_info{};
- const auto result = nfp_interface.GetTagInfo(tag_info);
- ctx.WriteBuffer(tag_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- RegisterInfo register_info{};
- const auto result = nfp_interface.GetRegisterInfo(register_info);
- ctx.WriteBuffer(register_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- CommonInfo common_info{};
- const auto result = nfp_interface.GetCommonInfo(common_info);
- ctx.WriteBuffer(common_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- ModelInfo model_info{};
- const auto result = nfp_interface.GetModelInfo(model_info);
- ctx.WriteBuffer(model_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(nfp_interface.GetActivateEvent());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(nfp_interface.GetDeactivateEvent());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetState(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 3, 0};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
-}
-
-void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(nfp_interface.GetCurrentState());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(nfp_interface.GetNpadId());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(sizeof(ApplicationArea));
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "(STUBBED) called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(availability_change_event->GetReadableEvent());
-}
-
-void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto access_id{rp.Pop<u32>()};
- const auto data{ctx.ReadBuffer()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
- device_handle, access_id, data.size());
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::NfcDisabled);
- return;
- }
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.RecreateApplicationArea(access_id, data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
- const char* name)
- : ServiceFramework{system_, name}, module{std::move(module_)},
- npad_id{Core::HID::NpadIdType::Player1}, service_context{system_, service_name} {
- activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
- deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
-}
-
-Module::Interface::~Interface() = default;
-
-void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IUser>(*this, system);
-}
-
-bool Module::Interface::LoadAmiiboFile(const std::string& filename) {
- constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
- const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
- Common::FS::FileType::BinaryFile};
-
- if (!amiibo_file.IsOpen()) {
- LOG_ERROR(Service_NFP, "Amiibo is already on use");
- return false;
- }
-
- // Workaround for files with missing password data
- std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
- if (amiibo_file.Read(buffer) < tag_size_without_password) {
- LOG_ERROR(Service_NFP, "Failed to read amiibo file");
- return false;
- }
- memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
-
- if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
- LOG_INFO(Service_NFP, "Invalid amiibo");
- return false;
- }
-
- file_path = filename;
- return true;
-}
-
-bool Module::Interface::LoadAmiibo(const std::string& filename) {
- if (device_state != DeviceState::SearchingForTag) {
- LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
- return false;
- }
-
- if (!LoadAmiiboFile(filename)) {
- return false;
- }
-
- device_state = DeviceState::TagFound;
- activate_event->GetWritableEvent().Signal();
- return true;
-}
-
-void Module::Interface::CloseAmiibo() {
- LOG_INFO(Service_NFP, "Remove amiibo");
- device_state = DeviceState::TagRemoved;
- is_data_decoded = false;
- is_application_area_initialized = false;
- encrypted_tag_data = {};
- tag_data = {};
- deactivate_event->GetWritableEvent().Signal();
-}
-
-Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
- return activate_event->GetReadableEvent();
-}
-
-Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
- return deactivate_event->GetReadableEvent();
-}
-
-void Module::Interface::Initialize() {
- device_state = DeviceState::Initialized;
- is_data_decoded = false;
- is_application_area_initialized = false;
- encrypted_tag_data = {};
- tag_data = {};
-}
-
-void Module::Interface::Finalize() {
- if (device_state == DeviceState::TagMounted) {
- Unmount();
- }
- if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
- StopDetection();
- }
- device_state = DeviceState::Unaviable;
-}
-
-Result Module::Interface::StartDetection(s32 protocol_) {
- auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
-
- // TODO(german77): Add callback for when nfc data is available
-
- if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
- npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
- device_state = DeviceState::SearchingForTag;
- protocol = protocol_;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::StopDetection() {
- auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
- npad_device->SetPollingMode(Common::Input::PollingMode::Active);
-
- if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
- CloseAmiibo();
- return ResultSuccess;
- }
- if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
- device_state = DeviceState::Initialized;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::Flush() {
- // Ignore write command if we can't encrypt the data
- if (!is_data_decoded) {
- return ResultSuccess;
- }
-
- constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
- EncryptedNTAG215File tmp_encrypted_tag_data{};
- const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
- Common::FS::FileType::BinaryFile};
-
- if (!amiibo_file.IsOpen()) {
- LOG_ERROR(Core, "Amiibo is already on use");
- return ErrCodes::WriteAmiiboFailed;
- }
-
- // Workaround for files with missing password data
- std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
- if (amiibo_file.Read(buffer) < tag_size_without_password) {
- LOG_ERROR(Core, "Failed to read amiibo file");
- return ErrCodes::WriteAmiiboFailed;
- }
- memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
-
- if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) {
- LOG_INFO(Service_NFP, "Invalid amiibo");
- return ErrCodes::WriteAmiiboFailed;
- }
-
- bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0;
- bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id ==
- tag_data.model_info.character_id;
- if (!is_uuid_equal || !is_character_equal) {
- LOG_ERROR(Service_NFP, "Not the same amiibo");
- return ErrCodes::WriteAmiiboFailed;
- }
-
- if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
- LOG_ERROR(Service_NFP, "Failed to encode data");
- return ErrCodes::WriteAmiiboFailed;
- }
-
- // Return to the start of the file
- if (!amiibo_file.Seek(0)) {
- LOG_ERROR(Service_NFP, "Error writting to file");
- return ErrCodes::WriteAmiiboFailed;
- }
-
- if (!amiibo_file.Write(encrypted_tag_data)) {
- LOG_ERROR(Service_NFP, "Error writting to file");
- return ErrCodes::WriteAmiiboFailed;
- }
-
- return ResultSuccess;
-}
-
-Result Module::Interface::Mount() {
- if (device_state != DeviceState::TagFound) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
- is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data);
- LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded);
-
- is_application_area_initialized = false;
- device_state = DeviceState::TagMounted;
- return ResultSuccess;
-}
-
-Result Module::Interface::Unmount() {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
-
- is_data_decoded = false;
- is_application_area_initialized = false;
- device_state = DeviceState::TagFound;
- return ResultSuccess;
-}
-
-Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
- if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
-
- tag_info = {
- .uuid = encrypted_tag_data.uuid,
- .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
- .protocol = protocol,
- .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
- };
-
- return ResultSuccess;
-}
-
-Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
-
- if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
- const auto& settings = tag_data.settings;
- // TODO: Validate this data
- common_info = {
- .last_write_year = settings.write_date.GetYear(),
- .last_write_month = settings.write_date.GetMonth(),
- .last_write_day = settings.write_date.GetDay(),
- .write_counter = settings.crc_counter,
- .version = 1,
- .application_area_size = sizeof(ApplicationArea),
- };
- return ResultSuccess;
- }
-
- // Generate a generic answer
- common_info = {
- .last_write_year = 2022,
- .last_write_month = 2,
- .last_write_day = 7,
- .write_counter = 0,
- .version = 1,
- .application_area_size = sizeof(ApplicationArea),
- };
- return ResultSuccess;
-}
-
-Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
-
- const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
- model_info = {
- .character_id = model_info_data.character_id,
- .character_variant = model_info_data.character_variant,
- .amiibo_type = model_info_data.amiibo_type,
- .model_number = model_info_data.model_number,
- .series = model_info_data.series,
- .constant_value = model_info_data.constant_value,
- };
- return ResultSuccess;
-}
-
-Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ErrCodes::TagRemoved;
- }
- return ErrCodes::WrongDeviceState;
- }
-
- Service::Mii::MiiManager manager;
-
- if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
- const auto& settings = tag_data.settings;
-
- // TODO: Validate this data
- register_info = {
- .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
- .first_write_year = settings.init_date.GetYear(),
- .first_write_month = settings.init_date.GetMonth(),
- .first_write_day = settings.init_date.GetDay(),
- .amiibo_name = GetAmiiboName(settings),
- .font_region = {},
+class IUserManager final : public ServiceFramework<IUserManager> {
+public:
+ explicit IUserManager(Core::System& system_) : ServiceFramework{system_, "nfp:user"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IUserManager::CreateUserInterface, "CreateUserInterface"},
};
+ // clang-format on
- return ResultSuccess;
+ RegisterHandlers(functions);
}
- // Generate a generic answer
- register_info = {
- .mii_char_info = manager.BuildDefault(0),
- .first_write_year = 2022,
- .first_write_month = 2,
- .first_write_day = 7,
- .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
- .font_region = {},
- };
- return ResultSuccess;
-}
+private:
+ void CreateUserInterface(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
-Result Module::Interface::OpenApplicationArea(u32 access_id) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ErrCodes::TagRemoved;
+ if (user_interface == nullptr) {
+ user_interface = std::make_shared<IUser>(system);
}
- return ErrCodes::WrongDeviceState;
- }
-
- // Fallback for lack of amiibo keys
- if (!is_data_decoded) {
- LOG_WARNING(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
- }
- if (tag_data.settings.settings.appdata_initialized == 0) {
- LOG_WARNING(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
- }
-
- if (tag_data.application_area_id != access_id) {
- LOG_WARNING(Service_NFP, "Wrong application area id");
- return ErrCodes::WrongApplicationAreaId;
- }
-
- is_application_area_initialized = true;
- return ResultSuccess;
-}
-
-Result Module::Interface::GetApplicationArea(ApplicationArea& data) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ErrCodes::TagRemoved;
- }
- return ErrCodes::WrongDeviceState;
- }
-
- if (!is_application_area_initialized) {
- LOG_ERROR(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
- }
-
- data = tag_data.application_area;
-
- return ResultSuccess;
-}
-
-Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ErrCodes::TagRemoved;
- }
- return ErrCodes::WrongDeviceState;
- }
-
- if (!is_application_area_initialized) {
- LOG_ERROR(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
- }
-
- if (data.size() != sizeof(ApplicationArea)) {
- LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
- return ResultUnknown;
- }
-
- std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
- return ResultSuccess;
-}
-
-Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ErrCodes::TagRemoved;
- }
- return ErrCodes::WrongDeviceState;
- }
-
- if (tag_data.settings.settings.appdata_initialized != 0) {
- LOG_ERROR(Service_NFP, "Application area already exist");
- return ErrCodes::ApplicationAreaExist;
- }
-
- if (data.size() != sizeof(ApplicationArea)) {
- LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
- return ResultUnknown;
- }
-
- std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
- tag_data.application_area_id = access_id;
-
- return ResultSuccess;
-}
-
-Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- if (device_state == DeviceState::TagRemoved) {
- return ErrCodes::TagRemoved;
- }
- return ErrCodes::WrongDeviceState;
- }
-
- if (data.size() != sizeof(ApplicationArea)) {
- LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
- return ResultUnknown;
- }
-
- std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
- tag_data.application_area_id = access_id;
-
- return ResultSuccess;
-}
-
-u64 Module::Interface::GetHandle() const {
- // Generate a handle based of the npad id
- return static_cast<u64>(npad_id);
-}
-
-DeviceState Module::Interface::GetCurrentState() const {
- return device_state;
-}
-
-Core::HID::NpadIdType Module::Interface::GetNpadId() const {
- // Return first connected npad id as a workaround for lack of a single nfc interface per
- // controller
- return system.HIDCore().GetFirstNpadId();
-}
-
-AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const {
- std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
- AmiiboName amiibo_name{};
-
- // Convert from big endian to little endian
- for (std::size_t i = 0; i < amiibo_name_length; i++) {
- settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IUser>(user_interface);
}
- // Convert from utf16 to utf8
- const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
- memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
-
- return amiibo_name;
-}
+ std::shared_ptr<IUser> user_interface;
+};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
- auto module = std::make_shared<Module>();
- std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager);
+ std::make_shared<IUserManager>(system)->InstallAsService(service_manager);
}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 0de0b48e7..a25c362b8 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -3,170 +3,9 @@
#pragma once
-#include <array>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/mii/types.h"
-#include "core/hle/service/nfp/amiibo_types.h"
#include "core/hle/service/service.h"
-namespace Kernel {
-class KEvent;
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Core::HID {
-enum class NpadIdType : u32;
-} // namespace Core::HID
-
namespace Service::NFP {
-using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
-
-struct TagInfo {
- TagUuid uuid;
- u8 uuid_length;
- INSERT_PADDING_BYTES(0x15);
- s32 protocol;
- u32 tag_type;
- INSERT_PADDING_BYTES(0x30);
-};
-static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
-
-struct CommonInfo {
- u16 last_write_year;
- u8 last_write_month;
- u8 last_write_day;
- u16 write_counter;
- u16 version;
- u32 application_area_size;
- INSERT_PADDING_BYTES(0x34);
-};
-static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
-
-struct ModelInfo {
- u16 character_id;
- u8 character_variant;
- AmiiboType amiibo_type;
- u16 model_number;
- AmiiboSeries series;
- u8 constant_value; // Must be 02
- INSERT_PADDING_BYTES(0x38); // Unknown
-};
-static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
-
-struct RegisterInfo {
- Service::Mii::CharInfo mii_char_info;
- u16 first_write_year;
- u8 first_write_month;
- u8 first_write_day;
- AmiiboName amiibo_name;
- u8 font_region;
- INSERT_PADDING_BYTES(0x7A);
-};
-static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
-
-class Module final {
-public:
- class Interface : public ServiceFramework<Interface> {
- public:
- explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
- const char* name);
- ~Interface() override;
-
- void CreateUserInterface(Kernel::HLERequestContext& ctx);
- bool LoadAmiibo(const std::string& filename);
- bool LoadAmiiboFile(const std::string& filename);
- void CloseAmiibo();
-
- void Initialize();
- void Finalize();
-
- Result StartDetection(s32 protocol_);
- Result StopDetection();
- Result Mount();
- Result Unmount();
- Result Flush();
-
- Result GetTagInfo(TagInfo& tag_info) const;
- Result GetCommonInfo(CommonInfo& common_info) const;
- Result GetModelInfo(ModelInfo& model_info) const;
- Result GetRegisterInfo(RegisterInfo& register_info) const;
-
- Result OpenApplicationArea(u32 access_id);
- Result GetApplicationArea(ApplicationArea& data) const;
- Result SetApplicationArea(const std::vector<u8>& data);
- Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
- Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
-
- u64 GetHandle() const;
- DeviceState GetCurrentState() const;
- Core::HID::NpadIdType GetNpadId() const;
-
- Kernel::KReadableEvent& GetActivateEvent() const;
- Kernel::KReadableEvent& GetDeactivateEvent() const;
-
- protected:
- std::shared_ptr<Module> module;
-
- private:
- AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
-
- const Core::HID::NpadIdType npad_id;
-
- bool is_data_decoded{};
- bool is_application_area_initialized{};
- s32 protocol;
- std::string file_path{};
- Kernel::KEvent* activate_event;
- Kernel::KEvent* deactivate_event;
- DeviceState device_state{DeviceState::Unaviable};
- KernelHelpers::ServiceContext service_context;
-
- NTAG215File tag_data{};
- EncryptedNTAG215File encrypted_tag_data{};
- };
-};
-
-class IUser final : public ServiceFramework<IUser> {
-public:
- explicit IUser(Module::Interface& nfp_interface_, Core::System& system_);
-
-private:
- void Initialize(Kernel::HLERequestContext& ctx);
- void Finalize(Kernel::HLERequestContext& ctx);
- void ListDevices(Kernel::HLERequestContext& ctx);
- void StartDetection(Kernel::HLERequestContext& ctx);
- void StopDetection(Kernel::HLERequestContext& ctx);
- void Mount(Kernel::HLERequestContext& ctx);
- void Unmount(Kernel::HLERequestContext& ctx);
- void OpenApplicationArea(Kernel::HLERequestContext& ctx);
- void GetApplicationArea(Kernel::HLERequestContext& ctx);
- void SetApplicationArea(Kernel::HLERequestContext& ctx);
- void Flush(Kernel::HLERequestContext& ctx);
- void CreateApplicationArea(Kernel::HLERequestContext& ctx);
- void GetTagInfo(Kernel::HLERequestContext& ctx);
- void GetRegisterInfo(Kernel::HLERequestContext& ctx);
- void GetCommonInfo(Kernel::HLERequestContext& ctx);
- void GetModelInfo(Kernel::HLERequestContext& ctx);
- void AttachActivateEvent(Kernel::HLERequestContext& ctx);
- void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
- void GetState(Kernel::HLERequestContext& ctx);
- void GetDeviceState(Kernel::HLERequestContext& ctx);
- void GetNpadId(Kernel::HLERequestContext& ctx);
- void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
- void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
- void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
-
- KernelHelpers::ServiceContext service_context;
-
- // TODO(german77): We should have a vector of interfaces
- Module::Interface& nfp_interface;
-
- State state{State::NonInitialized};
- Kernel::KEvent* availability_change_event;
-};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
new file mode 100644
index 000000000..ec895ac01
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -0,0 +1,681 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <atomic>
+
+#include "common/fs/file.h"
+#include "common/fs/path_util.h"
+#include "common/input.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "common/tiny_mt.h"
+#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/amiibo_crypto.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
+#include "core/hle/service/nfp/nfp_user.h"
+#include "core/hle/service/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+#include "core/hle/service/time/time_zone_types.h"
+
+namespace Service::NFP {
+NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+ KernelHelpers::ServiceContext& service_context_,
+ Kernel::KEvent* availability_change_event_)
+ : npad_id{npad_id_}, system{system_}, service_context{service_context_},
+ availability_change_event{availability_change_event_} {
+ activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
+ deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
+ npad_device = system.HIDCore().GetEmulatedController(npad_id);
+
+ Core::HID::ControllerUpdateCallback engine_callback{
+ .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
+ .is_npad_service = false,
+ };
+ is_controller_set = true;
+ callback_key = npad_device->SetCallback(engine_callback);
+
+ auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
+ current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point;
+}
+
+NfpDevice::~NfpDevice() {
+ if (!is_controller_set) {
+ return;
+ }
+ npad_device->DeleteCallback(callback_key);
+ is_controller_set = false;
+};
+
+void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
+ if (type == Core::HID::ControllerTriggerType::Connected ||
+ type == Core::HID::ControllerTriggerType::Disconnected) {
+ availability_change_event->GetWritableEvent().Signal();
+ return;
+ }
+
+ if (type != Core::HID::ControllerTriggerType::Nfc) {
+ return;
+ }
+
+ if (!npad_device->IsConnected()) {
+ return;
+ }
+
+ const auto nfc_status = npad_device->GetNfc();
+ switch (nfc_status.state) {
+ case Common::Input::NfcState::NewAmiibo:
+ LoadAmiibo(nfc_status.data);
+ break;
+ case Common::Input::NfcState::AmiiboRemoved:
+ if (device_state != DeviceState::SearchingForTag) {
+ CloseAmiibo();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
+ if (device_state != DeviceState::SearchingForTag) {
+ LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
+ return false;
+ }
+
+ if (data.size() != sizeof(EncryptedNTAG215File)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size());
+ return false;
+ }
+
+ memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
+
+ device_state = DeviceState::TagFound;
+ deactivate_event->GetReadableEvent().Clear();
+ activate_event->GetWritableEvent().Signal();
+ return true;
+}
+
+void NfpDevice::CloseAmiibo() {
+ LOG_INFO(Service_NFP, "Remove amiibo");
+
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+
+ device_state = DeviceState::TagRemoved;
+ encrypted_tag_data = {};
+ tag_data = {};
+ activate_event->GetReadableEvent().Clear();
+ deactivate_event->GetWritableEvent().Signal();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
+ return activate_event->GetReadableEvent();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const {
+ return deactivate_event->GetReadableEvent();
+}
+
+void NfpDevice::Initialize() {
+ device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
+ encrypted_tag_data = {};
+ tag_data = {};
+}
+
+void NfpDevice::Finalize() {
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ StopDetection();
+ }
+ device_state = DeviceState::Unavailable;
+}
+
+Result NfpDevice::StartDetection(s32 protocol_) {
+ if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
+ LOG_ERROR(Service_NFP, "Nfc not supported");
+ return NfcDisabled;
+ }
+
+ device_state = DeviceState::SearchingForTag;
+ protocol = protocol_;
+ return ResultSuccess;
+}
+
+Result NfpDevice::StopDetection() {
+ npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
+ if (device_state == DeviceState::Initialized) {
+ return ResultSuccess;
+ }
+
+ if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
+ CloseAmiibo();
+ return ResultSuccess;
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ device_state = DeviceState::Initialized;
+ return ResultSuccess;
+ }
+
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+}
+
+Result NfpDevice::Flush() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ auto& settings = tag_data.settings;
+
+ const auto& current_date = GetAmiiboDate(current_posix_time);
+ if (settings.write_date.raw_date != current_date.raw_date) {
+ settings.write_date = current_date;
+ settings.crc_counter++;
+ // TODO: Find how to calculate the crc check
+ // settings.crc = CalculateCRC(settings);
+ }
+
+ tag_data.write_counter++;
+
+ if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Failed to encode data");
+ return WriteAmiiboFailed;
+ }
+
+ std::vector<u8> data(sizeof(encrypted_tag_data));
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+
+ if (!npad_device->WriteNfc(data)) {
+ LOG_ERROR(Service_NFP, "Error writing to file");
+ return WriteAmiiboFailed;
+ }
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::Mount(MountTarget mount_target_) {
+ if (device_state != DeviceState::TagFound) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo");
+ return NotAnAmiibo;
+ }
+
+ if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
+ return CorruptedData;
+ }
+
+ device_state = DeviceState::TagMounted;
+ mount_target = mount_target_;
+ return ResultSuccess;
+}
+
+Result NfpDevice::Unmount() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ // Save data before unloading the amiibo
+ if (is_data_moddified) {
+ Flush();
+ }
+
+ device_state = DeviceState::TagFound;
+ mount_target = MountTarget::None;
+ is_app_area_open = false;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
+ if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ tag_info = {
+ .uuid = encrypted_tag_data.uuid.uid,
+ .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+ .protocol = TagProtocol::TypeA,
+ .tag_type = TagType::Type2,
+ };
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ const auto& settings = tag_data.settings;
+
+ // TODO: Validate this data
+ common_info = {
+ .last_write_date = settings.write_date.GetWriteDate(),
+ .write_counter = tag_data.write_counter,
+ .version = 0,
+ .application_area_size = sizeof(ApplicationArea),
+ };
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
+ model_info = {
+ .character_id = model_info_data.character_id,
+ .character_variant = model_info_data.character_variant,
+ .amiibo_type = model_info_data.amiibo_type,
+ .model_number = model_info_data.model_number,
+ .series = model_info_data.series,
+ };
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.amiibo_initialized == 0) {
+ return RegistrationIsNotInitialized;
+ }
+
+ Service::Mii::MiiManager manager;
+ const auto& settings = tag_data.settings;
+
+ // TODO: Validate this data
+ register_info = {
+ .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
+ .creation_date = settings.init_date.GetWriteDate(),
+ .amiibo_name = GetAmiiboName(settings),
+ .font_region = {},
+ };
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ Service::Mii::MiiManager manager;
+ auto& settings = tag_data.settings;
+
+ settings.init_date = GetAmiiboDate(current_posix_time);
+ settings.write_date = GetAmiiboDate(current_posix_time);
+ settings.crc_counter++;
+ // TODO: Find how to calculate the crc check
+ // settings.crc = CalculateCRC(settings);
+
+ SetAmiiboName(settings, amiibo_name);
+ tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0));
+ settings.settings.amiibo_initialized.Assign(1);
+
+ return Flush();
+}
+
+Result NfpDevice::RestoreAmiibo() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ // TODO: Load amiibo from backup on system
+ LOG_ERROR(Service_NFP, "Not Implemented");
+ return ResultSuccess;
+}
+
+Result NfpDevice::DeleteAllData() {
+ const auto result = DeleteApplicationArea();
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ Common::TinyMT rng{};
+ rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
+ tag_data.settings.settings.amiibo_initialized.Assign(0);
+
+ return Flush();
+}
+
+Result NfpDevice::OpenApplicationArea(u32 access_id) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_WARNING(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (tag_data.application_area_id != access_id) {
+ LOG_WARNING(Service_NFP, "Wrong application area id");
+ return WrongApplicationAreaId;
+ }
+
+ is_app_area_open = true;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!is_app_area_open) {
+ LOG_ERROR(Service_NFP, "Application area is not open");
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_ERROR(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ data.resize(sizeof(ApplicationArea));
+ }
+
+ memcpy(data.data(), tag_data.application_area.data(), data.size());
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!is_app_area_open) {
+ LOG_ERROR(Service_NFP, "Application area is not open");
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_ERROR(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+ return ResultUnknown;
+ }
+
+ Common::TinyMT rng{};
+ std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+ // Fill remaining data with random numbers
+ rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
+ sizeof(ApplicationArea) - data.size());
+
+ tag_data.applicaton_write_counter++;
+ is_data_moddified = true;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
+ LOG_ERROR(Service_NFP, "Application area already exist");
+ return ApplicationAreaExist;
+ }
+
+ return RecreateApplicationArea(access_id, data);
+}
+
+Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+ return WrongApplicationAreaSize;
+ }
+
+ Common::TinyMT rng{};
+ std::memcpy(tag_data.application_area.data(), data.data(), data.size());
+ // Fill remaining data with random numbers
+ rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
+ sizeof(ApplicationArea) - data.size());
+
+ // TODO: Investigate why the title id needs to be moddified
+ tag_data.title_id = system.GetCurrentProcessProgramID();
+ tag_data.title_id = tag_data.title_id | 0x30000000ULL;
+ tag_data.settings.settings.appdata_initialized.Assign(1);
+ tag_data.application_area_id = access_id;
+ tag_data.applicaton_write_counter++;
+ tag_data.unknown = {};
+
+ return Flush();
+}
+
+Result NfpDevice::DeleteApplicationArea() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ Common::TinyMT rng{};
+ rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
+ rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
+ rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
+ tag_data.settings.settings.appdata_initialized.Assign(0);
+ tag_data.applicaton_write_counter++;
+ tag_data.unknown = {};
+
+ return Flush();
+}
+
+u64 NfpDevice::GetHandle() const {
+ // Generate a handle based of the npad id
+ return static_cast<u64>(npad_id);
+}
+
+u32 NfpDevice::GetApplicationAreaSize() const {
+ return sizeof(ApplicationArea);
+}
+
+DeviceState NfpDevice::GetCurrentState() const {
+ return device_state;
+}
+
+Core::HID::NpadIdType NfpDevice::GetNpadId() const {
+ return npad_id;
+}
+
+AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
+ std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+ AmiiboName amiibo_name{};
+
+ // Convert from big endian to little endian
+ for (std::size_t i = 0; i < amiibo_name_length; i++) {
+ settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
+ }
+
+ // Convert from utf16 to utf8
+ const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
+ memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
+
+ return amiibo_name;
+}
+
+void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
+ std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+
+ // Convert from utf8 to utf16
+ const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data());
+ memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(),
+ amiibo_name_utf16.size() * sizeof(char16_t));
+
+ // Convert from little endian to big endian
+ for (std::size_t i = 0; i < amiibo_name_length; i++) {
+ settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
+ }
+}
+
+AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
+ const auto& time_zone_manager =
+ system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+ Time::TimeZone::CalendarInfo calendar_info{};
+ AmiiboDate amiibo_date{};
+
+ amiibo_date.SetYear(2000);
+ amiibo_date.SetMonth(1);
+ amiibo_date.SetDay(1);
+
+ if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) {
+ amiibo_date.SetYear(calendar_info.time.year);
+ amiibo_date.SetMonth(calendar_info.time.month);
+ amiibo_date.SetDay(calendar_info.time.day);
+ }
+
+ return amiibo_date;
+}
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
new file mode 100644
index 000000000..a5b72cf19
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/mii/types.h"
+#include "core/hle/service/nfp/nfp_types.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Core::HID {
+class EmulatedController;
+enum class ControllerTriggerType;
+enum class NpadIdType : u32;
+} // namespace Core::HID
+
+namespace Service::NFP {
+class NfpDevice {
+public:
+ NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+ KernelHelpers::ServiceContext& service_context_,
+ Kernel::KEvent* availability_change_event_);
+ ~NfpDevice();
+
+ void Initialize();
+ void Finalize();
+
+ Result StartDetection(s32 protocol_);
+ Result StopDetection();
+ Result Mount(MountTarget mount_target);
+ Result Unmount();
+ Result Flush();
+
+ Result GetTagInfo(TagInfo& tag_info) const;
+ Result GetCommonInfo(CommonInfo& common_info) const;
+ Result GetModelInfo(ModelInfo& model_info) const;
+ Result GetRegisterInfo(RegisterInfo& register_info) const;
+
+ Result SetNicknameAndOwner(const AmiiboName& amiibo_name);
+ Result RestoreAmiibo();
+ Result DeleteAllData();
+
+ Result OpenApplicationArea(u32 access_id);
+ Result GetApplicationArea(std::vector<u8>& data) const;
+ Result SetApplicationArea(std::span<const u8> data);
+ Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
+ Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
+ Result DeleteApplicationArea();
+
+ u64 GetHandle() const;
+ u32 GetApplicationAreaSize() const;
+ DeviceState GetCurrentState() const;
+ Core::HID::NpadIdType GetNpadId() const;
+
+ Kernel::KReadableEvent& GetActivateEvent() const;
+ Kernel::KReadableEvent& GetDeactivateEvent() const;
+
+private:
+ void NpadUpdate(Core::HID::ControllerTriggerType type);
+ bool LoadAmiibo(std::span<const u8> data);
+ void CloseAmiibo();
+
+ AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
+ void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
+ AmiiboDate GetAmiiboDate(s64 posix_time) const;
+
+ bool is_controller_set{};
+ int callback_key;
+ const Core::HID::NpadIdType npad_id;
+ Core::System& system;
+ Core::HID::EmulatedController* npad_device = nullptr;
+ KernelHelpers::ServiceContext& service_context;
+ Kernel::KEvent* activate_event = nullptr;
+ Kernel::KEvent* deactivate_event = nullptr;
+ Kernel::KEvent* availability_change_event = nullptr;
+
+ bool is_data_moddified{};
+ bool is_app_area_open{};
+ s32 protocol{};
+ s64 current_posix_time{};
+ MountTarget mount_target{MountTarget::None};
+ DeviceState device_state{DeviceState::Unavailable};
+
+ NTAG215File tag_data{};
+ EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
new file mode 100644
index 000000000..d8e4cf094
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFP {
+
+constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
+constexpr Result InvalidArgument(ErrorModule::NFP, 65);
+constexpr Result WrongApplicationAreaSize(ErrorModule::NFP, 68);
+constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
+constexpr Result NfcDisabled(ErrorModule::NFP, 80);
+constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result TagRemoved(ErrorModule::NFP, 97);
+constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120);
+constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result CorruptedData(ErrorModule::NFP, 144);
+constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
+constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
+constexpr Result NotAnAmiibo(ErrorModule::NFP, 178);
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/nfp_types.h
index bf2de811a..c09f9ddb6 100644
--- a/src/core/hle/service/nfp/amiibo_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -5,6 +5,7 @@
#include <array>
+#include "common/swap.h"
#include "core/hle/service/mii/types.h"
namespace Service::NFP {
@@ -27,7 +28,7 @@ enum class DeviceState : u32 {
TagFound,
TagRemoved,
TagMounted,
- Unaviable,
+ Unavailable,
Finalized,
};
@@ -36,6 +37,7 @@ enum class ModelType : u32 {
};
enum class MountTarget : u32 {
+ None,
Rom,
Ram,
All,
@@ -73,21 +75,101 @@ enum class AmiiboSeries : u8 {
Diablo,
};
-using TagUuid = std::array<u8, 10>;
+enum class TagType : u32 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class PackedTagType : u8 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class TagProtocol : u32 {
+ None,
+ TypeA, // ISO14443A
+ TypeB, // ISO14443B
+ TypeF, // Sony Felica
+};
+
+using UniqueSerialNumber = std::array<u8, 7>;
+using LockBytes = std::array<u8, 2>;
using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>;
+using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
+
+struct TagUuid {
+ UniqueSerialNumber uid;
+ u8 nintendo_id;
+ LockBytes lock_bytes;
+};
+static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
+
+struct WriteDate {
+ u16 year;
+ u8 month;
+ u8 day;
+};
+static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
struct AmiiboDate {
u16 raw_date{};
+ u16 GetValue() const {
+ return Common::swap16(raw_date);
+ }
+
u16 GetYear() const {
- return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000);
+ return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
}
u8 GetMonth() const {
- return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1);
+ return static_cast<u8>((GetValue() & 0x01E0) >> 5);
}
u8 GetDay() const {
- return static_cast<u8>(raw_date & 0x001F);
+ return static_cast<u8>(GetValue() & 0x001F);
+ }
+
+ WriteDate GetWriteDate() const {
+ if (!IsValidDate()) {
+ return {
+ .year = 2000,
+ .month = 1,
+ .day = 1,
+ };
+ }
+ return {
+ .year = GetYear(),
+ .month = GetMonth(),
+ .day = GetDay(),
+ };
+ }
+
+ void SetYear(u16 year) {
+ const u16 year_converted = static_cast<u16>((year - 2000) << 9);
+ raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
+ }
+ void SetMonth(u8 month) {
+ const u16 month_converted = static_cast<u16>(month << 5);
+ raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
+ }
+ void SetDay(u8 day) {
+ const u16 day_converted = static_cast<u16>(day);
+ raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
+ }
+
+ bool IsValidDate() const {
+ const bool is_day_valid = GetDay() > 0 && GetDay() < 32;
+ const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13;
+ const bool is_year_valid = GetYear() >= 2000;
+ return is_year_valid && is_month_valid && is_day_valid;
}
};
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
@@ -117,9 +199,9 @@ struct AmiiboModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
- u16 model_number;
+ u16_be model_number;
AmiiboSeries series;
- u8 constant_value; // Must be 02
+ PackedTagType tag_type;
INSERT_PADDING_BYTES(0x4); // Unknown
};
static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
@@ -134,7 +216,7 @@ static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid siz
#pragma pack(1)
struct EncryptedAmiiboFile {
u8 constant_value; // Must be A5
- u16 write_counter; // Number of times the amiibo has been written?
+ u16_be write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
@@ -146,18 +228,18 @@ struct EncryptedAmiiboFile {
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
std::array<u8, 0x2> unknown;
- HashData hash; // Probably a SHA256-HMAC hash?
+ std::array<u32, 0x8> unknown2;
ApplicationArea application_area; // Encrypted Game data
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215File {
- std::array<u8, 0x2> uuid2;
+ LockBytes lock_bytes; // Tag UUID
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
HashData hmac_data; // Hash
u8 constant_value; // Must be A5
- u16 write_counter; // Number of times the amiibo has been written?
+ u16_be write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings;
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
@@ -165,10 +247,11 @@ struct NTAG215File {
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id;
std::array<u8, 0x2> unknown;
- HashData hash; // Probably a SHA256-HMAC hash?
+ std::array<u32, 0x8> unknown2;
ApplicationArea application_area; // Encrypted Game data
HashData hmac_tag; // Hash
- std::array<u8, 0x8> uuid;
+ UniqueSerialNumber uid; // Unique serial number
+ u8 nintendo_id; // Tag UUID
AmiiboModelInfo model_info;
HashData keygen_salt; // Salt
u32 dynamic_lock; // Dynamic lock
@@ -194,4 +277,44 @@ static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable.");
+struct TagInfo {
+ UniqueSerialNumber uuid;
+ INSERT_PADDING_BYTES(0x3);
+ u8 uuid_length;
+ INSERT_PADDING_BYTES(0x15);
+ TagProtocol protocol;
+ TagType tag_type;
+ INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
+
+struct CommonInfo {
+ WriteDate last_write_date;
+ u16 write_counter;
+ u8 version;
+ INSERT_PADDING_BYTES(0x1);
+ u32 application_area_size;
+ INSERT_PADDING_BYTES(0x34);
+};
+static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+
+struct ModelInfo {
+ u16 character_id;
+ u8 character_variant;
+ AmiiboType amiibo_type;
+ u16 model_number;
+ AmiiboSeries series;
+ INSERT_PADDING_BYTES(0x39); // Unknown
+};
+static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+
+struct RegisterInfo {
+ Service::Mii::CharInfo mii_char_info;
+ WriteDate creation_date;
+ AmiiboName amiibo_name;
+ u8 font_region;
+ INSERT_PADDING_BYTES(0x7A);
+};
+static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
+
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 2d7b156cf..4ed53b534 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -1,18 +1,674 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <array>
+#include <atomic>
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
-NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_)
- : Interface(std::move(module_), system_, "nfp:user") {
+IUser::IUser(Core::System& system_)
+ : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
static const FunctionInfo functions[] = {
- {0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
+ {0, &IUser::Initialize, "Initialize"},
+ {1, &IUser::Finalize, "Finalize"},
+ {2, &IUser::ListDevices, "ListDevices"},
+ {3, &IUser::StartDetection, "StartDetection"},
+ {4, &IUser::StopDetection, "StopDetection"},
+ {5, &IUser::Mount, "Mount"},
+ {6, &IUser::Unmount, "Unmount"},
+ {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
+ {8, &IUser::GetApplicationArea, "GetApplicationArea"},
+ {9, &IUser::SetApplicationArea, "SetApplicationArea"},
+ {10, &IUser::Flush, "Flush"},
+ {11, &IUser::Restore, "Restore"},
+ {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
+ {13, &IUser::GetTagInfo, "GetTagInfo"},
+ {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
+ {15, &IUser::GetCommonInfo, "GetCommonInfo"},
+ {16, &IUser::GetModelInfo, "GetModelInfo"},
+ {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
+ {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
+ {19, &IUser::GetState, "GetState"},
+ {20, &IUser::GetDeviceState, "GetDeviceState"},
+ {21, &IUser::GetNpadId, "GetNpadId"},
+ {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
+ {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+ {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
+
+ availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
+
+ for (u32 device_index = 0; device_index < 10; device_index++) {
+ devices[device_index] =
+ std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+ service_context, availability_change_event);
+ }
+}
+
+void IUser::Initialize(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFC, "called");
+
+ state = State::Initialized;
+
+ for (auto& device : devices) {
+ device->Initialize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0};
+ rb.Push(ResultSuccess);
+}
+
+void IUser::Finalize(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ state = State::NonInitialized;
+
+ for (auto& device : devices) {
+ device->Finalize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanWriteBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ if (ctx.GetWriteBufferSize() == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ std::vector<u64> nfp_devices;
+ const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
+
+ for (auto& device : devices) {
+ if (nfp_devices.size() >= max_allowed_devices) {
+ continue;
+ }
+ if (device->GetCurrentState() != DeviceState::Unavailable) {
+ nfp_devices.push_back(device->GetHandle());
+ }
+ }
+
+ if (nfp_devices.size() == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ ctx.WriteBuffer(nfp_devices);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<s32>(nfp_devices.size()));
+}
+
+void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto nfp_protocol{rp.Pop<s32>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->StartDetection(nfp_protocol);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->StopDetection();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Mount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto model_type{rp.PopEnum<ModelType>()};
+ const auto mount_target{rp.PopEnum<MountTarget>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
+ model_type, mount_target);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Mount(mount_target);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Unmount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Unmount();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->OpenApplicationArea(access_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto data_size = ctx.GetWriteBufferSize();
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanWriteBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ const auto result = device.value()->GetApplicationArea(data);
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(static_cast<u32>(data_size));
+}
+
+void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanReadBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->SetApplicationArea(data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Flush(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Flush();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Restore(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->RestoreAmiibo();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+ access_id, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanReadBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->CreateApplicationArea(access_id, data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ TagInfo tag_info{};
+ const auto result = device.value()->GetTagInfo(tag_info);
+ ctx.WriteBuffer(tag_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ RegisterInfo register_info{};
+ const auto result = device.value()->GetRegisterInfo(register_info);
+ ctx.WriteBuffer(register_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ CommonInfo common_info{};
+ const auto result = device.value()->GetCommonInfo(common_info);
+ ctx.WriteBuffer(common_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ ModelInfo model_info{};
+ const auto result = device.value()->GetModelInfo(model_info);
+ ctx.WriteBuffer(model_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device.value()->GetActivateEvent());
+}
+
+void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device.value()->GetDeactivateEvent());
+}
+
+void IUser::GetState(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3, 0};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+}
+
+void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device.value()->GetCurrentState());
+}
+
+void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device.value()->GetNpadId());
+}
+
+void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(device.value()->GetApplicationAreaSize());
}
-NFP_User::~NFP_User() = default;
+void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(availability_change_event->GetReadableEvent());
+}
+
+void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+ access_id, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->RecreateApplicationArea(access_id, data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) {
+ for (auto& device : devices) {
+ if (device->GetHandle() == handle) {
+ return device;
+ }
+ }
+ return std::nullopt;
+}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 519ff56ee..68c60ae82 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -3,14 +3,52 @@
#pragma once
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP {
+class NfpDevice;
-class NFP_User final : public Module::Interface {
+class IUser final : public ServiceFramework<IUser> {
public:
- explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_);
- ~NFP_User() override;
+ explicit IUser(Core::System& system_);
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx);
+ void Finalize(Kernel::HLERequestContext& ctx);
+ void ListDevices(Kernel::HLERequestContext& ctx);
+ void StartDetection(Kernel::HLERequestContext& ctx);
+ void StopDetection(Kernel::HLERequestContext& ctx);
+ void Mount(Kernel::HLERequestContext& ctx);
+ void Unmount(Kernel::HLERequestContext& ctx);
+ void OpenApplicationArea(Kernel::HLERequestContext& ctx);
+ void GetApplicationArea(Kernel::HLERequestContext& ctx);
+ void SetApplicationArea(Kernel::HLERequestContext& ctx);
+ void Flush(Kernel::HLERequestContext& ctx);
+ void Restore(Kernel::HLERequestContext& ctx);
+ void CreateApplicationArea(Kernel::HLERequestContext& ctx);
+ void GetTagInfo(Kernel::HLERequestContext& ctx);
+ void GetRegisterInfo(Kernel::HLERequestContext& ctx);
+ void GetCommonInfo(Kernel::HLERequestContext& ctx);
+ void GetModelInfo(Kernel::HLERequestContext& ctx);
+ void AttachActivateEvent(Kernel::HLERequestContext& ctx);
+ void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
+ void GetState(Kernel::HLERequestContext& ctx);
+ void GetDeviceState(Kernel::HLERequestContext& ctx);
+ void GetNpadId(Kernel::HLERequestContext& ctx);
+ void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
+ void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
+ void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
+
+ std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
+
+ KernelHelpers::ServiceContext service_context;
+
+ std::array<std::shared_ptr<NfpDevice>, 10> devices{};
+
+ State state{State::NonInitialized};
+ Kernel::KEvent* availability_change_event;
};
} // namespace Service::NFP
diff --git a/src/core/hle/service/nvdrv/core/container.cpp b/src/core/hle/service/nvdrv/core/container.cpp
new file mode 100644
index 000000000..37ca24f5d
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/container.cpp
@@ -0,0 +1,50 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#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 "video_core/host1x/host1x.h"
+
+namespace Service::Nvidia::NvCore {
+
+struct ContainerImpl {
+ explicit ContainerImpl(Tegra::Host1x::Host1x& host1x_)
+ : file{host1x_}, manager{host1x_}, device_file_data{} {}
+ NvMap file;
+ SyncpointManager manager;
+ Container::Host1xDeviceFileData device_file_data;
+};
+
+Container::Container(Tegra::Host1x::Host1x& host1x_) {
+ impl = std::make_unique<ContainerImpl>(host1x_);
+}
+
+Container::~Container() = default;
+
+NvMap& Container::GetNvMapFile() {
+ return impl->file;
+}
+
+const NvMap& Container::GetNvMapFile() const {
+ return impl->file;
+}
+
+Container::Host1xDeviceFileData& Container::Host1xDeviceFile() {
+ return impl->device_file_data;
+}
+
+const Container::Host1xDeviceFileData& Container::Host1xDeviceFile() const {
+ return impl->device_file_data;
+}
+
+SyncpointManager& Container::GetSyncpointManager() {
+ return impl->manager;
+}
+
+const SyncpointManager& Container::GetSyncpointManager() const {
+ return impl->manager;
+}
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/container.h b/src/core/hle/service/nvdrv/core/container.h
new file mode 100644
index 000000000..b4b63ac90
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/container.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <deque>
+#include <memory>
+#include <unordered_map>
+
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra::Host1x {
+class Host1x;
+} // namespace Tegra::Host1x
+
+namespace Service::Nvidia::NvCore {
+
+class NvMap;
+class SyncpointManager;
+
+struct ContainerImpl;
+
+class Container {
+public:
+ explicit Container(Tegra::Host1x::Host1x& host1x);
+ ~Container();
+
+ NvMap& GetNvMapFile();
+
+ const NvMap& GetNvMapFile() const;
+
+ SyncpointManager& GetSyncpointManager();
+
+ const SyncpointManager& GetSyncpointManager() const;
+
+ struct Host1xDeviceFileData {
+ std::unordered_map<DeviceFD, u32> fd_to_id{};
+ std::deque<u32> syncpts_accumulated{};
+ u32 nvdec_next_id{};
+ u32 vic_next_id{};
+ };
+
+ Host1xDeviceFileData& Host1xDeviceFile();
+
+ const Host1xDeviceFileData& Host1xDeviceFile() const;
+
+private:
+ std::unique_ptr<ContainerImpl> impl;
+};
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp
new file mode 100644
index 000000000..fbd8a74a5
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/nvmap.cpp
@@ -0,0 +1,272 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
+#include "core/memory.h"
+#include "video_core/host1x/host1x.h"
+
+using Core::Memory::YUZU_PAGESIZE;
+
+namespace Service::Nvidia::NvCore {
+NvMap::Handle::Handle(u64 size_, Id id_)
+ : size(size_), aligned_size(size), orig_size(size), id(id_) {
+ flags.raw = 0;
+}
+
+NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) {
+ std::scoped_lock lock(mutex);
+
+ // Handles cannot be allocated twice
+ if (allocated) {
+ return NvResult::AccessDenied;
+ }
+
+ flags = pFlags;
+ kind = pKind;
+ align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
+
+ // This flag is only applicable for handles with an address passed
+ if (pAddress) {
+ flags.keep_uncached_after_free.Assign(0);
+ } else {
+ LOG_CRITICAL(Service_NVDRV,
+ "Mapping nvmap handles without a CPU side address is unimplemented!");
+ }
+
+ size = Common::AlignUp(size, YUZU_PAGESIZE);
+ aligned_size = Common::AlignUp(size, align);
+ address = pAddress;
+ allocated = true;
+
+ return NvResult::Success;
+}
+
+NvResult NvMap::Handle::Duplicate(bool internal_session) {
+ std::scoped_lock lock(mutex);
+ // Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
+ if (!allocated) [[unlikely]] {
+ return NvResult::BadValue;
+ }
+
+ // If we internally use FromId the duplication tracking of handles won't work accurately due to
+ // us not implementing per-process handle refs.
+ if (internal_session) {
+ internal_dupes++;
+ } else {
+ dupes++;
+ }
+
+ return NvResult::Success;
+}
+
+NvMap::NvMap(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {}
+
+void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
+ std::scoped_lock lock(handles_lock);
+
+ handles.emplace(handle_description->id, std::move(handle_description));
+}
+
+void NvMap::UnmapHandle(Handle& handle_description) {
+ // Remove pending unmap queue entry if needed
+ if (handle_description.unmap_queue_entry) {
+ unmap_queue.erase(*handle_description.unmap_queue_entry);
+ handle_description.unmap_queue_entry.reset();
+ }
+
+ // Free and unmap the handle from the SMMU
+ host1x.MemoryManager().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
+ handle_description.aligned_size);
+ host1x.Allocator().Free(handle_description.pin_virt_address,
+ static_cast<u32>(handle_description.aligned_size));
+ handle_description.pin_virt_address = 0;
+}
+
+bool NvMap::TryRemoveHandle(const Handle& handle_description) {
+ // No dupes left, we can remove from handle map
+ if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
+ std::scoped_lock lock(handles_lock);
+
+ auto it{handles.find(handle_description.id)};
+ if (it != handles.end()) {
+ handles.erase(it);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
+ if (!size) [[unlikely]] {
+ return NvResult::BadValue;
+ }
+
+ u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
+ auto handle_description{std::make_shared<Handle>(size, id)};
+ AddHandle(handle_description);
+
+ result_out = handle_description;
+ return NvResult::Success;
+}
+
+std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
+ std::scoped_lock lock(handles_lock);
+ try {
+ return handles.at(handle);
+ } catch (std::out_of_range&) {
+ return nullptr;
+ }
+}
+
+VAddr NvMap::GetHandleAddress(Handle::Id handle) {
+ std::scoped_lock lock(handles_lock);
+ try {
+ return handles.at(handle)->address;
+ } catch (std::out_of_range&) {
+ return 0;
+ }
+}
+
+u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
+ auto handle_description{GetHandle(handle)};
+ if (!handle_description) [[unlikely]] {
+ return 0;
+ }
+
+ std::scoped_lock lock(handle_description->mutex);
+ if (!handle_description->pins) {
+ // If we're in the unmap queue we can just remove ourselves and return since we're already
+ // mapped
+ {
+ // Lock now to prevent our queue entry from being removed for allocation in-between the
+ // following check and erase
+ std::scoped_lock queueLock(unmap_queue_lock);
+ if (handle_description->unmap_queue_entry) {
+ unmap_queue.erase(*handle_description->unmap_queue_entry);
+ handle_description->unmap_queue_entry.reset();
+
+ handle_description->pins++;
+ return handle_description->pin_virt_address;
+ }
+ }
+
+ // If not then allocate some space and map it
+ u32 address{};
+ auto& smmu_allocator = host1x.Allocator();
+ auto& smmu_memory_manager = host1x.MemoryManager();
+ while (!(address =
+ smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
+ // Free handles until the allocation succeeds
+ std::scoped_lock queueLock(unmap_queue_lock);
+ if (auto freeHandleDesc{unmap_queue.front()}) {
+ // Handles in the unmap queue are guaranteed not to be pinned so don't bother
+ // checking if they are before unmapping
+ std::scoped_lock freeLock(freeHandleDesc->mutex);
+ if (handle_description->pin_virt_address)
+ UnmapHandle(*freeHandleDesc);
+ } else {
+ LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
+ }
+ }
+
+ smmu_memory_manager.Map(static_cast<GPUVAddr>(address), handle_description->address,
+ handle_description->aligned_size);
+ handle_description->pin_virt_address = address;
+ }
+
+ handle_description->pins++;
+ return handle_description->pin_virt_address;
+}
+
+void NvMap::UnpinHandle(Handle::Id handle) {
+ auto handle_description{GetHandle(handle)};
+ if (!handle_description) {
+ return;
+ }
+
+ std::scoped_lock lock(handle_description->mutex);
+ if (--handle_description->pins < 0) {
+ LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
+ } else if (!handle_description->pins) {
+ std::scoped_lock queueLock(unmap_queue_lock);
+
+ // Add to the unmap queue allowing this handle's memory to be freed if needed
+ unmap_queue.push_back(handle_description);
+ handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
+ }
+}
+
+void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
+ auto handle_description{GetHandle(handle)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
+ return;
+ }
+
+ auto result = handle_description->Duplicate(internal_session);
+ if (result != NvResult::Success) {
+ LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
+ }
+}
+
+std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
+ std::weak_ptr<Handle> hWeak{GetHandle(handle)};
+ FreeInfo freeInfo;
+
+ // We use a weak ptr here so we can tell when the handle has been freed and report that back to
+ // guest
+ if (auto handle_description = hWeak.lock()) {
+ std::scoped_lock lock(handle_description->mutex);
+
+ if (internal_session) {
+ if (--handle_description->internal_dupes < 0)
+ LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
+ } else {
+ if (--handle_description->dupes < 0) {
+ LOG_WARNING(Service_NVDRV, "User duplicate count imbalance detected!");
+ } else if (handle_description->dupes == 0) {
+ // Force unmap the handle
+ if (handle_description->pin_virt_address) {
+ std::scoped_lock queueLock(unmap_queue_lock);
+ UnmapHandle(*handle_description);
+ }
+
+ handle_description->pins = 0;
+ }
+ }
+
+ // Try to remove the shared ptr to the handle from the map, if nothing else is using the
+ // handle then it will now be freed when `handle_description` goes out of scope
+ if (TryRemoveHandle(*handle_description)) {
+ LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
+ } else {
+ LOG_DEBUG(Service_NVDRV,
+ "Tried to free nvmap handle: {} but didn't as it still has duplicates",
+ handle);
+ }
+
+ freeInfo = {
+ .address = handle_description->address,
+ .size = handle_description->size,
+ .was_uncached = handle_description->flags.map_uncached.Value() != 0,
+ };
+ } else {
+ return std::nullopt;
+ }
+
+ // Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
+ if (!hWeak.expired()) {
+ LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
+ freeInfo.address = 0;
+ }
+
+ return freeInfo;
+}
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/nvmap.h b/src/core/hle/service/nvdrv/core/nvmap.h
new file mode 100644
index 000000000..b9dd3801f
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/nvmap.h
@@ -0,0 +1,175 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <atomic>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <assert.h>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra {
+
+namespace Host1x {
+class Host1x;
+} // namespace Host1x
+
+} // namespace Tegra
+
+namespace Service::Nvidia::NvCore {
+/**
+ * @brief The nvmap core class holds the global state for nvmap and provides methods to manage
+ * handles
+ */
+class NvMap {
+public:
+ /**
+ * @brief A handle to a contiguous block of memory in an application's address space
+ */
+ struct Handle {
+ std::mutex mutex;
+
+ u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
+ u64 size; //!< Page-aligned size of the memory the handle refers to
+ u64 aligned_size; //!< `align`-aligned size of the memory the handle refers to
+ u64 orig_size; //!< Original unaligned size of the memory this handle refers to
+
+ s32 dupes{1}; //!< How many guest references there are to this handle
+ s32 internal_dupes{0}; //!< How many emulator-internal references there are to this handle
+
+ using Id = u32;
+ Id id; //!< A globally unique identifier for this handle
+
+ s32 pins{};
+ u32 pin_virt_address{};
+ std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
+
+ union Flags {
+ u32 raw;
+ BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
+ BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
+ //!< allocated with a fixed address
+ BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
+ } flags{};
+ static_assert(sizeof(Flags) == sizeof(u32));
+
+ u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to,
+ //!< this can also be in the nvdrv tmem
+ bool is_shared_mem_mapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC
+ //!< call
+
+ u8 kind{}; //!< Used for memory compression
+ bool allocated{}; //!< If the handle has been allocated with `Alloc`
+
+ u64 dma_map_addr{}; //! remove me after implementing pinning.
+
+ Handle(u64 size, Id id);
+
+ /**
+ * @brief Sets up the handle with the given memory config, can allocate memory from the tmem
+ * if a 0 address is passed
+ */
+ [[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress);
+
+ /**
+ * @brief Increases the dupe counter of the handle for the given session
+ */
+ [[nodiscard]] NvResult Duplicate(bool internal_session);
+
+ /**
+ * @brief Obtains a pointer to the handle's memory and marks the handle it as having been
+ * mapped
+ */
+ u8* GetPointer() {
+ if (!address) {
+ return nullptr;
+ }
+
+ is_shared_mem_mapped = true;
+ return reinterpret_cast<u8*>(address);
+ }
+ };
+
+ /**
+ * @brief Encapsulates the result of a FreeHandle operation
+ */
+ struct FreeInfo {
+ u64 address; //!< Address the handle referred to before deletion
+ u64 size; //!< Page-aligned handle size
+ bool was_uncached; //!< If the handle was allocated as uncached
+ };
+
+ explicit NvMap(Tegra::Host1x::Host1x& host1x);
+
+ /**
+ * @brief Creates an unallocated handle of the given size
+ */
+ [[nodiscard]] NvResult CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out);
+
+ std::shared_ptr<Handle> GetHandle(Handle::Id handle);
+
+ VAddr GetHandleAddress(Handle::Id handle);
+
+ /**
+ * @brief Maps a handle into the SMMU address space
+ * @note This operation is refcounted, the number of calls to this must eventually match the
+ * number of calls to `UnpinHandle`
+ * @return The SMMU virtual address that the handle has been mapped to
+ */
+ u32 PinHandle(Handle::Id handle);
+
+ /**
+ * @brief When this has been called an equal number of times to `PinHandle` for the supplied
+ * handle it will be added to a list of handles to be freed when necessary
+ */
+ void UnpinHandle(Handle::Id handle);
+
+ /**
+ * @brief Tries to duplicate a handle
+ */
+ void DuplicateHandle(Handle::Id handle, bool internal_session = false);
+
+ /**
+ * @brief Tries to free a handle and remove a single dupe
+ * @note If a handle has no dupes left and has no other users a FreeInfo struct will be returned
+ * describing the prior state of the handle
+ */
+ std::optional<FreeInfo> FreeHandle(Handle::Id handle, bool internal_session);
+
+private:
+ std::list<std::shared_ptr<Handle>> unmap_queue{};
+ std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue`
+
+ std::unordered_map<Handle::Id, std::shared_ptr<Handle>>
+ handles{}; //!< Main owning map of handles
+ std::mutex handles_lock; //!< Protects access to `handles`
+
+ static constexpr u32 HandleIdIncrement{
+ 4}; //!< Each new handle ID is an increment of 4 from the previous
+ std::atomic<u32> next_handle_id{HandleIdIncrement};
+ Tegra::Host1x::Host1x& host1x;
+
+ void AddHandle(std::shared_ptr<Handle> handle);
+
+ /**
+ * @brief Unmaps and frees the SMMU memory region a handle is mapped to
+ * @note Both `unmap_queue_lock` and `handle_description.mutex` MUST be locked when calling this
+ */
+ void UnmapHandle(Handle& handle_description);
+
+ /**
+ * @brief Removes a handle from the map taking its dupes into account
+ * @note handle_description.mutex MUST be locked when calling this
+ * @return If the handle was removed from the map
+ */
+ bool TryRemoveHandle(const Handle& handle_description);
+};
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
new file mode 100644
index 000000000..eda2041a0
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
@@ -0,0 +1,121 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/assert.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
+#include "video_core/host1x/host1x.h"
+
+namespace Service::Nvidia::NvCore {
+
+SyncpointManager::SyncpointManager(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {
+ constexpr u32 VBlank0SyncpointId{26};
+ constexpr u32 VBlank1SyncpointId{27};
+
+ // Reserve both vblank syncpoints as client managed as they use Continuous Mode
+ // Refer to section 14.3.5.3 of the TRM for more information on Continuous Mode
+ // https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/drm/dc.c#L660
+ ReserveSyncpoint(VBlank0SyncpointId, true);
+ ReserveSyncpoint(VBlank1SyncpointId, true);
+
+ for (u32 syncpoint_id : channel_syncpoints) {
+ if (syncpoint_id) {
+ ReserveSyncpoint(syncpoint_id, false);
+ }
+ }
+}
+
+SyncpointManager::~SyncpointManager() = default;
+
+u32 SyncpointManager::ReserveSyncpoint(u32 id, bool client_managed) {
+ if (syncpoints.at(id).reserved) {
+ ASSERT_MSG(false, "Requested syncpoint is in use");
+ return 0;
+ }
+
+ syncpoints.at(id).reserved = true;
+ syncpoints.at(id).interface_managed = client_managed;
+
+ return id;
+}
+
+u32 SyncpointManager::FindFreeSyncpoint() {
+ for (u32 i{1}; i < syncpoints.size(); i++) {
+ if (!syncpoints[i].reserved) {
+ return i;
+ }
+ }
+ ASSERT_MSG(false, "Failed to find a free syncpoint!");
+ return 0;
+}
+
+u32 SyncpointManager::AllocateSyncpoint(bool client_managed) {
+ std::lock_guard lock(reservation_lock);
+ return ReserveSyncpoint(FindFreeSyncpoint(), client_managed);
+}
+
+void SyncpointManager::FreeSyncpoint(u32 id) {
+ std::lock_guard lock(reservation_lock);
+ ASSERT(syncpoints.at(id).reserved);
+ syncpoints.at(id).reserved = false;
+}
+
+bool SyncpointManager::IsSyncpointAllocated(u32 id) {
+ return (id <= SyncpointCount) && syncpoints[id].reserved;
+}
+
+bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) const {
+ const SyncpointInfo& syncpoint{syncpoints.at(id)};
+
+ if (!syncpoint.reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ // If the interface manages counters then we don't keep track of the maximum value as it handles
+ // sanity checking the values then
+ if (syncpoint.interface_managed) {
+ return static_cast<s32>(syncpoint.counter_min - threshold) >= 0;
+ } else {
+ return (syncpoint.counter_max - threshold) >= (syncpoint.counter_min - threshold);
+ }
+}
+
+u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ return syncpoints.at(id).counter_max += amount;
+}
+
+u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ return syncpoints.at(id).counter_min;
+}
+
+u32 SyncpointManager::UpdateMin(u32 id) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return 0;
+ }
+
+ syncpoints.at(id).counter_min = host1x.GetSyncpointManager().GetHostSyncpointValue(id);
+ return syncpoints.at(id).counter_min;
+}
+
+NvFence SyncpointManager::GetSyncpointFence(u32 id) {
+ if (!syncpoints.at(id).reserved) {
+ ASSERT(false);
+ return NvFence{};
+ }
+
+ return {.id = static_cast<s32>(id), .value = syncpoints.at(id).counter_max};
+}
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/core/syncpoint_manager.h b/src/core/hle/service/nvdrv/core/syncpoint_manager.h
new file mode 100644
index 000000000..b76ef9032
--- /dev/null
+++ b/src/core/hle/service/nvdrv/core/syncpoint_manager.h
@@ -0,0 +1,134 @@
+// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <atomic>
+#include <mutex>
+
+#include "common/common_types.h"
+#include "core/hle/service/nvdrv/nvdata.h"
+
+namespace Tegra::Host1x {
+class Host1x;
+} // namespace Tegra::Host1x
+
+namespace Service::Nvidia::NvCore {
+
+enum class ChannelType : u32 {
+ MsEnc = 0,
+ VIC = 1,
+ GPU = 2,
+ NvDec = 3,
+ Display = 4,
+ NvJpg = 5,
+ TSec = 6,
+ Max = 7
+};
+
+/**
+ * @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached
+ * versions of the HW syncpoints which are intermittently synced
+ * @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
+ * @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
+ * @url
+ * https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
+ */
+class SyncpointManager final {
+public:
+ explicit SyncpointManager(Tegra::Host1x::Host1x& host1x);
+ ~SyncpointManager();
+
+ /**
+ * @brief Checks if the given syncpoint is both allocated and below the number of HW syncpoints
+ */
+ bool IsSyncpointAllocated(u32 id);
+
+ /**
+ * @brief Finds a free syncpoint and reserves it
+ * @return The ID of the reserved syncpoint
+ */
+ u32 AllocateSyncpoint(bool client_managed);
+
+ /**
+ * @url
+ * https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
+ */
+ bool HasSyncpointExpired(u32 id, u32 threshold) const;
+
+ bool IsFenceSignalled(NvFence fence) const {
+ return HasSyncpointExpired(fence.id, fence.value);
+ }
+
+ /**
+ * @brief Atomically increments the maximum value of a syncpoint by the given amount
+ * @return The new max value of the syncpoint
+ */
+ u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
+
+ /**
+ * @return The minimum value of the syncpoint
+ */
+ u32 ReadSyncpointMinValue(u32 id);
+
+ /**
+ * @brief Synchronises the minimum value of the syncpoint to with the GPU
+ * @return The new minimum value of the syncpoint
+ */
+ u32 UpdateMin(u32 id);
+
+ /**
+ * @brief Frees the usage of a syncpoint.
+ */
+ void FreeSyncpoint(u32 id);
+
+ /**
+ * @return A fence that will be signalled once this syncpoint hits its maximum value
+ */
+ NvFence GetSyncpointFence(u32 id);
+
+ static constexpr std::array<u32, static_cast<u32>(ChannelType::Max)> channel_syncpoints{
+ 0x0, // `MsEnc` is unimplemented
+ 0xC, // `VIC`
+ 0x0, // `GPU` syncpoints are allocated per-channel instead
+ 0x36, // `NvDec`
+ 0x0, // `Display` is unimplemented
+ 0x37, // `NvJpg`
+ 0x0, // `TSec` is unimplemented
+ }; //!< Maps each channel ID to a constant syncpoint
+
+private:
+ /**
+ * @note reservation_lock should be locked when calling this
+ */
+ u32 ReserveSyncpoint(u32 id, bool client_managed);
+
+ /**
+ * @return The ID of the first free syncpoint
+ */
+ u32 FindFreeSyncpoint();
+
+ struct SyncpointInfo {
+ std::atomic<u32> counter_min; //!< The least value the syncpoint can be (The value it was
+ //!< when it was last synchronized with host1x)
+ std::atomic<u32> counter_max; //!< The maximum value the syncpoint can reach according to
+ //!< the current usage
+ bool interface_managed; //!< If the syncpoint is managed by a host1x client interface, a
+ //!< client interface is a HW block that can handle host1x
+ //!< transactions on behalf of a host1x client (Which would
+ //!< otherwise need to be manually synced using PIO which is
+ //!< synchronous and requires direct cooperation of the CPU)
+ bool reserved; //!< If the syncpoint is reserved or not, not to be confused with a reserved
+ //!< value
+ };
+
+ constexpr static std::size_t SyncpointCount{192};
+ std::array<SyncpointInfo, SyncpointCount> syncpoints{};
+ std::mutex reservation_lock;
+
+ Tegra::Host1x::Host1x& host1x;
+};
+
+} // namespace Service::Nvidia::NvCore
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 696e8121e..204b0e757 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -11,6 +11,10 @@ namespace Core {
class System;
}
+namespace Kernel {
+class KEvent;
+}
+
namespace Service::Nvidia::Devices {
/// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
@@ -64,6 +68,10 @@ public:
*/
virtual void OnClose(DeviceFD fd) = 0;
+ virtual Kernel::KEvent* QueryEvent(u32 event_id) {
+ return nullptr;
+ }
+
protected:
Core::System& system;
};
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
index 604711914..4122fc98d 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
@@ -5,15 +5,16 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
#include "core/perf_stats.h"
#include "video_core/gpu.h"
namespace Service::Nvidia::Devices {
-nvdisp_disp0::nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
+nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
+ : nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
nvdisp_disp0::~nvdisp_disp0() = default;
NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -39,8 +40,9 @@ void nvdisp_disp0::OnClose(DeviceFD fd) {}
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
u32 height, u32 stride, android::BufferTransformFlags transform,
- const Common::Rectangle<int>& crop_rect) {
- const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
+ const Common::Rectangle<int>& crop_rect,
+ std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences) {
+ const VAddr addr = nvmap.GetHandleAddress(buffer_handle);
LOG_TRACE(Service,
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
addr, offset, width, height, stride, format);
@@ -48,10 +50,15 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat form
const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
stride, format, transform, crop_rect};
+ system.GPU().RequestSwapBuffers(&framebuffer, fences, num_fences);
system.GetPerfStats().EndSystemFrame();
- system.GPU().SwapBuffers(&framebuffer);
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
system.GetPerfStats().BeginSystemFrame();
}
+Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
+ LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
index 67b105e02..04217ab12 100644
--- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
+++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
@@ -11,13 +11,18 @@
#include "core/hle/service/nvflinger/buffer_transform_flags.h"
#include "core/hle/service/nvflinger/pixel_format.h"
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
namespace Service::Nvidia::Devices {
class nvmap;
class nvdisp_disp0 final : public nvdevice {
public:
- explicit nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
+ explicit nvdisp_disp0(Core::System& system_, NvCore::Container& core);
~nvdisp_disp0() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -33,10 +38,14 @@ public:
/// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height,
u32 stride, android::BufferTransformFlags transform,
- const Common::Rectangle<int>& crop_rect);
+ const Common::Rectangle<int>& crop_rect,
+ std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences);
+
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
private:
- std::shared_ptr<nvmap> nvmap_dev;
+ NvCore::Container& container;
+ NvCore::NvMap& nvmap;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index 9867a648d..6411dbf43 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -1,21 +1,30 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <cstring>
#include <utility>
+#include "common/alignment.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/core/nvmap.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
-#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
namespace Service::Nvidia::Devices {
-nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
+nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Container& core)
+ : nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{},
+ gmmu{} {}
+
nvhost_as_gpu::~nvhost_as_gpu() = default;
NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -82,12 +91,52 @@ NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>&
IoctlAllocAsEx params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
- if (params.big_page_size == 0) {
- params.big_page_size = DEFAULT_BIG_PAGE_SIZE;
+ LOG_DEBUG(Service_NVDRV, "called, big_page_size=0x{:X}", params.big_page_size);
+
+ std::scoped_lock lock(mutex);
+
+ if (vm.initialised) {
+ ASSERT_MSG(false, "Cannot initialise an address space twice!");
+ return NvResult::InvalidState;
+ }
+
+ if (params.big_page_size) {
+ if (!std::has_single_bit(params.big_page_size)) {
+ LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: 0x{:X}!", params.big_page_size);
+ return NvResult::BadValue;
+ }
+
+ if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) {
+ LOG_ERROR(Service_NVDRV, "Unsupported big page size: 0x{:X}!", params.big_page_size);
+ return NvResult::BadValue;
+ }
+
+ vm.big_page_size = params.big_page_size;
+ vm.big_page_size_bits = static_cast<u32>(std::countr_zero(params.big_page_size));
+
+ vm.va_range_start = params.big_page_size << VM::VA_START_SHIFT;
+ }
+
+ // If this is unspecified then default values should be used
+ if (params.va_range_start) {
+ vm.va_range_start = params.va_range_start;
+ vm.va_range_split = params.va_range_split;
+ vm.va_range_end = params.va_range_end;
}
- big_page_size = params.big_page_size;
+ const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
+ const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
+ vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
+
+ const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
+ const auto end_big_pages{
+ static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
+ vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
+
+ gmmu = std::make_shared<Tegra::MemoryManager>(system, 40, vm.big_page_size_bits,
+ VM::PAGE_SIZE_BITS);
+ system.GPU().InitAddressSpace(*gmmu);
+ vm.initialised = true;
return NvResult::Success;
}
@@ -99,21 +148,76 @@ NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
params.page_size, params.flags);
- const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
- if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
- params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size);
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
+ if (params.page_size != VM::YUZU_PAGESIZE && params.page_size != vm.big_page_size) {
+ return NvResult::BadValue;
+ }
+
+ if (params.page_size != vm.big_page_size &&
+ ((params.flags & MappingFlags::Sparse) != MappingFlags::None)) {
+ UNIMPLEMENTED_MSG("Sparse small pages are not implemented!");
+ return NvResult::NotImplemented;
+ }
+
+ const u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
+ : vm.big_page_size_bits};
+
+ auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
+ : *vm.big_page_allocator};
+
+ if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
+ allocator.AllocateFixed(static_cast<u32>(params.offset >> page_size_bits), params.pages);
} else {
- params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
+ params.offset = static_cast<u64>(allocator.Allocate(params.pages)) << page_size_bits;
+ if (!params.offset) {
+ ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
+ return NvResult::InsufficientMemory;
+ }
}
- auto result = NvResult::Success;
- if (!params.offset) {
- LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
- result = NvResult::InsufficientMemory;
+ u64 size{static_cast<u64>(params.pages) * params.page_size};
+
+ if ((params.flags & MappingFlags::Sparse) != MappingFlags::None) {
+ gmmu->MapSparse(params.offset, size);
}
+ allocation_map[params.offset] = {
+ .size = size,
+ .mappings{},
+ .page_size = params.page_size,
+ .sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
+ .big_pages = params.page_size != VM::YUZU_PAGESIZE,
+ };
+
std::memcpy(output.data(), &params, output.size());
- return result;
+ return NvResult::Success;
+}
+
+void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
+ auto mapping{mapping_map.at(offset)};
+
+ if (!mapping->fixed) {
+ auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+ u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+ allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
+ static_cast<u32>(mapping->size >> page_size_bits));
+ }
+
+ // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
+ // Only FreeSpace can unmap them fully
+ if (mapping->sparse_alloc) {
+ gmmu->MapSparse(offset, mapping->size, mapping->big_page);
+ } else {
+ gmmu->Unmap(offset, mapping->size);
+ }
+
+ mapping_map.erase(offset);
}
NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -123,8 +227,40 @@ NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>&
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
params.pages, params.page_size);
- system.GPU().MemoryManager().Unmap(params.offset,
- static_cast<std::size_t>(params.pages) * params.page_size);
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
+ try {
+ auto allocation{allocation_map[params.offset]};
+
+ if (allocation.page_size != params.page_size ||
+ allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
+ return NvResult::BadValue;
+ }
+
+ for (const auto& mapping : allocation.mappings) {
+ FreeMappingLocked(mapping->offset);
+ }
+
+ // Unset sparse flag if required
+ if (allocation.sparse) {
+ gmmu->Unmap(params.offset, allocation.size);
+ }
+
+ auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
+ : *vm.big_page_allocator};
+ u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
+ : vm.big_page_size_bits};
+
+ allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
+ static_cast<u32>(allocation.size >> page_size_bits));
+ allocation_map.erase(params.offset);
+ } catch (const std::out_of_range&) {
+ return NvResult::BadValue;
+ }
std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
@@ -135,35 +271,52 @@ NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& out
LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
- auto result = NvResult::Success;
std::vector<IoctlRemapEntry> entries(num_entries);
std::memcpy(entries.data(), input.data(), input.size());
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
for (const auto& entry : entries) {
- LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
- entry.offset, entry.nvmap_handle, entry.pages);
+ GPUVAddr virtual_address{static_cast<u64>(entry.as_offset_big_pages)
+ << vm.big_page_size_bits};
+ u64 size{static_cast<u64>(entry.big_pages) << vm.big_page_size_bits};
- const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
- if (!object) {
- LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
- result = NvResult::InvalidState;
- break;
+ auto alloc{allocation_map.upper_bound(virtual_address)};
+
+ if (alloc-- == allocation_map.begin() ||
+ (virtual_address - alloc->first) + size > alloc->second.size) {
+ LOG_WARNING(Service_NVDRV, "Cannot remap into an unallocated region!");
+ return NvResult::BadValue;
}
- const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
- const auto size{static_cast<u64>(entry.pages) << 0x10};
- const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
- const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)};
+ if (!alloc->second.sparse) {
+ LOG_WARNING(Service_NVDRV, "Cannot remap a non-sparse mapping!");
+ return NvResult::BadValue;
+ }
- if (!addr) {
- LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
- result = NvResult::InvalidState;
- break;
+ const bool use_big_pages = alloc->second.big_pages;
+ if (!entry.handle) {
+ gmmu->MapSparse(virtual_address, size, use_big_pages);
+ } else {
+ auto handle{nvmap.GetHandle(entry.handle)};
+ if (!handle) {
+ return NvResult::BadValue;
+ }
+
+ VAddr cpu_address{static_cast<VAddr>(
+ handle->address +
+ (static_cast<u64>(entry.handle_offset_big_pages) << vm.big_page_size_bits))};
+
+ gmmu->Map(virtual_address, cpu_address, size, use_big_pages);
}
}
std::memcpy(output.data(), entries.data(), output.size());
- return result;
+ return NvResult::Success;
}
NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -173,79 +326,98 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8
LOG_DEBUG(Service_NVDRV,
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
", offset={}",
- params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
+ params.flags, params.handle, params.buffer_offset, params.mapping_size,
params.offset);
- const auto object{nvmap_dev->GetObject(params.nvmap_handle)};
- if (!object) {
- LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
- }
-
- // The real nvservices doesn't make a distinction between handles and ids, and
- // object can only have one handle and it will be the same as its id. Assert that this is the
- // case to prevent unexpected behavior.
- ASSERT(object->id == params.nvmap_handle);
- auto& gpu = system.GPU();
+ std::scoped_lock lock(mutex);
- u64 page_size{params.page_size};
- if (!page_size) {
- page_size = object->align;
+ if (!vm.initialised) {
+ return NvResult::BadValue;
}
- if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
- if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
- const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
- const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
+ // Remaps a subregion of an existing mapping to a different PA
+ if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
+ try {
+ auto mapping{mapping_map.at(params.offset)};
- if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) {
- LOG_CRITICAL(Service_NVDRV,
- "remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
- "mapping_size = {}, offset={}",
- params.flags, params.nvmap_handle, params.buffer_offset,
- params.mapping_size, params.offset);
-
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
+ if (mapping->size < params.mapping_size) {
+ LOG_WARNING(Service_NVDRV,
+ "Cannot remap a partially mapped GPU address space region: 0x{:X}",
+ params.offset);
+ return NvResult::BadValue;
}
- std::memcpy(output.data(), &params, output.size());
- return NvResult::Success;
- } else {
- LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
+ u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
+ VAddr cpu_address{mapping->ptr + params.buffer_offset};
+
+ gmmu->Map(gpu_address, cpu_address, params.mapping_size, mapping->big_page);
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
+ return NvResult::Success;
+ } catch (const std::out_of_range&) {
+ LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: 0x{:X}",
+ params.offset);
+ return NvResult::BadValue;
}
}
- // We can only map objects that have already been assigned a CPU address.
- ASSERT(object->status == nvmap::Object::Status::Allocated);
-
- const auto physical_address{object->addr + params.buffer_offset};
- u64 size{params.mapping_size};
- if (!size) {
- size = object->size;
+ auto handle{nvmap.GetHandle(params.handle)};
+ if (!handle) {
+ return NvResult::BadValue;
}
- const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
- if (is_alloc) {
- params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size);
- } else {
- params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
- }
+ VAddr cpu_address{static_cast<VAddr>(handle->address + params.buffer_offset)};
+ u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
+
+ bool big_page{[&]() {
+ if (Common::IsAligned(handle->align, vm.big_page_size)) {
+ return true;
+ } else if (Common::IsAligned(handle->align, VM::YUZU_PAGESIZE)) {
+ return false;
+ } else {
+ ASSERT(false);
+ return false;
+ }
+ }()};
+
+ if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
+ auto alloc{allocation_map.upper_bound(params.offset)};
- auto result = NvResult::Success;
- if (!params.offset) {
- LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
- result = NvResult::InvalidState;
+ if (alloc-- == allocation_map.begin() ||
+ (params.offset - alloc->first) + size > alloc->second.size) {
+ ASSERT_MSG(false, "Cannot perform a fixed mapping into an unallocated region!");
+ return NvResult::BadValue;
+ }
+
+ const bool use_big_pages = alloc->second.big_pages && big_page;
+ gmmu->Map(params.offset, cpu_address, size, use_big_pages);
+
+ auto mapping{std::make_shared<Mapping>(cpu_address, params.offset, size, true,
+ use_big_pages, alloc->second.sparse)};
+ alloc->second.mappings.push_back(mapping);
+ mapping_map[params.offset] = mapping;
} else {
- AddBufferMap(params.offset, size, physical_address, is_alloc);
+
+ auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+ u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
+ u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+ params.offset = static_cast<u64>(allocator.Allocate(
+ static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
+ << page_size_bits;
+ if (!params.offset) {
+ ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
+ return NvResult::InsufficientMemory;
+ }
+
+ gmmu->Map(params.offset, cpu_address, Common::AlignUp(size, page_size), big_page);
+
+ auto mapping{
+ std::make_shared<Mapping>(cpu_address, params.offset, size, false, big_page, false)};
+ mapping_map[params.offset] = mapping;
}
std::memcpy(output.data(), &params, output.size());
- return result;
+ return NvResult::Success;
}
NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -254,47 +426,82 @@ NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
- if (const auto size{RemoveBufferMap(params.offset)}; size) {
- system.GPU().MemoryManager().Unmap(params.offset, *size);
- } else {
- LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
+ std::scoped_lock lock(mutex);
+
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
+
+ try {
+ auto mapping{mapping_map.at(params.offset)};
+
+ if (!mapping->fixed) {
+ auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
+ u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
+
+ allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
+ static_cast<u32>(mapping->size >> page_size_bits));
+ }
+
+ // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
+ // Only FreeSpace can unmap them fully
+ if (mapping->sparse_alloc) {
+ gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
+ } else {
+ gmmu->Unmap(params.offset, mapping->size);
+ }
+
+ mapping_map.erase(params.offset);
+ } catch (const std::out_of_range&) {
+ LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at 0x{:X}", params.offset);
}
- std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
}
NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlBindChannel params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd);
+ LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
- channel = params.fd;
+ auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd);
+ gpu_channel_device->channel_state->memory_manager = gmmu;
return NvResult::Success;
}
+void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
+ params.buf_size = 2 * sizeof(VaRegion);
+
+ params.regions = std::array<VaRegion, 2>{
+ VaRegion{
+ .offset = vm.small_page_allocator->GetVAStart() << VM::PAGE_SIZE_BITS,
+ .page_size = VM::YUZU_PAGESIZE,
+ ._pad0_{},
+ .pages = vm.small_page_allocator->GetVALimit() - vm.small_page_allocator->GetVAStart(),
+ },
+ VaRegion{
+ .offset = vm.big_page_allocator->GetVAStart() << vm.big_page_size_bits,
+ .page_size = vm.big_page_size,
+ ._pad0_{},
+ .pages = vm.big_page_allocator->GetVALimit() - vm.big_page_allocator->GetVAStart(),
+ },
+ };
+}
+
NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
- params.buf_size);
-
- params.buf_size = 0x30;
+ LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
- params.small = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = DEFAULT_SMALL_PAGE_SIZE,
- .pages = 0x3fbfff,
- };
+ std::scoped_lock lock(mutex);
- params.big = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = big_page_size,
- .pages = 0x1bffff,
- };
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
- // TODO(ogniK): This probably can stay stubbed but should add support way way later
+ GetVARegionsImpl(params);
std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
@@ -305,62 +512,27 @@ NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u
IoctlGetVaRegions params{};
std::memcpy(&params, input.data(), input.size());
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
- params.buf_size);
-
- params.buf_size = 0x30;
+ LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
+ params.buf_size);
- params.small = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = 0x1000,
- .pages = 0x3fbfff,
- };
+ std::scoped_lock lock(mutex);
- params.big = IoctlVaRegion{
- .offset = 0x04000000,
- .page_size = big_page_size,
- .pages = 0x1bffff,
- };
+ if (!vm.initialised) {
+ return NvResult::BadValue;
+ }
- // TODO(ogniK): This probably can stay stubbed but should add support way way later
+ GetVARegionsImpl(params);
std::memcpy(output.data(), &params, output.size());
- std::memcpy(inline_output.data(), &params.small, sizeof(IoctlVaRegion));
- std::memcpy(inline_output.data() + sizeof(IoctlVaRegion), &params.big, sizeof(IoctlVaRegion));
+ std::memcpy(inline_output.data(), &params.regions[0], sizeof(VaRegion));
+ std::memcpy(inline_output.data() + sizeof(VaRegion), &params.regions[1], sizeof(VaRegion));
return NvResult::Success;
}
-std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
- const auto end{buffer_mappings.upper_bound(gpu_addr)};
- for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
- if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
- return iter->second;
- }
- }
-
- return std::nullopt;
-}
-
-void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
- bool is_allocated) {
- buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
-}
-
-std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
- if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
- std::size_t size{};
-
- if (iter->second.IsAllocated()) {
- size = iter->second.Size();
- }
-
- buffer_mappings.erase(iter);
-
- return size;
- }
-
- return std::nullopt;
+Kernel::KEvent* nvhost_as_gpu::QueryEvent(u32 event_id) {
+ LOG_CRITICAL(Service_NVDRV, "Unknown AS GPU Event {}", event_id);
+ return nullptr;
}
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
index 555843a6f..86fe71c75 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h
@@ -1,35 +1,50 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
+#include <bit>
+#include <list>
#include <map>
#include <memory>
+#include <mutex>
#include <optional>
#include <vector>
+#include "common/address_space.h"
#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::Devices {
+namespace Tegra {
+class MemoryManager;
+} // namespace Tegra
+
+namespace Service::Nvidia {
+class Module;
+}
-constexpr u32 DEFAULT_BIG_PAGE_SIZE = 1 << 16;
-constexpr u32 DEFAULT_SMALL_PAGE_SIZE = 1 << 12;
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
-class nvmap;
+namespace Service::Nvidia::Devices {
-enum class AddressSpaceFlags : u32 {
- None = 0x0,
- FixedOffset = 0x1,
- Remap = 0x100,
+enum class MappingFlags : u32 {
+ None = 0,
+ Fixed = 1 << 0,
+ Sparse = 1 << 1,
+ Remap = 1 << 8,
};
-DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
+DECLARE_ENUM_FLAG_OPERATORS(MappingFlags);
class nvhost_as_gpu final : public nvdevice {
public:
- explicit nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
+ explicit nvhost_as_gpu(Core::System& system_, Module& module, NvCore::Container& core);
~nvhost_as_gpu() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -42,46 +57,17 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
-private:
- class BufferMap final {
- public:
- constexpr BufferMap() = default;
-
- constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
- : start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
-
- constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
- bool is_allocated_)
- : start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_},
- is_allocated{is_allocated_} {}
-
- constexpr VAddr StartAddr() const {
- return start_addr;
- }
-
- constexpr VAddr EndAddr() const {
- return end_addr;
- }
-
- constexpr std::size_t Size() const {
- return end_addr - start_addr;
- }
-
- constexpr VAddr CpuAddr() const {
- return cpu_addr;
- }
-
- constexpr bool IsAllocated() const {
- return is_allocated;
- }
-
- private:
- GPUVAddr start_addr{};
- GPUVAddr end_addr{};
- VAddr cpu_addr{};
- bool is_allocated{};
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+ struct VaRegion {
+ u64 offset;
+ u32 page_size;
+ u32 _pad0_;
+ u64 pages;
};
+ static_assert(sizeof(VaRegion) == 0x18);
+private:
struct IoctlAllocAsEx {
u32_le flags{}; // usually passes 1
s32_le as_fd{}; // ignored; passes 0
@@ -96,7 +82,7 @@ private:
struct IoctlAllocSpace {
u32_le pages{};
u32_le page_size{};
- AddressSpaceFlags flags{};
+ MappingFlags flags{};
INSERT_PADDING_WORDS(1);
union {
u64_le offset;
@@ -113,19 +99,19 @@ private:
static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
struct IoctlRemapEntry {
- u16_le flags{};
- u16_le kind{};
- u32_le nvmap_handle{};
- u32_le map_offset{};
- u32_le offset{};
- u32_le pages{};
+ u16 flags;
+ u16 kind;
+ NvCore::NvMap::Handle::Id handle;
+ u32 handle_offset_big_pages;
+ u32 as_offset_big_pages;
+ u32 big_pages;
};
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
struct IoctlMapBufferEx {
- AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
- u32_le kind{}; // -1 is default
- u32_le nvmap_handle{};
+ MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
+ u32_le kind{}; // -1 is default
+ NvCore::NvMap::Handle::Id handle;
u32_le page_size{}; // 0 means don't care
s64_le buffer_offset{};
u64_le mapping_size{};
@@ -143,27 +129,15 @@ private:
};
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
- struct IoctlVaRegion {
- u64_le offset{};
- u32_le page_size{};
- INSERT_PADDING_WORDS(1);
- u64_le pages{};
- };
- static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
-
struct IoctlGetVaRegions {
u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
u32_le reserved{};
- IoctlVaRegion small{};
- IoctlVaRegion big{};
+ std::array<VaRegion, 2> regions{};
};
- static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
+ static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2,
"IoctlGetVaRegions is incorrect size");
- s32 channel{};
- u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
-
NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
@@ -172,18 +146,75 @@ private:
NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
+ void GetVARegionsImpl(IoctlGetVaRegions& params);
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output);
- std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
- void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
- std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
+ void FreeMappingLocked(u64 offset);
+
+ Module& module;
+
+ NvCore::Container& container;
+ NvCore::NvMap& nvmap;
- std::shared_ptr<nvmap> nvmap_dev;
+ struct Mapping {
+ VAddr ptr;
+ u64 offset;
+ u64 size;
+ bool fixed;
+ bool big_page; // Only valid if fixed == false
+ bool sparse_alloc;
+
+ Mapping(VAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
+ : ptr(ptr_), offset(offset_), size(size_), fixed(fixed_), big_page(big_page_),
+ sparse_alloc(sparse_alloc_) {}
+ };
+
+ struct Allocation {
+ u64 size;
+ std::list<std::shared_ptr<Mapping>> mappings;
+ u32 page_size;
+ bool sparse;
+ bool big_pages;
+ };
- // This is expected to be ordered, therefore we must use a map, not unordered_map
- std::map<GPUVAddr, BufferMap> buffer_mappings;
+ std::map<u64, std::shared_ptr<Mapping>>
+ mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
+ //!< mapping type, this is needed as what was originally a single buffer may
+ //!< have been split into multiple GPU side buffers with the remap flag.
+ std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
+ //!< which fixed buffers can be mapped into
+ std::mutex mutex; //!< Locks all AS operations
+
+ struct VM {
+ static constexpr u32 YUZU_PAGESIZE{0x1000};
+ static constexpr u32 PAGE_SIZE_BITS{std::countr_zero(YUZU_PAGESIZE)};
+
+ static constexpr u32 SUPPORTED_BIG_PAGE_SIZES{0x30000};
+ static constexpr u32 DEFAULT_BIG_PAGE_SIZE{0x20000};
+ u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
+ u32 big_page_size_bits{std::countr_zero(DEFAULT_BIG_PAGE_SIZE)};
+
+ static constexpr u32 VA_START_SHIFT{10};
+ static constexpr u64 DEFAULT_VA_SPLIT{1ULL << 34};
+ static constexpr u64 DEFAULT_VA_RANGE{1ULL << 37};
+ u64 va_range_start{DEFAULT_BIG_PAGE_SIZE << VA_START_SHIFT};
+ u64 va_range_split{DEFAULT_VA_SPLIT};
+ u64 va_range_end{DEFAULT_VA_RANGE};
+
+ using Allocator = Common::FlatAllocator<u32, 0, 32>;
+
+ std::unique_ptr<Allocator> big_page_allocator;
+ std::shared_ptr<Allocator>
+ small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
+
+ bool initialised{};
+ } vm;
+ std::shared_ptr<Tegra::MemoryManager> gmmu;
+
+ // s32 channel{};
+ // u32 big_page_size{VM::DEFAULT_BIG_PAGE_SIZE};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
index 527531f29..5bee4a3d3 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp
@@ -1,24 +1,39 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
+#include <bit>
#include <cstdlib>
#include <cstring>
+#include <fmt/format.h>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/scope_exit.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_writable_event.h"
+#include "core/hle/service/nvdrv/core/container.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
namespace Service::Nvidia::Devices {
nvhost_ctrl::nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
- SyncpointManager& syncpoint_manager_)
- : nvdevice{system_}, events_interface{events_interface_}, syncpoint_manager{
- syncpoint_manager_} {}
-nvhost_ctrl::~nvhost_ctrl() = default;
+ NvCore::Container& core_)
+ : nvdevice{system_}, events_interface{events_interface_}, core{core_},
+ syncpoint_manager{core_.GetSyncpointManager()} {}
+
+nvhost_ctrl::~nvhost_ctrl() {
+ for (auto& event : events) {
+ if (!event.registered) {
+ continue;
+ }
+ events_interface.FreeEvent(event.kevent);
+ }
+}
NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) {
@@ -30,13 +45,15 @@ NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
case 0x1c:
return IocCtrlClearEventWait(input, output);
case 0x1d:
- return IocCtrlEventWait(input, output, false);
- case 0x1e:
return IocCtrlEventWait(input, output, true);
+ case 0x1e:
+ return IocCtrlEventWait(input, output, false);
case 0x1f:
return IocCtrlEventRegister(input, output);
case 0x20:
return IocCtrlEventUnregister(input, output);
+ case 0x21:
+ return IocCtrlEventUnregisterBatch(input, output);
}
break;
default:
@@ -60,6 +77,7 @@ NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
}
void nvhost_ctrl::OnOpen(DeviceFD fd) {}
+
void nvhost_ctrl::OnClose(DeviceFD fd) {}
NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
@@ -71,116 +89,167 @@ NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector
}
NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
- bool is_async) {
+ bool is_allocation) {
IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
- params.syncpt_id, params.threshold, params.timeout, is_async);
+ LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
+ params.fence.id, params.fence.value, params.timeout, is_allocation);
- if (params.syncpt_id >= MaxSyncPoints) {
- return NvResult::BadParameter;
- }
+ bool must_unmark_fail = !is_allocation;
+ const u32 event_id = params.value.raw;
+ SCOPE_EXIT({
+ std::memcpy(output.data(), &params, sizeof(params));
+ if (must_unmark_fail) {
+ events[event_id].fails = 0;
+ }
+ });
- u32 event_id = params.value & 0x00FF;
+ const u32 fence_id = static_cast<u32>(params.fence.id);
- if (event_id >= MaxNvEvents) {
- std::memcpy(output.data(), &params, sizeof(params));
+ if (fence_id >= MaxSyncPoints) {
return NvResult::BadParameter;
}
- if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
- params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id);
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+ if (params.fence.value == 0) {
+ if (!syncpoint_manager.IsSyncpointAllocated(params.fence.id)) {
+ LOG_WARNING(Service_NVDRV,
+ "Unallocated syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
+ params.fence.id, params.fence.value, params.timeout, is_allocation);
+ } else {
+ params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
+ }
return NvResult::Success;
}
- if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id);
- syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
- params.value = new_value;
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+ if (syncpoint_manager.IsFenceSignalled(params.fence)) {
+ params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
return NvResult::Success;
}
- auto& event = events_interface.events[event_id];
- auto& gpu = system.GPU();
-
- // This is mostly to take into account unimplemented features. As synced
- // gpu is always synced.
- if (!gpu.IsAsync()) {
- event.event->GetWritableEvent().Signal();
- return NvResult::Success;
- }
- const u32 current_syncpoint_value = event.fence.value;
- const s32 diff = current_syncpoint_value - params.threshold;
- if (diff >= 0) {
- event.event->GetWritableEvent().Signal();
- params.value = current_syncpoint_value;
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+ if (const auto new_value = syncpoint_manager.UpdateMin(fence_id);
+ syncpoint_manager.IsFenceSignalled(params.fence)) {
+ params.value.raw = new_value;
return NvResult::Success;
}
- const u32 target_value = current_syncpoint_value - diff;
- if (!is_async) {
- params.value = 0;
+ auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
+ const u32 target_value = params.fence.value;
+
+ auto lock = NvEventsLock();
+
+ u32 slot = [&]() {
+ if (is_allocation) {
+ params.value.raw = 0;
+ return FindFreeNvEvent(fence_id);
+ } else {
+ return params.value.raw;
+ }
+ }();
+
+ must_unmark_fail = false;
+
+ const auto check_failing = [&]() {
+ if (events[slot].fails > 2) {
+ {
+ auto lk = system.StallProcesses();
+ host1x_syncpoint_manager.WaitHost(fence_id, target_value);
+ system.UnstallProcesses();
+ }
+ params.value.raw = target_value;
+ return true;
+ }
+ return false;
+ };
+
+ if (slot >= MaxNvEvents) {
+ return NvResult::BadParameter;
}
if (params.timeout == 0) {
- std::memcpy(output.data(), &params, sizeof(params));
+ if (check_failing()) {
+ events[slot].fails = 0;
+ return NvResult::Success;
+ }
return NvResult::Timeout;
}
- EventState status = events_interface.status[event_id];
- const bool bad_parameter = status == EventState::Busy;
- if (bad_parameter) {
- std::memcpy(output.data(), &params, sizeof(params));
+ auto& event = events[slot];
+
+ if (!event.registered) {
return NvResult::BadParameter;
}
- events_interface.SetEventStatus(event_id, EventState::Waiting);
- events_interface.assigned_syncpt[event_id] = params.syncpt_id;
- events_interface.assigned_value[event_id] = target_value;
- if (is_async) {
- params.value = params.syncpt_id << 4;
- } else {
- params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
- }
- params.value |= event_id;
- event.event->GetWritableEvent().Clear();
- if (events_interface.failed[event_id]) {
- {
- auto lk = system.StallProcesses();
- gpu.WaitFence(params.syncpt_id, target_value);
- system.UnstallProcesses();
- }
- std::memcpy(output.data(), &params, sizeof(params));
- events_interface.failed[event_id] = false;
+
+ if (event.IsBeingUsed()) {
+ return NvResult::BadParameter;
+ }
+
+ if (check_failing()) {
+ event.fails = 0;
return NvResult::Success;
}
- gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
- std::memcpy(output.data(), &params, sizeof(params));
+
+ params.value.raw = 0;
+
+ event.status.store(EventState::Waiting, std::memory_order_release);
+ event.assigned_syncpt = fence_id;
+ event.assigned_value = target_value;
+ if (is_allocation) {
+ params.value.syncpoint_id_for_allocation.Assign(static_cast<u16>(fence_id));
+ params.value.event_allocated.Assign(1);
+ } else {
+ params.value.syncpoint_id.Assign(fence_id);
+ }
+ params.value.raw |= slot;
+
+ event.wait_handle =
+ host1x_syncpoint_manager.RegisterHostAction(fence_id, target_value, [this, slot]() {
+ auto& event_ = events[slot];
+ if (event_.status.exchange(EventState::Signalling, std::memory_order_acq_rel) ==
+ EventState::Waiting) {
+ event_.kevent->GetWritableEvent().Signal();
+ }
+ event_.status.store(EventState::Signalled, std::memory_order_release);
+ });
return NvResult::Timeout;
}
+NvResult nvhost_ctrl::FreeEvent(u32 slot) {
+ if (slot >= MaxNvEvents) {
+ return NvResult::BadParameter;
+ }
+
+ auto& event = events[slot];
+
+ if (!event.registered) {
+ return NvResult::Success;
+ }
+
+ if (event.IsBeingUsed()) {
+ return NvResult::Busy;
+ }
+
+ FreeNvEvent(slot);
+ return NvResult::Success;
+}
+
NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
IocCtrlEventRegisterParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- const u32 event_id = params.user_event_id & 0x00FF;
+ const u32 event_id = params.user_event_id;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
- if (events_interface.registered[event_id]) {
- const auto event_state = events_interface.status[event_id];
- if (event_state != EventState::Free) {
- LOG_WARNING(Service_NVDRV, "Event already registered! Unregistering previous event");
- events_interface.UnregisterEvent(event_id);
- } else {
- return NvResult::BadParameter;
+
+ auto lock = NvEventsLock();
+
+ if (events[event_id].registered) {
+ const auto result = FreeEvent(event_id);
+ if (result != NvResult::Success) {
+ return result;
}
}
- events_interface.RegisterEvent(event_id);
+ CreateNvEvent(event_id);
return NvResult::Success;
}
@@ -190,34 +259,142 @@ NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input,
std::memcpy(&params, input.data(), sizeof(params));
const u32 event_id = params.user_event_id & 0x00FF;
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
- if (event_id >= MaxNvEvents) {
- return NvResult::BadParameter;
- }
- if (!events_interface.registered[event_id]) {
- return NvResult::BadParameter;
+
+ auto lock = NvEventsLock();
+ return FreeEvent(event_id);
+}
+
+NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(const std::vector<u8>& input,
+ std::vector<u8>& output) {
+ IocCtrlEventUnregisterBatchParams params{};
+ std::memcpy(&params, input.data(), sizeof(params));
+ u64 event_mask = params.user_events;
+ LOG_DEBUG(Service_NVDRV, " called, event_mask: {:X}", event_mask);
+
+ auto lock = NvEventsLock();
+ while (event_mask != 0) {
+ const u64 event_id = std::countr_zero(event_mask);
+ event_mask &= ~(1ULL << event_id);
+ const auto result = FreeEvent(static_cast<u32>(event_id));
+ if (result != NvResult::Success) {
+ return result;
+ }
}
- events_interface.UnregisterEvent(event_id);
return NvResult::Success;
}
NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
- IocCtrlEventSignalParams params{};
+ IocCtrlEventClearParams params{};
std::memcpy(&params, input.data(), sizeof(params));
- u32 event_id = params.event_id & 0x00FF;
- LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id);
+ u32 event_id = params.event_id.slot;
+ LOG_DEBUG(Service_NVDRV, "called, event_id: {:X}", event_id);
if (event_id >= MaxNvEvents) {
return NvResult::BadParameter;
}
- if (events_interface.status[event_id] == EventState::Waiting) {
- events_interface.LiberateEvent(event_id);
- }
- events_interface.failed[event_id] = true;
- syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id);
+ auto lock = NvEventsLock();
+
+ auto& event = events[event_id];
+ if (event.status.exchange(EventState::Cancelling, std::memory_order_acq_rel) ==
+ EventState::Waiting) {
+ auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
+ host1x_syncpoint_manager.DeregisterHostAction(event.assigned_syncpt, event.wait_handle);
+ syncpoint_manager.UpdateMin(event.assigned_syncpt);
+ event.wait_handle = {};
+ }
+ event.fails++;
+ event.status.store(EventState::Cancelled, std::memory_order_release);
+ event.kevent->GetWritableEvent().Clear();
return NvResult::Success;
}
+Kernel::KEvent* nvhost_ctrl::QueryEvent(u32 event_id) {
+ const auto desired_event = SyncpointEventValue{.raw = event_id};
+
+ const bool allocated = desired_event.event_allocated.Value() != 0;
+ const u32 slot{allocated ? desired_event.partial_slot.Value()
+ : static_cast<u32>(desired_event.slot)};
+ if (slot >= MaxNvEvents) {
+ ASSERT(false);
+ return nullptr;
+ }
+
+ const u32 syncpoint_id{allocated ? desired_event.syncpoint_id_for_allocation.Value()
+ : desired_event.syncpoint_id.Value()};
+
+ auto lock = NvEventsLock();
+
+ auto& event = events[slot];
+ if (event.registered && event.assigned_syncpt == syncpoint_id) {
+ ASSERT(event.kevent);
+ return event.kevent;
+ }
+ // Is this possible in hardware?
+ ASSERT_MSG(false, "Slot:{}, SyncpointID:{}, requested", slot, syncpoint_id);
+ return nullptr;
+}
+
+std::unique_lock<std::mutex> nvhost_ctrl::NvEventsLock() {
+ return std::unique_lock<std::mutex>(events_mutex);
+}
+
+void nvhost_ctrl::CreateNvEvent(u32 event_id) {
+ auto& event = events[event_id];
+ ASSERT(!event.kevent);
+ ASSERT(!event.registered);
+ ASSERT(!event.IsBeingUsed());
+ event.kevent = events_interface.CreateEvent(fmt::format("NVCTRL::NvEvent_{}", event_id));
+ event.status = EventState::Available;
+ event.registered = true;
+ const u64 mask = 1ULL << event_id;
+ event.fails = 0;
+ events_mask |= mask;
+ event.assigned_syncpt = 0;
+}
+
+void nvhost_ctrl::FreeNvEvent(u32 event_id) {
+ auto& event = events[event_id];
+ ASSERT(event.kevent);
+ ASSERT(event.registered);
+ ASSERT(!event.IsBeingUsed());
+ events_interface.FreeEvent(event.kevent);
+ event.kevent = nullptr;
+ event.status = EventState::Available;
+ event.registered = false;
+ const u64 mask = ~(1ULL << event_id);
+ events_mask &= mask;
+}
+
+u32 nvhost_ctrl::FindFreeNvEvent(u32 syncpoint_id) {
+ u32 slot{MaxNvEvents};
+ u32 free_slot{MaxNvEvents};
+ for (u32 i = 0; i < MaxNvEvents; i++) {
+ auto& event = events[i];
+ if (event.registered) {
+ if (!event.IsBeingUsed()) {
+ slot = i;
+ if (event.assigned_syncpt == syncpoint_id) {
+ return slot;
+ }
+ }
+ } else if (free_slot == MaxNvEvents) {
+ free_slot = i;
+ }
+ }
+ if (free_slot < MaxNvEvents) {
+ CreateNvEvent(free_slot);
+ return free_slot;
+ }
+
+ if (slot < MaxNvEvents) {
+ return slot;
+ }
+
+ LOG_CRITICAL(Service_NVDRV, "Failed to allocate an event");
+ return 0;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
index 4fbb89b15..0b56d7070 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h
@@ -1,20 +1,28 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// 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 <vector>
+#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
#include "core/hle/service/nvdrv/nvdrv.h"
+#include "video_core/host1x/syncpoint_manager.h"
+
+namespace Service::Nvidia::NvCore {
+class Container;
+class SyncpointManager;
+} // namespace Service::Nvidia::NvCore
namespace Service::Nvidia::Devices {
class nvhost_ctrl final : public nvdevice {
public:
explicit nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
- SyncpointManager& syncpoint_manager_);
+ NvCore::Container& core);
~nvhost_ctrl() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -27,7 +35,70 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+ union SyncpointEventValue {
+ u32 raw;
+
+ union {
+ BitField<0, 4, u32> partial_slot;
+ BitField<4, 28, u32> syncpoint_id;
+ };
+
+ struct {
+ u16 slot;
+ union {
+ BitField<0, 12, u16> syncpoint_id_for_allocation;
+ BitField<12, 1, u16> event_allocated;
+ };
+ };
+ };
+ static_assert(sizeof(SyncpointEventValue) == sizeof(u32));
+
private:
+ struct InternalEvent {
+ // Mask representing registered events
+
+ // Each kernel event associated to an NV event
+ Kernel::KEvent* kevent{};
+ // The status of the current NVEvent
+ std::atomic<EventState> status{};
+
+ // Tells the NVEvent that it has failed.
+ u32 fails{};
+ // When an NVEvent is waiting on GPU interrupt, this is the sync_point
+ // associated with it.
+ u32 assigned_syncpt{};
+ // This is the value of the GPU interrupt for which the NVEvent is waiting
+ // for.
+ u32 assigned_value{};
+
+ // Tells if an NVEvent is registered or not
+ bool registered{};
+
+ // Used for waiting on a syncpoint & canceling it.
+ Tegra::Host1x::SyncpointManager::ActionHandle wait_handle{};
+
+ bool IsBeingUsed() const {
+ const auto current_status = status.load(std::memory_order_acquire);
+ return current_status == EventState::Waiting ||
+ current_status == EventState::Cancelling ||
+ current_status == EventState::Signalling;
+ }
+ };
+
+ std::unique_lock<std::mutex> NvEventsLock();
+
+ void CreateNvEvent(u32 event_id);
+
+ void FreeNvEvent(u32 event_id);
+
+ u32 FindFreeNvEvent(u32 syncpoint_id);
+
+ std::array<InternalEvent, MaxNvEvents> events{};
+ std::mutex events_mutex;
+ u64 events_mask{};
+
struct IocSyncptReadParams {
u32_le id{};
u32_le value{};
@@ -83,27 +154,18 @@ private:
};
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
- struct IocCtrlEventSignalParams {
- u32_le event_id{};
+ struct IocCtrlEventClearParams {
+ SyncpointEventValue event_id{};
};
- static_assert(sizeof(IocCtrlEventSignalParams) == 4,
- "IocCtrlEventSignalParams is incorrect size");
+ static_assert(sizeof(IocCtrlEventClearParams) == 4,
+ "IocCtrlEventClearParams is incorrect size");
struct IocCtrlEventWaitParams {
- u32_le syncpt_id{};
- u32_le threshold{};
- s32_le timeout{};
- u32_le value{};
- };
- static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
-
- struct IocCtrlEventWaitAsyncParams {
- u32_le syncpt_id{};
- u32_le threshold{};
+ NvFence fence{};
u32_le timeout{};
- u32_le value{};
+ SyncpointEventValue value{};
};
- static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
+ static_assert(sizeof(IocCtrlEventWaitParams) == 16,
"IocCtrlEventWaitAsyncParams is incorrect size");
struct IocCtrlEventRegisterParams {
@@ -118,19 +180,25 @@ private:
static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
"IocCtrlEventUnregisterParams is incorrect size");
- struct IocCtrlEventKill {
+ struct IocCtrlEventUnregisterBatchParams {
u64_le user_events{};
};
- static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
+ static_assert(sizeof(IocCtrlEventUnregisterBatchParams) == 8,
+ "IocCtrlEventKill is incorrect size");
NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
- NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
+ NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
+ bool is_allocation);
NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult IocCtrlEventUnregisterBatch(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
+ NvResult FreeEvent(u32 slot);
+
EventInterface& events_interface;
- SyncpointManager& syncpoint_manager;
+ NvCore::Container& core;
+ NvCore::SyncpointManager& syncpoint_manager;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
index 2b3b7efea..ced57dfe6 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp
@@ -7,11 +7,19 @@
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
namespace Service::Nvidia::Devices {
-nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_) : nvdevice{system_} {}
-nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
+nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_)
+ : nvdevice{system_}, events_interface{events_interface_} {
+ error_notifier_event = events_interface.CreateEvent("CtrlGpuErrorNotifier");
+ unknown_event = events_interface.CreateEvent("CtrlGpuUknownEvent");
+}
+nvhost_ctrl_gpu::~nvhost_ctrl_gpu() {
+ events_interface.FreeEvent(error_notifier_event);
+ events_interface.FreeEvent(unknown_event);
+}
NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) {
@@ -286,4 +294,17 @@ NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u
return NvResult::Success;
}
+Kernel::KEvent* nvhost_ctrl_gpu::QueryEvent(u32 event_id) {
+ switch (event_id) {
+ case 1:
+ return error_notifier_event;
+ case 2:
+ return unknown_event;
+ default: {
+ LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
+ }
+ }
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
index 97e9a90cb..1e8f254e2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h
@@ -10,11 +10,15 @@
#include "common/swap.h"
#include "core/hle/service/nvdrv/devices/nvdevice.h"
+namespace Service::Nvidia {
+class EventInterface;
+}
+
namespace Service::Nvidia::Devices {
class nvhost_ctrl_gpu final : public nvdevice {
public:
- explicit nvhost_ctrl_gpu(Core::System& system_);
+ explicit nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_);
~nvhost_ctrl_gpu() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -27,6 +31,8 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
private:
struct IoctlGpuCharacteristics {
u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
@@ -160,6 +166,12 @@ private:
NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
+
+ EventInterface& events_interface;
+
+ // Events
+ Kernel::KEvent* error_notifier_event;
+ Kernel::KEvent* unknown_event;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index b98e63011..45a759fa8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -5,29 +5,46 @@
#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/core/nvmap.h"
+#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
+#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/memory.h"
+#include "video_core/control/channel_state.h"
+#include "video_core/engines/puller.h"
#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
namespace Service::Nvidia::Devices {
namespace {
-Tegra::CommandHeader BuildFenceAction(Tegra::GPU::FenceOperation op, u32 syncpoint_id) {
- Tegra::GPU::FenceAction result{};
+Tegra::CommandHeader BuildFenceAction(Tegra::Engines::Puller::FenceOperation op, u32 syncpoint_id) {
+ Tegra::Engines::Puller::FenceAction result{};
result.op.Assign(op);
result.syncpoint_id.Assign(syncpoint_id);
return {result.raw};
}
} // namespace
-nvhost_gpu::nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {
- channel_fence.id = syncpoint_manager_.AllocateSyncpoint();
- channel_fence.value = system_.GPU().GetSyncpointValue(channel_fence.id);
+nvhost_gpu::nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
+ NvCore::Container& core_)
+ : nvdevice{system_}, events_interface{events_interface_}, core{core_},
+ syncpoint_manager{core_.GetSyncpointManager()}, nvmap{core.GetNvMapFile()},
+ channel_state{system.GPU().AllocateChannel()} {
+ channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
+ sm_exception_breakpoint_int_report_event =
+ events_interface.CreateEvent("GpuChannelSMExceptionBreakpointInt");
+ sm_exception_breakpoint_pause_report_event =
+ events_interface.CreateEvent("GpuChannelSMExceptionBreakpointPause");
+ error_notifier_event = events_interface.CreateEvent("GpuChannelErrorNotifier");
}
-nvhost_gpu::~nvhost_gpu() = default;
+nvhost_gpu::~nvhost_gpu() {
+ events_interface.FreeEvent(sm_exception_breakpoint_int_report_event);
+ events_interface.FreeEvent(sm_exception_breakpoint_pause_report_event);
+ events_interface.FreeEvent(error_notifier_event);
+ syncpoint_manager.FreeSyncpoint(channel_syncpoint);
+}
NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output) {
@@ -167,9 +184,14 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
params.unk3);
- channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
+ if (channel_state->initialized) {
+ LOG_CRITICAL(Service_NVDRV, "Already allocated!");
+ return NvResult::AlreadyAllocated;
+ }
+
+ system.GPU().InitChannel(*channel_state);
- params.fence_out = channel_fence;
+ params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint);
std::memcpy(output.data(), &params, output.size());
return NvResult::Success;
@@ -188,39 +210,37 @@ NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::ve
static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) {
return {
- Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
Tegra::SubmissionMode::Increasing),
{fence.value},
- Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
Tegra::SubmissionMode::Increasing),
- BuildFenceAction(Tegra::GPU::FenceOperation::Acquire, fence.id),
+ BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Acquire, fence.id),
};
}
-static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence,
- u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence) {
std::vector<Tegra::CommandHeader> result{
- Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
Tegra::SubmissionMode::Increasing),
{}};
- for (u32 count = 0; count < add_increment; ++count) {
- result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
+ for (u32 count = 0; count < 2; ++count) {
+ result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
Tegra::SubmissionMode::Increasing));
- result.emplace_back(BuildFenceAction(Tegra::GPU::FenceOperation::Increment, fence.id));
+ result.emplace_back(
+ BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id));
}
return result;
}
-static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence,
- u32 add_increment) {
+static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence) {
std::vector<Tegra::CommandHeader> result{
- Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
+ Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1,
Tegra::SubmissionMode::Increasing),
{}};
- const std::vector<Tegra::CommandHeader> increment{
- BuildIncrementCommandList(fence, add_increment)};
+ const std::vector<Tegra::CommandHeader> increment{BuildIncrementCommandList(fence)};
result.insert(result.end(), increment.begin(), increment.end());
@@ -234,33 +254,41 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>
auto& gpu = system.GPU();
- params.fence_out.id = channel_fence.id;
+ std::scoped_lock lock(channel_mutex);
- if (params.flags.add_wait.Value() &&
- !syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) {
- gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)});
- }
+ const auto bind_id = channel_state->bind_id;
- if (params.flags.add_increment.Value() || params.flags.increment.Value()) {
- const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0;
- params.fence_out.value = syncpoint_manager.IncreaseSyncpoint(
- params.fence_out.id, params.AddIncrementValue() + increment_value);
- } else {
- params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id);
+ auto& flags = params.flags;
+
+ if (flags.fence_wait.Value()) {
+ if (flags.increment_value.Value()) {
+ return NvResult::BadParameter;
+ }
+
+ if (!syncpoint_manager.IsFenceSignalled(params.fence)) {
+ gpu.PushGPUEntries(bind_id, Tegra::CommandList{BuildWaitCommandList(params.fence)});
+ }
}
- gpu.PushGPUEntries(std::move(entries));
+ params.fence.id = channel_syncpoint;
+
+ u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) +
+ (flags.increment_value.Value() != 0 ? params.fence.value : 0)};
+ params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment);
+ gpu.PushGPUEntries(bind_id, std::move(entries));
- if (params.flags.add_increment.Value()) {
- if (params.flags.suppress_wfi) {
- gpu.PushGPUEntries(Tegra::CommandList{
- BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())});
+ if (flags.fence_increment.Value()) {
+ if (flags.suppress_wfi.Value()) {
+ gpu.PushGPUEntries(bind_id,
+ Tegra::CommandList{BuildIncrementCommandList(params.fence)});
} else {
- gpu.PushGPUEntries(Tegra::CommandList{
- BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())});
+ gpu.PushGPUEntries(bind_id,
+ Tegra::CommandList{BuildIncrementWithWfiCommandList(params.fence)});
}
}
+ flags.raw = 0;
+
std::memcpy(output.data(), &params, sizeof(IoctlSubmitGpfifo));
return NvResult::Success;
}
@@ -328,4 +356,19 @@ NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vect
return NvResult::Success;
}
+Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {
+ switch (event_id) {
+ case 1:
+ return sm_exception_breakpoint_int_report_event;
+ case 2:
+ return sm_exception_breakpoint_pause_report_event;
+ case 3:
+ return error_notifier_event;
+ default: {
+ LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
+ }
+ }
+ return nullptr;
+}
+
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
index 8a9f7775a..1e4ecd55b 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h
@@ -13,17 +13,31 @@
#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_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
+ NvCore::Container& core);
~nvhost_gpu() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -36,7 +50,10 @@ public:
void OnOpen(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,
@@ -146,17 +163,13 @@ private:
u32_le num_entries{}; // number of fence objects being submitted
union {
u32_le raw;
- BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
- BitField<1, 1, u32_le> add_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; // increment the returned fence
+ 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_out{}; // returned new fence object for others to wait on
-
- u32 AddIncrementValue() const {
- return flags.add_increment.Value() << 1;
- }
+ NvFence fence{}; // returned new fence object for others to wait on
};
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
"IoctlSubmitGpfifo is incorrect size");
@@ -191,9 +204,18 @@ private:
NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
- std::shared_ptr<nvmap> nvmap_dev;
- SyncpointManager& syncpoint_manager;
- NvFence channel_fence;
+ EventInterface& events_interface;
+ NvCore::Container& core;
+ NvCore::SyncpointManager& syncpoint_manager;
+ NvCore::NvMap& nvmap;
+ std::shared_ptr<Tegra::Control::ChannelState> channel_state;
+ 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
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
index a7385fce8..1703f9cc3 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
@@ -5,14 +5,14 @@
#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/nvhost_nvdec.h"
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-nvhost_nvdec::nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
+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, const std::vector<u8>& input,
@@ -21,8 +21,9 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
case 0x0:
switch (command.cmd) {
case 0x1: {
- if (!fd_to_id.contains(fd)) {
- fd_to_id[fd] = next_id++;
+ 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 Submit(fd, input, output);
}
@@ -73,8 +74,9 @@ void nvhost_nvdec::OnOpen(DeviceFD fd) {
void nvhost_nvdec::OnClose(DeviceFD fd) {
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
- const auto iter = fd_to_id.find(fd);
- if (iter != fd_to_id.end()) {
+ 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.AudioCore().SetNVDECActive(false);
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
index 29b3e6a36..c1b4e53e8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
@@ -10,8 +10,7 @@ namespace Service::Nvidia::Devices {
class nvhost_nvdec final : public nvhost_nvdec_common {
public:
- explicit nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_nvdec(Core::System& system_, NvCore::Container& core);
~nvhost_nvdec() override;
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -23,9 +22,6 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
-
-private:
- u32 next_id{};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
index 8b2cd9bf1..99eede702 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
@@ -8,10 +8,12 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.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/hle/service/nvdrv/devices/nvmap.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
#include "core/memory.h"
+#include "video_core/host1x/host1x.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"
@@ -44,10 +46,22 @@ std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::s
}
} // Anonymous namespace
-nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {}
-nvhost_nvdec_common::~nvhost_nvdec_common() = default;
+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(const std::vector<u8>& input) {
IoctlSetNvmapFD params{};
@@ -84,16 +98,16 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input,
for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
const SyncptIncr& syncpt_incr = syncpt_increments[i];
fence_thresholds[i] =
- syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments);
+ syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
}
}
for (const auto& cmd_buffer : command_buffers) {
- const auto object = nvmap_dev->GetObject(cmd_buffer.memory_id);
+ const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
- system.Memory().ReadBlock(object->addr + cmd_buffer.offset, cmdlist.data(),
+ system.Memory().ReadBlock(object->address + cmd_buffer.offset, cmdlist.data(),
cmdlist.size() * sizeof(u32));
- gpu.PushCommandBuffer(fd_to_id[fd], cmdlist);
+ gpu.PushCommandBuffer(core.Host1xDeviceFile().fd_to_id[fd], cmdlist);
}
std::memcpy(output.data(), &params, sizeof(IoctlSubmit));
// Some games expect command_buffers to be written back
@@ -112,10 +126,8 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
std::memcpy(&params, input.data(), sizeof(IoctlGetSyncpoint));
LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
- if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) {
- device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint();
- }
- params.value = device_syncpoints[params.param];
+ // const u32 id{NvCore::SyncpointManager::channel_syncpoints[static_cast<u32>(channel_type)]};
+ params.value = channel_syncpoint;
std::memcpy(output.data(), &params, sizeof(IoctlGetSyncpoint));
return NvResult::Success;
@@ -123,6 +135,7 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
IoctlGetWaitbase params{};
+ LOG_CRITICAL(Service_NVDRV, "called WAITBASE");
std::memcpy(&params, input.data(), sizeof(IoctlGetWaitbase));
params.value = 0; // Seems to be hard coded at 0
std::memcpy(output.data(), &params, sizeof(IoctlGetWaitbase));
@@ -136,28 +149,8 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
- auto& gpu = system.GPU();
-
for (auto& cmd_buffer : cmd_buffer_handles) {
- auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)};
- if (!object) {
- LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle);
- std::memcpy(output.data(), &params, output.size());
- return NvResult::InvalidState;
- }
- if (object->dma_map_addr == 0) {
- // NVDEC and VIC memory is in the 32-bit address space
- // MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space
- const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size);
- object->dma_map_addr = static_cast<u32>(low_addr);
- // Ensure that the dma_map_addr is indeed in the lower 32-bit address space.
- ASSERT(object->dma_map_addr == low_addr);
- }
- if (!object->dma_map_addr) {
- LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
- } else {
- cmd_buffer.map_address = object->dma_map_addr;
- }
+ cmd_buffer.map_address = nvmap.PinHandle(cmd_buffer.map_handle);
}
std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
@@ -167,11 +160,16 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
}
NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
- // This is intntionally stubbed.
- // Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame
- // addresses, and risk invalidating data before the async GPU thread is done with it
+ IoctlMapBuffer params{};
+ std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer));
+ std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
+
+ SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
+ for (auto& cmd_buffer : cmd_buffer_handles) {
+ nvmap.UnpinHandle(cmd_buffer.map_handle);
+ }
+
std::memset(output.data(), 0, output.size());
- LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
return NvResult::Success;
}
@@ -182,4 +180,9 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
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
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
index 12d39946d..fe76100c8 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
@@ -3,21 +3,26 @@
#pragma once
+#include <deque>
#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 {
-class SyncpointManager;
+
+namespace NvCore {
+class Container;
+class NvMap;
+} // namespace NvCore
namespace Devices {
-class nvmap;
class nvhost_nvdec_common : public nvdevice {
public:
- explicit nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_nvdec_common(Core::System& system_, NvCore::Container& core,
+ NvCore::ChannelType channel_type);
~nvhost_nvdec_common() override;
protected:
@@ -110,11 +115,15 @@ protected:
NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
- std::unordered_map<DeviceFD, u32> fd_to_id{};
+ Kernel::KEvent* QueryEvent(u32 event_id) override;
+
+ u32 channel_syncpoint;
s32_le nvmap_fd{};
u32_le submit_timeout{};
- std::shared_ptr<nvmap> nvmap_dev;
- SyncpointManager& syncpoint_manager;
+ NvCore::Container& core;
+ NvCore::SyncpointManager& syncpoint_manager;
+ NvCore::NvMap& nvmap;
+ NvCore::ChannelType channel_type;
std::array<u32, MaxSyncPoints> device_syncpoints{};
};
}; // namespace Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
index f58e8bada..73f97136e 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
@@ -4,13 +4,14 @@
#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/nvhost_vic.h"
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
-nvhost_vic::nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_)
- : nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
+
+nvhost_vic::nvhost_vic(Core::System& system_, NvCore::Container& core_)
+ : nvhost_nvdec_common{system_, core_, NvCore::ChannelType::VIC} {}
nvhost_vic::~nvhost_vic() = default;
@@ -19,11 +20,13 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i
switch (command.group) {
case 0x0:
switch (command.cmd) {
- case 0x1:
- if (!fd_to_id.contains(fd)) {
- fd_to_id[fd] = next_id++;
+ 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 Submit(fd, input, output);
+ }
case 0x2:
return GetSyncpoint(input, output);
case 0x3:
@@ -67,8 +70,9 @@ NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& i
void nvhost_vic::OnOpen(DeviceFD fd) {}
void nvhost_vic::OnClose(DeviceFD fd) {
- const auto iter = fd_to_id.find(fd);
- if (iter != fd_to_id.end()) {
+ 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);
}
}
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
index b41b195ae..f164caafb 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h
@@ -9,8 +9,7 @@ namespace Service::Nvidia::Devices {
class nvhost_vic final : public nvhost_nvdec_common {
public:
- explicit nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
- SyncpointManager& syncpoint_manager_);
+ explicit nvhost_vic(Core::System& system_, NvCore::Container& core);
~nvhost_vic();
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -22,8 +21,5 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
-
-private:
- u32 next_id{};
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index d8518149d..ddf273b5e 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -2,19 +2,26 @@
// 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/nvmap.h"
+#include "core/memory.h"
+
+using Core::Memory::YUZU_PAGESIZE;
namespace Service::Nvidia::Devices {
-nvmap::nvmap(Core::System& system_) : nvdevice{system_} {
- // Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
- // represent this.
- CreateObject(0);
-}
+nvmap::nvmap(Core::System& system_, NvCore::Container& container_)
+ : nvdevice{system_}, container{container_}, file{container.GetNvMapFile()} {}
nvmap::~nvmap() = default;
@@ -62,39 +69,21 @@ NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
void nvmap::OnOpen(DeviceFD fd) {}
void nvmap::OnClose(DeviceFD fd) {}
-VAddr nvmap::GetObjectAddress(u32 handle) const {
- auto object = GetObject(handle);
- ASSERT(object);
- ASSERT(object->status == Object::Status::Allocated);
- return object->addr;
-}
-
-u32 nvmap::CreateObject(u32 size) {
- // Create a new nvmap object and obtain a handle to it.
- auto object = std::make_shared<Object>();
- object->id = next_id++;
- object->size = size;
- object->status = Object::Status::Created;
- object->refcount = 1;
-
- const u32 handle = next_handle++;
-
- handles.insert_or_assign(handle, std::move(object));
-
- return handle;
-}
-
NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
-
- if (!params.size) {
- LOG_ERROR(Service_NVDRV, "Size is 0");
- return NvResult::BadValue;
+ 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;
}
-
- params.handle = CreateObject(params.size);
+ 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);
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
@@ -103,63 +92,68 @@ NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output)
NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
+ LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address);
if (!params.handle) {
- LOG_ERROR(Service_NVDRV, "Handle is 0");
+ LOG_CRITICAL(Service_NVDRV, "Handle is 0");
return NvResult::BadValue;
}
if ((params.align - 1) & params.align) {
- LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
+ LOG_CRITICAL(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
return NvResult::BadValue;
}
- const u32 min_alignment = 0x1000;
- if (params.align < min_alignment) {
- params.align = min_alignment;
+ // Force page size alignment at a minimum
+ if (params.align < YUZU_PAGESIZE) {
+ params.align = YUZU_PAGESIZE;
}
- auto object = GetObject(params.handle);
- if (!object) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+ 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 (object->status == Object::Status::Allocated) {
- LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
+ if (handle_description->allocated) {
+ LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
return NvResult::InsufficientMemory;
}
- object->flags = params.flags;
- object->align = params.align;
- object->kind = params.kind;
- object->addr = params.addr;
- object->status = Object::Status::Allocated;
-
+ const auto result =
+ handle_description->Alloc(params.flags, params.align, params.kind, params.address);
+ if (result != NvResult::Success) {
+ LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
+ return result;
+ }
+ ASSERT(system.CurrentProcess()
+ ->PageTable()
+ .LockForDeviceAddressSpace(handle_description->address, handle_description->size)
+ .IsSuccess());
std::memcpy(output.data(), &params, sizeof(params));
- return NvResult::Success;
+ return result;
}
NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
IocGetIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "called");
+ LOG_DEBUG(Service_NVDRV, "called");
+ // See the comment in FromId for extra info on this function
if (!params.handle) {
- LOG_ERROR(Service_NVDRV, "Handle is zero");
+ LOG_CRITICAL(Service_NVDRV, "Error!");
return NvResult::BadValue;
}
- auto object = GetObject(params.handle);
- if (!object) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- 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 = object->id;
-
+ params.id = handle_description->id;
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
@@ -168,26 +162,29 @@ NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output)
IocFromIdParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_WARNING(Service_NVDRV, "(STUBBED) called");
+ LOG_DEBUG(Service_NVDRV, "called, id:{}", params.id);
- auto itr = std::find_if(handles.begin(), handles.end(),
- [&](const auto& entry) { return entry.second->id == params.id; });
- if (itr == handles.end()) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+ // 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& object = itr->second;
- if (object->status != Object::Status::Allocated) {
- LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
+ auto handle_description{file.GetHandle(params.id)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
return NvResult::BadValue;
}
- itr->second->refcount++;
-
- // Return the existing handle instead of creating a new one.
- params.handle = itr->first;
-
+ 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;
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
@@ -198,35 +195,43 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
IocParamParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "(STUBBED) called type={}", params.param);
+ LOG_DEBUG(Service_NVDRV, "called type={}", params.param);
- auto object = GetObject(params.handle);
- if (!object) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
+ if (!params.handle) {
+ LOG_CRITICAL(Service_NVDRV, "Invalid handle!");
return NvResult::BadValue;
}
- if (object->status != Object::Status::Allocated) {
- LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
+ auto handle_description{file.GetHandle(params.handle)};
+ if (!handle_description) {
+ LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
return NvResult::BadValue;
}
- switch (static_cast<ParamTypes>(params.param)) {
- case ParamTypes::Size:
- params.result = object->size;
+ 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 ParamTypes::Alignment:
- params.result = object->align;
+ case HandleParameterType::Base:
+ params.result = static_cast<u32_le>(-22); // posix EINVAL
break;
- case ParamTypes::Heap:
- // TODO(Subv): Seems to be a hardcoded value?
- params.result = 0x40000000;
+ case HandleParameterType::Heap:
+ if (handle_description->allocated)
+ params.result = 0x40000000;
+ else
+ params.result = 0;
break;
- case ParamTypes::Kind:
- params.result = object->kind;
+ case HandleParameterType::Kind:
+ params.result = handle_description->kind;
+ break;
+ case HandleParameterType::IsSharedMemMapped:
+ params.result = handle_description->is_shared_mem_mapped;
break;
default:
- UNIMPLEMENTED();
+ return NvResult::BadValue;
}
std::memcpy(output.data(), &params, sizeof(params));
@@ -234,46 +239,29 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
}
NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
- // TODO(Subv): These flags are unconfirmed.
- enum FreeFlags {
- Freed = 0,
- NotFreedYet = 1,
- };
-
IocFreeParams params;
std::memcpy(&params, input.data(), sizeof(params));
- LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
+ LOG_DEBUG(Service_NVDRV, "called");
- auto itr = handles.find(params.handle);
- if (itr == handles.end()) {
- LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
- return NvResult::BadValue;
- }
- if (!itr->second->refcount) {
- LOG_ERROR(
- Service_NVDRV,
- "There is no references to this object. The object is already freed. handle={:08X}",
- params.handle);
- return NvResult::BadValue;
+ if (!params.handle) {
+ LOG_CRITICAL(Service_NVDRV, "Handle null freed?");
+ return NvResult::Success;
}
- itr->second->refcount--;
-
- params.size = itr->second->size;
-
- if (itr->second->refcount == 0) {
- params.flags = Freed;
- // The address of the nvmap is written to the output if we're finally freeing it, otherwise
- // 0 is written.
- params.address = itr->second->addr;
+ if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
+ ASSERT(system.CurrentProcess()
+ ->PageTable()
+ .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 {
- params.flags = NotFreedYet;
- params.address = 0;
+ // This is possible when there's internel dups or other duplicates.
}
- handles.erase(params.handle);
-
std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Success;
}
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h
index d5360d6e5..e9bfd0358 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.h
+++ b/src/core/hle/service/nvdrv/devices/nvmap.h
@@ -9,15 +9,23 @@
#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_);
+ 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, const std::vector<u8>& input,
std::vector<u8>& output) override;
NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
@@ -28,31 +36,15 @@ public:
void OnOpen(DeviceFD fd) override;
void OnClose(DeviceFD fd) override;
- /// Returns the allocated address of an nvmap object given its handle.
- VAddr GetObjectAddress(u32 handle) const;
-
- /// Represents an nvmap object.
- struct Object {
- enum class Status { Created, Allocated };
- u32 id;
- u32 size;
- u32 flags;
- u32 align;
- u8 kind;
- VAddr addr;
- Status status;
- u32 refcount;
- u32 dma_map_addr;
+ enum class HandleParameterType : u32_le {
+ Size = 1,
+ Alignment = 2,
+ Base = 3,
+ Heap = 4,
+ Kind = 5,
+ IsSharedMemMapped = 6
};
- std::shared_ptr<Object> GetObject(u32 handle) const {
- auto itr = handles.find(handle);
- if (itr != handles.end()) {
- return itr->second;
- }
- return {};
- }
-
private:
/// Id to use for the next handle that is created.
u32 next_handle = 0;
@@ -60,9 +52,6 @@ private:
/// Id to use for the next object that is created.
u32 next_id = 0;
- /// Mapping of currently allocated handles to the objects they represent.
- std::unordered_map<u32, std::shared_ptr<Object>> handles;
-
struct IocCreateParams {
// Input
u32_le size{};
@@ -83,11 +72,11 @@ private:
// Input
u32_le handle{};
u32_le heap_mask{};
- u32_le flags{};
+ NvCore::NvMap::Handle::Flags flags{};
u32_le align{};
u8 kind{};
INSERT_PADDING_BYTES(7);
- u64_le addr{};
+ u64_le address{};
};
static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
@@ -96,14 +85,14 @@ private:
INSERT_PADDING_BYTES(4);
u64_le address{};
u32_le size{};
- u32_le flags{};
+ NvCore::NvMap::Handle::Flags flags{};
};
static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
struct IocParamParams {
// Input
u32_le handle{};
- u32_le param{};
+ HandleParameterType param{};
// Output
u32_le result{};
};
@@ -117,14 +106,15 @@ private:
};
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
- u32 CreateObject(u32 size);
-
NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
+
+ NvCore::Container& container;
+ NvCore::NvMap& file;
};
} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/nvdata.h b/src/core/hle/service/nvdrv/nvdata.h
index 1d00394c8..0e2f47075 100644
--- a/src/core/hle/service/nvdrv/nvdata.h
+++ b/src/core/hle/service/nvdrv/nvdata.h
@@ -1,5 +1,6 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -78,11 +79,15 @@ enum class NvResult : u32 {
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 {
- Free = 0,
- Registered = 1,
- Waiting = 2,
- Busy = 3,
+ Available = 0,
+ Waiting = 1,
+ Cancelling = 2,
+ Signalling = 3,
+ Signalled = 4,
+ Cancelled = 5,
};
union Ioctl {
diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp
index 756eb7453..5e7b7468f 100644
--- a/src/core/hle/service/nvdrv/nvdrv.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv.cpp
@@ -1,5 +1,6 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <utility>
@@ -8,6 +9,7 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_writable_event.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"
@@ -15,17 +17,31 @@
#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/nvdrv/syncpoint_manager.h"
#include "core/hle/service/nvflinger/nvflinger.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 InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
Core::System& system) {
auto module_ = std::make_shared<Module>(system);
@@ -38,34 +54,54 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
}
Module::Module(Core::System& system)
- : syncpoint_manager{system.GPU()}, service_context{system, "nvdrv"} {
- for (u32 i = 0; i < MaxNvEvents; i++) {
- events_interface.events[i].event =
- service_context.CreateEvent(fmt::format("NVDRV::NvEvent_{}", i));
- events_interface.status[i] = EventState::Free;
- events_interface.registered[i] = false;
- }
- auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
- devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
- devices["/dev/nvhost-gpu"] =
- std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager);
- devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
- devices["/dev/nvmap"] = nvmap_dev;
- devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
- devices["/dev/nvhost-ctrl"] =
- std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager);
- devices["/dev/nvhost-nvdec"] =
- std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev, syncpoint_manager);
- devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
- devices["/dev/nvhost-vic"] =
- std::make_shared<Devices::nvhost_vic>(system, nvmap_dev, syncpoint_manager);
+ : service_context{system, "nvdrv"}, events_interface{*this}, container{system.Host1x()} {
+ builders["/dev/nvhost-as-gpu"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_as_gpu>(system, *this, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-gpu"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_gpu>(system, events_interface, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-ctrl-gpu"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_ctrl_gpu>(system, events_interface);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvmap"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvmap>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvdisp_disp0"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvdisp_disp0>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-ctrl"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_ctrl>(system, events_interface, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-nvdec"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_nvdec>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-nvjpg"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device = std::make_shared<Devices::nvhost_nvjpg>(system);
+ return open_files.emplace(fd, device).first;
+ };
+ builders["/dev/nvhost-vic"] = [this, &system](DeviceFD fd) {
+ std::shared_ptr<Devices::nvdevice> device =
+ std::make_shared<Devices::nvhost_vic>(system, container);
+ return open_files.emplace(fd, device).first;
+ };
}
-Module::~Module() {
- for (u32 i = 0; i < MaxNvEvents; i++) {
- service_context.CloseEvent(events_interface.events[i].event);
- }
-}
+Module::~Module() {}
NvResult Module::VerifyFD(DeviceFD fd) const {
if (fd < 0) {
@@ -82,18 +118,18 @@ NvResult Module::VerifyFD(DeviceFD fd) const {
}
DeviceFD Module::Open(const std::string& device_name) {
- if (devices.find(device_name) == devices.end()) {
+ 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;
}
- auto device = devices[device_name];
const DeviceFD fd = next_fd++;
+ auto& builder = it->second;
+ auto device = builder(fd)->second;
device->OnOpen(fd);
- open_files[fd] = std::move(device);
-
return fd;
}
@@ -168,22 +204,24 @@ NvResult Module::Close(DeviceFD fd) {
return NvResult::Success;
}
-void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
- for (u32 i = 0; i < MaxNvEvents; i++) {
- if (events_interface.assigned_syncpt[i] == syncpoint_id &&
- events_interface.assigned_value[i] == value) {
- events_interface.LiberateEvent(i);
- events_interface.events[i].event->GetWritableEvent().Signal();
- }
+NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
+ if (fd < 0) {
+ LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
+ return NvResult::InvalidState;
}
-}
-Kernel::KReadableEvent& Module::GetEvent(const u32 event_id) {
- return events_interface.events[event_id].event->GetReadableEvent();
-}
+ const auto itr = open_files.find(fd);
-Kernel::KWritableEvent& Module::GetEventWriteable(const u32 event_id) {
- return events_interface.events[event_id].event->GetWritableEvent();
+ 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
diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h
index c929e5106..146d046a9 100644
--- a/src/core/hle/service/nvdrv/nvdrv.h
+++ b/src/core/hle/service/nvdrv/nvdrv.h
@@ -1,16 +1,20 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// 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 <string>
#include <unordered_map>
#include <vector>
#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/nvdrv/syncpoint_manager.h"
#include "core/hle/service/nvflinger/ui/fence.h"
#include "core/hle/service/service.h"
@@ -28,81 +32,31 @@ class NVFlinger;
namespace Service::Nvidia {
+namespace NvCore {
+class Container;
class SyncpointManager;
+} // namespace NvCore
namespace Devices {
class nvdevice;
-}
+class nvhost_ctrl;
+} // namespace Devices
-/// Represents an Nvidia event
-struct NvEvent {
- Kernel::KEvent* event{};
- NvFence fence{};
-};
+class Module;
-struct EventInterface {
- // Mask representing currently busy events
- u64 events_mask{};
- // Each kernel event associated to an NV event
- std::array<NvEvent, MaxNvEvents> events;
- // The status of the current NVEvent
- std::array<EventState, MaxNvEvents> status{};
- // Tells if an NVEvent is registered or not
- std::array<bool, MaxNvEvents> registered{};
- // Tells the NVEvent that it has failed.
- std::array<bool, MaxNvEvents> failed{};
- // When an NVEvent is waiting on GPU interrupt, this is the sync_point
- // associated with it.
- std::array<u32, MaxNvEvents> assigned_syncpt{};
- // This is the value of the GPU interrupt for which the NVEvent is waiting
- // for.
- std::array<u32, MaxNvEvents> assigned_value{};
- // Constant to denote an unasigned syncpoint.
- static constexpr u32 unassigned_syncpt = 0xFFFFFFFF;
- std::optional<u32> GetFreeEvent() const {
- u64 mask = events_mask;
- for (u32 i = 0; i < MaxNvEvents; i++) {
- const bool is_free = (mask & 0x1) == 0;
- if (is_free) {
- if (status[i] == EventState::Registered || status[i] == EventState::Free) {
- return {i};
- }
- }
- mask = mask >> 1;
- }
- return std::nullopt;
- }
- void SetEventStatus(const u32 event_id, EventState new_status) {
- EventState old_status = status[event_id];
- if (old_status == new_status) {
- return;
- }
- status[event_id] = new_status;
- if (new_status == EventState::Registered) {
- registered[event_id] = true;
- }
- if (new_status == EventState::Waiting || new_status == EventState::Busy) {
- events_mask |= (1ULL << event_id);
- }
- }
- void RegisterEvent(const u32 event_id) {
- registered[event_id] = true;
- if (status[event_id] == EventState::Free) {
- status[event_id] = EventState::Registered;
- }
- }
- void UnregisterEvent(const u32 event_id) {
- registered[event_id] = false;
- if (status[event_id] == EventState::Registered) {
- status[event_id] = EventState::Free;
- }
- }
- void LiberateEvent(const u32 event_id) {
- status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free;
- events_mask &= ~(1ULL << event_id);
- assigned_syncpt[event_id] = unassigned_syncpt;
- assigned_value[event_id] = 0;
- }
+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 {
@@ -112,9 +66,9 @@ public:
/// Returns a pointer to one of the available devices, identified by its name.
template <typename T>
- std::shared_ptr<T> GetDevice(const std::string& name) {
- auto itr = devices.find(name);
- if (itr == devices.end())
+ 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);
}
@@ -137,28 +91,27 @@ public:
/// Closes a device file descriptor and returns operation success.
NvResult Close(DeviceFD fd);
- void SignalSyncpt(const u32 syncpoint_id, const u32 value);
-
- Kernel::KReadableEvent& GetEvent(u32 event_id);
-
- Kernel::KWritableEvent& GetEventWriteable(u32 event_id);
+ NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
private:
- /// Manages syncpoints on the host
- SyncpointManager syncpoint_manager;
+ friend class EventInterface;
+ friend class Service::NVFlinger::NVFlinger;
/// 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.
- std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files;
+ FilesContainerType open_files;
- /// Mapping of device node names to their implementation.
- std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
+ KernelHelpers::ServiceContext service_context;
EventInterface events_interface;
- KernelHelpers::ServiceContext service_context;
+ /// Manages syncpoints on the host
+ NvCore::Container container;
+
+ std::unordered_map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
};
/// Registers all NVDRV services with the specified service manager.
diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.cpp b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
index b5a980384..edbdfee43 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.cpp
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.cpp
@@ -1,10 +1,12 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <cinttypes>
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/service/nvdrv/nvdata.h"
#include "core/hle/service/nvdrv/nvdrv.h"
@@ -12,10 +14,6 @@
namespace Service::Nvidia {
-void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
- nvdrv->SignalSyncpt(syncpoint_id, value);
-}
-
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NVDRV, "called");
IPC::ResponseBuilder rb{ctx, 4};
@@ -164,8 +162,7 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto fd = rp.Pop<DeviceFD>();
- const auto event_id = rp.Pop<u32>() & 0x00FF;
- LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
+ const auto event_id = rp.Pop<u32>();
if (!is_initialized) {
ServiceError(ctx, NvResult::NotInitialized);
@@ -173,24 +170,20 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
return;
}
- const auto nv_result = nvdrv->VerifyFD(fd);
- if (nv_result != NvResult::Success) {
- LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd);
- ServiceError(ctx, nv_result);
- return;
- }
+ Kernel::KEvent* event = nullptr;
+ NvResult result = nvdrv->QueryEvent(fd, event_id, event);
- if (event_id < MaxNvEvents) {
+ if (result == NvResult::Success) {
IPC::ResponseBuilder rb{ctx, 3, 1};
rb.Push(ResultSuccess);
- auto& event = nvdrv->GetEvent(event_id);
- event.Clear();
- rb.PushCopyObjects(event);
+ 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(NvResult::BadParameter);
+ rb.PushEnum(result);
}
}
diff --git a/src/core/hle/service/nvdrv/nvdrv_interface.h b/src/core/hle/service/nvdrv/nvdrv_interface.h
index cbd37b52b..cd58a4f35 100644
--- a/src/core/hle/service/nvdrv/nvdrv_interface.h
+++ b/src/core/hle/service/nvdrv/nvdrv_interface.h
@@ -18,8 +18,6 @@ public:
explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
~NVDRV() override;
- void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value);
-
private:
void Open(Kernel::HLERequestContext& ctx);
void Ioctl1(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.cpp b/src/core/hle/service/nvdrv/syncpoint_manager.cpp
deleted file mode 100644
index a6fa943e8..000000000
--- a/src/core/hle/service/nvdrv/syncpoint_manager.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/assert.h"
-#include "core/hle/service/nvdrv/syncpoint_manager.h"
-#include "video_core/gpu.h"
-
-namespace Service::Nvidia {
-
-SyncpointManager::SyncpointManager(Tegra::GPU& gpu_) : gpu{gpu_} {}
-
-SyncpointManager::~SyncpointManager() = default;
-
-u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) {
- syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id);
- return GetSyncpointMin(syncpoint_id);
-}
-
-u32 SyncpointManager::AllocateSyncpoint() {
- for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) {
- if (!syncpoints[syncpoint_id].is_allocated) {
- syncpoints[syncpoint_id].is_allocated = true;
- return syncpoint_id;
- }
- }
- ASSERT_MSG(false, "No more available syncpoints!");
- return {};
-}
-
-u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) {
- for (u32 index = 0; index < value; ++index) {
- syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed);
- }
-
- return GetSyncpointMax(syncpoint_id);
-}
-
-} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvdrv/syncpoint_manager.h b/src/core/hle/service/nvdrv/syncpoint_manager.h
deleted file mode 100644
index 7f080f76e..000000000
--- a/src/core/hle/service/nvdrv/syncpoint_manager.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <atomic>
-
-#include "common/common_types.h"
-#include "core/hle/service/nvdrv/nvdata.h"
-
-namespace Tegra {
-class GPU;
-}
-
-namespace Service::Nvidia {
-
-class SyncpointManager final {
-public:
- explicit SyncpointManager(Tegra::GPU& gpu_);
- ~SyncpointManager();
-
- /**
- * Returns true if the specified syncpoint is expired for the given value.
- * @param syncpoint_id Syncpoint ID to check.
- * @param value Value to check against the specified syncpoint.
- * @returns True if the specified syncpoint is expired for the given value, otherwise False.
- */
- bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const {
- return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value);
- }
-
- /**
- * Gets the lower bound for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to get the lower bound for.
- * @returns The lower bound for the specified syncpoint.
- */
- u32 GetSyncpointMin(u32 syncpoint_id) const {
- return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed);
- }
-
- /**
- * Gets the uper bound for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to get the upper bound for.
- * @returns The upper bound for the specified syncpoint.
- */
- u32 GetSyncpointMax(u32 syncpoint_id) const {
- return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed);
- }
-
- /**
- * Refreshes the minimum value for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to be refreshed.
- * @returns The new syncpoint minimum value.
- */
- u32 RefreshSyncpoint(u32 syncpoint_id);
-
- /**
- * Allocates a new syncoint.
- * @returns The syncpoint ID for the newly allocated syncpoint.
- */
- u32 AllocateSyncpoint();
-
- /**
- * Increases the maximum value for the specified syncpoint.
- * @param syncpoint_id Syncpoint ID to be increased.
- * @param value Value to increase the specified syncpoint by.
- * @returns The new syncpoint maximum value.
- */
- u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value);
-
-private:
- struct Syncpoint {
- std::atomic<u32> min;
- std::atomic<u32> max;
- std::atomic<bool> is_allocated;
- };
-
- std::array<Syncpoint, MaxSyncPoints> syncpoints{};
-
- Tegra::GPU& gpu;
-};
-
-} // namespace Service::Nvidia
diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
index 4b3d5efd6..1ce67c771 100644
--- a/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.cpp
@@ -5,15 +5,18 @@
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
#include "common/logging/log.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvflinger/buffer_item.h"
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvflinger/buffer_queue_core.h"
#include "core/hle/service/nvflinger/producer_listener.h"
+#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
namespace Service::android {
-BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
- : core{std::move(core_)}, slots{core->slots} {}
+BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
+ Service::Nvidia::NvCore::NvMap& nvmap_)
+ : core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
BufferQueueConsumer::~BufferQueueConsumer() = default;
@@ -133,6 +136,8 @@ Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fenc
slots[slot].buffer_state = BufferState::Free;
+ nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true);
+
listener = core->connected_producer_listener;
LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);
diff --git a/src/core/hle/service/nvflinger/buffer_queue_consumer.h b/src/core/hle/service/nvflinger/buffer_queue_consumer.h
index b598c314f..4ec06ca13 100644
--- a/src/core/hle/service/nvflinger/buffer_queue_consumer.h
+++ b/src/core/hle/service/nvflinger/buffer_queue_consumer.h
@@ -13,6 +13,10 @@
#include "core/hle/service/nvflinger/buffer_queue_defs.h"
#include "core/hle/service/nvflinger/status.h"
+namespace Service::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
namespace Service::android {
class BufferItem;
@@ -21,7 +25,8 @@ class IConsumerListener;
class BufferQueueConsumer final {
public:
- explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
+ explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
+ Service::Nvidia::NvCore::NvMap& nvmap_);
~BufferQueueConsumer();
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
@@ -32,6 +37,7 @@ public:
private:
std::shared_ptr<BufferQueueCore> core;
BufferQueueDefs::SlotsType& slots;
+ Service::Nvidia::NvCore::NvMap& nvmap;
};
} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
index 337431488..d4ab23a10 100644
--- a/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.cpp
@@ -14,7 +14,7 @@
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nvdrv/nvdrv.h"
+#include "core/hle/service/nvdrv/core/nvmap.h"
#include "core/hle/service/nvflinger/buffer_queue_core.h"
#include "core/hle/service/nvflinger/buffer_queue_producer.h"
#include "core/hle/service/nvflinger/consumer_listener.h"
@@ -26,8 +26,10 @@
namespace Service::android {
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
- std::shared_ptr<BufferQueueCore> buffer_queue_core_)
- : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) {
+ std::shared_ptr<BufferQueueCore> buffer_queue_core_,
+ Service::Nvidia::NvCore::NvMap& nvmap_)
+ : service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
+ nvmap(nvmap_) {
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
}
@@ -530,6 +532,8 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
item.is_droppable = core->dequeue_buffer_cannot_block || async;
item.swap_interval = swap_interval;
+ nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true);
+
sticky_transform = sticky_transform_;
if (core->queue.empty()) {
diff --git a/src/core/hle/service/nvflinger/buffer_queue_producer.h b/src/core/hle/service/nvflinger/buffer_queue_producer.h
index 42d4722dc..0ba03a568 100644
--- a/src/core/hle/service/nvflinger/buffer_queue_producer.h
+++ b/src/core/hle/service/nvflinger/buffer_queue_producer.h
@@ -31,6 +31,10 @@ namespace Service::KernelHelpers {
class ServiceContext;
} // namespace Service::KernelHelpers
+namespace Service::Nvidia::NvCore {
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
namespace Service::android {
class BufferQueueCore;
@@ -39,7 +43,8 @@ class IProducerListener;
class BufferQueueProducer final : public IBinder {
public:
explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
- std::shared_ptr<BufferQueueCore> buffer_queue_core_);
+ std::shared_ptr<BufferQueueCore> buffer_queue_core_,
+ Service::Nvidia::NvCore::NvMap& nvmap_);
~BufferQueueProducer();
void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
@@ -78,6 +83,8 @@ private:
s32 next_callback_ticket{};
s32 current_callback_ticket{};
std::condition_variable_any callback_condition;
+
+ Service::Nvidia::NvCore::NvMap& nvmap;
};
} // namespace Service::android
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 9b382bf56..aa14d2cbc 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -22,7 +22,10 @@
#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
+#include "core/hle/service/vi/vi_results.h"
#include "video_core/gpu.h"
+#include "video_core/host1x/host1x.h"
+#include "video_core/host1x/syncpoint_manager.h"
namespace Service::NVFlinger {
@@ -30,7 +33,7 @@ constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
void NVFlinger::SplitVSync(std::stop_token stop_token) {
system.RegisterHostThread();
- std::string name = "yuzu:VSyncThread";
+ std::string name = "VSyncThread";
MicroProfileOnThreadCreate(name.c_str());
// Cleanup
@@ -104,10 +107,15 @@ NVFlinger::~NVFlinger() {
display.GetLayer(layer).Core().NotifyShutdown();
}
}
+
+ if (nvdrv) {
+ nvdrv->Close(disp_fd);
+ }
}
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
nvdrv = std::move(instance);
+ disp_fd = nvdrv->Open("/dev/nvdisp_disp0");
}
std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
@@ -141,7 +149,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
const auto buffer_id = next_buffer_queue_id++;
- display.CreateLayer(layer_id, buffer_id);
+ display.CreateLayer(layer_id, buffer_id, nvdrv->container);
}
void NVFlinger::CloseLayer(u64 layer_id) {
@@ -163,15 +171,15 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
return layer->GetBinderId();
}
-Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) {
+ResultVal<Kernel::KReadableEvent*> NVFlinger::FindVsyncEvent(u64 display_id) {
const auto lock_guard = Lock();
auto* const display = FindDisplay(display_id);
if (display == nullptr) {
- return nullptr;
+ return VI::ResultNotFound;
}
- return &display->GetVSyncEvent();
+ return display->GetVSyncEvent();
}
VI::Display* NVFlinger::FindDisplay(u64 display_id) {
@@ -261,30 +269,24 @@ void NVFlinger::Compose() {
return; // We are likely shutting down
}
- auto& gpu = system.GPU();
- const auto& multi_fence = buffer.fence;
- guard->unlock();
- for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
- const auto& fence = multi_fence.fences[fence_id];
- gpu.WaitFence(fence.id, fence.value);
- }
- guard->lock();
-
- MicroProfileFlip();
-
// Now send the buffer to the GPU for drawing.
// TODO(Subv): Support more than just disp0. The display device selection is probably based
// on which display we're drawing (Default, Internal, External, etc)
- auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
+ auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
ASSERT(nvdisp);
+ guard->unlock();
Common::Rectangle<int> crop_rect{
static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
- static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect);
+ static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect,
+ buffer.fence.fences, buffer.fence.num_fences);
+
+ MicroProfileFlip();
+ guard->lock();
swap_interval = buffer.swap_interval;
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 044ac6ac8..b62615de2 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -11,6 +11,7 @@
#include <vector>
#include "common/common_types.h"
+#include "core/hle/result.h"
#include "core/hle/service/kernel_helpers.h"
namespace Common {
@@ -71,8 +72,9 @@ public:
/// Gets the vsync event for the specified display.
///
- /// If an invalid display ID is provided, then nullptr is returned.
- [[nodiscard]] Kernel::KReadableEvent* FindVsyncEvent(u64 display_id);
+ /// If an invalid display ID is provided, then VI::ResultNotFound is returned.
+ /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
+ [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished.
@@ -114,6 +116,7 @@ private:
void SplitVSync(std::stop_token stop_token);
std::shared_ptr<Nvidia::Module> nvdrv;
+ s32 disp_fd;
std::list<VI::Display> displays;
diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp
index cc679cc81..9e94a462f 100644
--- a/src/core/hle/service/sockets/bsd.cpp
+++ b/src/core/hle/service/sockets/bsd.cpp
@@ -929,7 +929,7 @@ BSD::BSD(Core::System& system_, const char* name)
proxy_packet_received = room_member->BindOnProxyPacketReceived(
[this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); });
} else {
- LOG_ERROR(Service, "Network isn't initalized");
+ LOG_ERROR(Service, "Network isn't initialized");
}
}
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index b34febb50..288aafaaf 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -12,6 +12,7 @@
#include "core/hle/kernel/k_readable_event.h"
#include "core/hle/kernel/k_writable_event.h"
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nvdrv/core/container.h"
#include "core/hle/service/nvflinger/buffer_item_consumer.h"
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
#include "core/hle/service/nvflinger/buffer_queue_core.h"
@@ -19,6 +20,7 @@
#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
#include "core/hle/service/vi/display/vi_display.h"
#include "core/hle/service/vi/layer/vi_layer.h"
+#include "core/hle/service/vi/vi_results.h"
namespace Service::VI {
@@ -28,11 +30,13 @@ struct BufferQueue {
std::unique_ptr<android::BufferQueueConsumer> consumer;
};
-static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context) {
+static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context,
+ Service::Nvidia::NvCore::NvMap& nvmap) {
auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
- return {buffer_queue_core,
- std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core),
- std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)};
+ return {
+ buffer_queue_core,
+ std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core, nvmap),
+ std::make_unique<android::BufferQueueConsumer>(buffer_queue_core, nvmap)};
}
Display::Display(u64 id, std::string name_,
@@ -55,18 +59,29 @@ const Layer& Display::GetLayer(std::size_t index) const {
return *layers.at(index);
}
-Kernel::KReadableEvent& Display::GetVSyncEvent() {
- return vsync_event->GetReadableEvent();
+ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() {
+ if (got_vsync_event) {
+ return ResultPermissionDenied;
+ }
+
+ got_vsync_event = true;
+
+ return GetVSyncEventUnchecked();
+}
+
+Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
+ return &vsync_event->GetReadableEvent();
}
void Display::SignalVSyncEvent() {
vsync_event->GetWritableEvent().Signal();
}
-void Display::CreateLayer(u64 layer_id, u32 binder_id) {
+void Display::CreateLayer(u64 layer_id, u32 binder_id,
+ Service::Nvidia::NvCore::Container& nv_core) {
ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment");
- auto [core, producer, consumer] = CreateBufferQueue(service_context);
+ auto [core, producer, consumer] = CreateBufferQueue(service_context, nv_core.GetNvMapFile());
auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
buffer_item_consumer->Connect(false);
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 3838bb599..33d5f398c 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -9,6 +9,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "core/hle/result.h"
namespace Kernel {
class KEvent;
@@ -26,6 +27,11 @@ namespace Service::NVFlinger {
class HosBinderDriverServer;
}
+namespace Service::Nvidia::NvCore {
+class Container;
+class NvMap;
+} // namespace Service::Nvidia::NvCore
+
namespace Service::VI {
class Layer;
@@ -73,8 +79,16 @@ public:
return layers.size();
}
- /// Gets the readable vsync event.
- Kernel::KReadableEvent& GetVSyncEvent();
+ /**
+ * Gets the internal vsync event.
+ *
+ * @returns The internal Vsync event if it has not yet been retrieved,
+ * VI::ResultPermissionDenied otherwise.
+ */
+ [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent();
+
+ /// Gets the internal vsync event.
+ Kernel::KReadableEvent* GetVSyncEventUnchecked();
/// Signals the internal vsync event.
void SignalVSyncEvent();
@@ -84,7 +98,7 @@ public:
/// @param layer_id The ID to assign to the created layer.
/// @param binder_id The ID assigned to the buffer queue.
///
- void CreateLayer(u64 layer_id, u32 binder_id);
+ void CreateLayer(u64 layer_id, u32 binder_id, Service::Nvidia::NvCore::Container& core);
/// Closes and removes a layer from this display with the given ID.
///
@@ -118,6 +132,7 @@ private:
std::vector<std::unique_ptr<Layer>> layers;
Kernel::KEvent* vsync_event{};
+ bool got_vsync_event{false};
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 546879648..9c917cacf 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -29,16 +29,12 @@
#include "core/hle/service/service.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
+#include "core/hle/service/vi/vi_results.h"
#include "core/hle/service/vi/vi_s.h"
#include "core/hle/service/vi/vi_u.h"
namespace Service::VI {
-constexpr Result ERR_OPERATION_FAILED{ErrorModule::VI, 1};
-constexpr Result ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
-constexpr Result ERR_UNSUPPORTED{ErrorModule::VI, 6};
-constexpr Result ERR_NOT_FOUND{ErrorModule::VI, 7};
-
struct DisplayInfo {
/// The name of this particular display.
char display_name[0x40]{"Default"};
@@ -62,6 +58,7 @@ static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
class NativeWindow final {
public:
constexpr explicit NativeWindow(u32 id_) : id{id_} {}
+ constexpr explicit NativeWindow(const NativeWindow& other) = default;
private:
const u32 magic = 2;
@@ -348,7 +345,7 @@ private:
if (!layer_id) {
LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -498,7 +495,7 @@ private:
if (!display_id) {
LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -554,14 +551,14 @@ private:
if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) {
LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
- rb.Push(ERR_OPERATION_FAILED);
+ rb.Push(ResultOperationFailed);
return;
}
if (scaling_mode != NintendoScaleMode::ScaleToWindow &&
scaling_mode != NintendoScaleMode::PreserveAspectRatio) {
LOG_ERROR(Service_VI, "Unsupported scaling mode supplied.");
- rb.Push(ERR_UNSUPPORTED);
+ rb.Push(ResultNotSupported);
return;
}
@@ -594,7 +591,7 @@ private:
if (!display_id) {
LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -602,7 +599,7 @@ private:
if (!buffer_queue_id) {
LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -640,7 +637,7 @@ private:
if (!layer_id) {
LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -648,7 +645,7 @@ private:
if (!buffer_queue_id) {
LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(ResultNotFound);
return;
}
@@ -675,19 +672,23 @@ private:
IPC::RequestParser rp{ctx};
const u64 display_id = rp.Pop<u64>();
- LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id);
+ LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
- if (!vsync_event) {
- LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
+ if (vsync_event.Failed()) {
+ const auto result = vsync_event.Code();
+ if (result == ResultNotFound) {
+ LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
+ }
+
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_NOT_FOUND);
+ rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(ResultSuccess);
- rb.PushCopyObjects(vsync_event);
+ rb.PushCopyObjects(*vsync_event);
}
void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
@@ -764,7 +765,7 @@ private:
return ConvertedScaleMode::PreserveAspectRatio;
default:
LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
- return ERR_OPERATION_FAILED;
+ return ResultOperationFailed;
}
}
@@ -794,7 +795,7 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System&
if (!IsValidServiceAccess(permission, policy)) {
LOG_ERROR(Service_VI, "Permission denied for policy {}", policy);
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ERR_PERMISSION_DENIED);
+ rb.Push(ResultPermissionDenied);
return;
}
diff --git a/src/core/hle/service/vi/vi_results.h b/src/core/hle/service/vi/vi_results.h
new file mode 100644
index 000000000..a46c247d2
--- /dev/null
+++ b/src/core/hle/service/vi/vi_results.h
@@ -0,0 +1,13 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/result.h"
+
+namespace Service::VI {
+
+constexpr Result ResultOperationFailed{ErrorModule::VI, 1};
+constexpr Result ResultPermissionDenied{ErrorModule::VI, 5};
+constexpr Result ResultNotSupported{ErrorModule::VI, 6};
+constexpr Result ResultNotFound{ErrorModule::VI, 7};
+
+} // namespace Service::VI