diff --git a/adapter_host_probe.cpp b/adapter_host_probe.cpp index 438b124..9f5c09d 100644 --- a/adapter_host_probe.cpp +++ b/adapter_host_probe.cpp @@ -101,15 +101,28 @@ 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) { - if (stage != CONTROL_STAGE_SETUP || request == nullptr || + if (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) { return false; } + const bool status_request = + request->bRequest == kStatusRequest && + request->wIndex == kStatusIndex; + const bool compatible_id_request = + request->bRequest == XInput::kMsVendorRequest && + request->wIndex == XInput::kMsCompatIdIndex && + (g_mode == AdapterUsbMode::kSwitchProbe || + g_mode == AdapterUsbMode::kXInput); + if (!status_request && !compatible_id_request) { + return false; + } + if (stage != CONTROL_STAGE_SETUP) { + return true; + } - if (request->bRequest == kStatusRequest && - request->wIndex == kStatusIndex) { + if (status_request) { const uint32_t current_time = now_ms(); g_status_response[0] = g_mode == AdapterUsbMode::kXInput ? 1 : 0; @@ -121,10 +134,6 @@ bool adapter_host_probe_vendor_control(uint8_t rhport, uint8_t stage, sizeof(g_status_response)); } - if (request->bRequest != XInput::kMsVendorRequest || - request->wIndex != XInput::kMsCompatIdIndex) { - return false; - } if (g_mode == AdapterUsbMode::kSwitchProbe) { g_probe.note_ms_compat_id_request(now_ms()); diff --git a/adapter_mode_controller.cpp b/adapter_mode_controller.cpp index 5d04dd4..b74ad61 100644 --- a/adapter_mode_controller.cpp +++ b/adapter_mode_controller.cpp @@ -312,6 +312,13 @@ AdapterRequestedMode adapter_mode_controller_requested_mode() { return g_requested_mode; } +void adapter_mode_controller_on_usb_unmounted() { + if (g_requested_mode == AdapterRequestedMode::kAuto && + adapter_host_probe_mode() == AdapterUsbMode::kXInput) { + reboot_now(); + } +} + void adapter_mode_controller_process_input( uint8_t slot_index, bool active, uint32_t connection_generation, uint16_t* pre_hotkey_button_mask, uint32_t now_ms, diff --git a/adapter_mode_controller.h b/adapter_mode_controller.h index 43540f6..1908572 100644 --- a/adapter_mode_controller.h +++ b/adapter_mode_controller.h @@ -16,6 +16,11 @@ constexpr uint16_t ADAPTER_MODE_CHORD_BUTTON_MASK = void adapter_mode_controller_initialize_usb(); AdapterRequestedMode adapter_mode_controller_requested_mode(); +// Auto mode's XInput selection is valid for one USB attachment. Reboot to the +// Switch probe after it unmounts so the next host attachment is detected +// afresh. Persistent manual XInput mode is intentionally unaffected. +void adapter_mode_controller_on_usb_unmounted(); + // Observes the physical pre-hotkey mask and removes the mode chord from both // that mask and the state before profile processing. Slot state is isolated by // connection generation and all time comparisons are uint32-wrap safe. diff --git a/firmware/switch-pico-adapter-feasibility.elf b/firmware/switch-pico-adapter-feasibility.elf index d53f424..5691af9 100755 Binary files a/firmware/switch-pico-adapter-feasibility.elf and b/firmware/switch-pico-adapter-feasibility.elf differ diff --git a/firmware/switch-pico-adapter-feasibility.uf2 b/firmware/switch-pico-adapter-feasibility.uf2 index aec706b..4065007 100644 Binary files a/firmware/switch-pico-adapter-feasibility.uf2 and b/firmware/switch-pico-adapter-feasibility.uf2 differ diff --git a/profile_service.cpp b/profile_service.cpp index 5f17da6..94e583e 100644 --- a/profile_service.cpp +++ b/profile_service.cpp @@ -63,7 +63,7 @@ bool valid_identity(const ControllerIdentity& identity) { } void refresh_list_locked() { - g_list = {}; + g_list = ProfileServiceListSnapshot{}; g_list.metadata = g_metadata; g_list.count = 1; g_list.rows[0].identity = controller_identity_global(); @@ -183,7 +183,7 @@ void profile_service_prepare() { critical_section_init(&g_lock); g_metadata = {}; __atomic_store_n(&g_published_generation, 0, __ATOMIC_RELAXED); - g_list = {}; + g_list = ProfileServiceListSnapshot{}; g_selected = {}; g_active_profiles[0] = {}; g_active_profile_count = 0; diff --git a/switch-pico.cpp b/switch-pico.cpp index 58d695d..3dd0fc9 100644 --- a/switch-pico.cpp +++ b/switch-pico.cpp @@ -181,6 +181,11 @@ static void log_usb_state() { if (mounted != g_last_mounted) { g_last_mounted = mounted; LOG_PRINTF("[USB] %s\n", mounted ? "mounted" : "unmounted"); +#ifdef SWITCH_PICO_BLUEPAD32 + if (!mounted) { + adapter_mode_controller_on_usb_unmounted(); + } +#endif } #ifdef SWITCH_PICO_BLUEPAD32 diff --git a/tests/adapter_host_probe_test.cpp b/tests/adapter_host_probe_test.cpp index b346111..971251f 100644 --- a/tests/adapter_host_probe_test.cpp +++ b/tests/adapter_host_probe_test.cpp @@ -115,6 +115,12 @@ void test_manual_modes_bypass_and_consume_probe_state() { control_length) == 0 && alarm_count == 0, "manual XInput did not serve XInput descriptors without probing"); + require(adapter_host_probe_vendor_control( + 0, CONTROL_STAGE_DATA, &request) && + adapter_host_probe_vendor_control( + 0, CONTROL_STAGE_ACK, &request) && + control_count == 1, + "manual XInput did not retain the compatible-ID transfer"); reset_harness(kXInputBootMagic); adapter_host_probe_init(AdapterRequestedMode::kDInput); @@ -154,6 +160,12 @@ void test_probe_transition_resets_before_watchdog() { alarm_count == 1 && pending_alarm_delay_ms == 100 && reboot_count == 0, "auto probe did not queue the exact delayed transition"); + require(adapter_host_probe_vendor_control( + 0, CONTROL_STAGE_DATA, &request) && + adapter_host_probe_vendor_control( + 0, CONTROL_STAGE_ACK, &request) && + control_count == 1 && alarm_count == 1, + "auto probe did not retain the compatible-ID transfer"); pending_alarm(1, pending_alarm_user_data); require(reset_count == 1 && reboot_count == 1 && diff --git a/tests/adapter_mode_controller_test.cpp b/tests/adapter_mode_controller_test.cpp index e6fb36e..7e2aeae 100644 --- a/tests/adapter_mode_controller_test.cpp +++ b/tests/adapter_mode_controller_test.cpp @@ -532,6 +532,27 @@ void test_failed_recovery_never_reboots() { "or failed recovery acknowledged, released ownership, or retried"); } +void test_auto_xinput_disconnect_reboots_to_probe() { + reset_harness(AdapterRequestedMode::kAuto); + probed_active_mode = AdapterUsbMode::kXInput; + adapter_mode_controller_on_usb_unmounted(); + adapter_mode_controller_on_usb_unmounted(); + require(runtime_reset_count == 1 && reboot_count == 1, + "Auto XInput unmount did not schedule exactly one probe reboot"); + + reset_harness(AdapterRequestedMode::kXInput); + probed_active_mode = AdapterUsbMode::kXInput; + adapter_mode_controller_on_usb_unmounted(); + require(runtime_reset_count == 0 && reboot_count == 0, + "manual XInput mode rebooted after USB unmount"); + + reset_harness(AdapterRequestedMode::kAuto); + probed_active_mode = AdapterUsbMode::kSwitchProbe; + adapter_mode_controller_on_usb_unmounted(); + require(runtime_reset_count == 0 && reboot_count == 0, + "Auto Switch probe rebooted after USB unmount"); +} + } // namespace void configuration_service_initialize_pre_usb() { @@ -659,5 +680,6 @@ int main() { test_configuration_failure_and_correlated_reboot(); test_recovery_auto_wins_host_mode_interleaving(); test_failed_recovery_never_reboots(); + test_auto_xinput_disconnect_reboots_to_probe(); return 0; } diff --git a/tests/usb_output_driver_test.cpp b/tests/usb_output_driver_test.cpp index 1bfe4f6..319d75f 100644 --- a/tests/usb_output_driver_test.cpp +++ b/tests/usb_output_driver_test.cpp @@ -146,9 +146,12 @@ void test_microsoft_compatible_id_descriptor() { "Microsoft function count mismatch"); for (uint8_t instance = 0; instance < SWITCH_PICO_HID_INSTANCE_COUNT; ++instance) { - const uint8_t *function = &kMsCompatIdDescriptor[16 + instance * 24]; + const uint8_t* function = + &kMsCompatIdDescriptor[16 + instance * 24]; expect(function[0] == instance, "Microsoft descriptor interface mismatch"); + expect(function[1] == 1, + "XUSB function must own exactly one interface"); expect(std::memcmp(&function[2], "XUSB10", 6) == 0, "XUSB10 compatible ID missing"); } diff --git a/xinput_descriptors.h b/xinput_descriptors.h index 5776548..6ddc8a0 100644 --- a/xinput_descriptors.h +++ b/xinput_descriptors.h @@ -15,7 +15,7 @@ namespace XInput { constexpr uint16_t kDevelopmentVendorId = 0xcafe; constexpr uint16_t kDevelopmentProductId = 0x4010; -constexpr uint16_t kDevelopmentDeviceRevision = 0x0101; +constexpr uint16_t kDevelopmentDeviceRevision = 0x0105; constexpr uint8_t kInterfaceDescriptorSize = 39; constexpr uint16_t kConfigurationDescriptorSize = 9 + SWITCH_PICO_HID_INSTANCE_COUNT * kInterfaceDescriptorSize;