From 7e3be8675f6d5b99c78f709b70b8e1418c8b0baf Mon Sep 17 00:00:00 2001 From: Joey Yakimowich-Payne Date: Wed, 2 Sep 2026 08:20:14 -0600 Subject: [PATCH] Verify automatic XInput mode switching --- adapter_host_probe.cpp | 51 +++++++++++++++++++++++-------- adapter_host_probe.h | 1 - adapter_host_probe_state.h | 1 + switch-pico.cpp | 4 +-- tests/xinput_feasibility_test.cpp | 2 ++ 5 files changed, 42 insertions(+), 17 deletions(-) diff --git a/adapter_host_probe.cpp b/adapter_host_probe.cpp index 08e9954..daabb4f 100644 --- a/adapter_host_probe.cpp +++ b/adapter_host_probe.cpp @@ -18,13 +18,24 @@ namespace { constexpr uint32_t kXInputBootMagic = 0x58494e50; // "XINP" constexpr uint8_t kModeScratchRegister = 0; +constexpr uint8_t kStatusRequest = 0x21; +constexpr uint16_t kStatusIndex = 0x0005; +uint8_t g_status_response[4]{}; AdapterUsbMode g_mode = AdapterUsbMode::kSwitchProbe; AdapterHostProbeState g_probe; +alarm_id_t g_reboot_alarm = 0; uint32_t now_ms() { return static_cast(to_ms_since_boot(get_absolute_time())); } +int64_t reboot_to_xinput(alarm_id_t alarm_id, void *user_data) { + (void)alarm_id; + (void)user_data; + watchdog_hw->scratch[kModeScratchRegister] = kXInputBootMagic; + watchdog_reboot(0, 0, 0); + return 0; +} } // namespace @@ -54,8 +65,24 @@ bool adapter_host_probe_vendor_control(uint8_t rhport, uint8_t stage, if (stage != CONTROL_STAGE_SETUP || request == nullptr || request->bmRequestType_bit.direction != TUSB_DIR_IN || request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR || - request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_DEVICE || - request->bRequest != XInputFeasibility::kMsVendorRequest || + request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_DEVICE) { + return false; + } + + if (request->bRequest == kStatusRequest && + request->wIndex == kStatusIndex) { + const uint32_t current_time = now_ms(); + g_status_response[0] = + g_mode == AdapterUsbMode::kXInput ? 1 : 0; + g_status_response[1] = g_probe.saw_ms_os_string() ? 1 : 0; + g_status_response[2] = g_probe.windows_confirmed() ? 1 : 0; + g_status_response[3] = + g_probe.should_reboot(current_time) ? 1 : 0; + return tud_control_xfer(rhport, request, g_status_response, + sizeof(g_status_response)); + } + + if (request->bRequest != XInputFeasibility::kMsVendorRequest || request->wIndex != XInputFeasibility::kMsCompatIdIndex) { return false; } @@ -63,11 +90,19 @@ bool adapter_host_probe_vendor_control(uint8_t rhport, uint8_t stage, if (g_mode == AdapterUsbMode::kSwitchProbe) { g_probe.note_ms_compat_id_request(now_ms()); PROBE_LOG("[HOST PROBE] Microsoft compatible-ID request confirmed\n"); - return tud_control_xfer( + const bool queued = tud_control_xfer( rhport, request, const_cast( XInputFeasibility::kProbeMsCompatIdDescriptor), sizeof(XInputFeasibility::kProbeMsCompatIdDescriptor)); + if (queued && g_reboot_alarm == 0) { + g_reboot_alarm = add_alarm_in_ms( + AdapterHostProbeState::kRebootDelayMs, + reboot_to_xinput, nullptr, true); + PROBE_LOG("[HOST PROBE] XInput reboot alarm=%d\n", + static_cast(g_reboot_alarm)); + } + return queued; } return tud_control_xfer( @@ -75,13 +110,3 @@ bool adapter_host_probe_vendor_control(uint8_t rhport, uint8_t stage, const_cast(XInputFeasibility::kMsCompatIdDescriptor), sizeof(XInputFeasibility::kMsCompatIdDescriptor)); } - -void adapter_host_probe_task() { - if (g_mode != AdapterUsbMode::kSwitchProbe || - !g_probe.should_reboot(now_ms())) { - return; - } - watchdog_hw->scratch[kModeScratchRegister] = kXInputBootMagic; - PROBE_LOG("[HOST PROBE] rebooting once into XInput\n"); - watchdog_reboot(0, 0, 10); -} diff --git a/adapter_host_probe.h b/adapter_host_probe.h index 797605d..77629f3 100644 --- a/adapter_host_probe.h +++ b/adapter_host_probe.h @@ -14,4 +14,3 @@ AdapterUsbMode adapter_host_probe_mode(); void adapter_host_probe_note_string_descriptor(uint8_t index); bool adapter_host_probe_vendor_control(uint8_t rhport, uint8_t stage, tusb_control_request_t const *request); -void adapter_host_probe_task(); diff --git a/adapter_host_probe_state.h b/adapter_host_probe_state.h index 8dc21cd..d55ebed 100644 --- a/adapter_host_probe_state.h +++ b/adapter_host_probe_state.h @@ -7,6 +7,7 @@ class AdapterHostProbeState { static constexpr uint32_t kRebootDelayMs = 100; void note_ms_os_string() { saw_ms_os_string_ = true; } + bool saw_ms_os_string() const { return saw_ms_os_string_; } void note_ms_compat_id_request(uint32_t now_ms) { if (!saw_ms_os_string_) { diff --git a/switch-pico.cpp b/switch-pico.cpp index 6d29c5d..3a18b30 100644 --- a/switch-pico.cpp +++ b/switch-pico.cpp @@ -278,9 +278,7 @@ int main() { while (true) { tud_task(); // USB device tasks -#ifdef SWITCH_PICO_ADAPTER_FEASIBILITY - adapter_host_probe_task(); -#endif + #ifdef SWITCH_PICO_BLUEPAD32 switch (bootsel_pairing_button_task()) { case BootselPairingButtonEvent::kOpenPairing: diff --git a/tests/xinput_feasibility_test.cpp b/tests/xinput_feasibility_test.cpp index 4858d70..88873b4 100644 --- a/tests/xinput_feasibility_test.cpp +++ b/tests/xinput_feasibility_test.cpp @@ -160,6 +160,8 @@ void test_host_probe_sequence() { expect(!state.windows_confirmed(), "compatible-ID request without signature confirmed Windows"); state.note_ms_os_string(); + expect(state.saw_ms_os_string(), + "Microsoft OS string observation was not retained"); state.note_ms_compat_id_request(20); expect(state.windows_confirmed(), "two-stage Windows signature not confirmed");