fix: keep Joy-Con links fast and stop idle rumble writes
This commit is contained in:
parent
ed8f3a7c7f
commit
79a72d811d
10 changed files with 220 additions and 84 deletions
|
|
@ -1385,10 +1385,9 @@ void test_switch2_mode_roundtrip(bool right_first) {
|
|||
slot_snapshot(2).connection_generation == classic_before.connection_generation &&
|
||||
slot_snapshot(2).state.button_north &&
|
||||
classic.rumble_calls == classic_calls && classic.last_low == 71 &&
|
||||
negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24 &&
|
||||
interval_requests[left.conn.handle] == 1 && interval_requests[right.conn.handle] == 1,
|
||||
"mode roundtrip must preserve unrelated Classic state, haptics and physical mixed-link intervals");
|
||||
negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6,
|
||||
"mode roundtrip must preserve unrelated Classic state, haptics and fast Joy-Con links");
|
||||
ControllerProfile inactive{};
|
||||
require(runtime_profile_storage.get(pair_identity, 2, &inactive) == ProfileStorageResult::kOk &&
|
||||
inactive.weak_rumble_scale == 37,
|
||||
|
|
@ -3046,25 +3045,25 @@ void test_switch2_radio_policy(bool individual) {
|
|||
"a single Switch2 link must retain fast scheduling");
|
||||
ready_switch2(right);
|
||||
require(incoming_connections &&
|
||||
negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24,
|
||||
"two Switch2 links must reserve Classic reconnect airtime before a Classic device exists");
|
||||
negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6,
|
||||
"paired and individual Joy-Cons must retain fast links before Classic arrival");
|
||||
platform_on_device_connected(&other_ble);
|
||||
require(platform_on_device_ready(&other_ble) == UNI_ERROR_SUCCESS,
|
||||
"unrelated BLE controller must join");
|
||||
platform_on_device_connected(&classic);
|
||||
require(negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24 &&
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6 &&
|
||||
negotiated_intervals[other_ble.conn.handle] == 6,
|
||||
"Classic setup must retain reconnect airtime without retiming unrelated BLE");
|
||||
"Classic setup must not slow Joy-Cons or retime unrelated BLE");
|
||||
require(platform_on_device_ready(&classic) == UNI_ERROR_SUCCESS,
|
||||
"Classic controller must complete setup alongside the pair");
|
||||
const auto pair = slot_snapshot(0);
|
||||
platform_on_device_disconnected(&classic);
|
||||
require(negotiated_intervals[left.conn.handle] == 24 &&
|
||||
negotiated_intervals[right.conn.handle] == 24 &&
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6 &&
|
||||
slot_snapshot(0).connection_generation == pair.connection_generation,
|
||||
"Classic departure must preserve reconnect airtime without rebinding the pair");
|
||||
"Classic departure must preserve fast Joy-Con links without rebinding the pair");
|
||||
platform_on_device_connected(&classic);
|
||||
require(platform_on_device_ready(&classic) == UNI_ERROR_SUCCESS,
|
||||
"Classic reconnect must complete");
|
||||
|
|
@ -3073,16 +3072,29 @@ void test_switch2_radio_policy(bool individual) {
|
|||
"a single surviving Switch2 link must return to fast scheduling even with Classic");
|
||||
left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
ready_switch2(left);
|
||||
require(negotiated_intervals[left.conn.handle] == 24 &&
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6,
|
||||
"physical index and handle reuse must retain fast Joy-Con links");
|
||||
negotiated_intervals[left.conn.handle] = 24;
|
||||
negotiated_intervals[right.conn.handle] = 24;
|
||||
now_ms += 1000;
|
||||
process_configuration_timer(&g_configuration_timer);
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 6,
|
||||
"peer-negotiated slow Joy-Con links must reconcile to the fast default");
|
||||
platform_on_device_disconnected(&right);
|
||||
right = switch2_device(1, UNI_SW2_PRO_PID);
|
||||
ready_switch2(right);
|
||||
require(negotiated_intervals[left.conn.handle] == 6 &&
|
||||
negotiated_intervals[right.conn.handle] == 24,
|
||||
"physical index and handle reuse must negotiate mixed intervals on the new connection");
|
||||
"Joy-Con preference must not remove the existing Switch2 Pro coexistence policy");
|
||||
}
|
||||
|
||||
void test_switch2_radio_settling() {
|
||||
start_backend();
|
||||
auto classic = device(2, true, UNI_BT_CONN_PROTOCOL_BR_EDR);
|
||||
auto left = switch2_device(0, UNI_SW2_JOYCON_L_PID);
|
||||
auto right = switch2_device(1, UNI_SW2_JOYCON_R_PID);
|
||||
auto left = switch2_device(0, UNI_SW2_PRO_PID);
|
||||
auto right = switch2_device(1, UNI_SW2_PRO_PID);
|
||||
platform_on_device_connected(&classic);
|
||||
require(platform_on_device_ready(&classic) == UNI_ERROR_SUCCESS,
|
||||
"Classic-first connection must become ready");
|
||||
|
|
|
|||
|
|
@ -1149,6 +1149,79 @@ static void test_immediate_connection_failure_preserves_reconnect_discovery(void
|
|||
assert(!scan_running && !peers[0].used); // Explicit stop must not be overridden.
|
||||
}
|
||||
|
||||
static void test_idle_neutral_output_quiesces_and_resumes(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_JOYCON_L_PID, false);
|
||||
advance(100);
|
||||
assert(peer->rumbles == 3 && !rumble_active(peer));
|
||||
unsigned idle_writes = peer->rumbles;
|
||||
advance(1300);
|
||||
assert(peer->rumbles == idle_writes);
|
||||
// Repeated host stops must not restart an already completed neutral burst.
|
||||
for (unsigned i = 0; i < 20; ++i) {
|
||||
assert(uni_hid_parser_switch2_queue_rumble(&peer->device, 0, 0, 0, now_ms));
|
||||
advance(8);
|
||||
}
|
||||
assert(peer->rumbles == idle_writes);
|
||||
// Quiet output must not delay a future effect or a finite watchdog stop.
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 20, 50, 40, 80);
|
||||
advance(19);
|
||||
assert(peer->rumbles == idle_writes);
|
||||
advance(1);
|
||||
assert(rumble_active(peer));
|
||||
advance(20);
|
||||
assert(peer->rumbles > idle_writes + 1 && rumble_active(peer));
|
||||
advance(30);
|
||||
assert(!rumble_active(peer));
|
||||
advance(100);
|
||||
idle_writes = peer->rumbles;
|
||||
advance(1300);
|
||||
assert(peer->rumbles == idle_writes);
|
||||
uni_switch2_haptics_frame_t frame = native_frame(3, 0, 8);
|
||||
assert(uni_hid_parser_switch2_queue_haptics(&peer->device, &frame, now_ms));
|
||||
advance(1);
|
||||
assert_block(peer, 0, &frame.sides[0]);
|
||||
advance(100);
|
||||
assert_silent(peer, 0);
|
||||
idle_writes = peer->rumbles;
|
||||
advance(1300);
|
||||
assert(peer->rumbles == idle_writes);
|
||||
}
|
||||
|
||||
static void test_neutral_budget_waits_for_success_and_stale_active_completion(void) {
|
||||
struct fixture_peer* peer = ready_peer(UNI_SW2_JOYCON_R_PID, true);
|
||||
next_write_error = BTSTACK_ACL_BUFFERS_FULL;
|
||||
advance(1);
|
||||
assert(peer->rumbles == 0);
|
||||
advance(13);
|
||||
assert(peer->rumbles == 1 && peer->query == QUERY_WRITE);
|
||||
advance(100);
|
||||
assert(peer->rumbles == 1); // Pending ATT writes cannot count as repeated stops.
|
||||
query_done(peer, 0);
|
||||
advance(13);
|
||||
assert(peer->rumbles == 2);
|
||||
query_done(peer, 0);
|
||||
advance(13);
|
||||
assert(peer->rumbles == 3);
|
||||
query_done(peer, 0);
|
||||
advance(100);
|
||||
assert(peer->rumbles == 3 && peer->query == QUERY_NONE);
|
||||
// Reset while an active write borrows its buffer. Its stale completion must
|
||||
// invalidate the earlier idle state and require a fresh stop burst.
|
||||
uni_hid_parser_switch2_play_dual_rumble(&peer->device, 0, UINT16_MAX, 40, 80);
|
||||
advance(1);
|
||||
assert(rumble_active(peer) && peer->query == QUERY_WRITE);
|
||||
uni_hid_parser_switch2_reset_haptics(&peer->device);
|
||||
query_done(peer, 0);
|
||||
for (unsigned i = 0; i < 3; ++i) {
|
||||
advance(i ? 13 : 1);
|
||||
assert(peer->rumbles == 5 + i && peer->query == QUERY_WRITE);
|
||||
assert_silent(peer, 0);
|
||||
query_done(peer, 0);
|
||||
}
|
||||
advance(100);
|
||||
assert(peer->rumbles == 7 && peer->query == QUERY_NONE);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test_connected_callback_rejection();
|
||||
test_advertisement_bounds_and_admission();
|
||||
|
|
@ -1171,6 +1244,8 @@ int main(void) {
|
|||
test_hold_coalescing_requires_identical_samples_and_side_masks();
|
||||
test_expired_history_does_not_starve_fresh_sequences();
|
||||
test_gatt_busy_retries_without_disconnect_or_sequence_loss();
|
||||
test_idle_neutral_output_quiesces_and_resumes();
|
||||
test_neutral_budget_waits_for_success_and_stale_active_completion();
|
||||
reset();
|
||||
puts("Switch2 protocol boundaries, setup failure, pairing, calibration, physical input, motion and rumble passed");
|
||||
return 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue