summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/hle/service/hid/hid.cpp27
-rw-r--r--src/core/hle/service/hid/hidbus.cpp531
-rw-r--r--src/core/hle/service/hid/hidbus.h131
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.cpp72
-rw-r--r--src/core/hle/service/hid/hidbus/hidbus_base.h179
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.cpp286
-rw-r--r--src/core/hle/service/hid/hidbus/ringcon.h254
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.cpp51
-rw-r--r--src/core/hle/service/hid/hidbus/starlink.h39
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.cpp52
-rw-r--r--src/core/hle/service/hid/hidbus/stubbed.h39
11 files changed, 1635 insertions, 26 deletions
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index b2cec2253..9d3e0a658 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -16,6 +16,7 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/hid/errors.h"
#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/hid/hidbus.h"
#include "core/hle/service/hid/irs.h"
#include "core/hle/service/hid/xcd.h"
#include "core/memory.h"
@@ -2128,32 +2129,6 @@ public:
}
};
-class HidBus final : public ServiceFramework<HidBus> {
-public:
- explicit HidBus(Core::System& system_) : ServiceFramework{system_, "hidbus"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {1, nullptr, "GetBusHandle"},
- {2, nullptr, "IsExternalDeviceConnected"},
- {3, nullptr, "Initialize"},
- {4, nullptr, "Finalize"},
- {5, nullptr, "EnableExternalDevice"},
- {6, nullptr, "GetExternalDeviceId"},
- {7, nullptr, "SendCommandAsync"},
- {8, nullptr, "GetSendCommandAsynceResult"},
- {9, nullptr, "SetEventForSendCommandAsycResult"},
- {10, nullptr, "GetSharedMemoryHandle"},
- {11, nullptr, "EnableJoyPollingReceiveMode"},
- {12, nullptr, "DisableJoyPollingReceiveMode"},
- {13, nullptr, "GetPollingData"},
- {14, nullptr, "SetStatusManagerType"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
std::make_shared<Hid>(system)->InstallAsService(service_manager);
std::make_shared<HidBus>(system)->InstallAsService(service_manager);
diff --git a/src/core/hle/service/hid/hidbus.cpp b/src/core/hle/service/hid/hidbus.cpp
new file mode 100644
index 000000000..af7662a15
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.cpp
@@ -0,0 +1,531 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/logging/log.h"
+#include "common/settings.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/service/hid/hidbus.h"
+#include "core/hle/service/hid/hidbus/ringcon.h"
+#include "core/hle/service/hid/hidbus/starlink.h"
+#include "core/hle/service/hid/hidbus/stubbed.h"
+#include "core/hle/service/service.h"
+#include "core/memory.h"
+
+namespace Service::HID {
+// (15ms, 66Hz)
+constexpr auto hidbus_update_ns = std::chrono::nanoseconds{15 * 1000 * 1000};
+
+HidBus::HidBus(Core::System& system_)
+ : ServiceFramework{system_, "hidbus"}, service_context{system_, service_name} {
+
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, &HidBus::GetBusHandle, "GetBusHandle"},
+ {2, &HidBus::IsExternalDeviceConnected, "IsExternalDeviceConnected"},
+ {3, &HidBus::Initialize, "Initialize"},
+ {4, &HidBus::Finalize, "Finalize"},
+ {5, &HidBus::EnableExternalDevice, "EnableExternalDevice"},
+ {6, &HidBus::GetExternalDeviceId, "GetExternalDeviceId"},
+ {7, &HidBus::SendCommandAsync, "SendCommandAsync"},
+ {8, &HidBus::GetSendCommandAsynceResult, "GetSendCommandAsynceResult"},
+ {9, &HidBus::SetEventForSendCommandAsycResult, "SetEventForSendCommandAsycResult"},
+ {10, &HidBus::GetSharedMemoryHandle, "GetSharedMemoryHandle"},
+ {11, &HidBus::EnableJoyPollingReceiveMode, "EnableJoyPollingReceiveMode"},
+ {12, &HidBus::DisableJoyPollingReceiveMode, "DisableJoyPollingReceiveMode"},
+ {13, nullptr, "GetPollingData"},
+ {14, &HidBus::SetStatusManagerType, "SetStatusManagerType"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+
+ // Register update callbacks
+ hidbus_update_event = Core::Timing::CreateEvent(
+ "Hidbus::UpdateCallback",
+ [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ const auto guard = LockService();
+ UpdateHidbus(user_data, ns_late);
+ });
+
+ system_.CoreTiming().ScheduleEvent(hidbus_update_ns, hidbus_update_event);
+}
+
+HidBus::~HidBus() {
+ system.CoreTiming().UnscheduleEvent(hidbus_update_event, 0);
+}
+
+void HidBus::UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
+ auto& core_timing = system.CoreTiming();
+
+ if (is_hidbus_enabled) {
+ for (std::size_t i = 0; i < devices.size(); ++i) {
+ if (!devices[i].is_device_initializated) {
+ continue;
+ }
+ auto& device = devices[i].device;
+ device->OnUpdate();
+ auto& cur_entry = hidbus_status.entries[devices[i].handle.internal_index];
+ cur_entry.is_polling_mode = device->IsPollingMode();
+ cur_entry.polling_mode = device->GetPollingMode();
+ cur_entry.is_enabled = device->IsEnabled();
+
+ u8* shared_memory = system.Kernel().GetHidBusSharedMem().GetPointer();
+ std::memcpy(shared_memory + (i * sizeof(HidbusStatusManagerEntry)), &hidbus_status,
+ sizeof(HidbusStatusManagerEntry));
+ }
+ }
+
+ // If ns_late is higher than the update rate ignore the delay
+ if (ns_late > hidbus_update_ns) {
+ ns_late = {};
+ }
+
+ core_timing.ScheduleEvent(hidbus_update_ns - ns_late, hidbus_update_event);
+}
+
+std::optional<std::size_t> HidBus::GetDeviceIndexFromHandle(BusHandle handle) const {
+ for (std::size_t i = 0; i < devices.size(); ++i) {
+ const auto& device_handle = devices[i].handle;
+ if (handle.abstracted_pad_id == device_handle.abstracted_pad_id &&
+ handle.internal_index == device_handle.internal_index &&
+ handle.player_number == device_handle.player_number &&
+ handle.bus_type == device_handle.bus_type &&
+ handle.is_valid == device_handle.is_valid) {
+ return i;
+ }
+ }
+ return std::nullopt;
+}
+
+void HidBus::GetBusHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ Core::HID::NpadIdType npad_id;
+ INSERT_PADDING_WORDS_NOINIT(1);
+ BusType bus_type;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x18, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_HID, "called, npad_id={}, bus_type={}, applet_resource_user_id={}",
+ parameters.npad_id, parameters.bus_type, parameters.applet_resource_user_id);
+
+ bool is_handle_found = 0;
+ std::size_t handle_index = 0;
+
+ for (std::size_t i = 0; i < devices.size(); i++) {
+ const auto& handle = devices[i].handle;
+ if (!handle.is_valid) {
+ continue;
+ }
+ if (static_cast<Core::HID::NpadIdType>(handle.player_number) == parameters.npad_id &&
+ handle.bus_type == parameters.bus_type) {
+ is_handle_found = true;
+ handle_index = i;
+ break;
+ }
+ }
+
+ // Handle not found. Create a new one
+ if (!is_handle_found) {
+ for (std::size_t i = 0; i < devices.size(); i++) {
+ if (devices[i].handle.is_valid) {
+ continue;
+ }
+ devices[i].handle = {
+ .abstracted_pad_id = static_cast<u8>(i),
+ .internal_index = static_cast<u8>(i),
+ .player_number = static_cast<u8>(parameters.npad_id),
+ .bus_type = parameters.bus_type,
+ .is_valid = true,
+ };
+ handle_index = i;
+ break;
+ }
+ }
+
+ struct OutData {
+ bool is_valid;
+ INSERT_PADDING_BYTES(7);
+ BusHandle handle;
+ };
+ static_assert(sizeof(OutData) == 0x10, "OutData has incorrect size.");
+
+ const OutData out_data{
+ .is_valid = true,
+ .handle = devices[handle_index].handle,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(out_data);
+}
+
+void HidBus::IsExternalDeviceConnected(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "Called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ const bool is_attached = device->IsDeviceActivated();
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_attached);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::Initialize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={} bus_type={} internal_index={} "
+ "player_number={} is_valid={}, applet_resource_user_id={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
+
+ is_hidbus_enabled = true;
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto entry_index = devices[device_index.value()].handle.internal_index;
+ auto& cur_entry = hidbus_status.entries[entry_index];
+
+ if (bus_handle_.internal_index == 0 && Settings::values.enable_ring_controller) {
+ MakeDevice<RingController>(bus_handle_);
+ devices[device_index.value()].is_device_initializated = true;
+ devices[device_index.value()].device->ActivateDevice();
+ cur_entry.is_in_focus = true;
+ cur_entry.is_connected = true;
+ cur_entry.is_connected_result = ResultSuccess;
+ cur_entry.is_enabled = false;
+ cur_entry.is_polling_mode = false;
+ } else {
+ MakeDevice<HidbusStubbed>(bus_handle_);
+ devices[device_index.value()].is_device_initializated = true;
+ cur_entry.is_in_focus = true;
+ cur_entry.is_connected = false;
+ cur_entry.is_connected_result = ResultSuccess;
+ cur_entry.is_enabled = false;
+ cur_entry.is_polling_mode = false;
+ }
+
+ std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
+ sizeof(hidbus_status));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::Finalize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+ const auto applet_resource_user_id{rp.Pop<u64>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}, applet_resource_user_id={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid, applet_resource_user_id);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto entry_index = devices[device_index.value()].handle.internal_index;
+ auto& cur_entry = hidbus_status.entries[entry_index];
+ auto& device = devices[device_index.value()].device;
+ devices[device_index.value()].is_device_initializated = false;
+ device->DeactivateDevice();
+
+ cur_entry.is_in_focus = true;
+ cur_entry.is_connected = false;
+ cur_entry.is_connected_result = ResultSuccess;
+ cur_entry.is_enabled = false;
+ cur_entry.is_polling_mode = false;
+ std::memcpy(system.Kernel().GetHidBusSharedMem().GetPointer(), &hidbus_status,
+ sizeof(hidbus_status));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::EnableExternalDevice(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ struct Parameters {
+ bool enable;
+ INSERT_PADDING_BYTES_NOINIT(7);
+ BusHandle bus_handle;
+ u64 inval;
+ u64 applet_resource_user_id;
+ };
+ static_assert(sizeof(Parameters) == 0x20, "Parameters has incorrect size.");
+
+ const auto parameters{rp.PopRaw<Parameters>()};
+
+ LOG_INFO(Service_HID,
+ "called, enable={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}, inval={}, applet_resource_user_id{}",
+ parameters.enable, parameters.bus_handle.abstracted_pad_id,
+ parameters.bus_handle.bus_type, parameters.bus_handle.internal_index,
+ parameters.bus_handle.player_number, parameters.bus_handle.is_valid, parameters.inval,
+ parameters.applet_resource_user_id);
+
+ const auto device_index = GetDeviceIndexFromHandle(parameters.bus_handle);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->Enable(parameters.enable);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::GetExternalDeviceId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ u32 device_id = device->GetDeviceId();
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push<u32>(device_id);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::SendCommandAsync(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto data = ctx.ReadBuffer();
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, data_size={}, abstracted_pad_id={}, bus_type={}, internal_index={}, "
+ "player_number={}, is_valid={}",
+ data.size(), bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
+ bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->SetCommand(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+};
+
+void HidBus::GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_DEBUG(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ const std::vector<u8> data = device->GetReply();
+ const u64 data_size = ctx.WriteBuffer(data);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(ResultSuccess);
+ rb.Push<u64>(data_size);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+};
+
+void HidBus::SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ const auto& device = devices[device_index.value()].device;
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device->GetSendCommandAsycEvent());
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+};
+
+void HidBus::GetSharedMemoryHandle(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_HID, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(&system.Kernel().GetHidBusSharedMem());
+}
+
+void HidBus::EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto t_mem_size{rp.Pop<u32>()};
+ const auto t_mem_handle{ctx.GetCopyHandle(0)};
+ const auto polling_mode_{rp.PopEnum<JoyPollingMode>()};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ ASSERT_MSG(t_mem_size == 0x1000, "t_mem_size is not 0x1000 bytes");
+
+ auto t_mem =
+ system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
+
+ if (t_mem.IsNull()) {
+ LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+ }
+
+ ASSERT_MSG(t_mem->GetSize() == 0x1000, "t_mem has incorrect size");
+
+ LOG_INFO(Service_HID,
+ "called, t_mem_handle=0x{:08X}, polling_mode={}, abstracted_pad_id={}, bus_type={}, "
+ "internal_index={}, player_number={}, is_valid={}",
+ t_mem_handle, polling_mode_, bus_handle_.abstracted_pad_id, bus_handle_.bus_type,
+ bus_handle_.internal_index, bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->SetPollingMode(polling_mode_);
+ device->SetTransferMemoryPointer(system.Memory().GetPointer(t_mem->GetSourceAddress()));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto bus_handle_{rp.PopRaw<BusHandle>()};
+
+ LOG_INFO(Service_HID,
+ "called, abstracted_pad_id={}, bus_type={}, internal_index={}, player_number={}, "
+ "is_valid={}",
+ bus_handle_.abstracted_pad_id, bus_handle_.bus_type, bus_handle_.internal_index,
+ bus_handle_.player_number, bus_handle_.is_valid);
+
+ const auto device_index = GetDeviceIndexFromHandle(bus_handle_);
+
+ if (device_index) {
+ auto& device = devices[device_index.value()].device;
+ device->DisablePollingMode();
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Invalid handle");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultUnknown);
+ return;
+}
+
+void HidBus::SetStatusManagerType(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto manager_type{rp.PopEnum<StatusManagerType>()};
+
+ LOG_WARNING(Service_HID, "(STUBBED) called, manager_type={}", manager_type);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus.h b/src/core/hle/service/hid/hidbus.h
new file mode 100644
index 000000000..b10d5156a
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus.h
@@ -0,0 +1,131 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <functional>
+
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/service.h"
+
+namespace Core::Timing {
+struct EventType;
+} // namespace Core::Timing
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Service::HID {
+
+class HidBus final : public ServiceFramework<HidBus> {
+public:
+ explicit HidBus(Core::System& system_);
+ ~HidBus() override;
+
+private:
+ static const std::size_t max_number_of_handles = 0x13;
+
+ enum class HidBusDeviceId : std::size_t {
+ RingController = 0x20,
+ FamicomRight = 0x21,
+ Starlink = 0x28,
+ };
+
+ // This is nn::hidbus::detail::StatusManagerType
+ enum class StatusManagerType : u32 {
+ None,
+ Type16,
+ Type32,
+ };
+
+ // This is nn::hidbus::BusType
+ enum class BusType : u8 {
+ LeftJoyRail,
+ RightJoyRail,
+ InternalBus, // Lark microphone
+
+ MaxBusType,
+ };
+
+ // This is nn::hidbus::BusHandle
+ struct BusHandle {
+ u32 abstracted_pad_id;
+ u8 internal_index;
+ u8 player_number;
+ BusType bus_type;
+ bool is_valid;
+ };
+ static_assert(sizeof(BusHandle) == 0x8, "BusHandle is an invalid size");
+
+ // This is nn::hidbus::JoyPollingReceivedData
+ struct JoyPollingReceivedData {
+ std::array<u8, 0x30> data;
+ u64 out_size;
+ u64 sampling_number;
+ };
+ static_assert(sizeof(JoyPollingReceivedData) == 0x40,
+ "JoyPollingReceivedData is an invalid size");
+
+ struct HidbusStatusManagerEntry {
+ u8 is_connected{};
+ INSERT_PADDING_BYTES(0x3);
+ ResultCode is_connected_result{0};
+ u8 is_enabled{};
+ u8 is_in_focus{};
+ u8 is_polling_mode{};
+ u8 reserved{};
+ JoyPollingMode polling_mode{};
+ INSERT_PADDING_BYTES(0x70); // Unknown
+ };
+ static_assert(sizeof(HidbusStatusManagerEntry) == 0x80,
+ "HidbusStatusManagerEntry is an invalid size");
+
+ struct HidbusStatusManager {
+ std::array<HidbusStatusManagerEntry, max_number_of_handles> entries{};
+ INSERT_PADDING_BYTES(0x680); // Unused
+ };
+ static_assert(sizeof(HidbusStatusManager) <= 0x1000, "HidbusStatusManager is an invalid size");
+
+ struct HidbusDevice {
+ bool is_device_initializated{};
+ BusHandle handle{};
+ std::unique_ptr<HidbusBase> device{nullptr};
+ };
+
+ void GetBusHandle(Kernel::HLERequestContext& ctx);
+ void IsExternalDeviceConnected(Kernel::HLERequestContext& ctx);
+ void Initialize(Kernel::HLERequestContext& ctx);
+ void Finalize(Kernel::HLERequestContext& ctx);
+ void EnableExternalDevice(Kernel::HLERequestContext& ctx);
+ void GetExternalDeviceId(Kernel::HLERequestContext& ctx);
+ void SendCommandAsync(Kernel::HLERequestContext& ctx);
+ void GetSendCommandAsynceResult(Kernel::HLERequestContext& ctx);
+ void SetEventForSendCommandAsycResult(Kernel::HLERequestContext& ctx);
+ void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
+ void EnableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
+ void DisableJoyPollingReceiveMode(Kernel::HLERequestContext& ctx);
+ void SetStatusManagerType(Kernel::HLERequestContext& ctx);
+
+ void UpdateHidbus(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
+ std::optional<std::size_t> GetDeviceIndexFromHandle(BusHandle handle) const;
+
+ template <typename T>
+ void MakeDevice(BusHandle handle) {
+ const auto device_index = GetDeviceIndexFromHandle(handle);
+ if (device_index) {
+ devices[device_index.value()].device =
+ std::make_unique<T>(system.HIDCore(), service_context);
+ }
+ }
+
+ bool is_hidbus_enabled{false};
+ HidbusStatusManager hidbus_status{};
+ std::array<HidbusDevice, max_number_of_handles> devices{};
+ std::shared_ptr<Core::Timing::EventType> hidbus_update_event;
+ KernelHelpers::ServiceContext service_context;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.cpp b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
new file mode 100644
index 000000000..09bff10e5
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.cpp
@@ -0,0 +1,72 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+#include "core/hle/service/kernel_helpers.h"
+
+namespace Service::HID {
+
+HidbusBase::HidbusBase(KernelHelpers::ServiceContext& service_context_)
+ : service_context(service_context_) {
+ send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent");
+}
+HidbusBase::~HidbusBase() = default;
+
+void HidbusBase::ActivateDevice() {
+ if (is_activated) {
+ return;
+ }
+ is_activated = true;
+ OnInit();
+}
+
+void HidbusBase::DeactivateDevice() {
+ if (is_activated) {
+ OnRelease();
+ }
+ is_activated = false;
+}
+
+bool HidbusBase::IsDeviceActivated() const {
+ return is_activated;
+}
+
+void HidbusBase::Enable(bool enable) {
+ device_enabled = enable;
+}
+
+bool HidbusBase::IsEnabled() const {
+ return device_enabled;
+}
+
+bool HidbusBase::IsPollingMode() const {
+ return polling_mode_enabled;
+}
+
+JoyPollingMode HidbusBase::GetPollingMode() const {
+ return polling_mode;
+}
+
+void HidbusBase::SetPollingMode(JoyPollingMode mode) {
+ polling_mode = mode;
+ polling_mode_enabled = true;
+}
+
+void HidbusBase::DisablePollingMode() {
+ polling_mode_enabled = false;
+}
+
+void HidbusBase::SetTransferMemoryPointer(u8* t_mem) {
+ is_transfer_memory_set = true;
+ transfer_memory = t_mem;
+}
+
+Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const {
+ return send_command_async_event->GetReadableEvent();
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/hidbus_base.h b/src/core/hle/service/hid/hidbus/hidbus_base.h
new file mode 100644
index 000000000..13d073a3d
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/hidbus_base.h
@@ -0,0 +1,179 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "core/hle/result.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::KernelHelpers {
+class ServiceContext;
+}
+
+namespace Service::HID {
+
+// This is nn::hidbus::JoyPollingMode
+enum class JoyPollingMode : u32 {
+ SixAxisSensorDisable,
+ SixAxisSensorEnable,
+ ButtonOnly,
+};
+
+struct DataAccessorHeader {
+ ResultCode result{ResultUnknown};
+ INSERT_PADDING_WORDS(0x1);
+ std::array<u8, 0x18> unused{};
+ u64 latest_entry{};
+ u64 total_entries{};
+};
+static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size");
+
+struct JoyDisableSixAxisPollingData {
+ std::array<u8, 0x26> data;
+ u8 out_size;
+ INSERT_PADDING_BYTES(0x1);
+ u64 sampling_number;
+};
+static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30,
+ "JoyDisableSixAxisPollingData is an invalid size");
+
+struct JoyEnableSixAxisPollingData {
+ std::array<u8, 0x8> data;
+ u8 out_size;
+ INSERT_PADDING_BYTES(0x7);
+ u64 sampling_number;
+};
+static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18,
+ "JoyEnableSixAxisPollingData is an invalid size");
+
+struct JoyButtonOnlyPollingData {
+ std::array<u8, 0x2c> data;
+ u8 out_size;
+ INSERT_PADDING_BYTES(0x3);
+ u64 sampling_number;
+};
+static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38,
+ "JoyButtonOnlyPollingData is an invalid size");
+
+struct JoyDisableSixAxisPollingEntry {
+ u64 sampling_number;
+ JoyDisableSixAxisPollingData polling_data;
+};
+static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38,
+ "JoyDisableSixAxisPollingEntry is an invalid size");
+
+struct JoyEnableSixAxisPollingEntry {
+ u64 sampling_number;
+ JoyEnableSixAxisPollingData polling_data;
+};
+static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20,
+ "JoyEnableSixAxisPollingEntry is an invalid size");
+
+struct JoyButtonOnlyPollingEntry {
+ u64 sampling_number;
+ JoyButtonOnlyPollingData polling_data;
+};
+static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40,
+ "JoyButtonOnlyPollingEntry is an invalid size");
+
+struct JoyDisableSixAxisDataAccessor {
+ DataAccessorHeader header{};
+ std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{};
+};
+static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298,
+ "JoyDisableSixAxisDataAccessor is an invalid size");
+
+struct JoyEnableSixAxisDataAccessor {
+ DataAccessorHeader header{};
+ std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{};
+};
+static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190,
+ "JoyEnableSixAxisDataAccessor is an invalid size");
+
+struct ButtonOnlyPollingDataAccessor {
+ DataAccessorHeader header;
+ std::array<JoyButtonOnlyPollingEntry, 0xb> entries;
+};
+static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0,
+ "ButtonOnlyPollingDataAccessor is an invalid size");
+
+class HidbusBase {
+public:
+ explicit HidbusBase(KernelHelpers::ServiceContext& service_context_);
+ virtual ~HidbusBase();
+
+ void ActivateDevice();
+
+ void DeactivateDevice();
+
+ bool IsDeviceActivated() const;
+
+ // Enables/disables the device
+ void Enable(bool enable);
+
+ // returns true if device is enabled
+ bool IsEnabled() const;
+
+ // returns true if polling mode is enabled
+ bool IsPollingMode() const;
+
+ // returns polling mode
+ JoyPollingMode GetPollingMode() const;
+
+ // Sets and enables JoyPollingMode
+ void SetPollingMode(JoyPollingMode mode);
+
+ // Disables JoyPollingMode
+ void DisablePollingMode();
+
+ // Called on EnableJoyPollingReceiveMode
+ void SetTransferMemoryPointer(u8* t_mem);
+
+ Kernel::KReadableEvent& GetSendCommandAsycEvent() const;
+
+ virtual void OnInit() {}
+
+ virtual void OnRelease() {}
+
+ // Updates device transfer memory
+ virtual void OnUpdate() {}
+
+ // Returns the device ID of the joycon
+ virtual u8 GetDeviceId() const {
+ return {};
+ }
+
+ // Assigns a command from data
+ virtual bool SetCommand(const std::vector<u8>& data) {
+ return {};
+ }
+
+ // Returns a reply from a command
+ virtual std::vector<u8> GetReply() const {
+ return {};
+ }
+
+protected:
+ bool is_activated{};
+ bool device_enabled{};
+ bool polling_mode_enabled{};
+ JoyPollingMode polling_mode = {};
+ // TODO(German77): All data accessors need to be replaced with a ring lifo object
+ JoyDisableSixAxisDataAccessor disable_sixaxis_data{};
+ JoyEnableSixAxisDataAccessor enable_sixaxis_data{};
+ ButtonOnlyPollingDataAccessor button_only_data{};
+
+ u8* transfer_memory{nullptr};
+ bool is_transfer_memory_set{};
+
+ Kernel::KEvent* send_command_async_event;
+ KernelHelpers::ServiceContext& service_context;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.cpp b/src/core/hle/service/hid/hidbus/ringcon.cpp
new file mode 100644
index 000000000..5ec3cc83c
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.cpp
@@ -0,0 +1,286 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/emulated_devices.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/kernel/k_readable_event.h"
+#include "core/hle/service/hid/hidbus/ringcon.h"
+
+namespace Service::HID {
+
+RingController::RingController(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_)
+ : HidbusBase(service_context_) {
+ input = hid_core_.GetEmulatedDevices();
+}
+
+RingController::~RingController() = default;
+
+void RingController::OnInit() {
+ return;
+}
+
+void RingController::OnRelease() {
+ return;
+};
+
+void RingController::OnUpdate() {
+ if (!is_activated) {
+ return;
+ }
+
+ if (!device_enabled) {
+ return;
+ }
+
+ if (!polling_mode_enabled || !is_transfer_memory_set) {
+ return;
+ }
+
+ // TODO: Increment multitasking counters from motion and sensor data
+
+ switch (polling_mode) {
+ case JoyPollingMode::SixAxisSensorEnable: {
+ enable_sixaxis_data.header.total_entries = 10;
+ enable_sixaxis_data.header.result = ResultSuccess;
+ const auto& last_entry =
+ enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
+
+ enable_sixaxis_data.header.latest_entry =
+ (enable_sixaxis_data.header.latest_entry + 1) % 10;
+ auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry];
+
+ curr_entry.sampling_number = last_entry.sampling_number + 1;
+ curr_entry.polling_data.sampling_number = curr_entry.sampling_number;
+
+ const RingConData ringcon_value = GetSensorValue();
+ curr_entry.polling_data.out_size = sizeof(ringcon_value);
+ std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value));
+
+ std::memcpy(transfer_memory, &enable_sixaxis_data, sizeof(enable_sixaxis_data));
+ break;
+ }
+ default:
+ LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
+ break;
+ }
+}
+
+RingController::RingConData RingController::GetSensorValue() const {
+ RingConData ringcon_sensor_value{
+ .status = DataValid::Valid,
+ .data = 0,
+ };
+
+ const f32 force_value = input->GetRingSensorForce().force * range;
+ ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value;
+
+ return ringcon_sensor_value;
+}
+
+u8 RingController::GetDeviceId() const {
+ return device_id;
+}
+
+std::vector<u8> RingController::GetReply() const {
+ const RingConCommands current_command = command;
+
+ switch (current_command) {
+ case RingConCommands::GetFirmwareVersion:
+ return GetFirmwareVersionReply();
+ case RingConCommands::ReadId:
+ return GetReadIdReply();
+ case RingConCommands::c20105:
+ return GetC020105Reply();
+ case RingConCommands::ReadUnkCal:
+ return GetReadUnkCalReply();
+ case RingConCommands::ReadFactoryCal:
+ return GetReadFactoryCalReply();
+ case RingConCommands::ReadUserCal:
+ return GetReadUserCalReply();
+ case RingConCommands::ReadRepCount:
+ return GetReadRepCountReply();
+ case RingConCommands::ReadTotalPushCount:
+ return GetReadTotalPushCountReply();
+ case RingConCommands::ResetRepCount:
+ return GetResetRepCountReply();
+ case RingConCommands::SaveCalData:
+ return GetSaveDataReply();
+ default:
+ return GetErrorReply();
+ }
+}
+
+bool RingController::SetCommand(const std::vector<u8>& data) {
+ if (data.size() < 4) {
+ LOG_ERROR(Service_HID, "Command size not supported {}", data.size());
+ command = RingConCommands::Error;
+ return false;
+ }
+
+ std::memcpy(&command, data.data(), sizeof(RingConCommands));
+
+ switch (command) {
+ case RingConCommands::GetFirmwareVersion:
+ case RingConCommands::ReadId:
+ case RingConCommands::c20105:
+ case RingConCommands::ReadUnkCal:
+ case RingConCommands::ReadFactoryCal:
+ case RingConCommands::ReadUserCal:
+ case RingConCommands::ReadRepCount:
+ case RingConCommands::ReadTotalPushCount:
+ ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
+ send_command_async_event->GetWritableEvent().Signal();
+ return true;
+ case RingConCommands::ResetRepCount:
+ ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes");
+ total_rep_count = 0;
+ send_command_async_event->GetWritableEvent().Signal();
+ return true;
+ case RingConCommands::SaveCalData: {
+ ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes");
+
+ SaveCalData save_info{};
+ std::memcpy(&save_info, data.data(), sizeof(SaveCalData));
+ user_calibration = save_info.calibration;
+ send_command_async_event->GetWritableEvent().Signal();
+ return true;
+ }
+ default:
+ LOG_ERROR(Service_HID, "Command not implemented {}", command);
+ command = RingConCommands::Error;
+ // Signal a reply to avoid softlocking the game
+ send_command_async_event->GetWritableEvent().Signal();
+ return false;
+ }
+}
+
+std::vector<u8> RingController::GetFirmwareVersionReply() const {
+ const FirmwareVersionReply reply{
+ .status = DataValid::Valid,
+ .firmware = version,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadIdReply() const {
+ // The values are hardcoded from a real joycon
+ const ReadIdReply reply{
+ .status = DataValid::Valid,
+ .id_l_x0 = 8,
+ .id_l_x0_2 = 41,
+ .id_l_x4 = 22294,
+ .id_h_x0 = 19777,
+ .id_h_x0_2 = 13621,
+ .id_h_x4 = 8245,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetC020105Reply() const {
+ const Cmd020105Reply reply{
+ .status = DataValid::Valid,
+ .data = 1,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadUnkCalReply() const {
+ const ReadUnkCalReply reply{
+ .status = DataValid::Valid,
+ .data = 0,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadFactoryCalReply() const {
+ const ReadFactoryCalReply reply{
+ .status = DataValid::Valid,
+ .calibration = factory_calibration,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadUserCalReply() const {
+ const ReadUserCalReply reply{
+ .status = DataValid::Valid,
+ .calibration = user_calibration,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadRepCountReply() const {
+ const GetThreeByteReply reply{
+ .status = DataValid::Valid,
+ .data = {total_rep_count, 0, 0},
+ .crc = GetCrcValue({total_rep_count, 0, 0, 0}),
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetReadTotalPushCountReply() const {
+ const GetThreeByteReply reply{
+ .status = DataValid::Valid,
+ .data = {total_push_count, 0, 0},
+ .crc = GetCrcValue({total_push_count, 0, 0, 0}),
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetResetRepCountReply() const {
+ return GetReadRepCountReply();
+}
+
+std::vector<u8> RingController::GetSaveDataReply() const {
+ const StatusReply reply{
+ .status = DataValid::Valid,
+ };
+
+ return GetDataVector(reply);
+}
+
+std::vector<u8> RingController::GetErrorReply() const {
+ const ErrorReply reply{
+ .status = DataValid::BadCRC,
+ };
+
+ return GetDataVector(reply);
+}
+
+u8 RingController::GetCrcValue(const std::vector<u8>& data) const {
+ u8 crc = 0;
+ for (std::size_t index = 0; index < data.size(); index++) {
+ for (u8 i = 0x80; i > 0; i >>= 1) {
+ bool bit = (crc & 0x80) != 0;
+ if ((data[index] & i) != 0) {
+ bit = !bit;
+ }
+ crc <<= 1;
+ if (bit) {
+ crc ^= 0x8d;
+ }
+ }
+ }
+ return crc;
+}
+
+template <typename T>
+std::vector<u8> RingController::GetDataVector(const T& reply) const {
+ static_assert(std::is_trivially_copyable_v<T>);
+ std::vector<u8> data;
+ data.resize(sizeof(reply));
+ std::memcpy(data.data(), &reply, sizeof(reply));
+ return data;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/ringcon.h b/src/core/hle/service/hid/hidbus/ringcon.h
new file mode 100644
index 000000000..2dbc6150e
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/ringcon.h
@@ -0,0 +1,254 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/common_types.h"
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+
+namespace Core::HID {
+class EmulatedDevices;
+} // namespace Core::HID
+
+namespace Service::HID {
+
+class RingController final : public HidbusBase {
+public:
+ explicit RingController(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~RingController() override;
+
+ void OnInit() override;
+
+ void OnRelease() override;
+
+ // Updates ringcon transfer memory
+ void OnUpdate() override;
+
+ // Returns the device ID of the joycon
+ u8 GetDeviceId() const override;
+
+ // Assigns a command from data
+ bool SetCommand(const std::vector<u8>& data) override;
+
+ // Returns a reply from a command
+ std::vector<u8> GetReply() const override;
+
+private:
+ // These values are obtained from a real ring controller
+ static constexpr s16 idle_value = 2280;
+ static constexpr s16 idle_deadzone = 120;
+ static constexpr s16 range = 2500;
+
+ // Most missing command names are leftovers from other firmware versions
+ enum class RingConCommands : u32 {
+ GetFirmwareVersion = 0x00020000,
+ ReadId = 0x00020100,
+ JoyPolling = 0x00020101,
+ Unknown1 = 0x00020104,
+ c20105 = 0x00020105,
+ Unknown2 = 0x00020204,
+ Unknown3 = 0x00020304,
+ Unknown4 = 0x00020404,
+ ReadUnkCal = 0x00020504,
+ ReadFactoryCal = 0x00020A04,
+ Unknown5 = 0x00021104,
+ Unknown6 = 0x00021204,
+ Unknown7 = 0x00021304,
+ ReadUserCal = 0x00021A04,
+ ReadRepCount = 0x00023104,
+ ReadTotalPushCount = 0x00023204,
+ ResetRepCount = 0x04013104,
+ Unknown8 = 0x04011104,
+ Unknown9 = 0x04011204,
+ Unknown10 = 0x04011304,
+ SaveCalData = 0x10011A04,
+ Error = 0xFFFFFFFF,
+ };
+
+ enum class DataValid : u32 {
+ Valid,
+ BadCRC,
+ Cal,
+ };
+
+ struct FirmwareVersion {
+ u8 sub;
+ u8 main;
+ };
+ static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
+
+ struct FactoryCalibration {
+ s32_le os_max;
+ s32_le hk_max;
+ s32_le zero_min;
+ s32_le zero_max;
+ };
+ static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size");
+
+ struct CalibrationValue {
+ s16 value;
+ u16 crc;
+ };
+ static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size");
+
+ struct UserCalibration {
+ CalibrationValue os_max;
+ CalibrationValue hk_max;
+ CalibrationValue zero;
+ };
+ static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size");
+
+ struct SaveCalData {
+ RingConCommands command;
+ UserCalibration calibration;
+ INSERT_PADDING_BYTES_NOINIT(4);
+ };
+ static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size");
+ static_assert(std::is_trivially_copyable_v<SaveCalData>,
+ "SaveCalData must be trivially copyable");
+
+ struct FirmwareVersionReply {
+ DataValid status;
+ FirmwareVersion firmware;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size");
+
+ struct Cmd020105Reply {
+ DataValid status;
+ u8 data;
+ INSERT_PADDING_BYTES(0x3);
+ };
+ static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size");
+
+ struct StatusReply {
+ DataValid status;
+ };
+ static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size");
+
+ struct GetThreeByteReply {
+ DataValid status;
+ std::array<u8, 3> data;
+ u8 crc;
+ };
+ static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size");
+
+ struct ReadUnkCalReply {
+ DataValid status;
+ u16 data;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size");
+
+ struct ReadFactoryCalReply {
+ DataValid status;
+ FactoryCalibration calibration;
+ };
+ static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size");
+
+ struct ReadUserCalReply {
+ DataValid status;
+ UserCalibration calibration;
+ INSERT_PADDING_BYTES(0x4);
+ };
+ static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size");
+
+ struct ReadIdReply {
+ DataValid status;
+ u16 id_l_x0;
+ u16 id_l_x0_2;
+ u16 id_l_x4;
+ u16 id_h_x0;
+ u16 id_h_x0_2;
+ u16 id_h_x4;
+ };
+ static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size");
+
+ struct ErrorReply {
+ DataValid status;
+ INSERT_PADDING_BYTES(0x3);
+ };
+ static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size");
+
+ struct RingConData {
+ DataValid status;
+ s16_le data;
+ INSERT_PADDING_BYTES(0x2);
+ };
+ static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size");
+
+ // Returns RingConData struct with pressure sensor values
+ RingConData GetSensorValue() const;
+
+ // Returns 8 byte reply with firmware version
+ std::vector<u8> GetFirmwareVersionReply() const;
+
+ // Returns 16 byte reply with ID values
+ std::vector<u8> GetReadIdReply() const;
+
+ // (STUBBED) Returns 8 byte reply
+ std::vector<u8> GetC020105Reply() const;
+
+ // (STUBBED) Returns 8 byte empty reply
+ std::vector<u8> GetReadUnkCalReply() const;
+
+ // Returns 20 byte reply with factory calibration values
+ std::vector<u8> GetReadFactoryCalReply() const;
+
+ // Returns 20 byte reply with user calibration values
+ std::vector<u8> GetReadUserCalReply() const;
+
+ // Returns 8 byte reply
+ std::vector<u8> GetReadRepCountReply() const;
+
+ // Returns 8 byte reply
+ std::vector<u8> GetReadTotalPushCountReply() const;
+
+ // Returns 8 byte reply
+ std::vector<u8> GetResetRepCountReply() const;
+
+ // Returns 4 byte save data reply
+ std::vector<u8> GetSaveDataReply() const;
+
+ // Returns 8 byte error reply
+ std::vector<u8> GetErrorReply() const;
+
+ // Returns 8 bit redundancy check from provided data
+ u8 GetCrcValue(const std::vector<u8>& data) const;
+
+ // Converts structs to an u8 vector equivalent
+ template <typename T>
+ std::vector<u8> GetDataVector(const T& reply) const;
+
+ RingConCommands command{RingConCommands::Error};
+
+ // These counters are used in multitasking mode while the switch is sleeping
+ // Total steps taken
+ u8 total_rep_count = 0;
+ // Total times the ring was pushed
+ u8 total_push_count = 0;
+
+ const u8 device_id = 0x20;
+ const FirmwareVersion version = {
+ .sub = 0x0,
+ .main = 0x2c,
+ };
+ const FactoryCalibration factory_calibration = {
+ .os_max = idle_value + range + idle_deadzone,
+ .hk_max = idle_value - range - idle_deadzone,
+ .zero_min = idle_value - idle_deadzone,
+ .zero_max = idle_value + idle_deadzone,
+ };
+ UserCalibration user_calibration = {
+ .os_max = {.value = range, .crc = 228},
+ .hk_max = {.value = -range, .crc = 239},
+ .zero = {.value = idle_value, .crc = 225},
+ };
+
+ Core::HID::EmulatedDevices* input;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/starlink.cpp b/src/core/hle/service/hid/hidbus/starlink.cpp
new file mode 100644
index 000000000..3175c48da
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.cpp
@@ -0,0 +1,51 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/hidbus/starlink.h"
+
+namespace Service::HID {
+constexpr u8 DEVICE_ID = 0x28;
+
+Starlink::Starlink(Core::HID::HIDCore& hid_core_, KernelHelpers::ServiceContext& service_context_)
+ : HidbusBase(service_context_) {}
+Starlink::~Starlink() = default;
+
+void Starlink::OnInit() {
+ return;
+}
+
+void Starlink::OnRelease() {
+ return;
+};
+
+void Starlink::OnUpdate() {
+ if (!is_activated) {
+ return;
+ }
+ if (!device_enabled) {
+ return;
+ }
+ if (!polling_mode_enabled || !is_transfer_memory_set) {
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
+}
+
+u8 Starlink::GetDeviceId() const {
+ return DEVICE_ID;
+}
+
+std::vector<u8> Starlink::GetReply() const {
+ return {};
+}
+
+bool Starlink::SetCommand(const std::vector<u8>& data) {
+ LOG_ERROR(Service_HID, "Command not implemented");
+ return false;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/starlink.h b/src/core/hle/service/hid/hidbus/starlink.h
new file mode 100644
index 000000000..79770b68e
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/starlink.h
@@ -0,0 +1,39 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+
+class Starlink final : public HidbusBase {
+public:
+ explicit Starlink(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~Starlink() override;
+
+ void OnInit() override;
+
+ void OnRelease() override;
+
+ // Updates ringcon transfer memory
+ void OnUpdate() override;
+
+ // Returns the device ID of the joycon
+ u8 GetDeviceId() const override;
+
+ // Assigns a command from data
+ bool SetCommand(const std::vector<u8>& data) override;
+
+ // Returns a reply from a command
+ std::vector<u8> GetReply() const override;
+};
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/stubbed.cpp b/src/core/hle/service/hid/hidbus/stubbed.cpp
new file mode 100644
index 000000000..5474657be
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.cpp
@@ -0,0 +1,52 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hle/service/hid/hidbus/stubbed.h"
+
+namespace Service::HID {
+constexpr u8 DEVICE_ID = 0xFF;
+
+HidbusStubbed::HidbusStubbed(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_)
+ : HidbusBase(service_context_) {}
+HidbusStubbed::~HidbusStubbed() = default;
+
+void HidbusStubbed::OnInit() {
+ return;
+}
+
+void HidbusStubbed::OnRelease() {
+ return;
+};
+
+void HidbusStubbed::OnUpdate() {
+ if (!is_activated) {
+ return;
+ }
+ if (!device_enabled) {
+ return;
+ }
+ if (!polling_mode_enabled || !is_transfer_memory_set) {
+ return;
+ }
+
+ LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode);
+}
+
+u8 HidbusStubbed::GetDeviceId() const {
+ return DEVICE_ID;
+}
+
+std::vector<u8> HidbusStubbed::GetReply() const {
+ return {};
+}
+
+bool HidbusStubbed::SetCommand(const std::vector<u8>& data) {
+ LOG_ERROR(Service_HID, "Command not implemented");
+ return false;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hidbus/stubbed.h b/src/core/hle/service/hid/hidbus/stubbed.h
new file mode 100644
index 000000000..40acdfe8f
--- /dev/null
+++ b/src/core/hle/service/hid/hidbus/stubbed.h
@@ -0,0 +1,39 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/hid/hidbus/hidbus_base.h"
+
+namespace Core::HID {
+class EmulatedController;
+} // namespace Core::HID
+
+namespace Service::HID {
+
+class HidbusStubbed final : public HidbusBase {
+public:
+ explicit HidbusStubbed(Core::HID::HIDCore& hid_core_,
+ KernelHelpers::ServiceContext& service_context_);
+ ~HidbusStubbed() override;
+
+ void OnInit() override;
+
+ void OnRelease() override;
+
+ // Updates ringcon transfer memory
+ void OnUpdate() override;
+
+ // Returns the device ID of the joycon
+ u8 GetDeviceId() const override;
+
+ // Assigns a command from data
+ bool SetCommand(const std::vector<u8>& data) override;
+
+ // Returns a reply from a command
+ std::vector<u8> GetReply() const override;
+};
+
+} // namespace Service::HID