switch-pico/tests/cyw43_packet_transport_test.c
Joey Yakimowich-Payne 3c2d9fa723 feat: add Set B profiles and low-latency native haptics
Add schema-6 profiles, atomic catalog migration, shortcuts, Shift, configurable Turbo/Burst, macro recording and playback. Promote qualified 300 MHz native DualSense transport to AIO/XInput defaults with bounded bus transactions, credit batching and generation-safe persistent rumble.
2026-09-05 21:56:19 -06:00

409 lines
14 KiB
C

#include <assert.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "cyw43_btbus.h"
#include "cyw43_config.h"
#include "cybt_shared_bus_driver.h"
static uint8_t ring[BTSDIO_FWBUF_SIZE];
static cybt_fw_membuf_index_t indices;
static unsigned lock_depth;
static unsigned enters;
static unsigned exits;
static unsigned wake_polls;
static unsigned delays;
static unsigned snapshots;
static unsigned reads;
static unsigned publications;
static unsigned notifications;
static unsigned fail_read;
static bool fail_wake;
static bool fail_snapshot;
static bool fail_publication;
static bool fail_notification;
static int awake_value;
static bool append_on_header;
static bool expect_panic;
static jmp_buf panic_jump;
void cyw43_thread_enter(void) {
++lock_depth;
++enters;
}
void cyw43_thread_exit(void) {
assert(lock_depth != 0);
--lock_depth;
++exits;
}
void cyw43_delay_ms(uint32_t milliseconds) {
assert(lock_depth != 0);
assert(milliseconds == 1);
++delays;
}
_Noreturn void panic(const char *message, ...) {
if (!expect_panic) {
fprintf(stderr, "unexpected panic: %s\n", message);
abort();
}
assert(strcmp(message, "cyw43 buffer overflow") == 0);
longjmp(panic_jump, 1);
}
int test_cyw43_btbus_init(cyw43_ll_t *self) {
(void)self;
assert(lock_depth != 0);
memset(&indices, 0, sizeof(indices));
return 0;
}
cybt_result_t cybt_set_bt_awake(int value) {
assert(lock_depth != 0);
assert(value == 1);
return fail_wake ? CYBT_ERR_HCI_READ_FAILED : CYBT_SUCCESS;
}
int cybt_awake(void) {
assert(lock_depth != 0);
++wake_polls;
return awake_value;
}
cybt_result_t cybt_get_bt_buf_index(cybt_fw_membuf_index_t *out) {
assert(lock_depth != 0);
++snapshots;
if (fail_snapshot) {
return CYBT_ERR_HCI_READ_FAILED;
}
*out = indices;
return CYBT_SUCCESS;
}
static uint32_t put_packet(uint32_t offset, uint32_t payload_length, uint8_t type) {
uint32_t wire_length = 4 + ((payload_length + 3) & ~3u);
assert(wire_length <= BTSDIO_FWBUF_SIZE);
for (uint32_t byte = 0; byte < wire_length; ++byte) {
uint8_t value = (uint8_t)(byte - 4);
if (byte < 3) {
value = (uint8_t)(payload_length >> (byte * 8));
} else if (byte == 3) {
value = type;
}
ring[(offset + byte) & (BTSDIO_FWBUF_SIZE - 1)] = value;
}
return (offset + wire_length) & (BTSDIO_FWBUF_SIZE - 1);
}
cybt_result_t cybt_mem_read_idx(cybt_addr_idx_t index, uint32_t offset,
uint8_t *buffer, uint32_t length) {
assert(lock_depth != 0);
assert(index == B2H_BUF_ADDR_IDX);
assert(length >= 4 && (length & 3) == 0);
assert((offset & 3) == 0 && offset + length <= BTSDIO_FWBUF_SIZE);
assert(((uintptr_t)buffer & 3) == 0);
++reads;
if (reads == fail_read) {
/* Model a failed SPI transfer that has already modified its destination. */
buffer[0] = 0xEF;
return CYBT_ERR_HCI_READ_FAILED;
}
memcpy(buffer, ring + offset, length);
if (append_on_header && reads == 1) {
indices.bt2host_in_val = put_packet(indices.bt2host_in_val, 4, 2);
}
return CYBT_SUCCESS;
}
cybt_result_t cybt_reg_write_idx(cybt_addr_idx_t index, uint32_t value) {
assert(lock_depth != 0);
assert(index == B2H_BUF_OUT_ADDR_IDX);
assert(value < BTSDIO_FWBUF_SIZE && (value & 3) == 0);
++publications;
if (fail_publication) {
return CYBT_ERR_HCI_READ_FAILED;
}
indices.bt2host_out_val = value;
return CYBT_SUCCESS;
}
cybt_result_t cybt_toggle_bt_intr(void) {
assert(lock_depth != 0);
assert(publications > notifications);
++notifications;
return fail_notification ? CYBT_ERR_HCI_READ_FAILED : CYBT_SUCCESS;
}
static void reset(void) {
lock_depth = 0;
expect_panic = false;
assert(cyw43_btbus_init(NULL) == 0);
memset(ring, 0, sizeof(ring));
enters = exits = wake_polls = delays = snapshots = reads = publications = notifications = 0;
fail_read = 0;
fail_wake = fail_snapshot = fail_publication = fail_notification = false;
append_on_header = false;
awake_value = 1;
}
static void check_unconsumed(uint32_t original_out) {
assert(indices.bt2host_out_val == original_out);
assert(notifications == 0);
assert(lock_depth == 0 && enters == exits);
}
static void check_packet(const uint8_t *buffer, uint32_t length, uint8_t type) {
assert(buffer[0] == (uint8_t)length);
assert(buffer[1] == (uint8_t)(length >> 8));
assert(buffer[2] == (uint8_t)(length >> 16));
assert(buffer[3] == type);
for (uint32_t byte = 0; byte < length; ++byte) {
assert(buffer[4 + byte] == (uint8_t)byte);
}
}
static void test_empty_and_partial(void) {
_Alignas(4) uint8_t buffer[32];
uint32_t size = 123;
reset();
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 0);
assert(snapshots == 1 && reads == 0 && publications == 0);
check_unconsumed(0);
uint32_t end = put_packet(0, 5, 4);
for (uint32_t published = 1; published <= 3; ++published) {
indices.bt2host_in_val = published;
size = 123;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 0);
assert(reads == 0 && publications == 0);
check_unconsumed(0);
}
const uint32_t partial_lengths[] = {4, 8, 9, 11};
for (unsigned i = 0; i < sizeof(partial_lengths) / sizeof(partial_lengths[0]); ++i) {
indices.bt2host_in_val = partial_lengths[i];
size = 123;
unsigned before_reads = reads;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 0);
assert(reads == before_reads + 1 && publications == 0);
check_unconsumed(0);
}
indices.bt2host_in_val = end;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 9);
check_packet(buffer, 5, 4);
assert(indices.bt2host_out_val == end && publications == 1 && notifications == 1);
}
static void test_complete_wrapped_and_full(void) {
_Alignas(4) uint8_t buffer[BTSDIO_FWBUF_SIZE];
const uint32_t starts[] = {0, BTSDIO_FWBUF_SIZE - 4, BTSDIO_FWBUF_SIZE - 8};
for (unsigned i = 0; i < sizeof(starts) / sizeof(starts[0]); ++i) {
reset();
indices.bt2host_out_val = starts[i];
indices.bt2host_in_val = put_packet(starts[i], 9, 2);
uint32_t size = 0;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 13);
check_packet(buffer, 9, 2);
assert(indices.bt2host_out_val == indices.bt2host_in_val);
assert(snapshots == 1 && publications == 1 && notifications == 1);
assert(lock_depth == 0 && enters == exits);
}
reset();
indices.bt2host_in_val = put_packet(0, BTSDIO_FWBUF_SIZE - 8, 2);
uint32_t size = 0;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0);
assert(size == BTSDIO_FWBUF_SIZE - 4);
check_packet(buffer, BTSDIO_FWBUF_SIZE - 8, 2);
assert(indices.bt2host_out_val == indices.bt2host_in_val);
reset();
indices.bt2host_in_val = put_packet(0, 0, 0);
assert(cyw43_btbus_read(buffer, 4, &size) == 0 && size == 4);
assert(reads == 1 && publications == 1 && notifications == 1);
}
static void test_exact_capacity_and_snapshot_growth(void) {
_Alignas(4) uint8_t buffer[32];
uint32_t size = 0;
reset();
memset(buffer, 0xA5, sizeof(buffer));
indices.bt2host_in_val = put_packet(0, 5, 4);
assert(cyw43_btbus_read(buffer, 9, &size) == 0 && size == 9);
check_packet(buffer, 5, 4);
for (unsigned i = 9; i < sizeof(buffer); ++i) {
assert(buffer[i] == 0xA5);
}
reset();
indices.bt2host_in_val = put_packet(0, 5, 4);
uint32_t first_end = indices.bt2host_in_val;
append_on_header = true;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 9);
check_packet(buffer, 5, 4);
assert(indices.bt2host_out_val == first_end && snapshots == 1);
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 8);
check_packet(buffer, 4, 2);
assert(indices.bt2host_out_val == indices.bt2host_in_val);
assert(publications == 2 && notifications == 2);
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 0);
assert(publications == 2 && notifications == 2);
}
static void test_oversized_and_invalid(void) {
_Alignas(4) uint8_t buffer[BTSDIO_FWBUF_SIZE * 2];
uint32_t size = 123;
reset();
indices.bt2host_in_val = put_packet(0, 5, 4);
assert(cyw43_btbus_read(buffer, 8, &size) != 0 && size == 0);
check_unconsumed(0);
assert(reads == 1 && publications == 0);
ring[0] = ring[1] = ring[2] = 0xFF;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
check_unconsumed(0);
reset();
/* This would occupy the reserved word and make full indistinguishable from empty. */
put_packet(0, BTSDIO_FWBUF_SIZE - 4, 2);
indices.bt2host_in_val = 4;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
check_unconsumed(0);
reset();
assert(cyw43_btbus_read(NULL, sizeof(buffer), &size) != 0 && size == 0);
assert(cyw43_btbus_read(buffer, sizeof(buffer), NULL) != 0);
assert(cyw43_btbus_read(buffer + 1, sizeof(buffer) - 1, &size) != 0);
for (uint32_t capacity = 0; capacity < 4; ++capacity) {
assert(cyw43_btbus_read(buffer, capacity, &size) != 0 && size == 0);
}
assert(enters == 0 && snapshots == 0 && publications == 0);
check_unconsumed(0);
for (unsigned field = 0; field < 4; ++field) {
reset();
switch (field) {
case 0: indices.host2bt_in_val = BTSDIO_FWBUF_SIZE; break;
case 1: indices.host2bt_out_val = BTSDIO_FWBUF_SIZE; break;
case 2: indices.bt2host_in_val = BTSDIO_FWBUF_SIZE; break;
case 3: indices.bt2host_out_val = BTSDIO_FWBUF_SIZE; break;
}
uint32_t original_out = indices.bt2host_out_val;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
check_unconsumed(original_out);
assert(reads == 0 && publications == 0);
}
reset();
indices.bt2host_out_val = 1;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
check_unconsumed(1);
reset();
indices.bt2host_in_val = BTSDIO_FWBUF_SIZE - 1;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
check_unconsumed(0);
}
static void test_status_failures_and_retry(void) {
_Alignas(4) uint8_t buffer[32];
uint32_t size = 123;
reset();
fail_wake = true;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
assert(snapshots == 0 && wake_polls == 0);
check_unconsumed(0);
reset();
awake_value = -1;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
assert(snapshots == 0 && wake_polls == 1);
check_unconsumed(0);
reset();
awake_value = 0;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
assert(snapshots == 0 && wake_polls == 301 && delays == 301);
check_unconsumed(0);
reset();
fail_snapshot = true;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
assert(reads == 0 && publications == 0);
check_unconsumed(0);
/* Fail header, first wrapped payload segment, then second payload segment. */
for (unsigned failure = 1; failure <= 3; ++failure) {
reset();
indices.bt2host_out_val = BTSDIO_FWBUF_SIZE - 8;
indices.bt2host_in_val = put_packet(indices.bt2host_out_val, 9, 2);
fail_read = failure;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
assert(publications == 0);
check_unconsumed(BTSDIO_FWBUF_SIZE - 8);
fail_read = 0;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 13);
check_packet(buffer, 9, 2);
assert(indices.bt2host_out_val == indices.bt2host_in_val);
}
reset();
indices.bt2host_in_val = put_packet(0, 5, 4);
fail_read = 3; /* Final padded word when the caller provides exact capacity. */
assert(cyw43_btbus_read(buffer, 9, &size) != 0 && size == 0);
assert(publications == 0);
check_unconsumed(0);
reset();
indices.bt2host_in_val = put_packet(0, 5, 4);
fail_publication = true;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
check_unconsumed(0);
assert(publications == 1);
reset();
indices.bt2host_in_val = put_packet(0, 5, 4);
fail_notification = true;
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) != 0 && size == 0);
/* Notification follows the irreversible commit; rollback would violate ownership. */
assert(indices.bt2host_out_val == indices.bt2host_in_val);
assert(publications == 1 && notifications == 1);
assert(lock_depth == 0 && enters == exits);
}
static void test_overflow_and_controller_reset(void) {
_Alignas(4) uint8_t buffer[32];
uint32_t size = 0;
reset();
uint32_t first_end = put_packet(0, 5, 4);
indices.bt2host_in_val = put_packet(first_end, 8, 2);
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 9);
assert(indices.bt2host_out_val == first_end);
indices.bt2host_in_val -= 4; /* Published unread data disappeared. */
expect_panic = true;
if (setjmp(panic_jump) == 0) {
cyw43_btbus_read(buffer, sizeof(buffer), &size);
assert(false && "overflow must retain the SDK's fatal check");
}
expect_panic = false;
assert(indices.bt2host_out_val == first_end);
assert(publications == 1 && notifications == 1);
lock_depth = 0; /* panic does not return on hardware. */
reset();
first_end = put_packet(0, 5, 4);
indices.bt2host_in_val = put_packet(first_end, 8, 2);
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 9);
assert(cyw43_btbus_init(NULL) == 0);
assert(cyw43_btbus_read(buffer, sizeof(buffer), &size) == 0 && size == 0);
assert(indices.bt2host_out_val == 0 && lock_depth == 0);
}
int main(void) {
test_empty_and_partial();
test_complete_wrapped_and_full();
test_exact_capacity_and_snapshot_growth();
test_oversized_and_invalid();
test_status_failures_and_retry();
test_overflow_and_controller_reset();
puts("cyw43 packet transport regression checks passed");
return 0;
}