diff options
Diffstat (limited to 'src/core/hle/service/ir/extra_hid.cpp')
-rw-r--r-- | src/core/hle/service/ir/extra_hid.cpp | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/src/core/hle/service/ir/extra_hid.cpp b/src/core/hle/service/ir/extra_hid.cpp new file mode 100644 index 000000000..e7acc17a5 --- /dev/null +++ b/src/core/hle/service/ir/extra_hid.cpp @@ -0,0 +1,231 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/string_util.h" +#include "core/core_timing.h" +#include "core/hle/service/ir/extra_hid.h" +#include "core/settings.h" + +namespace Service { +namespace IR { + +enum class RequestID : u8 { + /** + * ConfigureHIDPolling request + * Starts HID input polling, or changes the polling interval if it is already started. + * Inputs: + * byte 0: request ID + * byte 1: polling interval in ms + * byte 2: unknown + */ + ConfigureHIDPolling = 1, + + /** + * ReadCalibrationData request + * Reads the calibration data stored in circle pad pro. + * Inputs: + * byte 0: request ID + * byte 1: expected response time in ms? + * byte 2-3: data offset (aligned to 0x10) + * byte 4-5: data size (aligned to 0x10) + */ + ReadCalibrationData = 2, + + // TODO(wwylele): there are three more request types (id = 3, 4 and 5) +}; + +enum class ResponseID : u8 { + + /** + * PollHID response + * Sends current HID status + * Output: + * byte 0: response ID + * byte 1-3: Right circle pad position. This three bytes are two little-endian 12-bit + * fields. The first one is for x-axis and the second one is for y-axis. + * byte 4: bit[0:4] battery level; bit[5] ZL button; bit[6] ZR button; bit[7] R button + * Note that for the three button fields, the bit is set when the button is NOT pressed. + * byte 5: unknown + */ + PollHID = 0x10, + + /** + * ReadCalibrationData response + * Sends the calibration data reads from circle pad pro. + * Output: + * byte 0: resonse ID + * byte 1-2: data offset (aligned to 0x10) + * byte 3-4: data size (aligned to 0x10) + * byte 5-...: calibration data + */ + ReadCalibrationData = 0x11, +}; + +ExtraHID::ExtraHID(SendFunc send_func) : IRDevice(send_func) { + LoadInputDevices(); + + // The data below was retrieved from a New 3DS + // TODO(wwylele): this data is probably writable (via request 3?) and thus should be saved to + // and loaded from somewhere. + calibration_data = std::array<u8, 0x40>{{ + // 0x00 + 0x00, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x08 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0xF5, + // 0x10 + 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x18 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, + // 0x20 + 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x28 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, + // 0x30 + 0xFF, 0x00, 0x08, 0x80, 0x85, 0xEB, 0x11, 0x3F, + // 0x38 + 0x85, 0xEB, 0x11, 0x3F, 0xFF, 0xFF, 0xFF, 0x65, + }}; + + hid_polling_callback_id = + CoreTiming::RegisterEvent("ExtraHID::SendHIDStatus", [this](u64, int cycles_late) { + SendHIDStatus(); + CoreTiming::ScheduleEvent(msToCycles(hid_period) - cycles_late, + hid_polling_callback_id); + }); +} + +ExtraHID::~ExtraHID() { + OnDisconnect(); +} + +void ExtraHID::OnConnect() {} + +void ExtraHID::OnDisconnect() { + CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); +} + +void ExtraHID::HandleConfigureHIDPollingRequest(const std::vector<u8>& request) { + if (request.size() != 3) { + LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request.size(), + Common::ArrayToString(request.data(), request.size()).c_str()); + return; + } + + // Change HID input polling interval + CoreTiming::UnscheduleEvent(hid_polling_callback_id, 0); + hid_period = request[1]; + CoreTiming::ScheduleEvent(msToCycles(hid_period), hid_polling_callback_id); +} + +void ExtraHID::HandleReadCalibrationDataRequest(const std::vector<u8>& request_buf) { + struct ReadCalibrationDataRequest { + RequestID request_id; + u8 expected_response_time; + u16_le offset; + u16_le size; + }; + static_assert(sizeof(ReadCalibrationDataRequest) == 6, + "ReadCalibrationDataRequest has wrong size"); + + if (request_buf.size() != sizeof(ReadCalibrationDataRequest)) { + LOG_ERROR(Service_IR, "Wrong request size (%zu): %s", request_buf.size(), + Common::ArrayToString(request_buf.data(), request_buf.size()).c_str()); + return; + } + + ReadCalibrationDataRequest request; + std::memcpy(&request, request_buf.data(), sizeof(request)); + + const u16 offset = Common::AlignDown(request.offset, 16); + const u16 size = Common::AlignDown(request.size, 16); + + if (offset + size > calibration_data.size()) { + LOG_ERROR(Service_IR, "Read beyond the end of calibration data! (offset=%u, size=%u)", + offset, size); + return; + } + + std::vector<u8> response(5); + response[0] = static_cast<u8>(ResponseID::ReadCalibrationData); + std::memcpy(&response[1], &request.offset, sizeof(request.offset)); + std::memcpy(&response[3], &request.size, sizeof(request.size)); + response.insert(response.end(), calibration_data.begin() + offset, + calibration_data.begin() + offset + size); + Send(response); +} + +void ExtraHID::OnReceive(const std::vector<u8>& data) { + switch (static_cast<RequestID>(data[0])) { + case RequestID::ConfigureHIDPolling: + HandleConfigureHIDPollingRequest(data); + break; + case RequestID::ReadCalibrationData: + HandleReadCalibrationDataRequest(data); + break; + default: + LOG_ERROR(Service_IR, "Unknown request: %s", + Common::ArrayToString(data.data(), data.size()).c_str()); + break; + } +} + +void ExtraHID::SendHIDStatus() { + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + struct { + union { + BitField<0, 8, u32_le> header; + BitField<8, 12, u32_le> c_stick_x; + BitField<20, 12, u32_le> c_stick_y; + } c_stick; + union { + BitField<0, 5, u8> battery_level; + BitField<5, 1, u8> zl_not_held; + BitField<6, 1, u8> zr_not_held; + BitField<7, 1, u8> r_not_held; + } buttons; + u8 unknown; + } response; + static_assert(sizeof(response) == 6, "HID status response has wrong size!"); + + constexpr int C_STICK_CENTER = 0x800; + // TODO(wwylele): this value is not accurately measured. We currently assume that the axis can + // take values in the whole range of a 12-bit integer. + constexpr int C_STICK_RADIUS = 0x7FF; + + float x, y; + std::tie(x, y) = c_stick->GetStatus(); + + response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID)); + response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x)); + response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y)); + response.buttons.battery_level.Assign(0x1F); + response.buttons.zl_not_held.Assign(!zl->GetStatus()); + response.buttons.zr_not_held.Assign(!zr->GetStatus()); + response.buttons.r_not_held.Assign(1); + response.unknown = 0; + + std::vector<u8> response_buffer(sizeof(response)); + memcpy(response_buffer.data(), &response, sizeof(response)); + Send(response_buffer); +} + +void ExtraHID::RequestInputDevicesReload() { + is_device_reload_pending.store(true); +} + +void ExtraHID::LoadInputDevices() { + zl = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZL]); + zr = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZR]); + c_stick = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +} // namespace IR +} // namespace Service |