Complete USB output mode qualification
This commit is contained in:
parent
1e36571770
commit
bd5d4b3177
23 changed files with 388 additions and 27 deletions
|
|
@ -143,7 +143,22 @@ void test_pending_commands_are_not_decoded_as_profile_writes() {
|
|||
active.profile_index == 0,
|
||||
"profile write did not publish one coherent generation");
|
||||
|
||||
constexpr uint32_t kResetTransactionId = 0xa5a55a5a;
|
||||
constexpr uint32_t kReservedInternalTransactionId = 0x80000019u;
|
||||
require(
|
||||
profile_service_begin(
|
||||
kReservedInternalTransactionId, identity, kProfileIndex,
|
||||
CONTROLLER_PROFILE_SCHEMA_VERSION, sizeof(encoded),
|
||||
profile_storage_crc32(encoded, sizeof(encoded))) ==
|
||||
ConfigurationTransactionStatus::kMalformed &&
|
||||
profile_service_reset(kReservedInternalTransactionId, identity,
|
||||
kProfileIndex) ==
|
||||
ConfigurationTransactionStatus::kMalformed &&
|
||||
profile_service_activate(kReservedInternalTransactionId, identity,
|
||||
kProfileIndex) ==
|
||||
ConfigurationTransactionStatus::kMalformed,
|
||||
"host profile mutations admitted the internal transaction namespace");
|
||||
|
||||
constexpr uint32_t kResetTransactionId = 0x25a55a5a;
|
||||
require(profile_service_reset(kResetTransactionId, identity,
|
||||
kProfileIndex) ==
|
||||
ConfigurationTransactionStatus::kPending,
|
||||
|
|
@ -233,6 +248,17 @@ void test_host_and_controller_mutations_are_serialized() {
|
|||
snapshot.transaction.status ==
|
||||
ConfigurationTransactionStatus::kReceiving,
|
||||
"busy controller activation replaced the host transaction");
|
||||
require(
|
||||
profile_service_append(kInternalTransactionId, 0, encoded,
|
||||
sizeof(encoded)) ==
|
||||
ConfigurationTransactionStatus::kMalformed &&
|
||||
profile_service_commit(kInternalTransactionId) ==
|
||||
ConfigurationTransactionStatus::kMalformed &&
|
||||
transaction_snapshot().transaction.transaction_id ==
|
||||
kHostTransactionId &&
|
||||
transaction_snapshot().transaction.status ==
|
||||
ConfigurationTransactionStatus::kReceiving,
|
||||
"reserved internal IDs corrupted a receiving host transaction");
|
||||
|
||||
require(profile_service_append(
|
||||
kHostTransactionId, 0, encoded, sizeof(encoded)) ==
|
||||
|
|
|
|||
|
|
@ -226,6 +226,12 @@ class FakeDevice:
|
|||
]
|
||||
),
|
||||
)
|
||||
if request == config_manager.OP_RUNTIME_DIAGNOSTICS:
|
||||
return make_response(
|
||||
request,
|
||||
struct.pack("<7I4B", 6, 1200, 120, 5000, 8, 2, 10,
|
||||
2, 2, 1, 1),
|
||||
)
|
||||
if request == config_manager.OP_CONFIGURATION_READ:
|
||||
return make_response(
|
||||
request,
|
||||
|
|
@ -1010,6 +1016,20 @@ def test_requested_and_active_mode_response_validation() -> None:
|
|||
"rumble",
|
||||
"motion",
|
||||
)
|
||||
diagnostics = config_manager.read_runtime_diagnostics(device)
|
||||
assert diagnostics == config_manager.RuntimeDiagnostics(
|
||||
initialization_stage=6,
|
||||
rumble_timer_ticks=1200,
|
||||
configuration_timer_ticks=120,
|
||||
controller_reports=5000,
|
||||
host_rumble_requests=8,
|
||||
local_feedback_requests=2,
|
||||
rumble_dispatches=10,
|
||||
active_slots=2,
|
||||
rumble_capable_slots=2,
|
||||
feedback_pending_slots=1,
|
||||
rumble_pending_slots=1,
|
||||
)
|
||||
|
||||
device.configuration = struct.pack("<HB5x", 60, 0xFF)
|
||||
with pytest.raises(
|
||||
|
|
@ -1267,7 +1287,7 @@ def test_profile_reset_and_activate_wait_for_correlated_transactions(
|
|||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
device = FakeDevice()
|
||||
generated_ids = iter((0, 0xA5A55A5A))
|
||||
generated_ids = iter((0, 0xFFFFFFFF))
|
||||
monkeypatch.setattr(
|
||||
config_manager.secrets, "randbits", lambda _bits: next(generated_ids)
|
||||
)
|
||||
|
|
@ -1296,13 +1316,13 @@ def test_profile_reset_and_activate_wait_for_correlated_transactions(
|
|||
assert (
|
||||
activated.transaction_id
|
||||
== device.profile_transaction_id
|
||||
== 0xA5A55A5A
|
||||
== config_manager.HOST_TRANSACTION_ID_MASK
|
||||
)
|
||||
assert activated.status == config_manager.STATUS_OK
|
||||
assert activated.stored_generation == 9
|
||||
assert device.profile_status_responses == [
|
||||
(0xA5A55A5A, config_manager.STATUS_PENDING),
|
||||
(0xA5A55A5A, config_manager.STATUS_OK),
|
||||
(config_manager.HOST_TRANSACTION_ID_MASK, config_manager.STATUS_PENDING),
|
||||
(config_manager.HOST_TRANSACTION_ID_MASK, config_manager.STATUS_OK),
|
||||
]
|
||||
assert device.active_profiles[identity.to_bytes()] == 3
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ uint32_t reboot_transaction_id = 0;
|
|||
uint32_t reboot_call_count = 0;
|
||||
bool refresh_requested = false;
|
||||
bool clear_requested = false;
|
||||
Bluepad32BackendDiagnostics current_diagnostics{};
|
||||
std::vector<uint8_t> control_payload;
|
||||
std::vector<uint8_t> next_out_payload;
|
||||
uint32_t begin_transaction_id = 0;
|
||||
|
|
@ -200,6 +201,23 @@ void test_vendor_requests() {
|
|||
control_payload[12] == 7,
|
||||
"pairing read did not use the versioned envelope");
|
||||
|
||||
current_diagnostics = {
|
||||
6, 1200, 120, 5000, 8, 2, 10, 2, 2, 1, 1,
|
||||
};
|
||||
request = setup_request(
|
||||
Operation::kRuntimeDiagnostics, TUSB_DIR_IN,
|
||||
kMaximumResponseSize);
|
||||
require(
|
||||
usb_configuration_management_vendor_control(
|
||||
0, CONTROL_STAGE_SETUP, &request) &&
|
||||
control_payload[5] ==
|
||||
static_cast<uint8_t>(Operation::kRuntimeDiagnostics) &&
|
||||
read_u32(control_payload, kResponseHeaderSize) == 6 &&
|
||||
read_u32(control_payload, kResponseHeaderSize + 4) == 1200 &&
|
||||
control_payload[kResponseHeaderSize + 28] == 2 &&
|
||||
control_payload[kResponseHeaderSize + 31] == 1,
|
||||
"runtime diagnostics did not expose backend counters");
|
||||
|
||||
perform_out(Operation::kPairingRefresh, {});
|
||||
require(refresh_requested,
|
||||
"pairing refresh was not dispatched");
|
||||
|
|
@ -733,6 +751,11 @@ void bluepad32_input_backend_pairing_snapshot(
|
|||
*out = current_pairings;
|
||||
}
|
||||
|
||||
void bluepad32_input_backend_diagnostics(
|
||||
Bluepad32BackendDiagnostics* out) {
|
||||
*out = current_diagnostics;
|
||||
}
|
||||
|
||||
bool adapter_host_probe_vendor_control(
|
||||
uint8_t, uint8_t, const tusb_control_request_t*) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -172,6 +172,39 @@ void test_input_report_mapping() {
|
|||
report.right_y == 0,
|
||||
"neutral axes mismatch");
|
||||
|
||||
ControllerState system_only{};
|
||||
system_only.button_system = true;
|
||||
const auto system_report = XInput::build_input_report(system_only);
|
||||
const std::array<uint8_t, sizeof(XInput::InputReport)> system_golden{
|
||||
0x00, 0x14, 0x00, 0x04};
|
||||
expect(std::memcmp(&system_report, system_golden.data(),
|
||||
system_golden.size()) == 0 &&
|
||||
system_report.buttons == XInput::kGuide,
|
||||
"system button did not serialize as the Guide bit");
|
||||
|
||||
ControllerState capture_only{};
|
||||
capture_only.button_capture = true;
|
||||
const auto capture_report = XInput::build_input_report(capture_only);
|
||||
const std::array<uint8_t, sizeof(XInput::InputReport)> capture_golden{
|
||||
0x00, 0x14, 0x00, 0x08};
|
||||
expect(std::memcmp(&capture_report, capture_golden.data(),
|
||||
capture_golden.size()) == 0 &&
|
||||
capture_report.buttons == XInput::kShare,
|
||||
"capture button did not serialize as the Share bit");
|
||||
|
||||
ControllerState system_and_capture{};
|
||||
system_and_capture.button_system = true;
|
||||
system_and_capture.button_capture = true;
|
||||
const auto combined_report =
|
||||
XInput::build_input_report(system_and_capture);
|
||||
const std::array<uint8_t, sizeof(XInput::InputReport)> combined_golden{
|
||||
0x00, 0x14, 0x00, 0x0c};
|
||||
expect(std::memcmp(&combined_report, combined_golden.data(),
|
||||
combined_golden.size()) == 0 &&
|
||||
combined_report.buttons ==
|
||||
static_cast<uint16_t>(XInput::kGuide | XInput::kShare),
|
||||
"Guide and Share bits collided in the serialized report");
|
||||
|
||||
state.dpad_up = true;
|
||||
state.button_south = true;
|
||||
state.button_east = true;
|
||||
|
|
@ -470,6 +503,8 @@ void test_xinput_boundary_dispatch() {
|
|||
ControllerState state{};
|
||||
state.button_south = (instance & 1u) == 0;
|
||||
state.button_north = (instance & 1u) != 0;
|
||||
state.button_system = true;
|
||||
state.button_capture = true;
|
||||
state.left_trigger =
|
||||
static_cast<uint16_t>(0x1000u * (instance + 1u));
|
||||
state.right_trigger =
|
||||
|
|
@ -490,6 +525,9 @@ void test_xinput_boundary_dispatch() {
|
|||
std::memcmp(input_endpoint.last_transfer.data(),
|
||||
&expected, sizeof(expected)) == 0,
|
||||
"XInput boundary changed an input report");
|
||||
expect((read_le16(input_endpoint.last_transfer.data() + 2) &
|
||||
0x0c00u) == 0x0c00u,
|
||||
"XInput boundary dropped Guide or Share from wButtons");
|
||||
}
|
||||
expect(!usb_output_driver_task(kInvalidInstance),
|
||||
"XInput task accepted an invalid instance");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue