#include "switch_protocol.h" #include #include #include #include #include "pico/time.h" #include "switch2_commands.h" #include "switch2_descriptors.h" #include "switch2_reports.h" #include "tusb.h" namespace { constexpr uint32_t kReportIntervalMs = 4; constexpr std::size_t kMaxVendorRequestLength = 64; constexpr std::size_t kMaxVendorResponseLength = 12; SwitchInputState input_state{}; Switch2InputReportId selected_report = Switch2InputReportId::Pro; uint32_t report_counter = 0; uint32_t last_report_time = 0; bool mounted = false; bool initialized = false; std::array pending_response{}; std::size_t pending_response_length = 0; SwitchRumbleCallback rumble_callback = nullptr; void reset_connection_state(bool is_mounted) { input_state = {}; selected_report = Switch2InputReportId::Pro; report_counter = 0; last_report_time = 0; mounted = is_mounted; initialized = false; pending_response_length = 0; } void flush_vendor_response() { if (pending_response_length == 0) return; const uint32_t written = tud_vendor_write( pending_response.data(), static_cast(pending_response_length)); if (written == 0) return; const std::size_t consumed = written; pending_response_length -= consumed; if (pending_response_length > 0) { std::memmove( pending_response.data(), pending_response.data() + consumed, pending_response_length); } tud_vendor_write_flush(); } } // namespace void switch_protocol_init() { rumble_callback = nullptr; reset_connection_state(false); } void switch_protocol_set_input(const SwitchInputState& state) { input_state = state; } void switch_protocol_task() { if (!mounted) return; const bool had_pending_response = pending_response_length > 0; flush_vendor_response(); if (had_pending_response || !initialized || pending_response_length > 0) return; const uint32_t now = static_cast( to_ms_since_boot(get_absolute_time())); if ((now - last_report_time) < kReportIntervalMs || !tud_hid_ready()) return; const Switch2InputReport report = switch2_build_input_report(selected_report, input_state, report_counter); if (tud_hid_report( static_cast(report.id), report.payload.data(), static_cast(report.payload.size()))) { ++report_counter; last_report_time = now; } } bool switch_protocol_is_ready() { return mounted && initialized; } void switch_protocol_set_rumble_callback(SwitchRumbleCallback callback) { rumble_callback = callback; } uint8_t const* tud_descriptor_device_cb() { return switch2_device_descriptor; } uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { return index == 0 ? switch2_configuration_descriptor : nullptr; } uint8_t const* tud_hid_descriptor_report_cb(uint8_t instance) { return instance == 0 ? switch2_hid_report_descriptor : nullptr; } uint16_t tud_hid_get_report_cb( uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { if (instance != 0 || report_type != HID_REPORT_TYPE_INPUT || buffer == nullptr || !mounted || !initialized) { return 0; } Switch2InputReportId id; if (report_id == static_cast(Switch2InputReportId::Common)) { id = Switch2InputReportId::Common; } else if (report_id == static_cast(Switch2InputReportId::Pro)) { id = Switch2InputReportId::Pro; } else { return 0; } const Switch2InputReport report = switch2_build_input_report(id, input_state, report_counter); const uint16_t length = reqlen < report.payload.size() ? reqlen : static_cast(report.payload.size()); std::memcpy(buffer, report.payload.data(), length); return length; } void tud_hid_set_report_cb( uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) { if (instance != 0 || report_type != HID_REPORT_TYPE_OUTPUT || buffer == nullptr) return; uint8_t const* payload = nullptr; if (report_id == 0 && bufsize == 64 && buffer[0] == 0x02) { payload = buffer + 1; } else if (report_id == 0x02 && bufsize == 63) { payload = buffer; } else { return; } // Report 0x02 carries two native HD-rumble payloads. No legacy UART mapping exists. (void)payload; } void tud_vendor_rx_cb(uint8_t itf, uint8_t const* buffer, uint16_t bufsize) { if (itf != 0) return; std::array request{}; const std::size_t request_length = bufsize < request.size() ? bufsize : request.size(); if (buffer != nullptr) { std::memcpy(request.data(), buffer, request_length); } tud_vendor_read_flush(); if (!mounted || buffer == nullptr) return; const Switch2VendorCommand command = switch2_classify_vendor_request(request.data(), request_length); switch (command) { case Switch2VendorCommand::Unsupported: return; case Switch2VendorCommand::SelectReport05: selected_report = Switch2InputReportId::Common; break; case Switch2VendorCommand::SelectReport09: selected_report = Switch2InputReportId::Pro; break; case Switch2VendorCommand::InitializeUsb: initialized = true; break; } pending_response_length = switch2_build_vendor_response( command, pending_response.data(), pending_response.size()); } void tud_mount_cb() { reset_connection_state(true); } void tud_umount_cb() { reset_connection_state(false); } uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { (void)langid; static uint16_t descriptor[32]; if (index == 0) { descriptor[1] = static_cast(switch2_string_language[0]) | (static_cast(switch2_string_language[1]) << 8); descriptor[0] = 0x0304; return descriptor; } const uint8_t* string = nullptr; std::size_t length = 0; switch (index) { case 1: string = switch2_string_manufacturer; length = switch2_string_manufacturer_length; break; case 2: string = switch2_string_product; length = switch2_string_product_length; break; case 3: string = switch2_string_serial; length = switch2_string_serial_length; break; default: return nullptr; } if (length > 31) length = 31; for (std::size_t position = 0; position < length; ++position) { descriptor[1 + position] = string[position]; } descriptor[0] = static_cast(0x0300 | (2 * length + 2)); return descriptor; }