diff options
Diffstat (limited to 'src/hid_core/hidbus')
| -rw-r--r-- | src/hid_core/hidbus/hidbus_base.cpp | 73 | ||||
| -rw-r--r-- | src/hid_core/hidbus/hidbus_base.h | 183 | ||||
| -rw-r--r-- | src/hid_core/hidbus/ringcon.cpp | 292 | ||||
| -rw-r--r-- | src/hid_core/hidbus/ringcon.h | 253 | ||||
| -rw-r--r-- | src/hid_core/hidbus/starlink.cpp | 50 | ||||
| -rw-r--r-- | src/hid_core/hidbus/starlink.h | 37 | ||||
| -rw-r--r-- | src/hid_core/hidbus/stubbed.cpp | 50 | ||||
| -rw-r--r-- | src/hid_core/hidbus/stubbed.h | 37 |
8 files changed, 975 insertions, 0 deletions
diff --git a/src/hid_core/hidbus/hidbus_base.cpp b/src/hid_core/hidbus/hidbus_base.cpp new file mode 100644 index 000000000..632bb173b --- /dev/null +++ b/src/hid_core/hidbus/hidbus_base.cpp | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/hle/kernel/k_event.h" | ||
| 5 | #include "core/hle/kernel/k_readable_event.h" | ||
| 6 | #include "core/hle/service/kernel_helpers.h" | ||
| 7 | #include "hid_core/hid_core.h" | ||
| 8 | #include "hid_core/hidbus/hidbus_base.h" | ||
| 9 | |||
| 10 | namespace Service::HID { | ||
| 11 | |||
| 12 | HidbusBase::HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& service_context_) | ||
| 13 | : system(system_), service_context(service_context_) { | ||
| 14 | send_command_async_event = service_context.CreateEvent("hidbus:SendCommandAsyncEvent"); | ||
| 15 | } | ||
| 16 | |||
| 17 | HidbusBase::~HidbusBase() { | ||
| 18 | service_context.CloseEvent(send_command_async_event); | ||
| 19 | }; | ||
| 20 | |||
| 21 | void HidbusBase::ActivateDevice() { | ||
| 22 | if (is_activated) { | ||
| 23 | return; | ||
| 24 | } | ||
| 25 | is_activated = true; | ||
| 26 | OnInit(); | ||
| 27 | } | ||
| 28 | |||
| 29 | void HidbusBase::DeactivateDevice() { | ||
| 30 | if (is_activated) { | ||
| 31 | OnRelease(); | ||
| 32 | } | ||
| 33 | is_activated = false; | ||
| 34 | } | ||
| 35 | |||
| 36 | bool HidbusBase::IsDeviceActivated() const { | ||
| 37 | return is_activated; | ||
| 38 | } | ||
| 39 | |||
| 40 | void HidbusBase::Enable(bool enable) { | ||
| 41 | device_enabled = enable; | ||
| 42 | } | ||
| 43 | |||
| 44 | bool HidbusBase::IsEnabled() const { | ||
| 45 | return device_enabled; | ||
| 46 | } | ||
| 47 | |||
| 48 | bool HidbusBase::IsPollingMode() const { | ||
| 49 | return polling_mode_enabled; | ||
| 50 | } | ||
| 51 | |||
| 52 | JoyPollingMode HidbusBase::GetPollingMode() const { | ||
| 53 | return polling_mode; | ||
| 54 | } | ||
| 55 | |||
| 56 | void HidbusBase::SetPollingMode(JoyPollingMode mode) { | ||
| 57 | polling_mode = mode; | ||
| 58 | polling_mode_enabled = true; | ||
| 59 | } | ||
| 60 | |||
| 61 | void HidbusBase::DisablePollingMode() { | ||
| 62 | polling_mode_enabled = false; | ||
| 63 | } | ||
| 64 | |||
| 65 | void HidbusBase::SetTransferMemoryAddress(Common::ProcessAddress t_mem) { | ||
| 66 | transfer_memory = t_mem; | ||
| 67 | } | ||
| 68 | |||
| 69 | Kernel::KReadableEvent& HidbusBase::GetSendCommandAsycEvent() const { | ||
| 70 | return send_command_async_event->GetReadableEvent(); | ||
| 71 | } | ||
| 72 | |||
| 73 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/hidbus_base.h b/src/hid_core/hidbus/hidbus_base.h new file mode 100644 index 000000000..ec41684e1 --- /dev/null +++ b/src/hid_core/hidbus/hidbus_base.h | |||
| @@ -0,0 +1,183 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <span> | ||
| 8 | #include "common/typed_address.h" | ||
| 9 | #include "core/hle/result.h" | ||
| 10 | |||
| 11 | namespace Core { | ||
| 12 | class System; | ||
| 13 | } | ||
| 14 | |||
| 15 | namespace Kernel { | ||
| 16 | class KEvent; | ||
| 17 | class KReadableEvent; | ||
| 18 | } // namespace Kernel | ||
| 19 | |||
| 20 | namespace Service::KernelHelpers { | ||
| 21 | class ServiceContext; | ||
| 22 | } | ||
| 23 | |||
| 24 | namespace Service::HID { | ||
| 25 | |||
| 26 | // This is nn::hidbus::JoyPollingMode | ||
| 27 | enum class JoyPollingMode : u32 { | ||
| 28 | SixAxisSensorDisable, | ||
| 29 | SixAxisSensorEnable, | ||
| 30 | ButtonOnly, | ||
| 31 | }; | ||
| 32 | |||
| 33 | struct DataAccessorHeader { | ||
| 34 | Result result{ResultUnknown}; | ||
| 35 | INSERT_PADDING_WORDS(0x1); | ||
| 36 | std::array<u8, 0x18> unused{}; | ||
| 37 | u64 latest_entry{}; | ||
| 38 | u64 total_entries{}; | ||
| 39 | }; | ||
| 40 | static_assert(sizeof(DataAccessorHeader) == 0x30, "DataAccessorHeader is an invalid size"); | ||
| 41 | |||
| 42 | struct JoyDisableSixAxisPollingData { | ||
| 43 | std::array<u8, 0x26> data; | ||
| 44 | u8 out_size; | ||
| 45 | INSERT_PADDING_BYTES(0x1); | ||
| 46 | u64 sampling_number; | ||
| 47 | }; | ||
| 48 | static_assert(sizeof(JoyDisableSixAxisPollingData) == 0x30, | ||
| 49 | "JoyDisableSixAxisPollingData is an invalid size"); | ||
| 50 | |||
| 51 | struct JoyEnableSixAxisPollingData { | ||
| 52 | std::array<u8, 0x8> data; | ||
| 53 | u8 out_size; | ||
| 54 | INSERT_PADDING_BYTES(0x7); | ||
| 55 | u64 sampling_number; | ||
| 56 | }; | ||
| 57 | static_assert(sizeof(JoyEnableSixAxisPollingData) == 0x18, | ||
| 58 | "JoyEnableSixAxisPollingData is an invalid size"); | ||
| 59 | |||
| 60 | struct JoyButtonOnlyPollingData { | ||
| 61 | std::array<u8, 0x2c> data; | ||
| 62 | u8 out_size; | ||
| 63 | INSERT_PADDING_BYTES(0x3); | ||
| 64 | u64 sampling_number; | ||
| 65 | }; | ||
| 66 | static_assert(sizeof(JoyButtonOnlyPollingData) == 0x38, | ||
| 67 | "JoyButtonOnlyPollingData is an invalid size"); | ||
| 68 | |||
| 69 | struct JoyDisableSixAxisPollingEntry { | ||
| 70 | u64 sampling_number; | ||
| 71 | JoyDisableSixAxisPollingData polling_data; | ||
| 72 | }; | ||
| 73 | static_assert(sizeof(JoyDisableSixAxisPollingEntry) == 0x38, | ||
| 74 | "JoyDisableSixAxisPollingEntry is an invalid size"); | ||
| 75 | |||
| 76 | struct JoyEnableSixAxisPollingEntry { | ||
| 77 | u64 sampling_number; | ||
| 78 | JoyEnableSixAxisPollingData polling_data; | ||
| 79 | }; | ||
| 80 | static_assert(sizeof(JoyEnableSixAxisPollingEntry) == 0x20, | ||
| 81 | "JoyEnableSixAxisPollingEntry is an invalid size"); | ||
| 82 | |||
| 83 | struct JoyButtonOnlyPollingEntry { | ||
| 84 | u64 sampling_number; | ||
| 85 | JoyButtonOnlyPollingData polling_data; | ||
| 86 | }; | ||
| 87 | static_assert(sizeof(JoyButtonOnlyPollingEntry) == 0x40, | ||
| 88 | "JoyButtonOnlyPollingEntry is an invalid size"); | ||
| 89 | |||
| 90 | struct JoyDisableSixAxisDataAccessor { | ||
| 91 | DataAccessorHeader header{}; | ||
| 92 | std::array<JoyDisableSixAxisPollingEntry, 0xb> entries{}; | ||
| 93 | }; | ||
| 94 | static_assert(sizeof(JoyDisableSixAxisDataAccessor) == 0x298, | ||
| 95 | "JoyDisableSixAxisDataAccessor is an invalid size"); | ||
| 96 | |||
| 97 | struct JoyEnableSixAxisDataAccessor { | ||
| 98 | DataAccessorHeader header{}; | ||
| 99 | std::array<JoyEnableSixAxisPollingEntry, 0xb> entries{}; | ||
| 100 | }; | ||
| 101 | static_assert(sizeof(JoyEnableSixAxisDataAccessor) == 0x190, | ||
| 102 | "JoyEnableSixAxisDataAccessor is an invalid size"); | ||
| 103 | |||
| 104 | struct ButtonOnlyPollingDataAccessor { | ||
| 105 | DataAccessorHeader header; | ||
| 106 | std::array<JoyButtonOnlyPollingEntry, 0xb> entries; | ||
| 107 | }; | ||
| 108 | static_assert(sizeof(ButtonOnlyPollingDataAccessor) == 0x2F0, | ||
| 109 | "ButtonOnlyPollingDataAccessor is an invalid size"); | ||
| 110 | |||
| 111 | class HidbusBase { | ||
| 112 | public: | ||
| 113 | explicit HidbusBase(Core::System& system_, KernelHelpers::ServiceContext& service_context_); | ||
| 114 | virtual ~HidbusBase(); | ||
| 115 | |||
| 116 | void ActivateDevice(); | ||
| 117 | |||
| 118 | void DeactivateDevice(); | ||
| 119 | |||
| 120 | bool IsDeviceActivated() const; | ||
| 121 | |||
| 122 | // Enables/disables the device | ||
| 123 | void Enable(bool enable); | ||
| 124 | |||
| 125 | // returns true if device is enabled | ||
| 126 | bool IsEnabled() const; | ||
| 127 | |||
| 128 | // returns true if polling mode is enabled | ||
| 129 | bool IsPollingMode() const; | ||
| 130 | |||
| 131 | // returns polling mode | ||
| 132 | JoyPollingMode GetPollingMode() const; | ||
| 133 | |||
| 134 | // Sets and enables JoyPollingMode | ||
| 135 | void SetPollingMode(JoyPollingMode mode); | ||
| 136 | |||
| 137 | // Disables JoyPollingMode | ||
| 138 | void DisablePollingMode(); | ||
| 139 | |||
| 140 | // Called on EnableJoyPollingReceiveMode | ||
| 141 | void SetTransferMemoryAddress(Common::ProcessAddress t_mem); | ||
| 142 | |||
| 143 | Kernel::KReadableEvent& GetSendCommandAsycEvent() const; | ||
| 144 | |||
| 145 | virtual void OnInit() {} | ||
| 146 | |||
| 147 | virtual void OnRelease() {} | ||
| 148 | |||
| 149 | // Updates device transfer memory | ||
| 150 | virtual void OnUpdate() {} | ||
| 151 | |||
| 152 | // Returns the device ID of the joycon | ||
| 153 | virtual u8 GetDeviceId() const { | ||
| 154 | return {}; | ||
| 155 | } | ||
| 156 | |||
| 157 | // Assigns a command from data | ||
| 158 | virtual bool SetCommand(std::span<const u8> data) { | ||
| 159 | return {}; | ||
| 160 | } | ||
| 161 | |||
| 162 | // Returns a reply from a command | ||
| 163 | virtual std::vector<u8> GetReply() const { | ||
| 164 | return {}; | ||
| 165 | } | ||
| 166 | |||
| 167 | protected: | ||
| 168 | bool is_activated{}; | ||
| 169 | bool device_enabled{}; | ||
| 170 | bool polling_mode_enabled{}; | ||
| 171 | JoyPollingMode polling_mode = {}; | ||
| 172 | // TODO(German77): All data accessors need to be replaced with a ring lifo object | ||
| 173 | JoyDisableSixAxisDataAccessor disable_sixaxis_data{}; | ||
| 174 | JoyEnableSixAxisDataAccessor enable_sixaxis_data{}; | ||
| 175 | ButtonOnlyPollingDataAccessor button_only_data{}; | ||
| 176 | |||
| 177 | Common::ProcessAddress transfer_memory{}; | ||
| 178 | |||
| 179 | Core::System& system; | ||
| 180 | Kernel::KEvent* send_command_async_event; | ||
| 181 | KernelHelpers::ServiceContext& service_context; | ||
| 182 | }; | ||
| 183 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/ringcon.cpp b/src/hid_core/hidbus/ringcon.cpp new file mode 100644 index 000000000..cedf25c16 --- /dev/null +++ b/src/hid_core/hidbus/ringcon.cpp | |||
| @@ -0,0 +1,292 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "core/core.h" | ||
| 5 | #include "core/hle/kernel/k_event.h" | ||
| 6 | #include "core/hle/kernel/k_readable_event.h" | ||
| 7 | #include "core/memory.h" | ||
| 8 | #include "hid_core/frontend/emulated_controller.h" | ||
| 9 | #include "hid_core/hid_core.h" | ||
| 10 | #include "hid_core/hidbus/ringcon.h" | ||
| 11 | |||
| 12 | namespace Service::HID { | ||
| 13 | |||
| 14 | RingController::RingController(Core::System& system_, | ||
| 15 | KernelHelpers::ServiceContext& service_context_) | ||
| 16 | : HidbusBase(system_, service_context_) { | ||
| 17 | input = system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1); | ||
| 18 | } | ||
| 19 | |||
| 20 | RingController::~RingController() = default; | ||
| 21 | |||
| 22 | void RingController::OnInit() { | ||
| 23 | input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, | ||
| 24 | Common::Input::PollingMode::Ring); | ||
| 25 | return; | ||
| 26 | } | ||
| 27 | |||
| 28 | void RingController::OnRelease() { | ||
| 29 | input->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, | ||
| 30 | Common::Input::PollingMode::Active); | ||
| 31 | return; | ||
| 32 | }; | ||
| 33 | |||
| 34 | void RingController::OnUpdate() { | ||
| 35 | if (!is_activated) { | ||
| 36 | return; | ||
| 37 | } | ||
| 38 | |||
| 39 | if (!device_enabled) { | ||
| 40 | return; | ||
| 41 | } | ||
| 42 | |||
| 43 | if (!polling_mode_enabled || transfer_memory == 0) { | ||
| 44 | return; | ||
| 45 | } | ||
| 46 | |||
| 47 | // TODO: Increment multitasking counters from motion and sensor data | ||
| 48 | |||
| 49 | switch (polling_mode) { | ||
| 50 | case JoyPollingMode::SixAxisSensorEnable: { | ||
| 51 | enable_sixaxis_data.header.total_entries = 10; | ||
| 52 | enable_sixaxis_data.header.result = ResultSuccess; | ||
| 53 | const auto& last_entry = | ||
| 54 | enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; | ||
| 55 | |||
| 56 | enable_sixaxis_data.header.latest_entry = | ||
| 57 | (enable_sixaxis_data.header.latest_entry + 1) % 10; | ||
| 58 | auto& curr_entry = enable_sixaxis_data.entries[enable_sixaxis_data.header.latest_entry]; | ||
| 59 | |||
| 60 | curr_entry.sampling_number = last_entry.sampling_number + 1; | ||
| 61 | curr_entry.polling_data.sampling_number = curr_entry.sampling_number; | ||
| 62 | |||
| 63 | const RingConData ringcon_value = GetSensorValue(); | ||
| 64 | curr_entry.polling_data.out_size = sizeof(ringcon_value); | ||
| 65 | std::memcpy(curr_entry.polling_data.data.data(), &ringcon_value, sizeof(ringcon_value)); | ||
| 66 | |||
| 67 | system.ApplicationMemory().WriteBlock(transfer_memory, &enable_sixaxis_data, | ||
| 68 | sizeof(enable_sixaxis_data)); | ||
| 69 | break; | ||
| 70 | } | ||
| 71 | default: | ||
| 72 | LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | RingController::RingConData RingController::GetSensorValue() const { | ||
| 78 | RingConData ringcon_sensor_value{ | ||
| 79 | .status = DataValid::Valid, | ||
| 80 | .data = 0, | ||
| 81 | }; | ||
| 82 | |||
| 83 | const f32 force_value = input->GetRingSensorForce().force * range; | ||
| 84 | ringcon_sensor_value.data = static_cast<s16>(force_value) + idle_value; | ||
| 85 | |||
| 86 | return ringcon_sensor_value; | ||
| 87 | } | ||
| 88 | |||
| 89 | u8 RingController::GetDeviceId() const { | ||
| 90 | return device_id; | ||
| 91 | } | ||
| 92 | |||
| 93 | std::vector<u8> RingController::GetReply() const { | ||
| 94 | const RingConCommands current_command = command; | ||
| 95 | |||
| 96 | switch (current_command) { | ||
| 97 | case RingConCommands::GetFirmwareVersion: | ||
| 98 | return GetFirmwareVersionReply(); | ||
| 99 | case RingConCommands::ReadId: | ||
| 100 | return GetReadIdReply(); | ||
| 101 | case RingConCommands::c20105: | ||
| 102 | return GetC020105Reply(); | ||
| 103 | case RingConCommands::ReadUnkCal: | ||
| 104 | return GetReadUnkCalReply(); | ||
| 105 | case RingConCommands::ReadFactoryCal: | ||
| 106 | return GetReadFactoryCalReply(); | ||
| 107 | case RingConCommands::ReadUserCal: | ||
| 108 | return GetReadUserCalReply(); | ||
| 109 | case RingConCommands::ReadRepCount: | ||
| 110 | return GetReadRepCountReply(); | ||
| 111 | case RingConCommands::ReadTotalPushCount: | ||
| 112 | return GetReadTotalPushCountReply(); | ||
| 113 | case RingConCommands::ResetRepCount: | ||
| 114 | return GetResetRepCountReply(); | ||
| 115 | case RingConCommands::SaveCalData: | ||
| 116 | return GetSaveDataReply(); | ||
| 117 | default: | ||
| 118 | return GetErrorReply(); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | bool RingController::SetCommand(std::span<const u8> data) { | ||
| 123 | if (data.size() < 4) { | ||
| 124 | LOG_ERROR(Service_HID, "Command size not supported {}", data.size()); | ||
| 125 | command = RingConCommands::Error; | ||
| 126 | return false; | ||
| 127 | } | ||
| 128 | |||
| 129 | std::memcpy(&command, data.data(), sizeof(RingConCommands)); | ||
| 130 | |||
| 131 | switch (command) { | ||
| 132 | case RingConCommands::GetFirmwareVersion: | ||
| 133 | case RingConCommands::ReadId: | ||
| 134 | case RingConCommands::c20105: | ||
| 135 | case RingConCommands::ReadUnkCal: | ||
| 136 | case RingConCommands::ReadFactoryCal: | ||
| 137 | case RingConCommands::ReadUserCal: | ||
| 138 | case RingConCommands::ReadRepCount: | ||
| 139 | case RingConCommands::ReadTotalPushCount: | ||
| 140 | ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); | ||
| 141 | send_command_async_event->Signal(); | ||
| 142 | return true; | ||
| 143 | case RingConCommands::ResetRepCount: | ||
| 144 | ASSERT_MSG(data.size() == 0x4, "data.size is not 0x4 bytes"); | ||
| 145 | total_rep_count = 0; | ||
| 146 | send_command_async_event->Signal(); | ||
| 147 | return true; | ||
| 148 | case RingConCommands::SaveCalData: { | ||
| 149 | ASSERT_MSG(data.size() == 0x14, "data.size is not 0x14 bytes"); | ||
| 150 | |||
| 151 | SaveCalData save_info{}; | ||
| 152 | std::memcpy(&save_info, data.data(), sizeof(SaveCalData)); | ||
| 153 | user_calibration = save_info.calibration; | ||
| 154 | send_command_async_event->Signal(); | ||
| 155 | return true; | ||
| 156 | } | ||
| 157 | default: | ||
| 158 | LOG_ERROR(Service_HID, "Command not implemented {}", command); | ||
| 159 | command = RingConCommands::Error; | ||
| 160 | // Signal a reply to avoid softlocking the game | ||
| 161 | send_command_async_event->Signal(); | ||
| 162 | return false; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | std::vector<u8> RingController::GetFirmwareVersionReply() const { | ||
| 167 | const FirmwareVersionReply reply{ | ||
| 168 | .status = DataValid::Valid, | ||
| 169 | .firmware = version, | ||
| 170 | }; | ||
| 171 | |||
| 172 | return GetDataVector(reply); | ||
| 173 | } | ||
| 174 | |||
| 175 | std::vector<u8> RingController::GetReadIdReply() const { | ||
| 176 | // The values are hardcoded from a real joycon | ||
| 177 | const ReadIdReply reply{ | ||
| 178 | .status = DataValid::Valid, | ||
| 179 | .id_l_x0 = 8, | ||
| 180 | .id_l_x0_2 = 41, | ||
| 181 | .id_l_x4 = 22294, | ||
| 182 | .id_h_x0 = 19777, | ||
| 183 | .id_h_x0_2 = 13621, | ||
| 184 | .id_h_x4 = 8245, | ||
| 185 | }; | ||
| 186 | |||
| 187 | return GetDataVector(reply); | ||
| 188 | } | ||
| 189 | |||
| 190 | std::vector<u8> RingController::GetC020105Reply() const { | ||
| 191 | const Cmd020105Reply reply{ | ||
| 192 | .status = DataValid::Valid, | ||
| 193 | .data = 1, | ||
| 194 | }; | ||
| 195 | |||
| 196 | return GetDataVector(reply); | ||
| 197 | } | ||
| 198 | |||
| 199 | std::vector<u8> RingController::GetReadUnkCalReply() const { | ||
| 200 | const ReadUnkCalReply reply{ | ||
| 201 | .status = DataValid::Valid, | ||
| 202 | .data = 0, | ||
| 203 | }; | ||
| 204 | |||
| 205 | return GetDataVector(reply); | ||
| 206 | } | ||
| 207 | |||
| 208 | std::vector<u8> RingController::GetReadFactoryCalReply() const { | ||
| 209 | const ReadFactoryCalReply reply{ | ||
| 210 | .status = DataValid::Valid, | ||
| 211 | .calibration = factory_calibration, | ||
| 212 | }; | ||
| 213 | |||
| 214 | return GetDataVector(reply); | ||
| 215 | } | ||
| 216 | |||
| 217 | std::vector<u8> RingController::GetReadUserCalReply() const { | ||
| 218 | const ReadUserCalReply reply{ | ||
| 219 | .status = DataValid::Valid, | ||
| 220 | .calibration = user_calibration, | ||
| 221 | }; | ||
| 222 | |||
| 223 | return GetDataVector(reply); | ||
| 224 | } | ||
| 225 | |||
| 226 | std::vector<u8> RingController::GetReadRepCountReply() const { | ||
| 227 | const GetThreeByteReply reply{ | ||
| 228 | .status = DataValid::Valid, | ||
| 229 | .data = {total_rep_count, 0, 0}, | ||
| 230 | .crc = GetCrcValue({total_rep_count, 0, 0, 0}), | ||
| 231 | }; | ||
| 232 | |||
| 233 | return GetDataVector(reply); | ||
| 234 | } | ||
| 235 | |||
| 236 | std::vector<u8> RingController::GetReadTotalPushCountReply() const { | ||
| 237 | const GetThreeByteReply reply{ | ||
| 238 | .status = DataValid::Valid, | ||
| 239 | .data = {total_push_count, 0, 0}, | ||
| 240 | .crc = GetCrcValue({total_push_count, 0, 0, 0}), | ||
| 241 | }; | ||
| 242 | |||
| 243 | return GetDataVector(reply); | ||
| 244 | } | ||
| 245 | |||
| 246 | std::vector<u8> RingController::GetResetRepCountReply() const { | ||
| 247 | return GetReadRepCountReply(); | ||
| 248 | } | ||
| 249 | |||
| 250 | std::vector<u8> RingController::GetSaveDataReply() const { | ||
| 251 | const StatusReply reply{ | ||
| 252 | .status = DataValid::Valid, | ||
| 253 | }; | ||
| 254 | |||
| 255 | return GetDataVector(reply); | ||
| 256 | } | ||
| 257 | |||
| 258 | std::vector<u8> RingController::GetErrorReply() const { | ||
| 259 | const ErrorReply reply{ | ||
| 260 | .status = DataValid::BadCRC, | ||
| 261 | }; | ||
| 262 | |||
| 263 | return GetDataVector(reply); | ||
| 264 | } | ||
| 265 | |||
| 266 | u8 RingController::GetCrcValue(const std::vector<u8>& data) const { | ||
| 267 | u8 crc = 0; | ||
| 268 | for (std::size_t index = 0; index < data.size(); index++) { | ||
| 269 | for (u8 i = 0x80; i > 0; i >>= 1) { | ||
| 270 | bool bit = (crc & 0x80) != 0; | ||
| 271 | if ((data[index] & i) != 0) { | ||
| 272 | bit = !bit; | ||
| 273 | } | ||
| 274 | crc <<= 1; | ||
| 275 | if (bit) { | ||
| 276 | crc ^= 0x8d; | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | return crc; | ||
| 281 | } | ||
| 282 | |||
| 283 | template <typename T> | ||
| 284 | std::vector<u8> RingController::GetDataVector(const T& reply) const { | ||
| 285 | static_assert(std::is_trivially_copyable_v<T>); | ||
| 286 | std::vector<u8> data; | ||
| 287 | data.resize(sizeof(reply)); | ||
| 288 | std::memcpy(data.data(), &reply, sizeof(reply)); | ||
| 289 | return data; | ||
| 290 | } | ||
| 291 | |||
| 292 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/ringcon.h b/src/hid_core/hidbus/ringcon.h new file mode 100644 index 000000000..0953e8100 --- /dev/null +++ b/src/hid_core/hidbus/ringcon.h | |||
| @@ -0,0 +1,253 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <array> | ||
| 7 | #include <span> | ||
| 8 | |||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "hid_core/hidbus/hidbus_base.h" | ||
| 11 | |||
| 12 | namespace Core::HID { | ||
| 13 | class EmulatedController; | ||
| 14 | } // namespace Core::HID | ||
| 15 | |||
| 16 | namespace Service::HID { | ||
| 17 | |||
| 18 | class RingController final : public HidbusBase { | ||
| 19 | public: | ||
| 20 | explicit RingController(Core::System& system_, KernelHelpers::ServiceContext& service_context_); | ||
| 21 | ~RingController() override; | ||
| 22 | |||
| 23 | void OnInit() override; | ||
| 24 | |||
| 25 | void OnRelease() override; | ||
| 26 | |||
| 27 | // Updates ringcon transfer memory | ||
| 28 | void OnUpdate() override; | ||
| 29 | |||
| 30 | // Returns the device ID of the joycon | ||
| 31 | u8 GetDeviceId() const override; | ||
| 32 | |||
| 33 | // Assigns a command from data | ||
| 34 | bool SetCommand(std::span<const u8> data) override; | ||
| 35 | |||
| 36 | // Returns a reply from a command | ||
| 37 | std::vector<u8> GetReply() const override; | ||
| 38 | |||
| 39 | private: | ||
| 40 | // These values are obtained from a real ring controller | ||
| 41 | static constexpr s16 idle_value = 2280; | ||
| 42 | static constexpr s16 idle_deadzone = 120; | ||
| 43 | static constexpr s16 range = 2500; | ||
| 44 | |||
| 45 | // Most missing command names are leftovers from other firmware versions | ||
| 46 | enum class RingConCommands : u32 { | ||
| 47 | GetFirmwareVersion = 0x00020000, | ||
| 48 | ReadId = 0x00020100, | ||
| 49 | JoyPolling = 0x00020101, | ||
| 50 | Unknown1 = 0x00020104, | ||
| 51 | c20105 = 0x00020105, | ||
| 52 | Unknown2 = 0x00020204, | ||
| 53 | Unknown3 = 0x00020304, | ||
| 54 | Unknown4 = 0x00020404, | ||
| 55 | ReadUnkCal = 0x00020504, | ||
| 56 | ReadFactoryCal = 0x00020A04, | ||
| 57 | Unknown5 = 0x00021104, | ||
| 58 | Unknown6 = 0x00021204, | ||
| 59 | Unknown7 = 0x00021304, | ||
| 60 | ReadUserCal = 0x00021A04, | ||
| 61 | ReadRepCount = 0x00023104, | ||
| 62 | ReadTotalPushCount = 0x00023204, | ||
| 63 | ResetRepCount = 0x04013104, | ||
| 64 | Unknown8 = 0x04011104, | ||
| 65 | Unknown9 = 0x04011204, | ||
| 66 | Unknown10 = 0x04011304, | ||
| 67 | SaveCalData = 0x10011A04, | ||
| 68 | Error = 0xFFFFFFFF, | ||
| 69 | }; | ||
| 70 | |||
| 71 | enum class DataValid : u32 { | ||
| 72 | Valid, | ||
| 73 | BadCRC, | ||
| 74 | Cal, | ||
| 75 | }; | ||
| 76 | |||
| 77 | struct FirmwareVersion { | ||
| 78 | u8 sub; | ||
| 79 | u8 main; | ||
| 80 | }; | ||
| 81 | static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size"); | ||
| 82 | |||
| 83 | struct FactoryCalibration { | ||
| 84 | s32_le os_max; | ||
| 85 | s32_le hk_max; | ||
| 86 | s32_le zero_min; | ||
| 87 | s32_le zero_max; | ||
| 88 | }; | ||
| 89 | static_assert(sizeof(FactoryCalibration) == 0x10, "FactoryCalibration is an invalid size"); | ||
| 90 | |||
| 91 | struct CalibrationValue { | ||
| 92 | s16 value; | ||
| 93 | u16 crc; | ||
| 94 | }; | ||
| 95 | static_assert(sizeof(CalibrationValue) == 0x4, "CalibrationValue is an invalid size"); | ||
| 96 | |||
| 97 | struct UserCalibration { | ||
| 98 | CalibrationValue os_max; | ||
| 99 | CalibrationValue hk_max; | ||
| 100 | CalibrationValue zero; | ||
| 101 | }; | ||
| 102 | static_assert(sizeof(UserCalibration) == 0xC, "UserCalibration is an invalid size"); | ||
| 103 | |||
| 104 | struct SaveCalData { | ||
| 105 | RingConCommands command; | ||
| 106 | UserCalibration calibration; | ||
| 107 | INSERT_PADDING_BYTES_NOINIT(4); | ||
| 108 | }; | ||
| 109 | static_assert(sizeof(SaveCalData) == 0x14, "SaveCalData is an invalid size"); | ||
| 110 | static_assert(std::is_trivially_copyable_v<SaveCalData>, | ||
| 111 | "SaveCalData must be trivially copyable"); | ||
| 112 | |||
| 113 | struct FirmwareVersionReply { | ||
| 114 | DataValid status; | ||
| 115 | FirmwareVersion firmware; | ||
| 116 | INSERT_PADDING_BYTES(0x2); | ||
| 117 | }; | ||
| 118 | static_assert(sizeof(FirmwareVersionReply) == 0x8, "FirmwareVersionReply is an invalid size"); | ||
| 119 | |||
| 120 | struct Cmd020105Reply { | ||
| 121 | DataValid status; | ||
| 122 | u8 data; | ||
| 123 | INSERT_PADDING_BYTES(0x3); | ||
| 124 | }; | ||
| 125 | static_assert(sizeof(Cmd020105Reply) == 0x8, "Cmd020105Reply is an invalid size"); | ||
| 126 | |||
| 127 | struct StatusReply { | ||
| 128 | DataValid status; | ||
| 129 | }; | ||
| 130 | static_assert(sizeof(StatusReply) == 0x4, "StatusReply is an invalid size"); | ||
| 131 | |||
| 132 | struct GetThreeByteReply { | ||
| 133 | DataValid status; | ||
| 134 | std::array<u8, 3> data; | ||
| 135 | u8 crc; | ||
| 136 | }; | ||
| 137 | static_assert(sizeof(GetThreeByteReply) == 0x8, "GetThreeByteReply is an invalid size"); | ||
| 138 | |||
| 139 | struct ReadUnkCalReply { | ||
| 140 | DataValid status; | ||
| 141 | u16 data; | ||
| 142 | INSERT_PADDING_BYTES(0x2); | ||
| 143 | }; | ||
| 144 | static_assert(sizeof(ReadUnkCalReply) == 0x8, "ReadUnkCalReply is an invalid size"); | ||
| 145 | |||
| 146 | struct ReadFactoryCalReply { | ||
| 147 | DataValid status; | ||
| 148 | FactoryCalibration calibration; | ||
| 149 | }; | ||
| 150 | static_assert(sizeof(ReadFactoryCalReply) == 0x14, "ReadFactoryCalReply is an invalid size"); | ||
| 151 | |||
| 152 | struct ReadUserCalReply { | ||
| 153 | DataValid status; | ||
| 154 | UserCalibration calibration; | ||
| 155 | INSERT_PADDING_BYTES(0x4); | ||
| 156 | }; | ||
| 157 | static_assert(sizeof(ReadUserCalReply) == 0x14, "ReadUserCalReply is an invalid size"); | ||
| 158 | |||
| 159 | struct ReadIdReply { | ||
| 160 | DataValid status; | ||
| 161 | u16 id_l_x0; | ||
| 162 | u16 id_l_x0_2; | ||
| 163 | u16 id_l_x4; | ||
| 164 | u16 id_h_x0; | ||
| 165 | u16 id_h_x0_2; | ||
| 166 | u16 id_h_x4; | ||
| 167 | }; | ||
| 168 | static_assert(sizeof(ReadIdReply) == 0x10, "ReadIdReply is an invalid size"); | ||
| 169 | |||
| 170 | struct ErrorReply { | ||
| 171 | DataValid status; | ||
| 172 | INSERT_PADDING_BYTES(0x3); | ||
| 173 | }; | ||
| 174 | static_assert(sizeof(ErrorReply) == 0x8, "ErrorReply is an invalid size"); | ||
| 175 | |||
| 176 | struct RingConData { | ||
| 177 | DataValid status; | ||
| 178 | s16_le data; | ||
| 179 | INSERT_PADDING_BYTES(0x2); | ||
| 180 | }; | ||
| 181 | static_assert(sizeof(RingConData) == 0x8, "RingConData is an invalid size"); | ||
| 182 | |||
| 183 | // Returns RingConData struct with pressure sensor values | ||
| 184 | RingConData GetSensorValue() const; | ||
| 185 | |||
| 186 | // Returns 8 byte reply with firmware version | ||
| 187 | std::vector<u8> GetFirmwareVersionReply() const; | ||
| 188 | |||
| 189 | // Returns 16 byte reply with ID values | ||
| 190 | std::vector<u8> GetReadIdReply() const; | ||
| 191 | |||
| 192 | // (STUBBED) Returns 8 byte reply | ||
| 193 | std::vector<u8> GetC020105Reply() const; | ||
| 194 | |||
| 195 | // (STUBBED) Returns 8 byte empty reply | ||
| 196 | std::vector<u8> GetReadUnkCalReply() const; | ||
| 197 | |||
| 198 | // Returns 20 byte reply with factory calibration values | ||
| 199 | std::vector<u8> GetReadFactoryCalReply() const; | ||
| 200 | |||
| 201 | // Returns 20 byte reply with user calibration values | ||
| 202 | std::vector<u8> GetReadUserCalReply() const; | ||
| 203 | |||
| 204 | // Returns 8 byte reply | ||
| 205 | std::vector<u8> GetReadRepCountReply() const; | ||
| 206 | |||
| 207 | // Returns 8 byte reply | ||
| 208 | std::vector<u8> GetReadTotalPushCountReply() const; | ||
| 209 | |||
| 210 | // Returns 8 byte reply | ||
| 211 | std::vector<u8> GetResetRepCountReply() const; | ||
| 212 | |||
| 213 | // Returns 4 byte save data reply | ||
| 214 | std::vector<u8> GetSaveDataReply() const; | ||
| 215 | |||
| 216 | // Returns 8 byte error reply | ||
| 217 | std::vector<u8> GetErrorReply() const; | ||
| 218 | |||
| 219 | // Returns 8 bit redundancy check from provided data | ||
| 220 | u8 GetCrcValue(const std::vector<u8>& data) const; | ||
| 221 | |||
| 222 | // Converts structs to an u8 vector equivalent | ||
| 223 | template <typename T> | ||
| 224 | std::vector<u8> GetDataVector(const T& reply) const; | ||
| 225 | |||
| 226 | RingConCommands command{RingConCommands::Error}; | ||
| 227 | |||
| 228 | // These counters are used in multitasking mode while the switch is sleeping | ||
| 229 | // Total steps taken | ||
| 230 | u8 total_rep_count = 0; | ||
| 231 | // Total times the ring was pushed | ||
| 232 | u8 total_push_count = 0; | ||
| 233 | |||
| 234 | const u8 device_id = 0x20; | ||
| 235 | const FirmwareVersion version = { | ||
| 236 | .sub = 0x0, | ||
| 237 | .main = 0x2c, | ||
| 238 | }; | ||
| 239 | const FactoryCalibration factory_calibration = { | ||
| 240 | .os_max = idle_value + range + idle_deadzone, | ||
| 241 | .hk_max = idle_value - range - idle_deadzone, | ||
| 242 | .zero_min = idle_value - idle_deadzone, | ||
| 243 | .zero_max = idle_value + idle_deadzone, | ||
| 244 | }; | ||
| 245 | UserCalibration user_calibration = { | ||
| 246 | .os_max = {.value = range, .crc = 228}, | ||
| 247 | .hk_max = {.value = -range, .crc = 239}, | ||
| 248 | .zero = {.value = idle_value, .crc = 225}, | ||
| 249 | }; | ||
| 250 | |||
| 251 | Core::HID::EmulatedController* input; | ||
| 252 | }; | ||
| 253 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/starlink.cpp b/src/hid_core/hidbus/starlink.cpp new file mode 100644 index 000000000..31b263aa1 --- /dev/null +++ b/src/hid_core/hidbus/starlink.cpp | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "hid_core/frontend/emulated_controller.h" | ||
| 5 | #include "hid_core/hid_core.h" | ||
| 6 | #include "hid_core/hidbus/starlink.h" | ||
| 7 | |||
| 8 | namespace Service::HID { | ||
| 9 | constexpr u8 DEVICE_ID = 0x28; | ||
| 10 | |||
| 11 | Starlink::Starlink(Core::System& system_, KernelHelpers::ServiceContext& service_context_) | ||
| 12 | : HidbusBase(system_, service_context_) {} | ||
| 13 | Starlink::~Starlink() = default; | ||
| 14 | |||
| 15 | void Starlink::OnInit() { | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | void Starlink::OnRelease() { | ||
| 20 | return; | ||
| 21 | }; | ||
| 22 | |||
| 23 | void Starlink::OnUpdate() { | ||
| 24 | if (!is_activated) { | ||
| 25 | return; | ||
| 26 | } | ||
| 27 | if (!device_enabled) { | ||
| 28 | return; | ||
| 29 | } | ||
| 30 | if (!polling_mode_enabled || transfer_memory == 0) { | ||
| 31 | return; | ||
| 32 | } | ||
| 33 | |||
| 34 | LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); | ||
| 35 | } | ||
| 36 | |||
| 37 | u8 Starlink::GetDeviceId() const { | ||
| 38 | return DEVICE_ID; | ||
| 39 | } | ||
| 40 | |||
| 41 | std::vector<u8> Starlink::GetReply() const { | ||
| 42 | return {}; | ||
| 43 | } | ||
| 44 | |||
| 45 | bool Starlink::SetCommand(std::span<const u8> data) { | ||
| 46 | LOG_ERROR(Service_HID, "Command not implemented"); | ||
| 47 | return false; | ||
| 48 | } | ||
| 49 | |||
| 50 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/starlink.h b/src/hid_core/hidbus/starlink.h new file mode 100644 index 000000000..ee37763b4 --- /dev/null +++ b/src/hid_core/hidbus/starlink.h | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | #include "hid_core/hidbus/hidbus_base.h" | ||
| 8 | |||
| 9 | namespace Core::HID { | ||
| 10 | class EmulatedController; | ||
| 11 | } // namespace Core::HID | ||
| 12 | |||
| 13 | namespace Service::HID { | ||
| 14 | |||
| 15 | class Starlink final : public HidbusBase { | ||
| 16 | public: | ||
| 17 | explicit Starlink(Core::System& system_, KernelHelpers::ServiceContext& service_context_); | ||
| 18 | ~Starlink() override; | ||
| 19 | |||
| 20 | void OnInit() override; | ||
| 21 | |||
| 22 | void OnRelease() override; | ||
| 23 | |||
| 24 | // Updates ringcon transfer memory | ||
| 25 | void OnUpdate() override; | ||
| 26 | |||
| 27 | // Returns the device ID of the joycon | ||
| 28 | u8 GetDeviceId() const override; | ||
| 29 | |||
| 30 | // Assigns a command from data | ||
| 31 | bool SetCommand(std::span<const u8> data) override; | ||
| 32 | |||
| 33 | // Returns a reply from a command | ||
| 34 | std::vector<u8> GetReply() const override; | ||
| 35 | }; | ||
| 36 | |||
| 37 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/stubbed.cpp b/src/hid_core/hidbus/stubbed.cpp new file mode 100644 index 000000000..f16051aa9 --- /dev/null +++ b/src/hid_core/hidbus/stubbed.cpp | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "hid_core/frontend/emulated_controller.h" | ||
| 5 | #include "hid_core/hid_core.h" | ||
| 6 | #include "hid_core/hidbus/stubbed.h" | ||
| 7 | |||
| 8 | namespace Service::HID { | ||
| 9 | constexpr u8 DEVICE_ID = 0xFF; | ||
| 10 | |||
| 11 | HidbusStubbed::HidbusStubbed(Core::System& system_, KernelHelpers::ServiceContext& service_context_) | ||
| 12 | : HidbusBase(system_, service_context_) {} | ||
| 13 | HidbusStubbed::~HidbusStubbed() = default; | ||
| 14 | |||
| 15 | void HidbusStubbed::OnInit() { | ||
| 16 | return; | ||
| 17 | } | ||
| 18 | |||
| 19 | void HidbusStubbed::OnRelease() { | ||
| 20 | return; | ||
| 21 | }; | ||
| 22 | |||
| 23 | void HidbusStubbed::OnUpdate() { | ||
| 24 | if (!is_activated) { | ||
| 25 | return; | ||
| 26 | } | ||
| 27 | if (!device_enabled) { | ||
| 28 | return; | ||
| 29 | } | ||
| 30 | if (!polling_mode_enabled || transfer_memory == 0) { | ||
| 31 | return; | ||
| 32 | } | ||
| 33 | |||
| 34 | LOG_ERROR(Service_HID, "Polling mode not supported {}", polling_mode); | ||
| 35 | } | ||
| 36 | |||
| 37 | u8 HidbusStubbed::GetDeviceId() const { | ||
| 38 | return DEVICE_ID; | ||
| 39 | } | ||
| 40 | |||
| 41 | std::vector<u8> HidbusStubbed::GetReply() const { | ||
| 42 | return {}; | ||
| 43 | } | ||
| 44 | |||
| 45 | bool HidbusStubbed::SetCommand(std::span<const u8> data) { | ||
| 46 | LOG_ERROR(Service_HID, "Command not implemented"); | ||
| 47 | return false; | ||
| 48 | } | ||
| 49 | |||
| 50 | } // namespace Service::HID | ||
diff --git a/src/hid_core/hidbus/stubbed.h b/src/hid_core/hidbus/stubbed.h new file mode 100644 index 000000000..7a711cea0 --- /dev/null +++ b/src/hid_core/hidbus/stubbed.h | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/common_types.h" | ||
| 7 | #include "hid_core/hidbus/hidbus_base.h" | ||
| 8 | |||
| 9 | namespace Core::HID { | ||
| 10 | class EmulatedController; | ||
| 11 | } // namespace Core::HID | ||
| 12 | |||
| 13 | namespace Service::HID { | ||
| 14 | |||
| 15 | class HidbusStubbed final : public HidbusBase { | ||
| 16 | public: | ||
| 17 | explicit HidbusStubbed(Core::System& system_, KernelHelpers::ServiceContext& service_context_); | ||
| 18 | ~HidbusStubbed() override; | ||
| 19 | |||
| 20 | void OnInit() override; | ||
| 21 | |||
| 22 | void OnRelease() override; | ||
| 23 | |||
| 24 | // Updates ringcon transfer memory | ||
| 25 | void OnUpdate() override; | ||
| 26 | |||
| 27 | // Returns the device ID of the joycon | ||
| 28 | u8 GetDeviceId() const override; | ||
| 29 | |||
| 30 | // Assigns a command from data | ||
| 31 | bool SetCommand(std::span<const u8> data) override; | ||
| 32 | |||
| 33 | // Returns a reply from a command | ||
| 34 | std::vector<u8> GetReply() const override; | ||
| 35 | }; | ||
| 36 | |||
| 37 | } // namespace Service::HID | ||