clean up old files
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/am/tcap.h"
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
TCAP::TCAP(Core::System& system_) : ServiceFramework{system_, "tcap"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetContinuousHighSkinTemperatureEvent"},
|
||||
{1, nullptr, "SetOperationMode"},
|
||||
{2, nullptr, "LoadAndApplySettings"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
TCAP::~TCAP() = default;
|
||||
|
||||
} // namespace Service::AM
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::AM {
|
||||
|
||||
class TCAP final : public ServiceFramework<TCAP> {
|
||||
public:
|
||||
explicit TCAP(Core::System& system_);
|
||||
~TCAP() override;
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
||||
@@ -1,21 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/audio/auddbg.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
AudDbg::AudDbg(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestSuspendForDebug"},
|
||||
{1, nullptr, "RequestResumeForDebug"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudDbg::~AudDbg() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class AudDbg final : public ServiceFramework<AudDbg> {
|
||||
public:
|
||||
explicit AudDbg(Core::System& system_, const char* name);
|
||||
~AudDbg() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,23 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/audio/audin_a.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
AudInA::AudInA(Core::System& system_) : ServiceFramework{system_, "audin:a"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestSuspend"},
|
||||
{1, nullptr, "RequestResume"},
|
||||
{2, nullptr, "GetProcessMasterVolume"},
|
||||
{3, nullptr, "SetProcessMasterVolume"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudInA::~AudInA() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class AudInA final : public ServiceFramework<AudInA> {
|
||||
public:
|
||||
explicit AudInA(Core::System& system_);
|
||||
~AudInA() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/audio/audout_a.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
AudOutA::AudOutA(Core::System& system_) : ServiceFramework{system_, "audout:a"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestSuspend"},
|
||||
{1, nullptr, "RequestResume"},
|
||||
{2, nullptr, "GetProcessMasterVolume"},
|
||||
{3, nullptr, "SetProcessMasterVolume"},
|
||||
{4, nullptr, "GetProcessRecordVolume"},
|
||||
{5, nullptr, "SetProcessRecordVolume"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudOutA::~AudOutA() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class AudOutA final : public ServiceFramework<AudOutA> {
|
||||
public:
|
||||
explicit AudOutA(Core::System& system_);
|
||||
~AudOutA() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,27 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/audio/audren_a.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
AudRenA::AudRenA(Core::System& system_) : ServiceFramework{system_, "audren:a"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "RequestSuspend"},
|
||||
{1, nullptr, "RequestResume"},
|
||||
{2, nullptr, "GetProcessMasterVolume"},
|
||||
{3, nullptr, "SetProcessMasterVolume"},
|
||||
{4, nullptr, "RegisterAppletResourceUserId"},
|
||||
{5, nullptr, "UnregisterAppletResourceUserId"},
|
||||
{6, nullptr, "GetProcessRecordVolume"},
|
||||
{7, nullptr, "SetProcessRecordVolume"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudRenA::~AudRenA() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class AudRenA final : public ServiceFramework<AudRenA> {
|
||||
public:
|
||||
explicit AudRenA(Core::System& system_);
|
||||
~AudRenA() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,29 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/audio/codecctl.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
CodecCtl::CodecCtl(Core::System& system_) : ServiceFramework{system_, "codecctl"} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Initialize"},
|
||||
{1, nullptr, "Finalize"},
|
||||
{2, nullptr, "Sleep"},
|
||||
{3, nullptr, "Wake"},
|
||||
{4, nullptr, "SetVolume"},
|
||||
{5, nullptr, "GetVolumeMax"},
|
||||
{6, nullptr, "GetVolumeMin"},
|
||||
{7, nullptr, "SetActiveTarget"},
|
||||
{8, nullptr, "GetActiveTarget"},
|
||||
{9, nullptr, "BindHeadphoneMicJackInterrupt"},
|
||||
{10, nullptr, "IsHeadphoneMicJackInserted"},
|
||||
{11, nullptr, "ClearHeadphoneMicJackInterrupt"},
|
||||
{12, nullptr, "IsRequested"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
CodecCtl::~CodecCtl() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,20 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
class CodecCtl final : public ServiceFramework<CodecCtl> {
|
||||
public:
|
||||
explicit CodecCtl(Core::System& system_);
|
||||
~CodecCtl() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
@@ -1,11 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::Friend {
|
||||
|
||||
constexpr Result ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15};
|
||||
}
|
||||
@@ -1,400 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/nfc/mifare_user.h"
|
||||
#include "core/hle/service/nfc/nfc_device.h"
|
||||
#include "core/hle/service/nfc/nfc_result.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
|
||||
MFIUser::MFIUser(Core::System& system_)
|
||||
: ServiceFramework{system_, "NFC::MFIUser"}, service_context{system_, service_name} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &MFIUser::Initialize, "Initialize"},
|
||||
{1, &MFIUser::Finalize, "Finalize"},
|
||||
{2, &MFIUser::ListDevices, "ListDevices"},
|
||||
{3, &MFIUser::StartDetection, "StartDetection"},
|
||||
{4, &MFIUser::StopDetection, "StopDetection"},
|
||||
{5, &MFIUser::Read, "Read"},
|
||||
{6, &MFIUser::Write, "Write"},
|
||||
{7, &MFIUser::GetTagInfo, "GetTagInfo"},
|
||||
{8, &MFIUser::GetActivateEventHandle, "GetActivateEventHandle"},
|
||||
{9, &MFIUser::GetDeactivateEventHandle, "GetDeactivateEventHandle"},
|
||||
{10, &MFIUser::GetState, "GetState"},
|
||||
{11, &MFIUser::GetDeviceState, "GetDeviceState"},
|
||||
{12, &MFIUser::GetNpadId, "GetNpadId"},
|
||||
{13, &MFIUser::GetAvailabilityChangeEventHandle, "GetAvailabilityChangeEventHandle"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
availability_change_event = service_context.CreateEvent("MFIUser:AvailabilityChangeEvent");
|
||||
|
||||
for (u32 device_index = 0; device_index < 10; device_index++) {
|
||||
devices[device_index] =
|
||||
std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
|
||||
service_context, availability_change_event);
|
||||
}
|
||||
}
|
||||
|
||||
MFIUser ::~MFIUser() {
|
||||
availability_change_event->Close();
|
||||
}
|
||||
|
||||
void MFIUser::Initialize(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 MFIUser::Finalize(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
state = State::NonInitialized;
|
||||
|
||||
for (auto& device : devices) {
|
||||
device->Finalize();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void MFIUser::ListDevices(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.CanWriteBuffer()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareInvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.GetWriteBufferSize() == 0) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareInvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u64> nfp_devices;
|
||||
const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
|
||||
|
||||
for (const auto& device : devices) {
|
||||
if (nfp_devices.size() >= max_allowed_devices) {
|
||||
continue;
|
||||
}
|
||||
if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
|
||||
nfp_devices.push_back(device->GetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
if (nfp_devices.empty()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(nfp_devices);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<s32>(nfp_devices.size()));
|
||||
}
|
||||
|
||||
void MFIUser::StartDetection(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = device.value()->StartDetection(NFP::TagProtocol::All);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void MFIUser::StopDetection(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = device.value()->StopDetection();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void MFIUser::Read(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto buffer{ctx.ReadBuffer()};
|
||||
const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareReadBlockParameter>()};
|
||||
std::vector<NFP::MifareReadBlockParameter> read_commands(number_of_commands);
|
||||
|
||||
memcpy(read_commands.data(), buffer.data(),
|
||||
number_of_commands * sizeof(NFP::MifareReadBlockParameter));
|
||||
|
||||
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}",
|
||||
device_handle, number_of_commands);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
Result result = ResultSuccess;
|
||||
std::vector<NFP::MifareReadBlockData> out_data(number_of_commands);
|
||||
for (std::size_t i = 0; i < number_of_commands; i++) {
|
||||
result = device.value()->MifareRead(read_commands[i], out_data[i]);
|
||||
if (result.IsError()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(out_data);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void MFIUser::Write(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto buffer{ctx.ReadBuffer()};
|
||||
const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareWriteBlockParameter>()};
|
||||
std::vector<NFP::MifareWriteBlockParameter> write_commands(number_of_commands);
|
||||
|
||||
memcpy(write_commands.data(), buffer.data(),
|
||||
number_of_commands * sizeof(NFP::MifareWriteBlockParameter));
|
||||
|
||||
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, write_commands_size={}",
|
||||
device_handle, number_of_commands);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
Result result = ResultSuccess;
|
||||
std::vector<NFP::MifareReadBlockData> out_data(number_of_commands);
|
||||
for (std::size_t i = 0; i < number_of_commands; i++) {
|
||||
result = device.value()->MifareWrite(write_commands[i]);
|
||||
if (result.IsError()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = device.value()->Flush();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void MFIUser::GetTagInfo(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
NFP::TagInfo tag_info{};
|
||||
const auto result = device.value()->GetTagInfo(tag_info, true);
|
||||
ctx.WriteBuffer(tag_info);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void MFIUser::GetActivateEventHandle(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(device.value()->GetActivateEvent());
|
||||
}
|
||||
|
||||
void MFIUser::GetDeactivateEventHandle(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(device.value()->GetDeactivateEvent());
|
||||
}
|
||||
|
||||
void MFIUser::GetState(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(state);
|
||||
}
|
||||
|
||||
void MFIUser::GetDeviceState(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(device.value()->GetCurrentState());
|
||||
}
|
||||
|
||||
void MFIUser::GetNpadId(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareDeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(device.value()->GetNpadId());
|
||||
}
|
||||
|
||||
void MFIUser::GetAvailabilityChangeEventHandle(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(MifareNfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(availability_change_event->GetReadableEvent());
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<NfcDevice>> MFIUser::GetNfcDevice(u64 handle) {
|
||||
for (auto& device : devices) {
|
||||
if (device->GetHandle() == handle) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
class NfcDevice;
|
||||
|
||||
class MFIUser final : public ServiceFramework<MFIUser> {
|
||||
public:
|
||||
explicit MFIUser(Core::System& system_);
|
||||
~MFIUser();
|
||||
|
||||
private:
|
||||
enum class State : u32 {
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
};
|
||||
|
||||
void Initialize(HLERequestContext& ctx);
|
||||
void Finalize(HLERequestContext& ctx);
|
||||
void ListDevices(HLERequestContext& ctx);
|
||||
void StartDetection(HLERequestContext& ctx);
|
||||
void StopDetection(HLERequestContext& ctx);
|
||||
void Read(HLERequestContext& ctx);
|
||||
void Write(HLERequestContext& ctx);
|
||||
void GetTagInfo(HLERequestContext& ctx);
|
||||
void GetActivateEventHandle(HLERequestContext& ctx);
|
||||
void GetDeactivateEventHandle(HLERequestContext& ctx);
|
||||
void GetState(HLERequestContext& ctx);
|
||||
void GetDeviceState(HLERequestContext& ctx);
|
||||
void GetNpadId(HLERequestContext& ctx);
|
||||
void GetAvailabilityChangeEventHandle(HLERequestContext& ctx);
|
||||
|
||||
std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
std::array<std::shared_ptr<NfcDevice>, 10> devices{};
|
||||
|
||||
State state{State::NonInitialized};
|
||||
Kernel::KEvent* availability_change_event;
|
||||
};
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -1,288 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/input.h"
|
||||
#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/kernel/k_event.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/nfc/nfc_device.h"
|
||||
#include "core/hle/service/nfc/nfc_result.h"
|
||||
#include "core/hle/service/nfc/nfc_user.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
NfcDevice::NfcDevice(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:NFCActivateEvent");
|
||||
deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent");
|
||||
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);
|
||||
}
|
||||
|
||||
NfcDevice::~NfcDevice() {
|
||||
activate_event->Close();
|
||||
deactivate_event->Close();
|
||||
if (!is_controller_set) {
|
||||
return;
|
||||
}
|
||||
npad_device->DeleteCallback(callback_key);
|
||||
is_controller_set = false;
|
||||
};
|
||||
|
||||
void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
|
||||
if (!is_initalized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == Core::HID::ControllerTriggerType::Connected) {
|
||||
Initialize();
|
||||
availability_change_event->Signal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == Core::HID::ControllerTriggerType::Disconnected) {
|
||||
device_state = NFP::DeviceState::Unavailable;
|
||||
availability_change_event->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:
|
||||
LoadNfcTag(nfc_status.data);
|
||||
break;
|
||||
case Common::Input::NfcState::AmiiboRemoved:
|
||||
if (device_state != NFP::DeviceState::SearchingForTag) {
|
||||
CloseNfcTag();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
|
||||
if (device_state != NFP::DeviceState::SearchingForTag) {
|
||||
LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
|
||||
LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
tag_data.resize(data.size());
|
||||
memcpy(tag_data.data(), data.data(), data.size());
|
||||
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
|
||||
|
||||
device_state = NFP::DeviceState::TagFound;
|
||||
deactivate_event->GetReadableEvent().Clear();
|
||||
activate_event->Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
void NfcDevice::CloseNfcTag() {
|
||||
LOG_INFO(Service_NFC, "Remove nfc tag");
|
||||
|
||||
device_state = NFP::DeviceState::TagRemoved;
|
||||
encrypted_tag_data = {};
|
||||
activate_event->GetReadableEvent().Clear();
|
||||
deactivate_event->Signal();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
|
||||
return activate_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
|
||||
return deactivate_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
void NfcDevice::Initialize() {
|
||||
device_state =
|
||||
npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable;
|
||||
encrypted_tag_data = {};
|
||||
is_initalized = true;
|
||||
}
|
||||
|
||||
void NfcDevice::Finalize() {
|
||||
if (device_state == NFP::DeviceState::SearchingForTag ||
|
||||
device_state == NFP::DeviceState::TagRemoved) {
|
||||
StopDetection();
|
||||
}
|
||||
device_state = NFP::DeviceState::Unavailable;
|
||||
is_initalized = false;
|
||||
}
|
||||
|
||||
Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
|
||||
if (device_state != NFP::DeviceState::Initialized &&
|
||||
device_state != NFP::DeviceState::TagRemoved) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::NFC) !=
|
||||
Common::Input::DriverResult::Success) {
|
||||
LOG_ERROR(Service_NFC, "Nfc not supported");
|
||||
return NfcDisabled;
|
||||
}
|
||||
|
||||
device_state = NFP::DeviceState::SearchingForTag;
|
||||
allowed_protocols = allowed_protocol;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::StopDetection() {
|
||||
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
|
||||
Common::Input::PollingMode::Active);
|
||||
|
||||
if (device_state == NFP::DeviceState::Initialized) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (device_state == NFP::DeviceState::TagFound ||
|
||||
device_state == NFP::DeviceState::TagMounted) {
|
||||
CloseNfcTag();
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (device_state == NFP::DeviceState::SearchingForTag ||
|
||||
device_state == NFP::DeviceState::TagRemoved) {
|
||||
device_state = NFP::DeviceState::Initialized;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
Result NfcDevice::Flush() {
|
||||
if (device_state != NFP::DeviceState::TagFound &&
|
||||
device_state != NFP::DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == NFP::DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (!npad_device->WriteNfc(tag_data)) {
|
||||
LOG_ERROR(Service_NFP, "Error writing to file");
|
||||
return MifareReadError;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
|
||||
if (device_state != NFP::DeviceState::TagFound &&
|
||||
device_state != NFP::DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == NFP::DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (is_mifare) {
|
||||
tag_info = {
|
||||
.uuid = encrypted_tag_data.uuid.uid,
|
||||
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
|
||||
.protocol = NFP::TagProtocol::TypeA,
|
||||
.tag_type = NFP::TagType::Type4,
|
||||
};
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
// Protocol and tag type may change here
|
||||
tag_info = {
|
||||
.uuid = encrypted_tag_data.uuid.uid,
|
||||
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
|
||||
.protocol = NFP::TagProtocol::TypeA,
|
||||
.tag_type = NFP::TagType::Type2,
|
||||
};
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::MifareRead(const NFP::MifareReadBlockParameter& parameter,
|
||||
NFP::MifareReadBlockData& read_block_data) {
|
||||
const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock);
|
||||
read_block_data.sector_number = parameter.sector_number;
|
||||
|
||||
if (device_state != NFP::DeviceState::TagFound &&
|
||||
device_state != NFP::DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == NFP::DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) {
|
||||
return MifareReadError;
|
||||
}
|
||||
|
||||
// TODO: Use parameter.sector_key to read encrypted data
|
||||
memcpy(read_block_data.data.data(), tag_data.data() + sector_index, sizeof(NFP::DataBlock));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::MifareWrite(const NFP::MifareWriteBlockParameter& parameter) {
|
||||
const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock);
|
||||
|
||||
if (device_state != NFP::DeviceState::TagFound &&
|
||||
device_state != NFP::DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == NFP::DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) {
|
||||
return MifareReadError;
|
||||
}
|
||||
|
||||
// TODO: Use parameter.sector_key to encrypt the data
|
||||
memcpy(tag_data.data() + sector_index, parameter.data.data(), sizeof(NFP::DataBlock));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
u64 NfcDevice::GetHandle() const {
|
||||
// Generate a handle based of the npad id
|
||||
return static_cast<u64>(npad_id);
|
||||
}
|
||||
|
||||
NFP::DeviceState NfcDevice::GetCurrentState() const {
|
||||
return device_state;
|
||||
}
|
||||
|
||||
Core::HID::NpadIdType NfcDevice::GetNpadId() const {
|
||||
return npad_id;
|
||||
}
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -1,78 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/kernel_helpers.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::NFC {
|
||||
class NfcDevice {
|
||||
public:
|
||||
NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
|
||||
KernelHelpers::ServiceContext& service_context_,
|
||||
Kernel::KEvent* availability_change_event_);
|
||||
~NfcDevice();
|
||||
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
|
||||
Result StartDetection(NFP::TagProtocol allowed_protocol);
|
||||
Result StopDetection();
|
||||
Result Flush();
|
||||
|
||||
Result GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const;
|
||||
|
||||
Result MifareRead(const NFP::MifareReadBlockParameter& parameter,
|
||||
NFP::MifareReadBlockData& read_block_data);
|
||||
|
||||
Result MifareWrite(const NFP::MifareWriteBlockParameter& parameter);
|
||||
|
||||
u64 GetHandle() const;
|
||||
NFP::DeviceState GetCurrentState() const;
|
||||
Core::HID::NpadIdType GetNpadId() const;
|
||||
|
||||
Kernel::KReadableEvent& GetActivateEvent() const;
|
||||
Kernel::KReadableEvent& GetDeactivateEvent() const;
|
||||
|
||||
private:
|
||||
void NpadUpdate(Core::HID::ControllerTriggerType type);
|
||||
bool LoadNfcTag(std::span<const u8> data);
|
||||
void CloseNfcTag();
|
||||
|
||||
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_initalized{};
|
||||
NFP::TagProtocol allowed_protocols{};
|
||||
NFP::DeviceState device_state{NFP::DeviceState::Unavailable};
|
||||
|
||||
NFP::EncryptedNTAG215File encrypted_tag_data{};
|
||||
std::vector<u8> tag_data{};
|
||||
};
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -1,365 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/nfc/nfc_device.h"
|
||||
#include "core/hle/service/nfc/nfc_result.h"
|
||||
#include "core/hle/service/nfc/nfc_user.h"
|
||||
#include "core/hle/service/time/clock_types.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
|
||||
IUser::IUser(Core::System& system_)
|
||||
: ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IUser::Initialize, "InitializeOld"},
|
||||
{1, &IUser::Finalize, "FinalizeOld"},
|
||||
{2, &IUser::GetState, "GetStateOld"},
|
||||
{3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"},
|
||||
{400, &IUser::Initialize, "Initialize"},
|
||||
{401, &IUser::Finalize, "Finalize"},
|
||||
{402, &IUser::GetState, "GetState"},
|
||||
{403, &IUser::IsNfcEnabled, "IsNfcEnabled"},
|
||||
{404, &IUser::ListDevices, "ListDevices"},
|
||||
{405, &IUser::GetDeviceState, "GetDeviceState"},
|
||||
{406, &IUser::GetNpadId, "GetNpadId"},
|
||||
{407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
|
||||
{408, &IUser::StartDetection, "StartDetection"},
|
||||
{409, &IUser::StopDetection, "StopDetection"},
|
||||
{410, &IUser::GetTagInfo, "GetTagInfo"},
|
||||
{411, &IUser::AttachActivateEvent, "AttachActivateEvent"},
|
||||
{412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
|
||||
{1000, nullptr, "ReadMifare"},
|
||||
{1001, nullptr, "WriteMifare"},
|
||||
{1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"},
|
||||
{1301, nullptr, "KeepPassThroughSession"},
|
||||
{1302, nullptr, "ReleasePassThroughSession"},
|
||||
};
|
||||
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<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
|
||||
service_context, availability_change_event);
|
||||
}
|
||||
}
|
||||
|
||||
IUser ::~IUser() {
|
||||
availability_change_event->Close();
|
||||
}
|
||||
|
||||
void IUser::Initialize(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(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
state = State::NonInitialized;
|
||||
|
||||
for (auto& device : devices) {
|
||||
device->Finalize();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void IUser::GetState(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(state);
|
||||
}
|
||||
|
||||
void IUser::IsNfcEnabled(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(state != State::NonInitialized);
|
||||
}
|
||||
|
||||
void IUser::ListDevices(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "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.GetWriteBufferNumElements<u64>();
|
||||
|
||||
for (auto& device : devices) {
|
||||
if (nfp_devices.size() >= max_allowed_devices) {
|
||||
continue;
|
||||
}
|
||||
if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
|
||||
nfp_devices.push_back(device->GetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
if (nfp_devices.empty()) {
|
||||
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::GetDeviceState(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
auto device = GetNfcDevice(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(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(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::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "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::StartDetection(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto nfp_protocol{rp.PopEnum<NFP::TagProtocol>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(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(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(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::GetTagInfo(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
NFP::TagInfo tag_info{};
|
||||
const auto result = device.value()->GetTagInfo(tag_info, false);
|
||||
ctx.WriteBuffer(tag_info);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void IUser::AttachActivateEvent(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(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(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(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::SendCommandByPassThrough(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()};
|
||||
const auto command_data{ctx.ReadBuffer()};
|
||||
|
||||
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}",
|
||||
device_handle, timeout.ToSeconds(), command_data.size());
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> out_data(1);
|
||||
// TODO: Request data from nfc device
|
||||
ctx.WriteBuffer(out_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<u32>(out_data.size()));
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) {
|
||||
for (auto& device : devices) {
|
||||
if (device->GetHandle() == handle) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -1,52 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
class NfcDevice;
|
||||
|
||||
class IUser final : public ServiceFramework<IUser> {
|
||||
public:
|
||||
explicit IUser(Core::System& system_);
|
||||
~IUser();
|
||||
|
||||
private:
|
||||
enum class State : u32 {
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
};
|
||||
|
||||
void Initialize(HLERequestContext& ctx);
|
||||
void Finalize(HLERequestContext& ctx);
|
||||
void GetState(HLERequestContext& ctx);
|
||||
void IsNfcEnabled(HLERequestContext& ctx);
|
||||
void ListDevices(HLERequestContext& ctx);
|
||||
void GetDeviceState(HLERequestContext& ctx);
|
||||
void GetNpadId(HLERequestContext& ctx);
|
||||
void AttachAvailabilityChangeEvent(HLERequestContext& ctx);
|
||||
void StartDetection(HLERequestContext& ctx);
|
||||
void StopDetection(HLERequestContext& ctx);
|
||||
void GetTagInfo(HLERequestContext& ctx);
|
||||
void AttachActivateEvent(HLERequestContext& ctx);
|
||||
void AttachDeactivateEvent(HLERequestContext& ctx);
|
||||
void SendCommandByPassThrough(HLERequestContext& ctx);
|
||||
|
||||
std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
std::array<std::shared_ptr<NfcDevice>, 10> devices{};
|
||||
|
||||
State state{State::NonInitialized};
|
||||
Kernel::KEvent* availability_change_event;
|
||||
};
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -1,405 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <array>
|
||||
#include <mbedtls/aes.h>
|
||||
#include <mbedtls/hmac_drbg.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/nfp/amiibo_crypto.h"
|
||||
|
||||
namespace Service::NFP::AmiiboCrypto {
|
||||
|
||||
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_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
|
||||
|
||||
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);
|
||||
LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
|
||||
|
||||
// Validate UUID
|
||||
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 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.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;
|
||||
}
|
||||
|
||||
// Check against all know constants on an amiibo binary
|
||||
if (ntag_file.static_lock != 0xE00F) {
|
||||
return false;
|
||||
}
|
||||
if (ntag_file.compability_container != 0xEEFF10F1U) {
|
||||
return false;
|
||||
}
|
||||
if (amiibo_data.constant_value != 0xA5) {
|
||||
return false;
|
||||
}
|
||||
if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
|
||||
return false;
|
||||
}
|
||||
if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
|
||||
return false;
|
||||
}
|
||||
if (ntag_file.CFG0 != 0x04000000U) {
|
||||
return false;
|
||||
}
|
||||
if (ntag_file.CFG1 != 0x5F) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsAmiiboValid(const NTAG215File& ntag_file) {
|
||||
return IsAmiiboValid(EncodedDataToNfcData(ntag_file));
|
||||
}
|
||||
|
||||
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
|
||||
NTAG215File encoded_data{};
|
||||
|
||||
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;
|
||||
encoded_data.constant_value = nfc_data.user_memory.constant_value;
|
||||
encoded_data.write_counter = nfc_data.user_memory.write_counter;
|
||||
encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version;
|
||||
encoded_data.settings = nfc_data.user_memory.settings;
|
||||
encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
|
||||
encoded_data.application_id = nfc_data.user_memory.application_id;
|
||||
encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter;
|
||||
encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
|
||||
encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte;
|
||||
encoded_data.unknown = nfc_data.user_memory.unknown;
|
||||
encoded_data.mii_extension = nfc_data.user_memory.mii_extension;
|
||||
encoded_data.unknown2 = nfc_data.user_memory.unknown2;
|
||||
encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc;
|
||||
encoded_data.application_area = nfc_data.user_memory.application_area;
|
||||
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
|
||||
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;
|
||||
encoded_data.CFG0 = nfc_data.CFG0;
|
||||
encoded_data.CFG1 = nfc_data.CFG1;
|
||||
encoded_data.password = nfc_data.password;
|
||||
|
||||
return encoded_data;
|
||||
}
|
||||
|
||||
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
|
||||
EncryptedNTAG215File nfc_data{};
|
||||
|
||||
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;
|
||||
nfc_data.user_memory.constant_value = encoded_data.constant_value;
|
||||
nfc_data.user_memory.write_counter = encoded_data.write_counter;
|
||||
nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version;
|
||||
nfc_data.user_memory.settings = encoded_data.settings;
|
||||
nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
|
||||
nfc_data.user_memory.application_id = encoded_data.application_id;
|
||||
nfc_data.user_memory.application_write_counter = encoded_data.application_write_counter;
|
||||
nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
|
||||
nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte;
|
||||
nfc_data.user_memory.unknown = encoded_data.unknown;
|
||||
nfc_data.user_memory.mii_extension = encoded_data.mii_extension;
|
||||
nfc_data.user_memory.unknown2 = encoded_data.unknown2;
|
||||
nfc_data.user_memory.register_info_crc = encoded_data.register_info_crc;
|
||||
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;
|
||||
nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
|
||||
nfc_data.dynamic_lock = encoded_data.dynamic_lock;
|
||||
nfc_data.CFG0 = encoded_data.CFG0;
|
||||
nfc_data.CFG1 = encoded_data.CFG1;
|
||||
nfc_data.password = encoded_data.password;
|
||||
|
||||
return nfc_data;
|
||||
}
|
||||
|
||||
u32 GetTagPassword(const TagUuid& uuid) {
|
||||
// Verify that the generated password is correct
|
||||
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;
|
||||
}
|
||||
|
||||
HashSeed GetSeed(const NTAG215File& data) {
|
||||
HashSeed seed{
|
||||
.magic = data.write_counter,
|
||||
.padding = {},
|
||||
.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,
|
||||
};
|
||||
|
||||
return seed;
|
||||
}
|
||||
|
||||
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
|
||||
const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length;
|
||||
const std::size_t string_size = key.type_string.size();
|
||||
std::vector<u8> output(string_size + seedPart1Len);
|
||||
|
||||
// Copy whole type string
|
||||
memccpy(output.data(), key.type_string.data(), '\0', string_size);
|
||||
|
||||
// Append (16 - magic_length) from the input seed
|
||||
memcpy(output.data() + string_size, &seed, seedPart1Len);
|
||||
|
||||
// Append all bytes from magicBytes
|
||||
output.insert(output.end(), key.magic_bytes.begin(),
|
||||
key.magic_bytes.begin() + key.magic_length);
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
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;
|
||||
ctx.buffer_size = sizeof(ctx.counter) + seed.size();
|
||||
memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
|
||||
|
||||
// Initialize HMAC context
|
||||
mbedtls_md_init(&hmac_ctx);
|
||||
mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
|
||||
mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
|
||||
}
|
||||
|
||||
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
|
||||
// If used at least once, reinitialize the HMAC
|
||||
if (ctx.used) {
|
||||
mbedtls_md_hmac_reset(&hmac_ctx);
|
||||
}
|
||||
|
||||
ctx.used = true;
|
||||
|
||||
// Store counter in big endian, and increment it
|
||||
ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
|
||||
ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
|
||||
ctx.counter++;
|
||||
|
||||
// Do HMAC magic
|
||||
mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
|
||||
ctx.buffer_size);
|
||||
mbedtls_md_hmac_finish(&hmac_ctx, output.data());
|
||||
}
|
||||
|
||||
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
|
||||
const auto seed = GetSeed(data);
|
||||
|
||||
// Generate internal seed
|
||||
const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
|
||||
|
||||
// Initialize context
|
||||
CryptoCtx ctx{};
|
||||
mbedtls_md_context_t hmac_ctx;
|
||||
CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
|
||||
|
||||
// Generate derived keys
|
||||
DerivedKeys derived_keys{};
|
||||
std::array<DrgbOutput, 2> temp{};
|
||||
CryptoStep(ctx, hmac_ctx, temp[0]);
|
||||
CryptoStep(ctx, hmac_ctx, temp[1]);
|
||||
memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
|
||||
|
||||
// Cleanup context
|
||||
mbedtls_md_free(&hmac_ctx);
|
||||
|
||||
return derived_keys;
|
||||
}
|
||||
|
||||
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
|
||||
mbedtls_aes_context aes;
|
||||
std::size_t nc_off = 0;
|
||||
std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
|
||||
std::array<u8, sizeof(keys.aes_iv)> stream_block{};
|
||||
|
||||
const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
|
||||
mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
|
||||
memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));
|
||||
|
||||
constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
|
||||
mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
|
||||
stream_block.data(),
|
||||
reinterpret_cast<const unsigned char*>(&in_data.settings),
|
||||
reinterpret_cast<unsigned char*>(&out_data.settings));
|
||||
|
||||
// Copy the rest of the data directly
|
||||
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.model_info = in_data.model_info;
|
||||
out_data.keygen_salt = in_data.keygen_salt;
|
||||
out_data.dynamic_lock = in_data.dynamic_lock;
|
||||
out_data.CFG0 = in_data.CFG0;
|
||||
out_data.CFG1 = in_data.CFG1;
|
||||
out_data.password = in_data.password;
|
||||
}
|
||||
|
||||
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
|
||||
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
|
||||
|
||||
const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin",
|
||||
Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::BinaryFile};
|
||||
|
||||
if (!keys_file.IsOpen()) {
|
||||
LOG_ERROR(Service_NFP, "Failed to open key file");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keys_file.Read(unfixed_info) != 1) {
|
||||
LOG_ERROR(Service_NFP, "Failed to read unfixed_info");
|
||||
return false;
|
||||
}
|
||||
if (keys_file.Read(locked_secret) != 1) {
|
||||
LOG_ERROR(Service_NFP, "Failed to read locked-secret");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsKeyAvailable() {
|
||||
const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
|
||||
return Common::FS::Exists(yuzu_keys_dir / "key_retail.bin");
|
||||
}
|
||||
|
||||
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
|
||||
InternalKey locked_secret{};
|
||||
InternalKey unfixed_info{};
|
||||
|
||||
if (!LoadKeys(locked_secret, unfixed_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate keys
|
||||
NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
|
||||
const auto data_keys = GenerateKey(unfixed_info, encoded_data);
|
||||
const auto tag_keys = GenerateKey(locked_secret, encoded_data);
|
||||
|
||||
// Decrypt
|
||||
Cipher(data_keys, encoded_data, tag_data);
|
||||
|
||||
// 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.uid),
|
||||
input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
|
||||
|
||||
// Regenerate data HMAC
|
||||
constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
|
||||
sizeof(HmacKey),
|
||||
reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
|
||||
reinterpret_cast<unsigned char*>(&tag_data.hmac_data));
|
||||
|
||||
if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
|
||||
LOG_ERROR(Service_NFP, "hmac_data doesn't match");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
|
||||
LOG_ERROR(Service_NFP, "hmac_tag doesn't match");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
|
||||
InternalKey locked_secret{};
|
||||
InternalKey unfixed_info{};
|
||||
|
||||
if (!LoadKeys(locked_secret, unfixed_info)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Generate keys
|
||||
const auto data_keys = GenerateKey(unfixed_info, tag_data);
|
||||
const auto tag_keys = GenerateKey(locked_secret, tag_data);
|
||||
|
||||
NTAG215File encoded_tag_data{};
|
||||
|
||||
// Generate tag HMAC
|
||||
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.uid),
|
||||
input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
|
||||
|
||||
// Init mbedtls HMAC context
|
||||
mbedtls_md_context_t ctx;
|
||||
mbedtls_md_init(&ctx);
|
||||
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
|
||||
|
||||
// Generate data HMAC
|
||||
mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
|
||||
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
|
||||
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.uid),
|
||||
input_length);
|
||||
mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
|
||||
|
||||
// HMAC cleanup
|
||||
mbedtls_md_free(&ctx);
|
||||
|
||||
// Encrypt
|
||||
Cipher(data_keys, tag_data, encoded_tag_data);
|
||||
|
||||
// Convert back to hardware
|
||||
encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Service::NFP::AmiiboCrypto
|
||||
@@ -1,106 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "core/hle/service/nfp/nfp_types.h"
|
||||
|
||||
struct mbedtls_md_context_t;
|
||||
|
||||
namespace Service::NFP::AmiiboCrypto {
|
||||
// Byte locations in Service::NFP::NTAG215File
|
||||
constexpr std::size_t HMAC_DATA_START = 0x8;
|
||||
constexpr std::size_t SETTINGS_START = 0x2c;
|
||||
constexpr std::size_t WRITE_COUNTER_START = 0x29;
|
||||
constexpr std::size_t HMAC_TAG_START = 0x1B4;
|
||||
constexpr std::size_t UUID_START = 0x1D4;
|
||||
constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
|
||||
|
||||
using HmacKey = std::array<u8, 0x10>;
|
||||
using DrgbOutput = std::array<u8, 0x20>;
|
||||
|
||||
struct HashSeed {
|
||||
u16_be magic;
|
||||
std::array<u8, 0xE> padding;
|
||||
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");
|
||||
|
||||
struct InternalKey {
|
||||
HmacKey hmac_key;
|
||||
std::array<char, 0xE> type_string;
|
||||
u8 reserved;
|
||||
u8 magic_length;
|
||||
std::array<u8, 0x10> magic_bytes;
|
||||
std::array<u8, 0x20> xor_pad;
|
||||
};
|
||||
static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
|
||||
static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
|
||||
|
||||
struct CryptoCtx {
|
||||
std::array<char, 480> buffer;
|
||||
bool used;
|
||||
std::size_t buffer_size;
|
||||
s16 counter;
|
||||
};
|
||||
|
||||
struct DerivedKeys {
|
||||
std::array<u8, 0x10> aes_key;
|
||||
std::array<u8, 0x10> aes_iv;
|
||||
std::array<u8, 0x10> hmac_key;
|
||||
};
|
||||
static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
|
||||
|
||||
/// Validates that the amiibo file is not corrupted
|
||||
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
|
||||
|
||||
/// Validates that the amiibo file is not corrupted
|
||||
bool IsAmiiboValid(const NTAG215File& ntag_file);
|
||||
|
||||
/// Converts from encrypted file format to encoded file format
|
||||
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
|
||||
|
||||
/// Converts from encoded file format to encrypted file format
|
||||
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
|
||||
|
||||
/// Returns password needed to allow write access to protected memory
|
||||
u32 GetTagPassword(const TagUuid& uuid);
|
||||
|
||||
// Generates Seed needed for key derivation
|
||||
HashSeed GetSeed(const NTAG215File& data);
|
||||
|
||||
// Middle step on the generation of derived keys
|
||||
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
|
||||
|
||||
// Initializes mbedtls context
|
||||
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
|
||||
const std::vector<u8>& seed);
|
||||
|
||||
// Feeds data to mbedtls context to generate the derived key
|
||||
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output);
|
||||
|
||||
// Generates the derived key from amiibo data
|
||||
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
|
||||
|
||||
// Encodes or decodes amiibo data
|
||||
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
|
||||
|
||||
/// Loads both amiibo keys from key_retail.bin
|
||||
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
|
||||
|
||||
/// Returns true if key_retail.bin exist
|
||||
bool IsKeyAvailable();
|
||||
|
||||
/// Decodes encrypted amiibo data returns true if output is valid
|
||||
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
|
||||
|
||||
/// Encodes plain amiibo data returns true if output is valid
|
||||
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
|
||||
|
||||
} // namespace Service::NFP::AmiiboCrypto
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,120 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/kernel_helpers.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(TagProtocol allowed_protocol);
|
||||
Result StopDetection();
|
||||
Result Mount(MountTarget mount_target);
|
||||
Result Unmount();
|
||||
|
||||
Result Flush();
|
||||
Result FlushDebug();
|
||||
Result FlushWithBreak(BreakType break_type);
|
||||
|
||||
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 GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const;
|
||||
Result GetAdminInfo(AdminInfo& admin_info) const;
|
||||
|
||||
Result DeleteRegisterInfo();
|
||||
Result SetRegisterInfoPrivate(const AmiiboName& amiibo_name);
|
||||
Result RestoreAmiibo();
|
||||
Result Format();
|
||||
|
||||
Result OpenApplicationArea(u32 access_id);
|
||||
Result GetApplicationAreaId(u32& application_area_id) const;
|
||||
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();
|
||||
Result ExistApplicationArea(bool& has_application_area);
|
||||
|
||||
Result GetAll(NfpData& data) const;
|
||||
Result SetAll(const NfpData& data);
|
||||
Result BreakTag(BreakType break_type);
|
||||
Result ReadBackupData();
|
||||
Result WriteBackupData();
|
||||
Result WriteNtf();
|
||||
|
||||
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;
|
||||
u64 RemoveVersionByte(u64 application_id) const;
|
||||
void UpdateSettingsCrc();
|
||||
void UpdateRegisterInfoCrc();
|
||||
|
||||
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_initalized{};
|
||||
bool is_data_moddified{};
|
||||
bool is_app_area_open{};
|
||||
bool is_plain_amiibo{};
|
||||
TagProtocol allowed_protocols{};
|
||||
s64 current_posix_time{};
|
||||
MountTarget mount_target{MountTarget::None};
|
||||
DeviceState device_state{DeviceState::Unavailable};
|
||||
|
||||
NTAG215File tag_data{};
|
||||
EncryptedNTAG215File encrypted_tag_data{};
|
||||
};
|
||||
|
||||
} // namespace Service::NFP
|
||||
@@ -1,672 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/ipc_helpers.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 {
|
||||
|
||||
IUser::IUser(Core::System& system_)
|
||||
: ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
|
||||
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, &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);
|
||||
}
|
||||
}
|
||||
|
||||
IUser ::~IUser() {
|
||||
availability_change_event->Close();
|
||||
}
|
||||
|
||||
void IUser::Initialize(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFP, "called");
|
||||
|
||||
state = State::Initialized;
|
||||
|
||||
for (auto& device : devices) {
|
||||
device->Initialize();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void IUser::Finalize(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(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(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.GetWriteBufferNumElements<u64>();
|
||||
|
||||
for (const 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.empty()) {
|
||||
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(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto nfp_protocol{rp.PopEnum<TagProtocol>()};
|
||||
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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFP, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(state);
|
||||
}
|
||||
|
||||
void IUser::GetDeviceState(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(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(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());
|
||||
}
|
||||
|
||||
void IUser::AttachAvailabilityChangeEvent(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(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
|
||||
@@ -1,63 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NFP {
|
||||
class NfpDevice;
|
||||
|
||||
class IUser final : public ServiceFramework<IUser> {
|
||||
public:
|
||||
explicit IUser(Core::System& system_);
|
||||
~IUser();
|
||||
|
||||
private:
|
||||
enum class State : u32 {
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
};
|
||||
|
||||
void Initialize(HLERequestContext& ctx);
|
||||
void Finalize(HLERequestContext& ctx);
|
||||
void ListDevices(HLERequestContext& ctx);
|
||||
void StartDetection(HLERequestContext& ctx);
|
||||
void StopDetection(HLERequestContext& ctx);
|
||||
void Mount(HLERequestContext& ctx);
|
||||
void Unmount(HLERequestContext& ctx);
|
||||
void OpenApplicationArea(HLERequestContext& ctx);
|
||||
void GetApplicationArea(HLERequestContext& ctx);
|
||||
void SetApplicationArea(HLERequestContext& ctx);
|
||||
void Flush(HLERequestContext& ctx);
|
||||
void Restore(HLERequestContext& ctx);
|
||||
void CreateApplicationArea(HLERequestContext& ctx);
|
||||
void GetTagInfo(HLERequestContext& ctx);
|
||||
void GetRegisterInfo(HLERequestContext& ctx);
|
||||
void GetCommonInfo(HLERequestContext& ctx);
|
||||
void GetModelInfo(HLERequestContext& ctx);
|
||||
void AttachActivateEvent(HLERequestContext& ctx);
|
||||
void AttachDeactivateEvent(HLERequestContext& ctx);
|
||||
void GetState(HLERequestContext& ctx);
|
||||
void GetDeviceState(HLERequestContext& ctx);
|
||||
void GetNpadId(HLERequestContext& ctx);
|
||||
void GetApplicationAreaSize(HLERequestContext& ctx);
|
||||
void AttachAvailabilityChangeEvent(HLERequestContext& ctx);
|
||||
void RecreateApplicationArea(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
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/binder/IBinder.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class HLERequestContext;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
enum class TransactionId {
|
||||
RequestBuffer = 1,
|
||||
SetBufferCount = 2,
|
||||
DequeueBuffer = 3,
|
||||
DetachBuffer = 4,
|
||||
DetachNextBuffer = 5,
|
||||
AttachBuffer = 6,
|
||||
QueueBuffer = 7,
|
||||
CancelBuffer = 8,
|
||||
Query = 9,
|
||||
Connect = 10,
|
||||
Disconnect = 11,
|
||||
AllocateBuffers = 13,
|
||||
SetPreallocatedBuffer = 14,
|
||||
GetBufferHistory = 17,
|
||||
};
|
||||
|
||||
class IBinder {
|
||||
public:
|
||||
virtual ~IBinder() = default;
|
||||
virtual void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code,
|
||||
u32 flags) = 0;
|
||||
virtual Kernel::KReadableEvent& GetNativeHandle() = 0;
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,46 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItem.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hle/service/nvflinger/ui/fence.h"
|
||||
#include "core/hle/service/nvflinger/window.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class GraphicBuffer;
|
||||
|
||||
class BufferItem final {
|
||||
public:
|
||||
constexpr BufferItem() = default;
|
||||
|
||||
std::shared_ptr<GraphicBuffer> graphic_buffer;
|
||||
Fence fence;
|
||||
Common::Rectangle<s32> crop;
|
||||
NativeWindowTransform transform{};
|
||||
u32 scaling_mode{};
|
||||
s64 timestamp{};
|
||||
bool is_auto_timestamp{};
|
||||
u64 frame_number{};
|
||||
|
||||
// The default value for buf, used to indicate this doesn't correspond to a slot.
|
||||
static constexpr s32 INVALID_BUFFER_SLOT = -1;
|
||||
union {
|
||||
s32 slot{INVALID_BUFFER_SLOT};
|
||||
s32 buf;
|
||||
};
|
||||
|
||||
bool is_droppable{};
|
||||
bool acquire_called{};
|
||||
bool transform_to_display_inverse{};
|
||||
s32 swap_interval{};
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,59 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferItemConsumer.cpp
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/nvflinger/buffer_item.h"
|
||||
#include "core/hle/service/nvflinger/buffer_item_consumer.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
BufferItemConsumer::BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer_)
|
||||
: ConsumerBase{std::move(consumer_)} {}
|
||||
|
||||
Status BufferItemConsumer::AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
|
||||
bool wait_for_fence) {
|
||||
if (!item) {
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (const auto status = AcquireBufferLocked(item, present_when); status != Status::NoError) {
|
||||
if (status != Status::NoBufferAvailable) {
|
||||
LOG_ERROR(Service_NVFlinger, "Failed to acquire buffer: {}", status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
if (wait_for_fence) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
item->graphic_buffer = slots[item->slot].graphic_buffer;
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferItemConsumer::ReleaseBuffer(const BufferItem& item, const Fence& release_fence) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (const auto status = AddReleaseFenceLocked(item.buf, item.graphic_buffer, release_fence);
|
||||
status != Status::NoError) {
|
||||
LOG_ERROR(Service_NVFlinger, "Failed to add fence: {}", status);
|
||||
}
|
||||
|
||||
if (const auto status = ReleaseBufferLocked(item.buf, item.graphic_buffer);
|
||||
status != Status::NoError) {
|
||||
LOG_WARNING(Service_NVFlinger, "Failed to release buffer: {}", status);
|
||||
return status;
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,28 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferItemConsumer.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/consumer_base.h"
|
||||
#include "core/hle/service/nvflinger/status.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class BufferItem;
|
||||
|
||||
class BufferItemConsumer final : public ConsumerBase {
|
||||
public:
|
||||
explicit BufferItemConsumer(std::unique_ptr<BufferQueueConsumer> consumer);
|
||||
Status AcquireBuffer(BufferItem* item, std::chrono::nanoseconds present_when,
|
||||
bool wait_for_fence = true);
|
||||
Status ReleaseBuffer(const BufferItem& item, const Fence& release_fence);
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,213 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
|
||||
|
||||
#include "common/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_,
|
||||
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||
: core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
|
||||
|
||||
BufferQueueConsumer::~BufferQueueConsumer() = default;
|
||||
|
||||
Status BufferQueueConsumer::AcquireBuffer(BufferItem* out_buffer,
|
||||
std::chrono::nanoseconds expected_present) {
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
// Check that the consumer doesn't currently have the maximum number of buffers acquired.
|
||||
const s32 num_acquired_buffers{
|
||||
static_cast<s32>(std::count_if(slots.begin(), slots.end(), [](const auto& slot) {
|
||||
return slot.buffer_state == BufferState::Acquired;
|
||||
}))};
|
||||
|
||||
if (num_acquired_buffers >= core->max_acquired_buffer_count + 1) {
|
||||
LOG_ERROR(Service_NVFlinger, "max acquired buffer count reached: {} (max {})",
|
||||
num_acquired_buffers, core->max_acquired_buffer_count);
|
||||
return Status::InvalidOperation;
|
||||
}
|
||||
|
||||
// Check if the queue is empty.
|
||||
if (core->queue.empty()) {
|
||||
return Status::NoBufferAvailable;
|
||||
}
|
||||
|
||||
auto front(core->queue.begin());
|
||||
|
||||
// If expected_present is specified, we may not want to return a buffer yet.
|
||||
if (expected_present.count() != 0) {
|
||||
constexpr auto MAX_REASONABLE_NSEC = 1000000000LL; // 1 second
|
||||
|
||||
// The expected_present argument indicates when the buffer is expected to be presented
|
||||
// on-screen.
|
||||
while (core->queue.size() > 1 && !core->queue[0].is_auto_timestamp) {
|
||||
const auto& buffer_item{core->queue[1]};
|
||||
|
||||
// If entry[1] is timely, drop entry[0] (and repeat).
|
||||
const auto desired_present = buffer_item.timestamp;
|
||||
if (desired_present < expected_present.count() - MAX_REASONABLE_NSEC ||
|
||||
desired_present > expected_present.count()) {
|
||||
// This buffer is set to display in the near future, or desired_present is garbage.
|
||||
LOG_DEBUG(Service_NVFlinger, "nodrop desire={} expect={}", desired_present,
|
||||
expected_present.count());
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "drop desire={} expect={} size={}", desired_present,
|
||||
expected_present.count(), core->queue.size());
|
||||
|
||||
if (core->StillTracking(*front)) {
|
||||
// Front buffer is still in mSlots, so mark the slot as free
|
||||
slots[front->slot].buffer_state = BufferState::Free;
|
||||
}
|
||||
|
||||
core->queue.erase(front);
|
||||
front = core->queue.begin();
|
||||
}
|
||||
|
||||
// See if the front buffer is ready to be acquired.
|
||||
const auto desired_present = front->timestamp;
|
||||
if (desired_present > expected_present.count() &&
|
||||
desired_present < expected_present.count() + MAX_REASONABLE_NSEC) {
|
||||
LOG_DEBUG(Service_NVFlinger, "defer desire={} expect={}", desired_present,
|
||||
expected_present.count());
|
||||
return Status::PresentLater;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "accept desire={} expect={}", desired_present,
|
||||
expected_present.count());
|
||||
}
|
||||
|
||||
const auto slot = front->slot;
|
||||
*out_buffer = *front;
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "acquiring slot={}", slot);
|
||||
|
||||
// If the buffer has previously been acquired by the consumer, set graphic_buffer to nullptr to
|
||||
// avoid unnecessarily remapping this buffer on the consumer side.
|
||||
if (out_buffer->acquire_called) {
|
||||
out_buffer->graphic_buffer = nullptr;
|
||||
}
|
||||
|
||||
core->queue.erase(front);
|
||||
|
||||
// We might have freed a slot while dropping old buffers, or the producer may be blocked
|
||||
// waiting for the number of buffers in the queue to decrease.
|
||||
core->SignalDequeueCondition();
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence) {
|
||||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot {} out of range", slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::shared_ptr<IProducerListener> listener;
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
// If the frame number has changed because the buffer has been reallocated, we can ignore
|
||||
// this ReleaseBuffer for the old buffer.
|
||||
if (frame_number != slots[slot].frame_number) {
|
||||
return Status::StaleBufferSlot;
|
||||
}
|
||||
|
||||
// Make sure this buffer hasn't been queued while acquired by the consumer.
|
||||
auto current(core->queue.begin());
|
||||
while (current != core->queue.end()) {
|
||||
if (current->slot == slot) {
|
||||
LOG_ERROR(Service_NVFlinger, "buffer slot {} pending release is currently queued",
|
||||
slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
++current;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
core->SignalDequeueCondition();
|
||||
}
|
||||
|
||||
// Call back without lock held
|
||||
if (listener != nullptr) {
|
||||
listener->OnBufferReleased();
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueConsumer::Connect(std::shared_ptr<IConsumerListener> consumer_listener,
|
||||
bool controlled_by_app) {
|
||||
if (consumer_listener == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "consumer_listener may not be nullptr");
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "controlled_by_app={}", controlled_by_app);
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
core->consumer_listener = std::move(consumer_listener);
|
||||
core->consumer_controlled_by_app = controlled_by_app;
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueConsumer::GetReleasedBuffers(u64* out_slot_mask) {
|
||||
if (out_slot_mask == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "out_slot_mask may not be nullptr");
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
u64 mask = 0;
|
||||
for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
|
||||
if (!slots[s].acquire_called) {
|
||||
mask |= (1ULL << s);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from the mask queued buffers for which acquire has been called, since the consumer
|
||||
// will not receive their buffer addresses and so must retain their cached information
|
||||
auto current(core->queue.begin());
|
||||
while (current != core->queue.end()) {
|
||||
if (current->acquire_called) {
|
||||
mask &= ~(1ULL << current->slot);
|
||||
}
|
||||
++current;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "returning mask {}", mask);
|
||||
*out_slot_mask = mask;
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,43 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueConsumer.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/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;
|
||||
class BufferQueueCore;
|
||||
class IConsumerListener;
|
||||
|
||||
class BufferQueueConsumer final {
|
||||
public:
|
||||
explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
|
||||
Service::Nvidia::NvCore::NvMap& nvmap_);
|
||||
~BufferQueueConsumer();
|
||||
|
||||
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
|
||||
Status ReleaseBuffer(s32 slot, u64 frame_number, const Fence& release_fence);
|
||||
Status Connect(std::shared_ptr<IConsumerListener> consumer_listener, bool controlled_by_app);
|
||||
Status GetReleasedBuffers(u64* out_slot_mask);
|
||||
|
||||
private:
|
||||
std::shared_ptr<BufferQueueCore> core;
|
||||
BufferQueueDefs::SlotsType& slots;
|
||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,115 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueCore.cpp
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
BufferQueueCore::BufferQueueCore() = default;
|
||||
|
||||
BufferQueueCore::~BufferQueueCore() = default;
|
||||
|
||||
void BufferQueueCore::NotifyShutdown() {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
is_shutting_down = true;
|
||||
|
||||
SignalDequeueCondition();
|
||||
}
|
||||
|
||||
void BufferQueueCore::SignalDequeueCondition() {
|
||||
dequeue_possible.store(true);
|
||||
dequeue_condition.notify_all();
|
||||
}
|
||||
|
||||
bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) {
|
||||
if (is_shutting_down) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); });
|
||||
dequeue_possible.store(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
s32 BufferQueueCore::GetMinUndequeuedBufferCountLocked(bool async) const {
|
||||
// If DequeueBuffer is allowed to error out, we don't have to add an extra buffer.
|
||||
if (!use_async_buffer) {
|
||||
return max_acquired_buffer_count;
|
||||
}
|
||||
|
||||
if (dequeue_buffer_cannot_block || async) {
|
||||
return max_acquired_buffer_count + 1;
|
||||
}
|
||||
|
||||
return max_acquired_buffer_count;
|
||||
}
|
||||
|
||||
s32 BufferQueueCore::GetMinMaxBufferCountLocked(bool async) const {
|
||||
return GetMinUndequeuedBufferCountLocked(async) + 1;
|
||||
}
|
||||
|
||||
s32 BufferQueueCore::GetMaxBufferCountLocked(bool async) const {
|
||||
const auto min_buffer_count = GetMinMaxBufferCountLocked(async);
|
||||
auto max_buffer_count = std::max(default_max_buffer_count, min_buffer_count);
|
||||
|
||||
if (override_max_buffer_count != 0) {
|
||||
ASSERT(override_max_buffer_count >= min_buffer_count);
|
||||
max_buffer_count = override_max_buffer_count;
|
||||
}
|
||||
|
||||
// Any buffers that are dequeued by the producer or sitting in the queue waiting to be consumed
|
||||
// need to have their slots preserved.
|
||||
for (s32 slot = max_buffer_count; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
|
||||
const auto state = slots[slot].buffer_state;
|
||||
if (state == BufferState::Queued || state == BufferState::Dequeued) {
|
||||
max_buffer_count = slot + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return max_buffer_count;
|
||||
}
|
||||
|
||||
s32 BufferQueueCore::GetPreallocatedBufferCountLocked() const {
|
||||
return static_cast<s32>(std::count_if(slots.begin(), slots.end(),
|
||||
[](const auto& slot) { return slot.is_preallocated; }));
|
||||
}
|
||||
|
||||
void BufferQueueCore::FreeBufferLocked(s32 slot) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
|
||||
|
||||
slots[slot].graphic_buffer.reset();
|
||||
|
||||
slots[slot].buffer_state = BufferState::Free;
|
||||
slots[slot].frame_number = UINT32_MAX;
|
||||
slots[slot].acquire_called = false;
|
||||
slots[slot].fence = Fence::NoFence();
|
||||
}
|
||||
|
||||
void BufferQueueCore::FreeAllBuffersLocked() {
|
||||
buffer_has_been_queued = false;
|
||||
|
||||
for (s32 slot = 0; slot < BufferQueueDefs::NUM_BUFFER_SLOTS; ++slot) {
|
||||
FreeBufferLocked(slot);
|
||||
}
|
||||
}
|
||||
|
||||
bool BufferQueueCore::StillTracking(const BufferItem& item) const {
|
||||
const BufferSlot& slot = slots[item.slot];
|
||||
|
||||
return (slot.graphic_buffer != nullptr) && (item.graphic_buffer == slot.graphic_buffer);
|
||||
}
|
||||
|
||||
void BufferQueueCore::WaitWhileAllocatingLocked() const {
|
||||
while (is_allocating) {
|
||||
is_allocating_condition.wait(mutex);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,80 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueCore.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/service/nvflinger/buffer_item.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue_defs.h"
|
||||
#include "core/hle/service/nvflinger/pixel_format.h"
|
||||
#include "core/hle/service/nvflinger/status.h"
|
||||
#include "core/hle/service/nvflinger/window.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class IConsumerListener;
|
||||
class IProducerListener;
|
||||
|
||||
class BufferQueueCore final {
|
||||
friend class BufferQueueProducer;
|
||||
friend class BufferQueueConsumer;
|
||||
|
||||
public:
|
||||
static constexpr s32 INVALID_BUFFER_SLOT = BufferItem::INVALID_BUFFER_SLOT;
|
||||
|
||||
BufferQueueCore();
|
||||
~BufferQueueCore();
|
||||
|
||||
void NotifyShutdown();
|
||||
|
||||
private:
|
||||
void SignalDequeueCondition();
|
||||
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
|
||||
|
||||
s32 GetMinUndequeuedBufferCountLocked(bool async) const;
|
||||
s32 GetMinMaxBufferCountLocked(bool async) const;
|
||||
s32 GetMaxBufferCountLocked(bool async) const;
|
||||
s32 GetPreallocatedBufferCountLocked() const;
|
||||
void FreeBufferLocked(s32 slot);
|
||||
void FreeAllBuffersLocked();
|
||||
bool StillTracking(const BufferItem& item) const;
|
||||
void WaitWhileAllocatingLocked() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex;
|
||||
bool is_abandoned{};
|
||||
bool consumer_controlled_by_app{};
|
||||
std::shared_ptr<IConsumerListener> consumer_listener;
|
||||
u32 consumer_usage_bit{};
|
||||
NativeWindowApi connected_api{NativeWindowApi::NoConnectedApi};
|
||||
std::shared_ptr<IProducerListener> connected_producer_listener;
|
||||
BufferQueueDefs::SlotsType slots{};
|
||||
std::vector<BufferItem> queue;
|
||||
s32 override_max_buffer_count{};
|
||||
std::condition_variable dequeue_condition;
|
||||
std::atomic<bool> dequeue_possible{};
|
||||
const bool use_async_buffer{}; // This is always disabled on HOS
|
||||
bool dequeue_buffer_cannot_block{};
|
||||
PixelFormat default_buffer_format{PixelFormat::Rgba8888};
|
||||
u32 default_width{1};
|
||||
u32 default_height{1};
|
||||
s32 default_max_buffer_count{2};
|
||||
const s32 max_acquired_buffer_count{}; // This is always zero on HOS
|
||||
bool buffer_has_been_queued{};
|
||||
u64 frame_counter{};
|
||||
u32 transform_hint{};
|
||||
bool is_allocating{};
|
||||
mutable std::condition_variable_any is_allocating_condition;
|
||||
bool is_shutting_down{};
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,21 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueDefs.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/buffer_slot.h"
|
||||
|
||||
namespace Service::android::BufferQueueDefs {
|
||||
|
||||
// BufferQueue will keep track of at most this value of buffers.
|
||||
constexpr s32 NUM_BUFFER_SLOTS = 64;
|
||||
|
||||
using SlotsType = std::array<BufferSlot, NUM_BUFFER_SLOTS>;
|
||||
|
||||
} // namespace Service::android::BufferQueueDefs
|
||||
@@ -1,933 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueProducer.cpp
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/service/kernel_helpers.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"
|
||||
#include "core/hle/service/nvflinger/parcel.h"
|
||||
#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
|
||||
#include "core/hle/service/nvflinger/window.h"
|
||||
#include "core/hle/service/vi/vi.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
||||
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");
|
||||
}
|
||||
|
||||
BufferQueueProducer::~BufferQueueProducer() {
|
||||
service_context.CloseEvent(buffer_wait_event);
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
|
||||
BufferQueueDefs::NUM_BUFFER_SLOTS);
|
||||
return Status::BadValue;
|
||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
|
||||
slots[slot].buffer_state);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
slots[slot].request_buffer_called = true;
|
||||
*buf = slots[slot].graphic_buffer;
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::SetBufferCount(s32 buffer_count) {
|
||||
LOG_DEBUG(Service_NVFlinger, "count = {}", buffer_count);
|
||||
|
||||
std::shared_ptr<IConsumerListener> listener;
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
if (buffer_count > BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
LOG_ERROR(Service_NVFlinger, "buffer_count {} too large (max {})", buffer_count,
|
||||
BufferQueueDefs::NUM_BUFFER_SLOTS);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
// There must be no dequeued buffers when changing the buffer count.
|
||||
for (s32 s{}; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
|
||||
if (slots[s].buffer_state == BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_NVFlinger, "buffer owned by producer");
|
||||
return Status::BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer_count == 0) {
|
||||
core->override_max_buffer_count = 0;
|
||||
core->SignalDequeueCondition();
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
const s32 min_buffer_slots = core->GetMinMaxBufferCountLocked(false);
|
||||
if (buffer_count < min_buffer_slots) {
|
||||
LOG_ERROR(Service_NVFlinger, "requested buffer count {} is less than minimum {}",
|
||||
buffer_count, min_buffer_slots);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
// Here we are guaranteed that the producer doesn't have any dequeued buffers and will
|
||||
// release all of its buffer references.
|
||||
if (core->GetPreallocatedBufferCountLocked() <= 0) {
|
||||
core->FreeAllBuffersLocked();
|
||||
}
|
||||
|
||||
core->override_max_buffer_count = buffer_count;
|
||||
core->SignalDequeueCondition();
|
||||
buffer_wait_event->Signal();
|
||||
listener = core->consumer_listener;
|
||||
}
|
||||
|
||||
// Call back without lock held
|
||||
if (listener != nullptr) {
|
||||
listener->OnBuffersReleased();
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags,
|
||||
std::unique_lock<std::mutex>& lk) const {
|
||||
bool try_again = true;
|
||||
|
||||
while (try_again) {
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
|
||||
if (async && core->override_max_buffer_count) {
|
||||
if (core->override_max_buffer_count < max_buffer_count) {
|
||||
LOG_ERROR(Service_NVFlinger, "async mode is invalid with buffer count override");
|
||||
return Status::BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Free up any buffers that are in slots beyond the max buffer count
|
||||
for (s32 s = max_buffer_count; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
|
||||
ASSERT(slots[s].buffer_state == BufferState::Free);
|
||||
if (slots[s].graphic_buffer != nullptr) {
|
||||
core->FreeBufferLocked(s);
|
||||
*return_flags |= Status::ReleaseAllBuffers;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for a free buffer to give to the client
|
||||
*found = BufferQueueCore::INVALID_BUFFER_SLOT;
|
||||
s32 dequeued_count{};
|
||||
s32 acquired_count{};
|
||||
for (s32 s{}; s < max_buffer_count; ++s) {
|
||||
switch (slots[s].buffer_state) {
|
||||
case BufferState::Dequeued:
|
||||
++dequeued_count;
|
||||
break;
|
||||
case BufferState::Acquired:
|
||||
++acquired_count;
|
||||
break;
|
||||
case BufferState::Free:
|
||||
// We return the oldest of the free buffers to avoid stalling the producer if
|
||||
// possible, since the consumer may still have pending reads of in-flight buffers
|
||||
if (*found == BufferQueueCore::INVALID_BUFFER_SLOT ||
|
||||
slots[s].frame_number < slots[*found].frame_number) {
|
||||
*found = s;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Producers are not allowed to dequeue more than one buffer if they did not set a buffer
|
||||
// count
|
||||
if (!core->override_max_buffer_count && dequeued_count) {
|
||||
LOG_ERROR(Service_NVFlinger,
|
||||
"can't dequeue multiple buffers without setting the buffer count");
|
||||
return Status::InvalidOperation;
|
||||
}
|
||||
|
||||
// See whether a buffer has been queued since the last SetBufferCount so we know whether to
|
||||
// perform the min undequeued buffers check below
|
||||
if (core->buffer_has_been_queued) {
|
||||
// Make sure the producer is not trying to dequeue more buffers than allowed
|
||||
const s32 new_undequeued_count = max_buffer_count - (dequeued_count + 1);
|
||||
const s32 min_undequeued_count = core->GetMinUndequeuedBufferCountLocked(async);
|
||||
if (new_undequeued_count < min_undequeued_count) {
|
||||
LOG_ERROR(Service_NVFlinger,
|
||||
"min undequeued buffer count({}) exceeded (dequeued={} undequeued={})",
|
||||
min_undequeued_count, dequeued_count, new_undequeued_count);
|
||||
return Status::InvalidOperation;
|
||||
}
|
||||
}
|
||||
|
||||
// If we disconnect and reconnect quickly, we can be in a state where our slots are empty
|
||||
// but we have many buffers in the queue. This can cause us to run out of memory if we
|
||||
// outrun the consumer. Wait here if it looks like we have too many buffers queued up.
|
||||
const bool too_many_buffers = core->queue.size() > static_cast<size_t>(max_buffer_count);
|
||||
if (too_many_buffers) {
|
||||
LOG_ERROR(Service_NVFlinger, "queue size is {}, waiting", core->queue.size());
|
||||
}
|
||||
|
||||
// If no buffer is found, or if the queue has too many buffers outstanding, wait for a
|
||||
// buffer to be acquired or released, or for the max buffer count to change.
|
||||
try_again = (*found == BufferQueueCore::INVALID_BUFFER_SLOT) || too_many_buffers;
|
||||
if (try_again) {
|
||||
// Return an error if we're in non-blocking mode (producer and consumer are controlled
|
||||
// by the application).
|
||||
if (core->dequeue_buffer_cannot_block &&
|
||||
(acquired_count <= core->max_acquired_buffer_count)) {
|
||||
return Status::WouldBlock;
|
||||
}
|
||||
|
||||
if (!core->WaitForDequeueCondition(lk)) {
|
||||
// We are no longer running
|
||||
return Status::NoError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool async, u32 width,
|
||||
u32 height, PixelFormat format, u32 usage) {
|
||||
LOG_DEBUG(Service_NVFlinger, "async={} w={} h={} format={}, usage={}", async ? "true" : "false",
|
||||
width, height, format, usage);
|
||||
|
||||
if ((width != 0 && height == 0) || (width == 0 && height != 0)) {
|
||||
LOG_ERROR(Service_NVFlinger, "invalid size: w={} h={}", width, height);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
Status return_flags = Status::NoError;
|
||||
bool attached_by_consumer = false;
|
||||
{
|
||||
std::unique_lock lock{core->mutex};
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
if (format == PixelFormat::NoFormat) {
|
||||
format = core->default_buffer_format;
|
||||
}
|
||||
|
||||
// Enable the usage bits the consumer requested
|
||||
usage |= core->consumer_usage_bit;
|
||||
|
||||
s32 found{};
|
||||
Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags, lock);
|
||||
if (status != Status::NoError) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// This should not happen
|
||||
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
|
||||
LOG_ERROR(Service_NVFlinger, "no available buffer slots");
|
||||
return Status::Busy;
|
||||
}
|
||||
|
||||
*out_slot = found;
|
||||
|
||||
attached_by_consumer = slots[found].attached_by_consumer;
|
||||
|
||||
const bool use_default_size = !width && !height;
|
||||
if (use_default_size) {
|
||||
width = core->default_width;
|
||||
height = core->default_height;
|
||||
}
|
||||
|
||||
slots[found].buffer_state = BufferState::Dequeued;
|
||||
|
||||
const std::shared_ptr<GraphicBuffer>& buffer(slots[found].graphic_buffer);
|
||||
if ((buffer == nullptr) || (buffer->Width() != width) || (buffer->Height() != height) ||
|
||||
(buffer->Format() != format) || ((buffer->Usage() & usage) != usage)) {
|
||||
slots[found].acquire_called = false;
|
||||
slots[found].graphic_buffer = nullptr;
|
||||
slots[found].request_buffer_called = false;
|
||||
slots[found].fence = Fence::NoFence();
|
||||
|
||||
return_flags |= Status::BufferNeedsReallocation;
|
||||
}
|
||||
|
||||
*out_fence = slots[found].fence;
|
||||
slots[found].fence = Fence::NoFence();
|
||||
}
|
||||
|
||||
if ((return_flags & Status::BufferNeedsReallocation) != Status::None) {
|
||||
LOG_DEBUG(Service_NVFlinger, "allocating a new buffer for slot {}", *out_slot);
|
||||
|
||||
auto graphic_buffer = std::make_shared<GraphicBuffer>(width, height, format, usage);
|
||||
if (graphic_buffer == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "creating GraphicBuffer failed");
|
||||
return Status::NoMemory;
|
||||
}
|
||||
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
slots[*out_slot].frame_number = UINT32_MAX;
|
||||
slots[*out_slot].graphic_buffer = graphic_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
if (attached_by_consumer) {
|
||||
return_flags |= Status::BufferNeedsReallocation;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "returning slot={} frame={}, flags={}", *out_slot,
|
||||
slots[*out_slot].frame_number, return_flags);
|
||||
|
||||
return return_flags;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::DetachBuffer(s32 slot) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot {} out of range [0, {})", slot,
|
||||
BufferQueueDefs::NUM_BUFFER_SLOTS);
|
||||
return Status::BadValue;
|
||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
|
||||
slots[slot].buffer_state);
|
||||
return Status::BadValue;
|
||||
} else if (!slots[slot].request_buffer_called) {
|
||||
LOG_ERROR(Service_NVFlinger, "buffer in slot {} has not been requested", slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
core->FreeBufferLocked(slot);
|
||||
core->SignalDequeueCondition();
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer,
|
||||
Fence* out_fence) {
|
||||
if (out_buffer == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "out_buffer must not be nullptr");
|
||||
return Status::BadValue;
|
||||
} else if (out_fence == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "out_fence must not be nullptr");
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
// Find the oldest valid slot
|
||||
int found = BufferQueueCore::INVALID_BUFFER_SLOT;
|
||||
for (int s = 0; s < BufferQueueDefs::NUM_BUFFER_SLOTS; ++s) {
|
||||
if (slots[s].buffer_state == BufferState::Free && slots[s].graphic_buffer != nullptr) {
|
||||
if (found == BufferQueueCore::INVALID_BUFFER_SLOT ||
|
||||
slots[s].frame_number < slots[found].frame_number) {
|
||||
found = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
|
||||
return Status::NoMemory;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "Detached slot {}", found);
|
||||
|
||||
*out_buffer = slots[found].graphic_buffer;
|
||||
*out_fence = slots[found].fence;
|
||||
|
||||
core->FreeBufferLocked(found);
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::AttachBuffer(s32* out_slot,
|
||||
const std::shared_ptr<GraphicBuffer>& buffer) {
|
||||
if (out_slot == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "out_slot must not be nullptr");
|
||||
return Status::BadValue;
|
||||
} else if (buffer == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "Cannot attach nullptr buffer");
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::unique_lock lock{core->mutex};
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
Status return_flags = Status::NoError;
|
||||
s32 found{};
|
||||
|
||||
const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags, lock);
|
||||
if (status != Status::NoError) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// This should not happen
|
||||
if (found == BufferQueueCore::INVALID_BUFFER_SLOT) {
|
||||
LOG_ERROR(Service_NVFlinger, "No available buffer slots");
|
||||
return Status::Busy;
|
||||
}
|
||||
|
||||
*out_slot = found;
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "Returning slot {} flags={}", *out_slot, return_flags);
|
||||
|
||||
slots[*out_slot].graphic_buffer = buffer;
|
||||
slots[*out_slot].buffer_state = BufferState::Dequeued;
|
||||
slots[*out_slot].fence = Fence::NoFence();
|
||||
slots[*out_slot].request_buffer_called = true;
|
||||
|
||||
return return_flags;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||
QueueBufferOutput* output) {
|
||||
s64 timestamp{};
|
||||
bool is_auto_timestamp{};
|
||||
Common::Rectangle<s32> crop;
|
||||
NativeWindowScalingMode scaling_mode{};
|
||||
NativeWindowTransform transform;
|
||||
u32 sticky_transform_{};
|
||||
bool async{};
|
||||
s32 swap_interval{};
|
||||
Fence fence{};
|
||||
|
||||
input.Deflate(×tamp, &is_auto_timestamp, &crop, &scaling_mode, &transform,
|
||||
&sticky_transform_, &async, &swap_interval, &fence);
|
||||
|
||||
switch (scaling_mode) {
|
||||
case NativeWindowScalingMode::Freeze:
|
||||
case NativeWindowScalingMode::ScaleToWindow:
|
||||
case NativeWindowScalingMode::ScaleCrop:
|
||||
case NativeWindowScalingMode::NoScaleCrop:
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_NVFlinger, "unknown scaling mode {}", scaling_mode);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::shared_ptr<IConsumerListener> frame_available_listener;
|
||||
std::shared_ptr<IConsumerListener> frame_replaced_listener;
|
||||
s32 callback_ticket{};
|
||||
BufferItem item;
|
||||
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
const s32 max_buffer_count = core->GetMaxBufferCountLocked(async);
|
||||
if (async && core->override_max_buffer_count) {
|
||||
if (core->override_max_buffer_count < max_buffer_count) {
|
||||
LOG_ERROR(Service_NVFlinger, "async mode is invalid with "
|
||||
"buffer count override");
|
||||
return Status::BadValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= max_buffer_count) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
|
||||
max_buffer_count);
|
||||
return Status::BadValue;
|
||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_NVFlinger,
|
||||
"slot {} is not owned by the producer "
|
||||
"(state = {})",
|
||||
slot, slots[slot].buffer_state);
|
||||
return Status::BadValue;
|
||||
} else if (!slots[slot].request_buffer_called) {
|
||||
LOG_ERROR(Service_NVFlinger,
|
||||
"slot {} was queued without requesting "
|
||||
"a buffer",
|
||||
slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger,
|
||||
"slot={} frame={} time={} crop=[{},{},{},{}] transform={} scale={}", slot,
|
||||
core->frame_counter + 1, timestamp, crop.Left(), crop.Top(), crop.Right(),
|
||||
crop.Bottom(), transform, scaling_mode);
|
||||
|
||||
const std::shared_ptr<GraphicBuffer>& graphic_buffer(slots[slot].graphic_buffer);
|
||||
Common::Rectangle<s32> buffer_rect(graphic_buffer->Width(), graphic_buffer->Height());
|
||||
Common::Rectangle<s32> cropped_rect;
|
||||
[[maybe_unused]] const bool unused = crop.Intersect(buffer_rect, &cropped_rect);
|
||||
|
||||
if (cropped_rect != crop) {
|
||||
LOG_ERROR(Service_NVFlinger, "crop rect is not contained within the buffer in slot {}",
|
||||
slot);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
slots[slot].fence = fence;
|
||||
slots[slot].buffer_state = BufferState::Queued;
|
||||
++core->frame_counter;
|
||||
slots[slot].frame_number = core->frame_counter;
|
||||
|
||||
item.acquire_called = slots[slot].acquire_called;
|
||||
item.graphic_buffer = slots[slot].graphic_buffer;
|
||||
item.crop = crop;
|
||||
item.transform = transform & ~NativeWindowTransform::InverseDisplay;
|
||||
item.transform_to_display_inverse =
|
||||
(transform & NativeWindowTransform::InverseDisplay) != NativeWindowTransform::None;
|
||||
item.scaling_mode = static_cast<u32>(scaling_mode);
|
||||
item.timestamp = timestamp;
|
||||
item.is_auto_timestamp = is_auto_timestamp;
|
||||
item.frame_number = core->frame_counter;
|
||||
item.slot = slot;
|
||||
item.fence = fence;
|
||||
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()) {
|
||||
// When the queue is empty, we can simply queue this buffer
|
||||
core->queue.push_back(item);
|
||||
frame_available_listener = core->consumer_listener;
|
||||
} else {
|
||||
// When the queue is not empty, we need to look at the front buffer
|
||||
// state to see if we need to replace it
|
||||
auto front(core->queue.begin());
|
||||
|
||||
if (front->is_droppable) {
|
||||
// If the front queued buffer is still being tracked, we first
|
||||
// mark it as freed
|
||||
if (core->StillTracking(*front)) {
|
||||
slots[front->slot].buffer_state = BufferState::Free;
|
||||
// Reset the frame number of the freed buffer so that it is the first in line to
|
||||
// be dequeued again
|
||||
slots[front->slot].frame_number = 0;
|
||||
}
|
||||
// Overwrite the droppable buffer with the incoming one
|
||||
*front = item;
|
||||
frame_replaced_listener = core->consumer_listener;
|
||||
} else {
|
||||
core->queue.push_back(item);
|
||||
frame_available_listener = core->consumer_listener;
|
||||
}
|
||||
}
|
||||
|
||||
core->buffer_has_been_queued = true;
|
||||
core->SignalDequeueCondition();
|
||||
output->Inflate(core->default_width, core->default_height, core->transform_hint,
|
||||
static_cast<u32>(core->queue.size()));
|
||||
|
||||
// Take a ticket for the callback functions
|
||||
callback_ticket = next_callback_ticket++;
|
||||
}
|
||||
|
||||
// Don't send the GraphicBuffer through the callback, and don't send the slot number, since the
|
||||
// consumer shouldn't need it
|
||||
item.graphic_buffer.reset();
|
||||
item.slot = BufferItem::INVALID_BUFFER_SLOT;
|
||||
|
||||
// Call back without the main BufferQueue lock held, but with the callback lock held so we can
|
||||
// ensure that callbacks occur in order
|
||||
{
|
||||
std::scoped_lock lock{callback_mutex};
|
||||
while (callback_ticket != current_callback_ticket) {
|
||||
callback_condition.wait(callback_mutex);
|
||||
}
|
||||
|
||||
if (frame_available_listener != nullptr) {
|
||||
frame_available_listener->OnFrameAvailable(item);
|
||||
} else if (frame_replaced_listener != nullptr) {
|
||||
frame_replaced_listener->OnFrameReplaced(item);
|
||||
}
|
||||
|
||||
++current_callback_ticket;
|
||||
callback_condition.notify_all();
|
||||
}
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
void BufferQueueProducer::CancelBuffer(s32 slot, const Fence& fence) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return;
|
||||
}
|
||||
|
||||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot index {} out of range [0, {})", slot,
|
||||
BufferQueueDefs::NUM_BUFFER_SLOTS);
|
||||
return;
|
||||
} else if (slots[slot].buffer_state != BufferState::Dequeued) {
|
||||
LOG_ERROR(Service_NVFlinger, "slot {} is not owned by the producer (state = {})", slot,
|
||||
slots[slot].buffer_state);
|
||||
return;
|
||||
}
|
||||
|
||||
slots[slot].buffer_state = BufferState::Free;
|
||||
slots[slot].frame_number = 0;
|
||||
slots[slot].fence = fence;
|
||||
|
||||
core->SignalDequeueCondition();
|
||||
buffer_wait_event->Signal();
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::Query(NativeWindow what, s32* out_value) {
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
if (out_value == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "outValue was nullptr");
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
u32 value{};
|
||||
switch (what) {
|
||||
case NativeWindow::Width:
|
||||
value = core->default_width;
|
||||
break;
|
||||
case NativeWindow::Height:
|
||||
value = core->default_height;
|
||||
break;
|
||||
case NativeWindow::Format:
|
||||
value = static_cast<u32>(core->default_buffer_format);
|
||||
break;
|
||||
case NativeWindow::MinUndequeedBuffers:
|
||||
value = core->GetMinUndequeuedBufferCountLocked(false);
|
||||
break;
|
||||
case NativeWindow::StickyTransform:
|
||||
value = sticky_transform;
|
||||
break;
|
||||
case NativeWindow::ConsumerRunningBehind:
|
||||
value = (core->queue.size() > 1);
|
||||
break;
|
||||
case NativeWindow::ConsumerUsageBits:
|
||||
value = core->consumer_usage_bit;
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "what = {}, value = {}", what, value);
|
||||
|
||||
*out_value = static_cast<s32>(value);
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::Connect(const std::shared_ptr<IProducerListener>& listener,
|
||||
NativeWindowApi api, bool producer_controlled_by_app,
|
||||
QueueBufferOutput* output) {
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "api = {} producer_controlled_by_app = {}", api,
|
||||
producer_controlled_by_app);
|
||||
|
||||
if (core->is_abandoned) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has been abandoned");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
if (core->consumer_listener == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "BufferQueue has no consumer");
|
||||
return Status::NoInit;
|
||||
}
|
||||
|
||||
if (output == nullptr) {
|
||||
LOG_ERROR(Service_NVFlinger, "output was nullptr");
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
if (core->connected_api != NativeWindowApi::NoConnectedApi) {
|
||||
LOG_ERROR(Service_NVFlinger, "already connected (cur = {} req = {})", core->connected_api,
|
||||
api);
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
Status status = Status::NoError;
|
||||
switch (api) {
|
||||
case NativeWindowApi::Egl:
|
||||
case NativeWindowApi::Cpu:
|
||||
case NativeWindowApi::Media:
|
||||
case NativeWindowApi::Camera:
|
||||
core->connected_api = api;
|
||||
output->Inflate(core->default_width, core->default_height, core->transform_hint,
|
||||
static_cast<u32>(core->queue.size()));
|
||||
core->connected_producer_listener = listener;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_NVFlinger, "unknown api = {}", api);
|
||||
status = Status::BadValue;
|
||||
break;
|
||||
}
|
||||
|
||||
core->buffer_has_been_queued = false;
|
||||
core->dequeue_buffer_cannot_block =
|
||||
core->consumer_controlled_by_app && producer_controlled_by_app;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::Disconnect(NativeWindowApi api) {
|
||||
LOG_DEBUG(Service_NVFlinger, "api = {}", api);
|
||||
|
||||
Status status = Status::NoError;
|
||||
std::shared_ptr<IConsumerListener> listener;
|
||||
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
if (core->is_abandoned) {
|
||||
// Disconnecting after the surface has been abandoned is a no-op.
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
// HACK: We are not Android. Remove handle for items in queue, and clear queue.
|
||||
// Allows synchronous destruction of nvmap handles.
|
||||
for (auto& item : core->queue) {
|
||||
nvmap.FreeHandle(item.graphic_buffer->BufferId(), true);
|
||||
}
|
||||
core->queue.clear();
|
||||
|
||||
switch (api) {
|
||||
case NativeWindowApi::Egl:
|
||||
case NativeWindowApi::Cpu:
|
||||
case NativeWindowApi::Media:
|
||||
case NativeWindowApi::Camera:
|
||||
if (core->connected_api == api) {
|
||||
core->FreeAllBuffersLocked();
|
||||
core->connected_producer_listener = nullptr;
|
||||
core->connected_api = NativeWindowApi::NoConnectedApi;
|
||||
core->SignalDequeueCondition();
|
||||
buffer_wait_event->Signal();
|
||||
listener = core->consumer_listener;
|
||||
} else {
|
||||
LOG_ERROR(Service_NVFlinger, "still connected to another api (cur = {} req = {})",
|
||||
core->connected_api, api);
|
||||
status = Status::BadValue;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Service_NVFlinger, "unknown api = {}", api);
|
||||
status = Status::BadValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Call back without lock held
|
||||
if (listener != nullptr) {
|
||||
listener->OnBuffersReleased();
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
|
||||
const std::shared_ptr<GraphicBuffer>& buffer) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot {}", slot);
|
||||
|
||||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
|
||||
slots[slot] = {};
|
||||
slots[slot].graphic_buffer = buffer;
|
||||
slots[slot].frame_number = 0;
|
||||
|
||||
// Most games preallocate a buffer and pass a valid buffer here. However, it is possible for
|
||||
// this to be called with an empty buffer, Naruto Ultimate Ninja Storm is a game that does this.
|
||||
if (buffer) {
|
||||
slots[slot].is_preallocated = true;
|
||||
|
||||
core->override_max_buffer_count = core->GetPreallocatedBufferCountLocked();
|
||||
core->default_width = buffer->Width();
|
||||
core->default_height = buffer->Height();
|
||||
core->default_buffer_format = buffer->Format();
|
||||
}
|
||||
|
||||
core->SignalDequeueCondition();
|
||||
buffer_wait_event->Signal();
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
void BufferQueueProducer::Transact(Kernel::HLERequestContext& ctx, TransactionId code, u32 flags) {
|
||||
Status status{Status::NoError};
|
||||
InputParcel parcel_in{ctx.ReadBuffer()};
|
||||
OutputParcel parcel_out{};
|
||||
|
||||
switch (code) {
|
||||
case TransactionId::Connect: {
|
||||
const auto enable_listener = parcel_in.Read<bool>();
|
||||
const auto api = parcel_in.Read<NativeWindowApi>();
|
||||
const auto producer_controlled_by_app = parcel_in.Read<bool>();
|
||||
|
||||
UNIMPLEMENTED_IF_MSG(enable_listener, "Listener is unimplemented!");
|
||||
|
||||
std::shared_ptr<IProducerListener> listener;
|
||||
QueueBufferOutput output{};
|
||||
|
||||
status = Connect(listener, api, producer_controlled_by_app, &output);
|
||||
|
||||
parcel_out.Write(output);
|
||||
break;
|
||||
}
|
||||
case TransactionId::SetPreallocatedBuffer: {
|
||||
const auto slot = parcel_in.Read<s32>();
|
||||
const auto buffer = parcel_in.ReadObject<GraphicBuffer>();
|
||||
|
||||
status = SetPreallocatedBuffer(slot, buffer);
|
||||
break;
|
||||
}
|
||||
case TransactionId::DequeueBuffer: {
|
||||
const auto is_async = parcel_in.Read<bool>();
|
||||
const auto width = parcel_in.Read<u32>();
|
||||
const auto height = parcel_in.Read<u32>();
|
||||
const auto pixel_format = parcel_in.Read<PixelFormat>();
|
||||
const auto usage = parcel_in.Read<u32>();
|
||||
|
||||
s32 slot{};
|
||||
Fence fence{};
|
||||
|
||||
status = DequeueBuffer(&slot, &fence, is_async, width, height, pixel_format, usage);
|
||||
|
||||
parcel_out.Write(slot);
|
||||
parcel_out.WriteObject(&fence);
|
||||
break;
|
||||
}
|
||||
case TransactionId::RequestBuffer: {
|
||||
const auto slot = parcel_in.Read<s32>();
|
||||
|
||||
std::shared_ptr<GraphicBuffer> buf;
|
||||
|
||||
status = RequestBuffer(slot, &buf);
|
||||
|
||||
parcel_out.WriteObject(buf);
|
||||
break;
|
||||
}
|
||||
case TransactionId::QueueBuffer: {
|
||||
const auto slot = parcel_in.Read<s32>();
|
||||
|
||||
QueueBufferInput input{parcel_in};
|
||||
QueueBufferOutput output;
|
||||
|
||||
status = QueueBuffer(slot, input, &output);
|
||||
|
||||
parcel_out.Write(output);
|
||||
break;
|
||||
}
|
||||
case TransactionId::Query: {
|
||||
const auto what = parcel_in.Read<NativeWindow>();
|
||||
|
||||
s32 value{};
|
||||
|
||||
status = Query(what, &value);
|
||||
|
||||
parcel_out.Write(value);
|
||||
break;
|
||||
}
|
||||
case TransactionId::CancelBuffer: {
|
||||
const auto slot = parcel_in.Read<s32>();
|
||||
const auto fence = parcel_in.ReadFlattened<Fence>();
|
||||
|
||||
CancelBuffer(slot, fence);
|
||||
break;
|
||||
}
|
||||
case TransactionId::Disconnect: {
|
||||
const auto api = parcel_in.Read<NativeWindowApi>();
|
||||
|
||||
status = Disconnect(api);
|
||||
break;
|
||||
}
|
||||
case TransactionId::DetachBuffer: {
|
||||
const auto slot = parcel_in.Read<s32>();
|
||||
|
||||
status = DetachBuffer(slot);
|
||||
break;
|
||||
}
|
||||
case TransactionId::SetBufferCount: {
|
||||
const auto buffer_count = parcel_in.Read<s32>();
|
||||
|
||||
status = SetBufferCount(buffer_count);
|
||||
break;
|
||||
}
|
||||
case TransactionId::GetBufferHistory:
|
||||
LOG_WARNING(Service_NVFlinger, "(STUBBED) called, transaction=GetBufferHistory");
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unimplemented TransactionId {}", code);
|
||||
break;
|
||||
}
|
||||
|
||||
parcel_out.Write(status);
|
||||
|
||||
ctx.WriteBuffer(parcel_out.Serialize());
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& BufferQueueProducer::GetNativeHandle() {
|
||||
return buffer_wait_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,90 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferQueueProducer.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
#include "core/hle/service/nvflinger/binder.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue_defs.h"
|
||||
#include "core/hle/service/nvflinger/buffer_slot.h"
|
||||
#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
|
||||
#include "core/hle/service/nvflinger/pixel_format.h"
|
||||
#include "core/hle/service/nvflinger/status.h"
|
||||
#include "core/hle/service/nvflinger/window.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KernelCore;
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Service::KernelHelpers {
|
||||
class ServiceContext;
|
||||
} // namespace Service::KernelHelpers
|
||||
|
||||
namespace Service::Nvidia::NvCore {
|
||||
class NvMap;
|
||||
} // namespace Service::Nvidia::NvCore
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class BufferQueueCore;
|
||||
class IProducerListener;
|
||||
|
||||
class BufferQueueProducer final : public IBinder {
|
||||
public:
|
||||
explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||
Service::Nvidia::NvCore::NvMap& nvmap_);
|
||||
~BufferQueueProducer();
|
||||
|
||||
void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
|
||||
|
||||
Kernel::KReadableEvent& GetNativeHandle() override;
|
||||
|
||||
public:
|
||||
Status RequestBuffer(s32 slot, std::shared_ptr<GraphicBuffer>* buf);
|
||||
Status SetBufferCount(s32 buffer_count);
|
||||
Status DequeueBuffer(s32* out_slot, android::Fence* out_fence, bool async, u32 width,
|
||||
u32 height, PixelFormat format, u32 usage);
|
||||
Status DetachBuffer(s32 slot);
|
||||
Status DetachNextBuffer(std::shared_ptr<GraphicBuffer>* out_buffer, Fence* out_fence);
|
||||
Status AttachBuffer(s32* outSlot, const std::shared_ptr<GraphicBuffer>& buffer);
|
||||
Status QueueBuffer(s32 slot, const QueueBufferInput& input, QueueBufferOutput* output);
|
||||
void CancelBuffer(s32 slot, const Fence& fence);
|
||||
Status Query(NativeWindow what, s32* out_value);
|
||||
Status Connect(const std::shared_ptr<IProducerListener>& listener, NativeWindowApi api,
|
||||
bool producer_controlled_by_app, QueueBufferOutput* output);
|
||||
|
||||
Status Disconnect(NativeWindowApi api);
|
||||
Status SetPreallocatedBuffer(s32 slot, const std::shared_ptr<GraphicBuffer>& buffer);
|
||||
|
||||
private:
|
||||
BufferQueueProducer(const BufferQueueProducer&) = delete;
|
||||
|
||||
Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags,
|
||||
std::unique_lock<std::mutex>& lk) const;
|
||||
|
||||
Kernel::KEvent* buffer_wait_event{};
|
||||
Service::KernelHelpers::ServiceContext& service_context;
|
||||
|
||||
std::shared_ptr<BufferQueueCore> core;
|
||||
BufferQueueDefs::SlotsType& slots;
|
||||
u32 sticky_transform{};
|
||||
std::mutex callback_mutex;
|
||||
s32 next_callback_ticket{};
|
||||
s32 current_callback_ticket{};
|
||||
std::condition_variable_any callback_condition;
|
||||
|
||||
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,38 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/BufferSlot.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/ui/fence.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class GraphicBuffer;
|
||||
|
||||
enum class BufferState : u32 {
|
||||
Free = 0,
|
||||
Dequeued = 1,
|
||||
Queued = 2,
|
||||
Acquired = 3,
|
||||
};
|
||||
|
||||
struct BufferSlot final {
|
||||
constexpr BufferSlot() = default;
|
||||
|
||||
std::shared_ptr<GraphicBuffer> graphic_buffer;
|
||||
BufferState buffer_state{BufferState::Free};
|
||||
bool request_buffer_called{};
|
||||
u64 frame_number{};
|
||||
Fence fence;
|
||||
bool acquire_called{};
|
||||
bool attached_by_consumer{};
|
||||
bool is_preallocated{};
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
enum class BufferTransformFlags : u32 {
|
||||
/// No transform flags are set
|
||||
Unset = 0x00,
|
||||
/// Flip source image horizontally (around the vertical axis)
|
||||
FlipH = 0x01,
|
||||
/// Flip source image vertically (around the horizontal axis)
|
||||
FlipV = 0x02,
|
||||
/// Rotate source image 90 degrees clockwise
|
||||
Rotate90 = 0x04,
|
||||
/// Rotate source image 180 degrees
|
||||
Rotate180 = 0x03,
|
||||
/// Rotate source image 270 degrees clockwise
|
||||
Rotate270 = 0x07,
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,133 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/ConsumerBase.cpp
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.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/consumer_base.h"
|
||||
#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
ConsumerBase::ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_)
|
||||
: consumer{std::move(consumer_)} {}
|
||||
|
||||
ConsumerBase::~ConsumerBase() {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
ASSERT_MSG(is_abandoned, "consumer is not abandoned!");
|
||||
}
|
||||
|
||||
void ConsumerBase::Connect(bool controlled_by_app) {
|
||||
consumer->Connect(shared_from_this(), controlled_by_app);
|
||||
}
|
||||
|
||||
void ConsumerBase::FreeBufferLocked(s32 slot_index) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot_index={}", slot_index);
|
||||
|
||||
slots[slot_index].graphic_buffer = nullptr;
|
||||
slots[slot_index].fence = Fence::NoFence();
|
||||
slots[slot_index].frame_number = 0;
|
||||
}
|
||||
|
||||
void ConsumerBase::OnFrameAvailable(const BufferItem& item) {
|
||||
LOG_DEBUG(Service_NVFlinger, "called");
|
||||
}
|
||||
|
||||
void ConsumerBase::OnFrameReplaced(const BufferItem& item) {
|
||||
LOG_DEBUG(Service_NVFlinger, "called");
|
||||
}
|
||||
|
||||
void ConsumerBase::OnBuffersReleased() {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "called");
|
||||
|
||||
if (is_abandoned) {
|
||||
// Nothing to do if we're already abandoned.
|
||||
return;
|
||||
}
|
||||
|
||||
u64 mask = 0;
|
||||
consumer->GetReleasedBuffers(&mask);
|
||||
for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
|
||||
if (mask & (1ULL << i)) {
|
||||
FreeBufferLocked(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConsumerBase::OnSidebandStreamChanged() {}
|
||||
|
||||
Status ConsumerBase::AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when) {
|
||||
Status err = consumer->AcquireBuffer(item, present_when);
|
||||
if (err != Status::NoError) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (item->graphic_buffer != nullptr) {
|
||||
slots[item->slot].graphic_buffer = item->graphic_buffer;
|
||||
}
|
||||
|
||||
slots[item->slot].frame_number = item->frame_number;
|
||||
slots[item->slot].fence = item->fence;
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "slot={}", item->slot);
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status ConsumerBase::AddReleaseFenceLocked(s32 slot,
|
||||
const std::shared_ptr<GraphicBuffer>& graphic_buffer,
|
||||
const Fence& fence) {
|
||||
LOG_DEBUG(Service_NVFlinger, "slot={}", slot);
|
||||
|
||||
// If consumer no longer tracks this graphic_buffer, we can safely
|
||||
// drop this fence, as it will never be received by the producer.
|
||||
|
||||
if (!StillTracking(slot, graphic_buffer)) {
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
slots[slot].fence = fence;
|
||||
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status ConsumerBase::ReleaseBufferLocked(s32 slot,
|
||||
const std::shared_ptr<GraphicBuffer>& graphic_buffer) {
|
||||
// If consumer no longer tracks this graphic_buffer (we received a new
|
||||
// buffer on the same slot), the buffer producer is definitely no longer
|
||||
// tracking it.
|
||||
|
||||
if (!StillTracking(slot, graphic_buffer)) {
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "slot={}", slot);
|
||||
Status err = consumer->ReleaseBuffer(slot, slots[slot].frame_number, slots[slot].fence);
|
||||
if (err == Status::StaleBufferSlot) {
|
||||
FreeBufferLocked(slot);
|
||||
}
|
||||
|
||||
slots[slot].fence = Fence::NoFence();
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
bool ConsumerBase::StillTracking(s32 slot,
|
||||
const std::shared_ptr<GraphicBuffer>& graphic_buffer) const {
|
||||
if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (slots[slot].graphic_buffer != nullptr &&
|
||||
slots[slot].graphic_buffer->Handle() == graphic_buffer->Handle());
|
||||
}
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,60 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/ConsumerBase.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue_defs.h"
|
||||
#include "core/hle/service/nvflinger/consumer_listener.h"
|
||||
#include "core/hle/service/nvflinger/status.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class BufferItem;
|
||||
class BufferQueueConsumer;
|
||||
|
||||
class ConsumerBase : public IConsumerListener, public std::enable_shared_from_this<ConsumerBase> {
|
||||
public:
|
||||
void Connect(bool controlled_by_app);
|
||||
|
||||
protected:
|
||||
explicit ConsumerBase(std::unique_ptr<BufferQueueConsumer> consumer_);
|
||||
~ConsumerBase() override;
|
||||
|
||||
void OnFrameAvailable(const BufferItem& item) override;
|
||||
void OnFrameReplaced(const BufferItem& item) override;
|
||||
void OnBuffersReleased() override;
|
||||
void OnSidebandStreamChanged() override;
|
||||
|
||||
void FreeBufferLocked(s32 slot_index);
|
||||
Status AcquireBufferLocked(BufferItem* item, std::chrono::nanoseconds present_when);
|
||||
Status ReleaseBufferLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer);
|
||||
bool StillTracking(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer) const;
|
||||
Status AddReleaseFenceLocked(s32 slot, const std::shared_ptr<GraphicBuffer>& graphic_buffer,
|
||||
const Fence& fence);
|
||||
|
||||
struct Slot final {
|
||||
std::shared_ptr<GraphicBuffer> graphic_buffer;
|
||||
Fence fence;
|
||||
u64 frame_number{};
|
||||
};
|
||||
|
||||
protected:
|
||||
std::array<Slot, BufferQueueDefs::NUM_BUFFER_SLOTS> slots;
|
||||
|
||||
bool is_abandoned{};
|
||||
|
||||
std::unique_ptr<BufferQueueConsumer> consumer;
|
||||
|
||||
mutable std::mutex mutex;
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IConsumerListener.h
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class BufferItem;
|
||||
|
||||
/// ConsumerListener is the interface through which the BufferQueue notifies the consumer of events
|
||||
/// that the consumer may wish to react to.
|
||||
class IConsumerListener {
|
||||
public:
|
||||
IConsumerListener() = default;
|
||||
virtual ~IConsumerListener() = default;
|
||||
|
||||
virtual void OnFrameAvailable(const BufferItem& item) = 0;
|
||||
virtual void OnFrameReplaced(const BufferItem& item) = 0;
|
||||
virtual void OnBuffersReleased() = 0;
|
||||
virtual void OnSidebandStreamChanged() = 0;
|
||||
};
|
||||
|
||||
}; // namespace Service::android
|
||||
@@ -1,18 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/IGraphicBufferProducer.cpp
|
||||
|
||||
#include "core/hle/service/nvflinger/graphic_buffer_producer.h"
|
||||
#include "core/hle/service/nvflinger/parcel.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
QueueBufferInput::QueueBufferInput(InputParcel& parcel) {
|
||||
parcel.ReadFlattened(*this);
|
||||
}
|
||||
|
||||
QueueBufferOutput::QueueBufferOutput() = default;
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,76 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2010 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IGraphicBufferProducer.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hle/service/nvflinger/ui/fence.h"
|
||||
#include "core/hle/service/nvflinger/window.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class InputParcel;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct QueueBufferInput final {
|
||||
explicit QueueBufferInput(InputParcel& parcel);
|
||||
|
||||
void Deflate(s64* timestamp_, bool* is_auto_timestamp_, Common::Rectangle<s32>* crop_,
|
||||
NativeWindowScalingMode* scaling_mode_, NativeWindowTransform* transform_,
|
||||
u32* sticky_transform_, bool* async_, s32* swap_interval_, Fence* fence_) const {
|
||||
*timestamp_ = timestamp;
|
||||
*is_auto_timestamp_ = static_cast<bool>(is_auto_timestamp);
|
||||
*crop_ = crop;
|
||||
*scaling_mode_ = scaling_mode;
|
||||
*transform_ = transform;
|
||||
*sticky_transform_ = sticky_transform;
|
||||
*async_ = static_cast<bool>(async);
|
||||
*swap_interval_ = swap_interval;
|
||||
*fence_ = fence;
|
||||
}
|
||||
|
||||
private:
|
||||
s64 timestamp{};
|
||||
s32 is_auto_timestamp{};
|
||||
Common::Rectangle<s32> crop{};
|
||||
NativeWindowScalingMode scaling_mode{};
|
||||
NativeWindowTransform transform{};
|
||||
u32 sticky_transform{};
|
||||
s32 async{};
|
||||
s32 swap_interval{};
|
||||
Fence fence{};
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(QueueBufferInput) == 84, "QueueBufferInput has wrong size");
|
||||
|
||||
struct QueueBufferOutput final {
|
||||
QueueBufferOutput();
|
||||
|
||||
void Deflate(u32* width_, u32* height_, u32* transform_hint_, u32* num_pending_buffers_) const {
|
||||
*width_ = width;
|
||||
*height_ = height;
|
||||
*transform_hint_ = transform_hint;
|
||||
*num_pending_buffers_ = num_pending_buffers;
|
||||
}
|
||||
|
||||
void Inflate(u32 width_, u32 height_, u32 transform_hint_, u32 num_pending_buffers_) {
|
||||
width = width_;
|
||||
height = height_;
|
||||
transform_hint = transform_hint_;
|
||||
num_pending_buffers = num_pending_buffers_;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 width{};
|
||||
u32 height{};
|
||||
u32 transform_hint{};
|
||||
u32 num_pending_buffers{};
|
||||
};
|
||||
static_assert(sizeof(QueueBufferOutput) == 16, "QueueBufferOutput has wrong size");
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,36 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
|
||||
HosBinderDriverServer::HosBinderDriverServer(Core::System& system_)
|
||||
: service_context(system_, "HosBinderDriverServer") {}
|
||||
|
||||
HosBinderDriverServer::~HosBinderDriverServer() {}
|
||||
|
||||
u64 HosBinderDriverServer::RegisterProducer(std::unique_ptr<android::IBinder>&& binder) {
|
||||
std::scoped_lock lk{lock};
|
||||
|
||||
last_id++;
|
||||
|
||||
producers[last_id] = std::move(binder);
|
||||
|
||||
return last_id;
|
||||
}
|
||||
|
||||
android::IBinder* HosBinderDriverServer::TryGetProducer(u64 id) {
|
||||
std::scoped_lock lk{lock};
|
||||
|
||||
if (auto search = producers.find(id); search != producers.end()) {
|
||||
return search->second.get();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Service::NVFlinger
|
||||
@@ -1,37 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/nvflinger/binder.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
|
||||
class HosBinderDriverServer final {
|
||||
public:
|
||||
explicit HosBinderDriverServer(Core::System& system_);
|
||||
~HosBinderDriverServer();
|
||||
|
||||
u64 RegisterProducer(std::unique_ptr<android::IBinder>&& binder);
|
||||
|
||||
android::IBinder* TryGetProducer(u64 id);
|
||||
|
||||
private:
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
std::unordered_map<u64, std::unique_ptr<android::IBinder>> producers;
|
||||
std::mutex lock;
|
||||
u64 last_id{};
|
||||
};
|
||||
|
||||
} // namespace Service::NVFlinger
|
||||
@@ -1,335 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_readable_event.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||
#include "core/hle/service/nvflinger/buffer_item_consumer.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
||||
#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
|
||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||
#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 {
|
||||
|
||||
constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60};
|
||||
|
||||
void NVFlinger::SplitVSync(std::stop_token stop_token) {
|
||||
system.RegisterHostThread();
|
||||
std::string name = "VSyncThread";
|
||||
MicroProfileOnThreadCreate(name.c_str());
|
||||
|
||||
// Cleanup
|
||||
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
|
||||
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
vsync_signal.wait(false);
|
||||
vsync_signal.store(false);
|
||||
|
||||
guard->lock();
|
||||
|
||||
Compose();
|
||||
|
||||
guard->unlock();
|
||||
}
|
||||
}
|
||||
|
||||
NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_)
|
||||
: system(system_), service_context(system_, "nvflinger"),
|
||||
hos_binder_driver_server(hos_binder_driver_server_) {
|
||||
displays.emplace_back(0, "Default", hos_binder_driver_server, service_context, system);
|
||||
displays.emplace_back(1, "External", hos_binder_driver_server, service_context, system);
|
||||
displays.emplace_back(2, "Edid", hos_binder_driver_server, service_context, system);
|
||||
displays.emplace_back(3, "Internal", hos_binder_driver_server, service_context, system);
|
||||
displays.emplace_back(4, "Null", hos_binder_driver_server, service_context, system);
|
||||
guard = std::make_shared<std::mutex>();
|
||||
|
||||
// Schedule the screen composition events
|
||||
multi_composition_event = Core::Timing::CreateEvent(
|
||||
"ScreenComposition",
|
||||
[this](std::uintptr_t, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
vsync_signal.store(true);
|
||||
vsync_signal.notify_all();
|
||||
return std::chrono::nanoseconds(GetNextTicks());
|
||||
});
|
||||
|
||||
single_composition_event = Core::Timing::CreateEvent(
|
||||
"ScreenComposition",
|
||||
[this](std::uintptr_t, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto lock_guard = Lock();
|
||||
Compose();
|
||||
|
||||
return std::chrono::nanoseconds(GetNextTicks());
|
||||
});
|
||||
|
||||
if (system.IsMulticore()) {
|
||||
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event);
|
||||
vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); });
|
||||
} else {
|
||||
system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event);
|
||||
}
|
||||
}
|
||||
|
||||
NVFlinger::~NVFlinger() {
|
||||
if (system.IsMulticore()) {
|
||||
system.CoreTiming().UnscheduleEvent(multi_composition_event, {});
|
||||
vsync_thread.request_stop();
|
||||
vsync_signal.store(true);
|
||||
vsync_signal.notify_all();
|
||||
} else {
|
||||
system.CoreTiming().UnscheduleEvent(single_composition_event, {});
|
||||
}
|
||||
|
||||
ShutdownLayers();
|
||||
|
||||
if (nvdrv) {
|
||||
nvdrv->Close(disp_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void NVFlinger::ShutdownLayers() {
|
||||
for (auto& display : displays) {
|
||||
for (size_t layer = 0; layer < display.GetNumLayers(); ++layer) {
|
||||
display.GetLayer(layer).Core().NotifyShutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
const auto lock_guard = Lock();
|
||||
|
||||
LOG_DEBUG(Service_NVFlinger, "Opening \"{}\" display", name);
|
||||
|
||||
const auto itr =
|
||||
std::find_if(displays.begin(), displays.end(),
|
||||
[&](const VI::Display& display) { return display.GetName() == name; });
|
||||
|
||||
if (itr == displays.end()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return itr->GetID();
|
||||
}
|
||||
|
||||
bool NVFlinger::CloseDisplay(u64 display_id) {
|
||||
const auto lock_guard = Lock();
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
display->Reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
|
||||
const auto lock_guard = Lock();
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const u64 layer_id = next_layer_id++;
|
||||
CreateLayerAtId(*display, layer_id);
|
||||
return layer_id;
|
||||
}
|
||||
|
||||
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
|
||||
const auto buffer_id = next_buffer_queue_id++;
|
||||
display.CreateLayer(layer_id, buffer_id, nvdrv->container);
|
||||
}
|
||||
|
||||
void NVFlinger::CloseLayer(u64 layer_id) {
|
||||
const auto lock_guard = Lock();
|
||||
|
||||
for (auto& display : displays) {
|
||||
display.CloseLayer(layer_id);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
|
||||
const auto lock_guard = Lock();
|
||||
const auto* const layer = FindOrCreateLayer(display_id, layer_id);
|
||||
|
||||
if (layer == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return layer->GetBinderId();
|
||||
}
|
||||
|
||||
ResultVal<Kernel::KReadableEvent*> NVFlinger::FindVsyncEvent(u64 display_id) {
|
||||
const auto lock_guard = Lock();
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return VI::ResultNotFound;
|
||||
}
|
||||
|
||||
return display->GetVSyncEvent();
|
||||
}
|
||||
|
||||
VI::Display* NVFlinger::FindDisplay(u64 display_id) {
|
||||
const auto itr =
|
||||
std::find_if(displays.begin(), displays.end(),
|
||||
[&](const VI::Display& display) { return display.GetID() == display_id; });
|
||||
|
||||
if (itr == displays.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &*itr;
|
||||
}
|
||||
|
||||
const VI::Display* NVFlinger::FindDisplay(u64 display_id) const {
|
||||
const auto itr =
|
||||
std::find_if(displays.begin(), displays.end(),
|
||||
[&](const VI::Display& display) { return display.GetID() == display_id; });
|
||||
|
||||
if (itr == displays.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &*itr;
|
||||
}
|
||||
|
||||
VI::Layer* NVFlinger::FindLayer(u64 display_id, u64 layer_id) {
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return display->FindLayer(layer_id);
|
||||
}
|
||||
|
||||
const VI::Layer* NVFlinger::FindLayer(u64 display_id, u64 layer_id) const {
|
||||
const auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return display->FindLayer(layer_id);
|
||||
}
|
||||
|
||||
VI::Layer* NVFlinger::FindOrCreateLayer(u64 display_id, u64 layer_id) {
|
||||
auto* const display = FindDisplay(display_id);
|
||||
|
||||
if (display == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* layer = display->FindLayer(layer_id);
|
||||
|
||||
if (layer == nullptr) {
|
||||
LOG_DEBUG(Service_NVFlinger, "Layer at id {} not found. Trying to create it.", layer_id);
|
||||
CreateLayerAtId(*display, layer_id);
|
||||
return display->FindLayer(layer_id);
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
void NVFlinger::Compose() {
|
||||
for (auto& display : displays) {
|
||||
// Trigger vsync for this display at the end of drawing
|
||||
SCOPE_EXIT({ display.SignalVSyncEvent(); });
|
||||
|
||||
// Don't do anything for displays without layers.
|
||||
if (!display.HasLayers())
|
||||
continue;
|
||||
|
||||
// TODO(Subv): Support more than 1 layer.
|
||||
VI::Layer& layer = display.GetLayer(0);
|
||||
|
||||
android::BufferItem buffer{};
|
||||
const auto status = layer.GetConsumer().AcquireBuffer(&buffer, {}, false);
|
||||
|
||||
if (status != android::Status::NoError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& igbp_buffer = *buffer.graphic_buffer;
|
||||
|
||||
if (!system.IsPoweredOn()) {
|
||||
return; // We are likely shutting down
|
||||
}
|
||||
|
||||
// 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>(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,
|
||||
buffer.fence.fences, buffer.fence.num_fences);
|
||||
|
||||
MicroProfileFlip();
|
||||
guard->lock();
|
||||
|
||||
swap_interval = buffer.swap_interval;
|
||||
|
||||
layer.GetConsumer().ReleaseBuffer(buffer, android::Fence::NoFence());
|
||||
}
|
||||
}
|
||||
|
||||
s64 NVFlinger::GetNextTicks() const {
|
||||
const auto& settings = Settings::values;
|
||||
auto speed_scale = 1.f;
|
||||
if (settings.use_multi_core.GetValue()) {
|
||||
if (settings.use_speed_limit.GetValue()) {
|
||||
// Scales the speed based on speed_limit setting on MC. SC is handled by
|
||||
// SpeedLimiter::DoSpeedLimiting.
|
||||
speed_scale = 100.f / settings.speed_limit.GetValue();
|
||||
} else {
|
||||
// Run at unlocked framerate.
|
||||
speed_scale = 0.01f;
|
||||
}
|
||||
}
|
||||
|
||||
// As an extension, treat nonpositive swap interval as framerate multiplier.
|
||||
const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval)
|
||||
: 60.f / static_cast<f32>(swap_interval);
|
||||
|
||||
return static_cast<s64>(speed_scale * (1000000000.f / effective_fps));
|
||||
}
|
||||
|
||||
} // namespace Service::NVFlinger
|
||||
@@ -1,155 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/polyfill_thread.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
|
||||
namespace Common {
|
||||
class Event;
|
||||
} // namespace Common
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Kernel {
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Service::Nvidia {
|
||||
class Module;
|
||||
} // namespace Service::Nvidia
|
||||
|
||||
namespace Service::VI {
|
||||
class Display;
|
||||
class Layer;
|
||||
} // namespace Service::VI
|
||||
|
||||
namespace Service::android {
|
||||
class BufferQueueCore;
|
||||
class BufferQueueProducer;
|
||||
} // namespace Service::android
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
|
||||
class NVFlinger final {
|
||||
public:
|
||||
explicit NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_);
|
||||
~NVFlinger();
|
||||
|
||||
void ShutdownLayers();
|
||||
|
||||
/// Sets the NVDrv module instance to use to send buffers to the GPU.
|
||||
void SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance);
|
||||
|
||||
/// Opens the specified display and returns the ID.
|
||||
///
|
||||
/// If an invalid display name is provided, then an empty optional is returned.
|
||||
[[nodiscard]] std::optional<u64> OpenDisplay(std::string_view name);
|
||||
|
||||
/// Closes the specified display by its ID.
|
||||
///
|
||||
/// Returns false if an invalid display ID is provided.
|
||||
[[nodiscard]] bool CloseDisplay(u64 display_id);
|
||||
|
||||
/// Creates a layer on the specified display and returns the layer ID.
|
||||
///
|
||||
/// If an invalid display ID is specified, then an empty optional is returned.
|
||||
[[nodiscard]] std::optional<u64> CreateLayer(u64 display_id);
|
||||
|
||||
/// Closes a layer on all displays for the given layer ID.
|
||||
void CloseLayer(u64 layer_id);
|
||||
|
||||
/// Finds the buffer queue ID of the specified layer in the specified display.
|
||||
///
|
||||
/// If an invalid display ID or layer ID is provided, then an empty optional is returned.
|
||||
[[nodiscard]] std::optional<u32> FindBufferQueueId(u64 display_id, u64 layer_id);
|
||||
|
||||
/// Gets the vsync event for the specified display.
|
||||
///
|
||||
/// 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.
|
||||
void Compose();
|
||||
|
||||
[[nodiscard]] s64 GetNextTicks() const;
|
||||
|
||||
private:
|
||||
struct Layer {
|
||||
std::unique_ptr<android::BufferQueueCore> core;
|
||||
std::unique_ptr<android::BufferQueueProducer> producer;
|
||||
};
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::unique_lock<std::mutex> Lock() const {
|
||||
return std::unique_lock{*guard};
|
||||
}
|
||||
|
||||
/// Finds the display identified by the specified ID.
|
||||
[[nodiscard]] VI::Display* FindDisplay(u64 display_id);
|
||||
|
||||
/// Finds the display identified by the specified ID.
|
||||
[[nodiscard]] const VI::Display* FindDisplay(u64 display_id) const;
|
||||
|
||||
/// Finds the layer identified by the specified ID in the desired display.
|
||||
[[nodiscard]] VI::Layer* FindLayer(u64 display_id, u64 layer_id);
|
||||
|
||||
/// Finds the layer identified by the specified ID in the desired display.
|
||||
[[nodiscard]] const VI::Layer* FindLayer(u64 display_id, u64 layer_id) const;
|
||||
|
||||
/// Finds the layer identified by the specified ID in the desired display,
|
||||
/// or creates the layer if it is not found.
|
||||
/// To be used when the system expects the specified ID to already exist.
|
||||
[[nodiscard]] VI::Layer* FindOrCreateLayer(u64 display_id, u64 layer_id);
|
||||
|
||||
/// Creates a layer with the specified layer ID in the desired display.
|
||||
void CreateLayerAtId(VI::Display& display, u64 layer_id);
|
||||
|
||||
void SplitVSync(std::stop_token stop_token);
|
||||
|
||||
std::shared_ptr<Nvidia::Module> nvdrv;
|
||||
s32 disp_fd;
|
||||
|
||||
std::list<VI::Display> displays;
|
||||
|
||||
/// Id to use for the next layer that is created, this counter is shared among all displays.
|
||||
u64 next_layer_id = 1;
|
||||
/// Id to use for the next buffer queue that is created, this counter is shared among all
|
||||
/// layers.
|
||||
u32 next_buffer_queue_id = 1;
|
||||
|
||||
s32 swap_interval = 1;
|
||||
|
||||
/// Event that handles screen composition.
|
||||
std::shared_ptr<Core::Timing::EventType> multi_composition_event;
|
||||
std::shared_ptr<Core::Timing::EventType> single_composition_event;
|
||||
|
||||
std::shared_ptr<std::mutex> guard;
|
||||
|
||||
Core::System& system;
|
||||
|
||||
std::atomic<bool> vsync_signal;
|
||||
|
||||
std::jthread vsync_thread;
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
HosBinderDriverServer& hos_binder_driver_server;
|
||||
};
|
||||
|
||||
} // namespace Service::NVFlinger
|
||||
@@ -1,177 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
struct ParcelHeader {
|
||||
u32 data_size;
|
||||
u32 data_offset;
|
||||
u32 objects_size;
|
||||
u32 objects_offset;
|
||||
};
|
||||
static_assert(sizeof(ParcelHeader) == 16, "ParcelHeader has wrong size");
|
||||
|
||||
class InputParcel final {
|
||||
public:
|
||||
explicit InputParcel(std::span<const u8> in_data) : read_buffer(std::move(in_data)) {
|
||||
DeserializeHeader();
|
||||
[[maybe_unused]] const std::u16string token = ReadInterfaceToken();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Read(T& val) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
|
||||
ASSERT(read_index + sizeof(T) <= read_buffer.size());
|
||||
|
||||
std::memcpy(&val, read_buffer.data() + read_index, sizeof(T));
|
||||
read_index += sizeof(T);
|
||||
read_index = Common::AlignUp(read_index, 4);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T Read() {
|
||||
T val;
|
||||
Read(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ReadFlattened(T& val) {
|
||||
const auto flattened_size = Read<s64>();
|
||||
ASSERT(sizeof(T) == flattened_size);
|
||||
Read(val);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T ReadFlattened() {
|
||||
T val;
|
||||
ReadFlattened(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T ReadUnaligned() {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
|
||||
ASSERT(read_index + sizeof(T) <= read_buffer.size());
|
||||
|
||||
T val;
|
||||
std::memcpy(&val, read_buffer.data() + read_index, sizeof(T));
|
||||
read_index += sizeof(T);
|
||||
return val;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const std::shared_ptr<T> ReadObject() {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
|
||||
|
||||
const auto is_valid{Read<bool>()};
|
||||
|
||||
if (is_valid) {
|
||||
auto result = std::make_shared<T>();
|
||||
ReadFlattened(*result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::u16string ReadInterfaceToken() {
|
||||
[[maybe_unused]] const u32 unknown = Read<u32>();
|
||||
const u32 length = Read<u32>();
|
||||
|
||||
std::u16string token;
|
||||
token.reserve(length + 1);
|
||||
|
||||
for (u32 ch = 0; ch < length + 1; ++ch) {
|
||||
token.push_back(ReadUnaligned<u16>());
|
||||
}
|
||||
|
||||
read_index = Common::AlignUp(read_index, 4);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
void DeserializeHeader() {
|
||||
ASSERT(read_buffer.size() > sizeof(ParcelHeader));
|
||||
|
||||
ParcelHeader header{};
|
||||
std::memcpy(&header, read_buffer.data(), sizeof(ParcelHeader));
|
||||
|
||||
read_index = header.data_offset;
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<const u8> read_buffer;
|
||||
std::size_t read_index = 0;
|
||||
};
|
||||
|
||||
class OutputParcel final {
|
||||
public:
|
||||
static constexpr std::size_t DefaultBufferSize = 0x40;
|
||||
|
||||
OutputParcel() : buffer(DefaultBufferSize) {}
|
||||
|
||||
template <typename T>
|
||||
explicit OutputParcel(const T& out_data) : buffer(DefaultBufferSize) {
|
||||
Write(out_data);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Write(const T& val) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
|
||||
|
||||
if (buffer.size() < write_index + sizeof(T)) {
|
||||
buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize);
|
||||
}
|
||||
|
||||
std::memcpy(buffer.data() + write_index, &val, sizeof(T));
|
||||
write_index += sizeof(T);
|
||||
write_index = Common::AlignUp(write_index, 4);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void WriteObject(const T* ptr) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
|
||||
|
||||
if (!ptr) {
|
||||
Write<u32>(0);
|
||||
return;
|
||||
}
|
||||
|
||||
Write<u32>(1);
|
||||
Write<s64>(sizeof(T));
|
||||
Write(*ptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void WriteObject(const std::shared_ptr<T> ptr) {
|
||||
WriteObject(ptr.get());
|
||||
}
|
||||
|
||||
std::vector<u8> Serialize() const {
|
||||
ParcelHeader header{};
|
||||
header.data_size = static_cast<u32>(write_index - sizeof(ParcelHeader));
|
||||
header.data_offset = sizeof(ParcelHeader);
|
||||
header.objects_size = 4;
|
||||
header.objects_offset = static_cast<u32>(sizeof(ParcelHeader) + header.data_size);
|
||||
std::memcpy(buffer.data(), &header, sizeof(ParcelHeader));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::vector<u8> buffer;
|
||||
std::size_t write_index = sizeof(ParcelHeader);
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,21 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
enum class PixelFormat : u32 {
|
||||
NoFormat = 0,
|
||||
Rgba8888 = 1,
|
||||
Rgbx8888 = 2,
|
||||
Rgb888 = 3,
|
||||
Rgb565 = 4,
|
||||
Bgra8888 = 5,
|
||||
Rgba5551 = 6,
|
||||
Rgba4444 = 7,
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,17 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2014 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/gui/IProducerListener.h
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class IProducerListener {
|
||||
public:
|
||||
virtual ~IProducerListener() = default;
|
||||
virtual void OnBufferReleased() = 0;
|
||||
};
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,28 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
enum class Status : s32 {
|
||||
None = 0,
|
||||
NoError = 0,
|
||||
StaleBufferSlot = 1,
|
||||
NoBufferAvailable = 2,
|
||||
PresentLater = 3,
|
||||
WouldBlock = -11,
|
||||
NoMemory = -12,
|
||||
Busy = -16,
|
||||
NoInit = -19,
|
||||
BadValue = -22,
|
||||
InvalidOperation = -37,
|
||||
BufferNeedsReallocation = 1,
|
||||
ReleaseAllBuffers = 2,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(Status);
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2012 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/Fence.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvdrv/nvdata.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class Fence {
|
||||
public:
|
||||
constexpr Fence() = default;
|
||||
|
||||
static constexpr Fence NoFence() {
|
||||
Fence fence;
|
||||
fence.fences[0].id = -1;
|
||||
return fence;
|
||||
}
|
||||
|
||||
public:
|
||||
u32 num_fences{};
|
||||
std::array<Service::Nvidia::NvFence, 4> fences{};
|
||||
};
|
||||
static_assert(sizeof(Fence) == 36, "Fence has wrong size");
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,100 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2007 The Android Open Source Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Parts of this implementation were based on:
|
||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/include/ui/GraphicBuffer.h
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/nvflinger/pixel_format.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
class GraphicBuffer final {
|
||||
public:
|
||||
constexpr GraphicBuffer() = default;
|
||||
|
||||
constexpr GraphicBuffer(u32 width_, u32 height_, PixelFormat format_, u32 usage_)
|
||||
: width{static_cast<s32>(width_)}, height{static_cast<s32>(height_)}, format{format_},
|
||||
usage{static_cast<s32>(usage_)} {}
|
||||
|
||||
constexpr u32 Width() const {
|
||||
return static_cast<u32>(width);
|
||||
}
|
||||
|
||||
constexpr u32 Height() const {
|
||||
return static_cast<u32>(height);
|
||||
}
|
||||
|
||||
constexpr u32 Stride() const {
|
||||
return static_cast<u32>(stride);
|
||||
}
|
||||
|
||||
constexpr u32 Usage() const {
|
||||
return static_cast<u32>(usage);
|
||||
}
|
||||
|
||||
constexpr PixelFormat Format() const {
|
||||
return format;
|
||||
}
|
||||
|
||||
constexpr u32 BufferId() const {
|
||||
return buffer_id;
|
||||
}
|
||||
|
||||
constexpr PixelFormat ExternalFormat() const {
|
||||
return external_format;
|
||||
}
|
||||
|
||||
constexpr u32 Handle() const {
|
||||
return handle;
|
||||
}
|
||||
|
||||
constexpr u32 Offset() const {
|
||||
return offset;
|
||||
}
|
||||
|
||||
constexpr bool NeedsReallocation(u32 width_, u32 height_, PixelFormat format_,
|
||||
u32 usage_) const {
|
||||
if (static_cast<s32>(width_) != width) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (static_cast<s32>(height_) != height) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (format_ != format) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((static_cast<u32>(usage) & usage_) != usage_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 magic{};
|
||||
s32 width{};
|
||||
s32 height{};
|
||||
s32 stride{};
|
||||
PixelFormat format{};
|
||||
s32 usage{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32 index{};
|
||||
INSERT_PADDING_WORDS(3);
|
||||
u32 buffer_id{};
|
||||
INSERT_PADDING_WORDS(6);
|
||||
PixelFormat external_format{};
|
||||
INSERT_PADDING_WORDS(10);
|
||||
u32 handle{};
|
||||
u32 offset{};
|
||||
INSERT_PADDING_WORDS(60);
|
||||
};
|
||||
static_assert(sizeof(GraphicBuffer) == 0x16C, "GraphicBuffer has wrong size");
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,53 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Service::android {
|
||||
|
||||
/// Attributes queryable with Query
|
||||
enum class NativeWindow : s32 {
|
||||
Width = 0,
|
||||
Height = 1,
|
||||
Format = 2,
|
||||
MinUndequeedBuffers = 3,
|
||||
QueuesToWindowComposer = 4,
|
||||
ConcreteType = 5,
|
||||
DefaultWidth = 6,
|
||||
DefaultHeight = 7,
|
||||
TransformHint = 8,
|
||||
ConsumerRunningBehind = 9,
|
||||
ConsumerUsageBits = 10,
|
||||
StickyTransform = 11,
|
||||
DefaultDataSpace = 12,
|
||||
BufferAge = 13,
|
||||
};
|
||||
|
||||
/// Parameter for Connect/Disconnect
|
||||
enum class NativeWindowApi : s32 {
|
||||
NoConnectedApi = 0,
|
||||
Egl = 1,
|
||||
Cpu = 2,
|
||||
Media = 3,
|
||||
Camera = 4,
|
||||
};
|
||||
|
||||
/// Scaling mode parameter for QueueBuffer
|
||||
enum class NativeWindowScalingMode : s32 {
|
||||
Freeze = 0,
|
||||
ScaleToWindow = 1,
|
||||
ScaleCrop = 2,
|
||||
NoScaleCrop = 3,
|
||||
};
|
||||
|
||||
/// Transform parameter for QueueBuffer
|
||||
enum class NativeWindowTransform : u32 {
|
||||
None = 0x0,
|
||||
InverseDisplay = 0x08,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(NativeWindowTransform);
|
||||
|
||||
} // namespace Service::android
|
||||
@@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/sockets/ethc.h"
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
ETHC_C::ETHC_C(Core::System& system_) : ServiceFramework{system_, "ethc:c"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Initialize"},
|
||||
{1, nullptr, "Cancel"},
|
||||
{2, nullptr, "GetResult"},
|
||||
{3, nullptr, "GetMediaList"},
|
||||
{4, nullptr, "SetMediaType"},
|
||||
{5, nullptr, "GetMediaType"},
|
||||
{6, nullptr, "Unknown6"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ETHC_C::~ETHC_C() = default;
|
||||
|
||||
ETHC_I::ETHC_I(Core::System& system_) : ServiceFramework{system_, "ethc:i"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetReadableHandle"},
|
||||
{1, nullptr, "Cancel"},
|
||||
{2, nullptr, "GetResult"},
|
||||
{3, nullptr, "GetInterfaceList"},
|
||||
{4, nullptr, "GetInterfaceCount"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ETHC_I::~ETHC_I() = default;
|
||||
|
||||
} // namespace Service::Sockets
|
||||
@@ -1,26 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::Sockets {
|
||||
|
||||
class ETHC_C final : public ServiceFramework<ETHC_C> {
|
||||
public:
|
||||
explicit ETHC_C(Core::System& system_);
|
||||
~ETHC_C() override;
|
||||
};
|
||||
|
||||
class ETHC_I final : public ServiceFramework<ETHC_I> {
|
||||
public:
|
||||
explicit ETHC_I(Core::System& system_);
|
||||
~ETHC_I() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
@@ -1,186 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hle/service/wlan/wlan.h"
|
||||
|
||||
namespace Service::WLAN {
|
||||
|
||||
class WLANInfra final : public ServiceFramework<WLANInfra> {
|
||||
public:
|
||||
explicit WLANInfra(Core::System& system_) : ServiceFramework{system_, "wlan:inf"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "OpenMode"},
|
||||
{1, nullptr, "CloseMode"},
|
||||
{2, nullptr, "GetMacAddress"},
|
||||
{3, nullptr, "StartScan"},
|
||||
{4, nullptr, "StopScan"},
|
||||
{5, nullptr, "Connect"},
|
||||
{6, nullptr, "CancelConnect"},
|
||||
{7, nullptr, "Disconnect"},
|
||||
{8, nullptr, "GetConnectionEvent"},
|
||||
{9, nullptr, "GetConnectionStatus"},
|
||||
{10, nullptr, "GetState"},
|
||||
{11, nullptr, "GetScanResult"},
|
||||
{12, nullptr, "GetRssi"},
|
||||
{13, nullptr, "ChangeRxAntenna"},
|
||||
{14, nullptr, "GetFwVersion"},
|
||||
{15, nullptr, "RequestSleep"},
|
||||
{16, nullptr, "RequestWakeUp"},
|
||||
{17, nullptr, "RequestIfUpDown"},
|
||||
{18, nullptr, "Unknown18"},
|
||||
{19, nullptr, "Unknown19"},
|
||||
{20, nullptr, "Unknown20"},
|
||||
{21, nullptr, "Unknown21"},
|
||||
{22, nullptr, "Unknown22"},
|
||||
{23, nullptr, "Unknown23"},
|
||||
{24, nullptr, "Unknown24"},
|
||||
{25, nullptr, "Unknown25"},
|
||||
{26, nullptr, "Unknown26"},
|
||||
{27, nullptr, "Unknown27"},
|
||||
{28, nullptr, "Unknown28"},
|
||||
{29, nullptr, "Unknown29"},
|
||||
{30, nullptr, "Unknown30"},
|
||||
{31, nullptr, "Unknown31"},
|
||||
{32, nullptr, "Unknown32"},
|
||||
{33, nullptr, "Unknown33"},
|
||||
{34, nullptr, "Unknown34"},
|
||||
{35, nullptr, "Unknown35"},
|
||||
{36, nullptr, "Unknown36"},
|
||||
{37, nullptr, "Unknown37"},
|
||||
{38, nullptr, "Unknown38"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class WLANLocal final : public ServiceFramework<WLANLocal> {
|
||||
public:
|
||||
explicit WLANLocal(Core::System& system_) : ServiceFramework{system_, "wlan:lcl"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Unknown0"},
|
||||
{1, nullptr, "Unknown1"},
|
||||
{2, nullptr, "Unknown2"},
|
||||
{3, nullptr, "Unknown3"},
|
||||
{4, nullptr, "Unknown4"},
|
||||
{5, nullptr, "Unknown5"},
|
||||
{6, nullptr, "GetMacAddress"},
|
||||
{7, nullptr, "CreateBss"},
|
||||
{8, nullptr, "DestroyBss"},
|
||||
{9, nullptr, "StartScan"},
|
||||
{10, nullptr, "StopScan"},
|
||||
{11, nullptr, "Connect"},
|
||||
{12, nullptr, "CancelConnect"},
|
||||
{13, nullptr, "Join"},
|
||||
{14, nullptr, "CancelJoin"},
|
||||
{15, nullptr, "Disconnect"},
|
||||
{16, nullptr, "SetBeaconLostCount"},
|
||||
{17, nullptr, "Unknown17"},
|
||||
{18, nullptr, "Unknown18"},
|
||||
{19, nullptr, "Unknown19"},
|
||||
{20, nullptr, "GetBssIndicationEvent"},
|
||||
{21, nullptr, "GetBssIndicationInfo"},
|
||||
{22, nullptr, "GetState"},
|
||||
{23, nullptr, "GetAllowedChannels"},
|
||||
{24, nullptr, "AddIe"},
|
||||
{25, nullptr, "DeleteIe"},
|
||||
{26, nullptr, "Unknown26"},
|
||||
{27, nullptr, "Unknown27"},
|
||||
{28, nullptr, "CreateRxEntry"},
|
||||
{29, nullptr, "DeleteRxEntry"},
|
||||
{30, nullptr, "Unknown30"},
|
||||
{31, nullptr, "Unknown31"},
|
||||
{32, nullptr, "AddMatchingDataToRxEntry"},
|
||||
{33, nullptr, "RemoveMatchingDataFromRxEntry"},
|
||||
{34, nullptr, "GetScanResult"},
|
||||
{35, nullptr, "Unknown35"},
|
||||
{36, nullptr, "SetActionFrameWithBeacon"},
|
||||
{37, nullptr, "CancelActionFrameWithBeacon"},
|
||||
{38, nullptr, "CreateRxEntryForActionFrame"},
|
||||
{39, nullptr, "DeleteRxEntryForActionFrame"},
|
||||
{40, nullptr, "Unknown40"},
|
||||
{41, nullptr, "Unknown41"},
|
||||
{42, nullptr, "CancelGetActionFrame"},
|
||||
{43, nullptr, "GetRssi"},
|
||||
{44, nullptr, "Unknown44"},
|
||||
{45, nullptr, "Unknown45"},
|
||||
{46, nullptr, "Unknown46"},
|
||||
{47, nullptr, "Unknown47"},
|
||||
{48, nullptr, "Unknown48"},
|
||||
{49, nullptr, "Unknown49"},
|
||||
{50, nullptr, "Unknown50"},
|
||||
{51, nullptr, "Unknown51"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class WLANLocalGetFrame final : public ServiceFramework<WLANLocalGetFrame> {
|
||||
public:
|
||||
explicit WLANLocalGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:lg"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Unknown"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class WLANSocketGetFrame final : public ServiceFramework<WLANSocketGetFrame> {
|
||||
public:
|
||||
explicit WLANSocketGetFrame(Core::System& system_) : ServiceFramework{system_, "wlan:sg"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Unknown"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
class WLANSocketManager final : public ServiceFramework<WLANSocketManager> {
|
||||
public:
|
||||
explicit WLANSocketManager(Core::System& system_) : ServiceFramework{system_, "wlan:soc"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "Unknown0"},
|
||||
{1, nullptr, "Unknown1"},
|
||||
{2, nullptr, "Unknown2"},
|
||||
{3, nullptr, "Unknown3"},
|
||||
{4, nullptr, "Unknown4"},
|
||||
{5, nullptr, "Unknown5"},
|
||||
{6, nullptr, "GetMacAddress"},
|
||||
{7, nullptr, "SwitchTsfTimerFunction"},
|
||||
{8, nullptr, "Unknown8"},
|
||||
{9, nullptr, "Unknown9"},
|
||||
{10, nullptr, "Unknown10"},
|
||||
{11, nullptr, "Unknown11"},
|
||||
{12, nullptr, "Unknown12"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
};
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system) {
|
||||
std::make_shared<WLANInfra>(system)->InstallAsService(sm);
|
||||
std::make_shared<WLANLocal>(system)->InstallAsService(sm);
|
||||
std::make_shared<WLANLocalGetFrame>(system)->InstallAsService(sm);
|
||||
std::make_shared<WLANSocketGetFrame>(system)->InstallAsService(sm);
|
||||
std::make_shared<WLANSocketManager>(system)->InstallAsService(sm);
|
||||
}
|
||||
|
||||
} // namespace Service::WLAN
|
||||
@@ -1,18 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::WLAN {
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& sm, Core::System& system);
|
||||
|
||||
} // namespace Service::WLAN
|
||||
Reference in New Issue
Block a user