fix(native-usb): service masked completions and trace startup stalls

Keep native backend locking IRQ-permitting and service USB hardware from SRAM during BOOTSEL sampling. Add bounded input/control flight recording, root IN and EP0 snapshots, and masked-window handoff coverage. Record PC qualification and the remaining Switch stability limitations.
This commit is contained in:
Joey Yakimowich-Payne 2026-09-14 13:55:35 -06:00
commit d424fe0f4a
12 changed files with 884 additions and 195 deletions

View file

@ -9,6 +9,9 @@
#if defined(SWITCH2_PROBE_HUB) && SWITCH2_PROBE_HUB
extern bool native_hub_select_device(uint8_t address, uint8_t owner, uint32_t cutoff);
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
#include "usb/native_hub/native_hub_trace.h"
#endif
#endif
#if !PICO_RP2350 || defined(__riscv)
@ -30,6 +33,10 @@ extern bool native_hub_select_device(uint8_t address, uint8_t owner, uint32_t cu
#define PID_OUT 0xe1u
#define PID_IN 0x69u
#define PID_SETUP 0x2du
// NRZI SYNC+PID words, with K=2 and J=1 packed into two-bit samples.
#define TOKEN_OUT_SIGNATURE 0xaa66a666u
#define TOKEN_IN_SIGNATURE 0x95a6a666u
#define TOKEN_SETUP_SIGNATURE 0x9a56a666u
#define NO_READER 2u
#define SETUP_SEQUENCE_MASK 0x3fffffffu
#define SETUP_SLOT_SHIFT 30u
@ -175,9 +182,9 @@ static void build_table(routing_table* table,
void probe_router_init(uint32_t system_clock_hz) {
// Explicit SRAM data: Core1 must never fetch flash during durable saves.
token_words[6] = 0xaa66a666u;
token_words[10] = 0x95a6a666u;
token_words[5] = 0x9a56a666u;
token_words[6] = TOKEN_OUT_SIGNATURE;
token_words[10] = TOKEN_IN_SIGNATURE;
token_words[5] = TOKEN_SETUP_SIGNATURE;
const uint8_t addresses[PROBE_ROUTER_SLOTS] = {0u, PROBE_ROUTER_UNASSIGNED,
PROBE_ROUTER_UNASSIGNED};
memset(&counters, 0, sizeof(counters));
@ -295,7 +302,7 @@ static __force_inline bool sample_line(uint32_t* deadline, uint32_t* line) {
}
static __force_inline void route_header(const routing_table* table, uint32_t address,
bool setup, uint32_t initial_address,
uint32_t signature, uint32_t initial_address,
uint32_t cutoff, raw_packet* packet) {
// TinyUSB clears SETUP_REC only AFTER copying the hardware-validated SETUP
// into its event callback. Until then, preserve both address and owner.
@ -306,8 +313,21 @@ static __force_inline void route_header(const routing_table* table, uint32_t add
return;
#if defined(SWITCH2_PROBE_HUB) && SWITCH2_PROBE_HUB
if (atomic_read(&enabled) != 0u) {
if (!native_hub_select_device((uint8_t)address, table->owner[address], cutoff))
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
const uint8_t pid = signature == TOKEN_OUT_SIGNATURE ? PID_OUT :
signature == TOKEN_IN_SIGNATURE ? PID_IN : PID_SETUP;
const bool selected = (pid == PID_OUT || (pid == PID_IN && table->owner[address] == 0))
? native_hub_select_device_traced((uint8_t)address, table->owner[address], cutoff, pid)
: native_hub_select_device((uint8_t)address, table->owner[address], cutoff);
#else
const bool selected = native_hub_select_device((uint8_t)address, table->owner[address], cutoff);
#endif
if (!selected) {
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_note_failed_select((uint8_t)address, table->owner[address], cutoff, pid);
#endif
return;
}
if (initial_address != address) ++packet->retargets;
}
#else
@ -324,7 +344,7 @@ static __force_inline void route_header(const routing_table* table, uint32_t add
// Candidate observations qualify calibration only. Runtime ownership
// comes from the hardware address frozen by SETUP_REC. A missed software
// candidate must not reject a correctly addressed, hardware-accepted SETUP.
if (setup)
if (signature == TOKEN_SETUP_SIGNATURE)
publish_setup(table->owner[address]);
}
@ -438,7 +458,7 @@ edge:
if ((base) + (bit) == 19u && decoder != NULL) { \
uint8_t candidate = early_decoder[word1 & 0xffu]; \
if (candidate < 128u) \
route_header(table, candidate, word0 == 0x9a56a666u, initial_address, \
route_header(table, candidate, word0, initial_address, \
deadline + 11u * FS_BIT_CYCLES, &result); \
} \
} while (0)
@ -465,7 +485,7 @@ edge:
address_wire = (address_wire | (address_wire >> 1u)) & 0x3333u; \
address_wire = (address_wire | (address_wire >> 2u)) & 0x0f0fu; \
address_wire = (address_wire | (address_wire >> 4u)) & 0xffu; \
route_header(table, decoder[address_wire], word0 == 0x9a56a666u, \
route_header(table, decoder[address_wire], word0, \
initial_address, deadline + 7u * FS_BIT_CYCLES, &result); \
if (result.late) { result.count = (base) + (bit) + 1u; goto done; } \
} \

View file

@ -124,6 +124,10 @@ int probe_debug_printf(const char* format, ...) {
if (result <= 0) return result;
const size_t size = (size_t)result < sizeof(message) ? (size_t)result : sizeof(message) - 1;
const uint32_t interrupts = save_and_disable_interrupts();
#if SWITCH2_PROBE_HUB && defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
const uint32_t mask_started = time_us_32();
const uint32_t trace_parent = native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_LOG_COPY);
#endif
if (LOG_CAPACITY - (log_written - log_read) >= size) {
for (size_t i = 0; i < size; ++i)
log_bytes[(log_written + i) % LOG_CAPACITY] = message[i];
@ -132,7 +136,14 @@ int probe_debug_printf(const char* format, ...) {
} else {
log_dropped += (uint32_t)result;
}
#if SWITCH2_PROBE_HUB && defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_trace_phase(trace_parent);
const uint32_t mask_elapsed = time_us_32() - mask_started;
#endif
restore_interrupts(interrupts);
#if SWITCH2_PROBE_HUB && defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_note_log_mask(mask_elapsed, (uint32_t)size, interrupts != 0);
#endif
return result;
}
@ -847,12 +858,24 @@ int main(void) {
uint32_t last_heartbeat = 0;
while (true) {
#if SWITCH2_PROBE_HUB
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_RADIO_POLL);
#endif
probe_controller_input_task();
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_USB_TASK);
#endif
native_hub_task();
#else
tud_task();
#endif
#if SWITCH2_PROBE_HUB && defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_LOG_DRAIN);
#endif
drain_log();
#if SWITCH2_PROBE_HUB && defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_PROTOCOL);
#endif
const uint32_t now = to_ms_since_boot(get_absolute_time());
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
probe_bootsel_task(now);
@ -917,6 +940,9 @@ int main(void) {
}
#endif
}
#if SWITCH2_PROBE_HUB && defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_SLEEP);
#endif
sleep_us(100);
}
}

View file

@ -369,12 +369,16 @@ function(switch2_usb_probe_configure target)
endif()
if(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_FULL_INPUT)
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
pico_set_program_version(${target} "0.78-native-tx-bank-trace")
pico_set_program_version(${target} "0.87-native-enumeration-flight")
else()
pico_set_program_version(${target} "0.78-native-gamepad")
pico_set_program_version(${target} "0.86-native-flash-service")
endif()
elseif(SWITCH2_PROBE_HUB)
pico_set_program_version(${target} "0.78-native-hub-tx-bank")
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
pico_set_program_version(${target} "0.87-native-hub-enumeration-flight")
else()
pico_set_program_version(${target} "0.86-native-hub-flash-service")
endif()
elseif(SWITCH2_PROBE_JOIN_CHORD_GATE)
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
pico_set_program_version(${target} "0.37-pair-chord-trace")