summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Narr the Reg2022-12-20 13:54:17 -0600
committerGravatar Narr the Reg2023-01-19 18:05:21 -0600
commit6aa6301acdf1c29b80ebfc7fca2aac326400760f (patch)
treec81414cfa80044843486e0e8a3346423c1771184
parentservice: hid: Set led pattern and fix color detection (diff)
downloadyuzu-6aa6301acdf1c29b80ebfc7fca2aac326400760f.tar.gz
yuzu-6aa6301acdf1c29b80ebfc7fca2aac326400760f.tar.xz
yuzu-6aa6301acdf1c29b80ebfc7fca2aac326400760f.zip
input_common: Add joycon low level functions
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp286
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h146
3 files changed, 434 insertions, 0 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 41885d0d2..566be9f90 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -57,6 +57,8 @@ if (ENABLE_SDL2)
57 drivers/sdl_driver.h 57 drivers/sdl_driver.h
58 helpers/joycon_driver.cpp 58 helpers/joycon_driver.cpp
59 helpers/joycon_driver.h 59 helpers/joycon_driver.h
60 helpers/joycon_protocol/common_protocol.cpp
61 helpers/joycon_protocol/common_protocol.h
60 helpers/joycon_protocol/joycon_types.h 62 helpers/joycon_protocol/joycon_types.h
61 ) 63 )
62 target_link_libraries(input_common PRIVATE SDL2::SDL2) 64 target_link_libraries(input_common PRIVATE SDL2::SDL2)
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
new file mode 100644
index 000000000..43a036e02
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,286 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/common_protocol.h"
6
7namespace InputCommon::Joycon {
8JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
9 : hidapi_handle{hidapi_handle_} {}
10
11u8 JoyconCommonProtocol::GetCounter() {
12 hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
13 return hidapi_handle->packet_counter;
14}
15
16void JoyconCommonProtocol::SetBlocking() {
17 SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
18}
19
20void JoyconCommonProtocol::SetNonBlocking() {
21 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
22}
23
24DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
25 std::vector<u8> buffer;
26 const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
27 controller_type = ControllerType::None;
28
29 if (result == DriverResult::Success) {
30 controller_type = static_cast<ControllerType>(buffer[0]);
31 // Fallback to 3rd party pro controllers
32 if (controller_type == ControllerType::None) {
33 controller_type = ControllerType::Pro;
34 }
35 }
36
37 return result;
38}
39
40DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
41 ControllerType controller_type{ControllerType::None};
42 const auto result = GetDeviceType(controller_type);
43 if (result != DriverResult::Success || controller_type == ControllerType::None) {
44 return DriverResult::UnsupportedControllerType;
45 }
46
47 hidapi_handle->handle =
48 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
49
50 if (!hidapi_handle->handle) {
51 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
52 device_info->vendor_id, device_info->product_id);
53 return DriverResult::HandleInUse;
54 }
55
56 SetNonBlocking();
57 return DriverResult::Success;
58}
59
60DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
61 const std::vector<u8> buffer{static_cast<u8>(report_mode)};
62 std::vector<u8> output;
63 return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer, output);
64}
65
66DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
67 const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
68
69 if (result == -1) {
70 return DriverResult::ErrorWritingData;
71 }
72
73 return DriverResult::Success;
74}
75
76DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
77 constexpr int timeout_mili = 100;
78 constexpr int MaxTries = 10;
79 int tries = 0;
80 output.resize(MaxSubCommandResponseSize);
81
82 do {
83 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
84 MaxSubCommandResponseSize, timeout_mili);
85
86 if (result < 1) {
87 LOG_ERROR(Input, "No response from joycon");
88 }
89 if (tries++ > MaxTries) {
90 return DriverResult::Timeout;
91 }
92 } while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
93
94 if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
95 return DriverResult::WrongReply;
96 }
97
98 return DriverResult::Success;
99}
100
101DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
102 std::vector<u8>& output) {
103 std::vector<u8> local_buffer(MaxResponseSize);
104
105 local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
106 local_buffer[1] = GetCounter();
107 local_buffer[10] = static_cast<u8>(sc);
108 for (std::size_t i = 0; i < buffer.size(); ++i) {
109 local_buffer[11 + i] = buffer[i];
110 }
111
112 auto result = SendData(local_buffer);
113
114 if (result != DriverResult::Success) {
115 return result;
116 }
117
118 result = GetSubCommandResponse(sc, output);
119
120 return DriverResult::Success;
121}
122
123DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
124 std::vector<u8> local_buffer(MaxResponseSize);
125
126 local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
127 local_buffer[1] = GetCounter();
128
129 memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
130
131 return SendData(local_buffer);
132}
133
134DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
135 constexpr std::size_t MaxTries = 10;
136 std::size_t tries = 0;
137 std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
138 std::vector<u8> local_buffer(size + 20);
139
140 buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
141 buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
142 do {
143 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
144 if (result != DriverResult::Success) {
145 return result;
146 }
147
148 if (tries++ > MaxTries) {
149 return DriverResult::Timeout;
150 }
151 } while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
152
153 // Remove header from output
154 output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
155 return DriverResult::Success;
156}
157
158DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
159 std::vector<u8> output;
160
161 const std::vector<u8> mcu_state{static_cast<u8>(enable ? 1 : 0)};
162 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state, output);
163
164 if (result != DriverResult::Success) {
165 LOG_ERROR(Input, "SendMCUData failed with error {}", result);
166 }
167
168 return result;
169}
170
171DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
172 LOG_DEBUG(Input, "ConfigureMCU");
173 std::vector<u8> output;
174
175 std::array<u8, sizeof(MCUConfig)> config_buffer;
176 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
177 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
178
179 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer, output);
180
181 if (result != DriverResult::Success) {
182 LOG_ERROR(Input, "Set MCU config failed with error {}", result);
183 }
184
185 return result;
186}
187
188DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
189 std::vector<u8>& output) {
190 const int report_mode = static_cast<u8>(report_mode_);
191 constexpr int TimeoutMili = 200;
192 constexpr int MaxTries = 9;
193 int tries = 0;
194 output.resize(0x170);
195
196 do {
197 int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
198
199 if (result < 1) {
200 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
201 }
202 if (tries++ > MaxTries) {
203 return DriverResult::Timeout;
204 }
205 } while (output[0] != report_mode || output[49] == 0xFF);
206
207 if (output[0] != report_mode || output[49] == 0xFF) {
208 return DriverResult::WrongReply;
209 }
210
211 return DriverResult::Success;
212}
213
214DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
215 std::span<const u8> buffer,
216 std::vector<u8>& output) {
217 std::vector<u8> local_buffer(MaxResponseSize);
218
219 local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
220 local_buffer[1] = GetCounter();
221 local_buffer[9] = static_cast<u8>(sc);
222 for (std::size_t i = 0; i < buffer.size(); ++i) {
223 local_buffer[10 + i] = buffer[i];
224 }
225
226 auto result = SendData(local_buffer);
227
228 if (result != DriverResult::Success) {
229 return result;
230 }
231
232 result = GetMCUDataResponse(report_mode, output);
233
234 return DriverResult::Success;
235}
236
237DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
238 std::vector<u8> output;
239 constexpr std::size_t MaxTries{8};
240 std::size_t tries{};
241
242 do {
243 const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
244 const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
245
246 if (result != DriverResult::Success) {
247 return result;
248 }
249
250 if (tries++ > MaxTries) {
251 return DriverResult::WrongReply;
252 }
253 } while (output[49] != 1 || output[56] != static_cast<u8>(mode));
254
255 return DriverResult::Success;
256}
257
258// crc-8-ccitt / polynomial 0x07 look up table
259static constexpr uint8_t mcu_crc8_table[256] = {
260 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
261 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
262 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
263 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
264 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
265 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
266 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
267 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
268 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
269 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
270 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
271 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
272 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
273 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
274 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
275 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
276
277u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
278 u8 crc8 = 0x0;
279
280 for (int i = 0; i < size; ++i) {
281 crc8 = mcu_crc8_table[(u8)(crc8 ^ buffer[i])];
282 }
283 return crc8;
284}
285
286} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
new file mode 100644
index 000000000..a65e4aa76
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -0,0 +1,146 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <memory>
12#include <span>
13#include <vector>
14
15#include "common/common_types.h"
16#include "input_common/helpers/joycon_protocol/joycon_types.h"
17
18namespace InputCommon::Joycon {
19
20/// Joycon driver functions that handle low level communication
21class JoyconCommonProtocol {
22public:
23 explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
24
25 /**
26 * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
27 * data to read before returning.
28 */
29 void SetBlocking();
30
31 /**
32 * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
33 * immediately with a value of 0 if there is no data to be read
34 */
35 void SetNonBlocking();
36
37 /**
38 * Sends a request to obtain the joycon type from device
39 * @returns controller type of the joycon
40 */
41 DriverResult GetDeviceType(ControllerType& controller_type);
42
43 /**
44 * Verifies and sets the joycon_handle if device is valid
45 * @param device info from the driver
46 * @returns success if the device is valid
47 */
48 DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
49
50 /**
51 * Sends a request to set the polling mode of the joycon
52 * @param report_mode polling mode to be set
53 */
54 DriverResult SetReportMode(Joycon::ReportMode report_mode);
55
56 /**
57 * Sends data to the joycon device
58 * @param buffer data to be send
59 */
60 DriverResult SendData(std::span<const u8> buffer);
61
62 /**
63 * Waits for incoming data of the joycon device that matchs the subcommand
64 * @param sub_command type of data to be returned
65 * @returns a buffer containing the responce
66 */
67 DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
68
69 /**
70 * Sends a sub command to the device and waits for it's reply
71 * @param sc sub command to be send
72 * @param buffer data to be send
73 * @returns output buffer containing the responce
74 */
75 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
76
77 /**
78 * Sends vibration data to the joycon
79 * @param buffer data to be send
80 */
81 DriverResult SendVibrationReport(std::span<const u8> buffer);
82
83 /**
84 * Reads the SPI memory stored on the joycon
85 * @param Initial address location
86 * @param size in bytes to be read
87 * @returns output buffer containing the responce
88 */
89 DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
90
91 /**
92 * Enables MCU chip on the joycon
93 * @param enable if true the chip will be enabled
94 */
95 DriverResult EnableMCU(bool enable);
96
97 /**
98 * Configures the MCU to the correspoinding mode
99 * @param MCUConfig configuration
100 */
101 DriverResult ConfigureMCU(const MCUConfig& config);
102
103 /**
104 * Waits until there's MCU data available. On timeout returns error
105 * @param report mode of the expected reply
106 * @returns a buffer containing the responce
107 */
108 DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
109
110 /**
111 * Sends data to the MCU chip and waits for it's reply
112 * @param report mode of the expected reply
113 * @param sub command to be send
114 * @param buffer data to be send
115 * @returns output buffer containing the responce
116 */
117 DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
118 std::vector<u8>& output);
119
120 /**
121 * Wait's until the MCU chip is on the specified mode
122 * @param report mode of the expected reply
123 * @param MCUMode configuration
124 */
125 DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
126
127 /**
128 * Calculates the checksum from the MCU data
129 * @param buffer containing the data to be send
130 * @param size of the buffer in bytes
131 * @returns byte with the correct checksum
132 */
133 u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
134
135private:
136 /**
137 * Increments and returns the packet counter of the handle
138 * @param joycon_handle device to send the data
139 * @returns packet counter value
140 */
141 u8 GetCounter();
142
143 std::shared_ptr<JoyconHandle> hidapi_handle;
144};
145
146} // namespace InputCommon::Joycon