Fix XUSB binding after reconnect
This commit is contained in:
parent
e68cdcd40f
commit
1e36571770
11 changed files with 74 additions and 11 deletions
|
|
@ -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,
|
bool adapter_host_probe_vendor_control(uint8_t rhport, uint8_t stage,
|
||||||
tusb_control_request_t const *request) {
|
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.direction != TUSB_DIR_IN ||
|
||||||
request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR ||
|
request->bmRequestType_bit.type != TUSB_REQ_TYPE_VENDOR ||
|
||||||
request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_DEVICE) {
|
request->bmRequestType_bit.recipient != TUSB_REQ_RCPT_DEVICE) {
|
||||||
return false;
|
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 &&
|
if (status_request) {
|
||||||
request->wIndex == kStatusIndex) {
|
|
||||||
const uint32_t current_time = now_ms();
|
const uint32_t current_time = now_ms();
|
||||||
g_status_response[0] =
|
g_status_response[0] =
|
||||||
g_mode == AdapterUsbMode::kXInput ? 1 : 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));
|
sizeof(g_status_response));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request->bRequest != XInput::kMsVendorRequest ||
|
|
||||||
request->wIndex != XInput::kMsCompatIdIndex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_mode == AdapterUsbMode::kSwitchProbe) {
|
if (g_mode == AdapterUsbMode::kSwitchProbe) {
|
||||||
g_probe.note_ms_compat_id_request(now_ms());
|
g_probe.note_ms_compat_id_request(now_ms());
|
||||||
|
|
|
||||||
|
|
@ -312,6 +312,13 @@ AdapterRequestedMode adapter_mode_controller_requested_mode() {
|
||||||
return g_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(
|
void adapter_mode_controller_process_input(
|
||||||
uint8_t slot_index, bool active, uint32_t connection_generation,
|
uint8_t slot_index, bool active, uint32_t connection_generation,
|
||||||
uint16_t* pre_hotkey_button_mask, uint32_t now_ms,
|
uint16_t* pre_hotkey_button_mask, uint32_t now_ms,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@ constexpr uint16_t ADAPTER_MODE_CHORD_BUTTON_MASK =
|
||||||
void adapter_mode_controller_initialize_usb();
|
void adapter_mode_controller_initialize_usb();
|
||||||
AdapterRequestedMode adapter_mode_controller_requested_mode();
|
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
|
// 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
|
// that mask and the state before profile processing. Slot state is isolated by
|
||||||
// connection generation and all time comparisons are uint32-wrap safe.
|
// connection generation and all time comparisons are uint32-wrap safe.
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -63,7 +63,7 @@ bool valid_identity(const ControllerIdentity& identity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void refresh_list_locked() {
|
void refresh_list_locked() {
|
||||||
g_list = {};
|
g_list = ProfileServiceListSnapshot{};
|
||||||
g_list.metadata = g_metadata;
|
g_list.metadata = g_metadata;
|
||||||
g_list.count = 1;
|
g_list.count = 1;
|
||||||
g_list.rows[0].identity = controller_identity_global();
|
g_list.rows[0].identity = controller_identity_global();
|
||||||
|
|
@ -183,7 +183,7 @@ void profile_service_prepare() {
|
||||||
critical_section_init(&g_lock);
|
critical_section_init(&g_lock);
|
||||||
g_metadata = {};
|
g_metadata = {};
|
||||||
__atomic_store_n(&g_published_generation, 0, __ATOMIC_RELAXED);
|
__atomic_store_n(&g_published_generation, 0, __ATOMIC_RELAXED);
|
||||||
g_list = {};
|
g_list = ProfileServiceListSnapshot{};
|
||||||
g_selected = {};
|
g_selected = {};
|
||||||
g_active_profiles[0] = {};
|
g_active_profiles[0] = {};
|
||||||
g_active_profile_count = 0;
|
g_active_profile_count = 0;
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,11 @@ static void log_usb_state() {
|
||||||
if (mounted != g_last_mounted) {
|
if (mounted != g_last_mounted) {
|
||||||
g_last_mounted = mounted;
|
g_last_mounted = mounted;
|
||||||
LOG_PRINTF("[USB] %s\n", mounted ? "mounted" : "unmounted");
|
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
|
#ifdef SWITCH_PICO_BLUEPAD32
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,12 @@ void test_manual_modes_bypass_and_consume_probe_state() {
|
||||||
control_length) == 0 &&
|
control_length) == 0 &&
|
||||||
alarm_count == 0,
|
alarm_count == 0,
|
||||||
"manual XInput did not serve XInput descriptors without probing");
|
"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);
|
reset_harness(kXInputBootMagic);
|
||||||
adapter_host_probe_init(AdapterRequestedMode::kDInput);
|
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 &&
|
alarm_count == 1 && pending_alarm_delay_ms == 100 &&
|
||||||
reboot_count == 0,
|
reboot_count == 0,
|
||||||
"auto probe did not queue the exact delayed transition");
|
"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);
|
pending_alarm(1, pending_alarm_user_data);
|
||||||
require(reset_count == 1 && reboot_count == 1 &&
|
require(reset_count == 1 && reboot_count == 1 &&
|
||||||
|
|
|
||||||
|
|
@ -532,6 +532,27 @@ void test_failed_recovery_never_reboots() {
|
||||||
"or failed recovery acknowledged, released ownership, or retried");
|
"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
|
} // namespace
|
||||||
|
|
||||||
void configuration_service_initialize_pre_usb() {
|
void configuration_service_initialize_pre_usb() {
|
||||||
|
|
@ -659,5 +680,6 @@ int main() {
|
||||||
test_configuration_failure_and_correlated_reboot();
|
test_configuration_failure_and_correlated_reboot();
|
||||||
test_recovery_auto_wins_host_mode_interleaving();
|
test_recovery_auto_wins_host_mode_interleaving();
|
||||||
test_failed_recovery_never_reboots();
|
test_failed_recovery_never_reboots();
|
||||||
|
test_auto_xinput_disconnect_reboots_to_probe();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,9 +146,12 @@ void test_microsoft_compatible_id_descriptor() {
|
||||||
"Microsoft function count mismatch");
|
"Microsoft function count mismatch");
|
||||||
for (uint8_t instance = 0;
|
for (uint8_t instance = 0;
|
||||||
instance < SWITCH_PICO_HID_INSTANCE_COUNT; ++instance) {
|
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,
|
expect(function[0] == instance,
|
||||||
"Microsoft descriptor interface mismatch");
|
"Microsoft descriptor interface mismatch");
|
||||||
|
expect(function[1] == 1,
|
||||||
|
"XUSB function must own exactly one interface");
|
||||||
expect(std::memcmp(&function[2], "XUSB10", 6) == 0,
|
expect(std::memcmp(&function[2], "XUSB10", 6) == 0,
|
||||||
"XUSB10 compatible ID missing");
|
"XUSB10 compatible ID missing");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace XInput {
|
||||||
|
|
||||||
constexpr uint16_t kDevelopmentVendorId = 0xcafe;
|
constexpr uint16_t kDevelopmentVendorId = 0xcafe;
|
||||||
constexpr uint16_t kDevelopmentProductId = 0x4010;
|
constexpr uint16_t kDevelopmentProductId = 0x4010;
|
||||||
constexpr uint16_t kDevelopmentDeviceRevision = 0x0101;
|
constexpr uint16_t kDevelopmentDeviceRevision = 0x0105;
|
||||||
constexpr uint8_t kInterfaceDescriptorSize = 39;
|
constexpr uint8_t kInterfaceDescriptorSize = 39;
|
||||||
constexpr uint16_t kConfigurationDescriptorSize =
|
constexpr uint16_t kConfigurationDescriptorSize =
|
||||||
9 + SWITCH_PICO_HID_INSTANCE_COUNT * kInterfaceDescriptorSize;
|
9 + SWITCH_PICO_HID_INSTANCE_COUNT * kInterfaceDescriptorSize;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue