switch-pico/tests/adapter_host_probe_test.cpp

220 lines
8.2 KiB
C++

#include "adapter/adapter_host_probe.h"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include "hardware/structs/watchdog.h"
#include "pico/time.h"
#include "usb/xinput/xinput_descriptors.h"
namespace {
constexpr uint32_t kXInputBootMagic = 0x58494e50u;
watchdog_hw_t watchdog_registers{};
uint64_t now_ms = 0;
alarm_callback_t pending_alarm = nullptr;
void* pending_alarm_user_data = nullptr;
int64_t pending_alarm_delay_ms = 0;
int alarm_count = 0;
int reset_count = 0;
int reboot_count = 0;
int call_sequence = 0;
int reset_sequence = 0;
int reboot_sequence = 0;
bool control_result = true;
int control_count = 0;
const void* control_buffer = nullptr;
uint16_t control_length = 0;
void require(bool condition, const char* message) {
if (!condition) {
std::cerr << "FAIL: " << message << '\n';
std::exit(1);
}
}
void reset_harness(uint32_t scratch) {
watchdog_registers = {};
watchdog_registers.scratch[0] = scratch;
now_ms = 0;
pending_alarm = nullptr;
pending_alarm_user_data = nullptr;
pending_alarm_delay_ms = 0;
alarm_count = 0;
reset_count = 0;
reboot_count = 0;
call_sequence = 0;
reset_sequence = 0;
reboot_sequence = 0;
control_result = true;
control_count = 0;
control_buffer = nullptr;
control_length = 0;
}
tusb_control_request_t microsoft_request() {
tusb_control_request_t request{};
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 = XInput::kMsVendorRequest;
request.wIndex = XInput::kMsCompatIdIndex;
return request;
}
void test_auto_scratch_lifecycle() {
reset_harness(0);
adapter_host_probe_init(AdapterRequestedMode::kAuto);
require(adapter_host_probe_mode() == AdapterUsbMode::kSwitchProbe &&
watchdog_registers.scratch[0] == 0,
"auto cold boot did not select Switch probe");
reset_harness(kXInputBootMagic);
adapter_host_probe_init(AdapterRequestedMode::kAuto);
require(adapter_host_probe_mode() == AdapterUsbMode::kXInput &&
watchdog_registers.scratch[0] == 0,
"auto transition token did not select one XInput boot");
adapter_host_probe_init(AdapterRequestedMode::kAuto);
require(adapter_host_probe_mode() == AdapterUsbMode::kSwitchProbe,
"consumed scratch token caused a reboot loop");
reset_harness(0xa5a55a5au);
adapter_host_probe_init(AdapterRequestedMode::kAuto);
require(adapter_host_probe_mode() == AdapterUsbMode::kSwitchProbe &&
watchdog_registers.scratch[0] == 0,
"stale non-token scratch was not consumed safely");
}
void test_manual_modes_bypass_and_consume_probe_state() {
tusb_control_request_t request = microsoft_request();
reset_harness(kXInputBootMagic);
adapter_host_probe_init(AdapterRequestedMode::kSwitch);
require(adapter_host_probe_mode() == AdapterUsbMode::kSwitch &&
watchdog_registers.scratch[0] == 0,
"manual Switch honored stale auto scratch");
adapter_host_probe_note_string_descriptor(0xee);
require(!adapter_host_probe_vendor_control(
0, CONTROL_STAGE_SETUP, &request) &&
control_count == 0 && alarm_count == 0,
"manual Switch entered the host probe path");
reset_harness(0xdeadbeefu);
adapter_host_probe_init(AdapterRequestedMode::kXInput);
require(adapter_host_probe_mode() == AdapterUsbMode::kXInput &&
watchdog_registers.scratch[0] == 0,
"manual XInput honored stale scratch");
require(adapter_host_probe_vendor_control(
0, CONTROL_STAGE_SETUP, &request) &&
control_count == 1 &&
control_length == sizeof(XInput::kMsCompatIdDescriptor) &&
std::memcmp(control_buffer, XInput::kMsCompatIdDescriptor,
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);
require(adapter_host_probe_mode() == AdapterUsbMode::kDInput &&
watchdog_registers.scratch[0] == 0 && alarm_count == 0,
"manual DInput did not bypass and consume stale auto scratch");
adapter_host_probe_note_string_descriptor(0xee);
require(!adapter_host_probe_vendor_control(
0, CONTROL_STAGE_SETUP, &request) &&
control_count == 0 && alarm_count == 0,
"manual DInput entered the XInput probe path");
reset_harness(kXInputBootMagic);
adapter_host_probe_init(AdapterRequestedMode::kMac);
require(adapter_host_probe_mode() == AdapterUsbMode::kMac &&
watchdog_registers.scratch[0] == 0 && alarm_count == 0,
"manual Mac did not bypass and consume stale auto scratch");
adapter_host_probe_note_string_descriptor(0xee);
require(!adapter_host_probe_vendor_control(
0, CONTROL_STAGE_SETUP, &request) &&
control_count == 0 && alarm_count == 0,
"manual Mac entered the XInput probe path");
}
void test_probe_transition_resets_before_watchdog() {
reset_harness(0);
adapter_host_probe_init(AdapterRequestedMode::kAuto);
adapter_host_probe_note_string_descriptor(0xee);
tusb_control_request_t request = microsoft_request();
require(adapter_host_probe_vendor_control(
0, CONTROL_STAGE_SETUP, &request) &&
control_length ==
sizeof(XInput::kProbeMsCompatIdDescriptor) &&
std::memcmp(control_buffer,
XInput::kProbeMsCompatIdDescriptor,
control_length) == 0 &&
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 &&
reset_sequence < reboot_sequence &&
watchdog_registers.scratch[0] == kXInputBootMagic,
"probe reboot did not reset synthetic state before scratch reset");
adapter_host_probe_init(AdapterRequestedMode::kAuto);
require(adapter_host_probe_mode() == AdapterUsbMode::kXInput &&
watchdog_registers.scratch[0] == 0,
"probe transition was not consumed on the next boot");
}
} // namespace
watchdog_hw_t* watchdog_hw = &watchdog_registers;
absolute_time_t get_absolute_time() { return now_ms; }
uint64_t to_ms_since_boot(absolute_time_t time) { return time; }
alarm_id_t add_alarm_in_ms(int64_t delay_ms, alarm_callback_t callback,
void* user_data, bool) {
++alarm_count;
pending_alarm_delay_ms = delay_ms;
pending_alarm = callback;
pending_alarm_user_data = user_data;
return alarm_count;
}
bool tud_control_xfer(uint8_t, const tusb_control_request_t*, void* buffer,
uint16_t length) {
++control_count;
control_buffer = buffer;
control_length = length;
return control_result;
}
void controller_profile_runtime_reset() {
++reset_count;
reset_sequence = ++call_sequence;
}
void watchdog_reboot(uint32_t, uint32_t, uint32_t) {
++reboot_count;
reboot_sequence = ++call_sequence;
}
int main() {
test_auto_scratch_lifecycle();
test_manual_modes_bypass_and_consume_probe_state();
test_probe_transition_resets_before_watchdog();
return 0;
}