fix(native-usb): isolate two-pair transport and hand off read status in IRQ
This commit is contained in:
parent
c26ea25f93
commit
9f8678af96
51 changed files with 8159 additions and 815 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -38,14 +38,20 @@ extern bool native_hub_select_device(uint8_t address, uint8_t owner, uint32_t cu
|
|||
#define TOKEN_IN_SIGNATURE 0x95a6a666u
|
||||
#define TOKEN_SETUP_SIGNATURE 0x9a56a666u
|
||||
#define NO_READER 2u
|
||||
#define SETUP_SEQUENCE_MASK 0x3fffffffu
|
||||
#define SETUP_SLOT_SHIFT 30u
|
||||
#define SETUP_INVALID (3u << SETUP_SLOT_SHIFT)
|
||||
#if PROBE_ROUTER_SLOTS > 3u
|
||||
#define SETUP_SLOT_BITS 3u
|
||||
#else
|
||||
#define SETUP_SLOT_BITS 2u
|
||||
#endif
|
||||
#define SETUP_SLOT_SHIFT (32u - SETUP_SLOT_BITS)
|
||||
#define SETUP_SEQUENCE_MASK ((1u << SETUP_SLOT_SHIFT) - 1u)
|
||||
#define SETUP_INVALID_OWNER ((1u << SETUP_SLOT_BITS) - 1u)
|
||||
#define SETUP_INVALID (SETUP_INVALID_OWNER << SETUP_SLOT_SHIFT)
|
||||
#define RAW_BITS 40u
|
||||
|
||||
_Static_assert(SIO_GPIO_HI_IN_USB_DP_BITS == (1u << 24), "SIO USB DP layout");
|
||||
_Static_assert(SIO_GPIO_HI_IN_USB_DM_BITS == (1u << 25), "SIO USB DM layout");
|
||||
_Static_assert(PROBE_ROUTER_SLOTS == 3u, "Packed setup owner has three slots");
|
||||
_Static_assert(PROBE_ROUTER_SLOTS <= SETUP_INVALID_OWNER, "Packed setup owner must reserve an invalid value");
|
||||
|
||||
typedef struct {
|
||||
uint8_t owner[128];
|
||||
|
|
@ -103,7 +109,7 @@ static __force_inline void invalidate_setup(void) {
|
|||
|
||||
static __force_inline void publish_setup(uint8_t slot) {
|
||||
const uint32_t sequence = (atomic_read(&setup_publication) + 1u) & SETUP_SEQUENCE_MASK;
|
||||
const uint32_t owner = slot < PROBE_ROUTER_SLOTS ? slot : 3u;
|
||||
const uint32_t owner = slot < PROBE_ROUTER_SLOTS ? slot : SETUP_INVALID_OWNER;
|
||||
__atomic_store_n(&setup_publication, sequence | (owner << SETUP_SLOT_SHIFT),
|
||||
__ATOMIC_RELEASE);
|
||||
}
|
||||
|
|
@ -185,8 +191,9 @@ void probe_router_init(uint32_t system_clock_hz) {
|
|||
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};
|
||||
uint8_t addresses[PROBE_ROUTER_SLOTS];
|
||||
memset(addresses, PROBE_ROUTER_UNASSIGNED, sizeof(addresses));
|
||||
addresses[0] = 0u;
|
||||
memset(&counters, 0, sizeof(counters));
|
||||
published_generation = 0u;
|
||||
reader_index = NO_READER;
|
||||
|
|
@ -313,14 +320,12 @@ 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) {
|
||||
const bool selected = native_hub_select_device((uint8_t)address, table->owner[address], cutoff);
|
||||
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
// Keep diagnostic PID classification behind the address-critical call.
|
||||
__asm volatile ("" : "+r"(signature) : : "memory");
|
||||
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)
|
||||
|
|
@ -328,6 +333,9 @@ static __force_inline void route_header(const routing_table* table, uint32_t add
|
|||
#endif
|
||||
return;
|
||||
}
|
||||
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
native_hub_note_selected_token((uint8_t)address, table->owner[address], cutoff, pid);
|
||||
#endif
|
||||
if (initial_address != address) ++packet->retargets;
|
||||
}
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(SWITCH2_PROBE_HUB) && SWITCH2_PROBE_HUB && defined(PROBE_CONTROLLER_COUNT)
|
||||
#define PROBE_ROUTER_SLOTS (PROBE_CONTROLLER_COUNT + 1u)
|
||||
#else
|
||||
#define PROBE_ROUTER_SLOTS 3u
|
||||
#endif
|
||||
#define PROBE_ROUTER_UNASSIGNED 0xffu
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,14 @@ project(switch2_usb_probe C CXX ASM)
|
|||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/probe_build.cmake)
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT)
|
||||
# Match the proven hub clock/flash timing without enabling the radio.
|
||||
add_compile_definitions(
|
||||
SWITCH_PICO_SYS_CLOCK_MHZ=240 SWITCH_PICO_OVERCLOCK_MV=1300
|
||||
PICO_FLASH_SPI_CLKDIV=4 PICO_EMBED_XIP_SETUP=1
|
||||
PICO_STACK_SIZE=16384 PICO_CORE1_STACK_SIZE=4096
|
||||
CYW43_PIO_CLOCK_DIV_INT=0 CYW43_PIO_CLOCK_DIV_FRAC8=0)
|
||||
endif()
|
||||
pico_sdk_init()
|
||||
add_executable(switch2-usb-probe
|
||||
../../src/firmware/configuration/configuration_storage.cpp
|
||||
|
|
@ -12,4 +20,11 @@ add_executable(switch2-usb-probe
|
|||
target_compile_definitions(switch2-usb-probe PRIVATE
|
||||
PICO_FLASH_ASSUME_CORE1_SAFE=1 PICO_FLASH_ASSERT_ON_UNSAFE=0)
|
||||
switch2_usb_probe_configure(switch2-usb-probe)
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT)
|
||||
target_sources(switch2-usb-probe PRIVATE
|
||||
bootsel.cpp
|
||||
../../src/firmware/usb/usb_configuration_management.cpp
|
||||
../../src/firmware/platform/pico/system_clock.cpp)
|
||||
target_link_libraries(switch2-usb-probe PRIVATE hardware_adc hardware_vreg hardware_powman)
|
||||
endif()
|
||||
pico_add_extra_outputs(switch2-usb-probe)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ struct BootselTransfer {
|
|||
bool pending;
|
||||
bool validated;
|
||||
};
|
||||
// Control state is independent even when the two children enumerate together.
|
||||
// Each root/child control transfer owns its validation state independently.
|
||||
BootselTransfer bootsel_transfers[PROBE_CONTROLLER_COUNT + 1];
|
||||
bool bootsel_delay_started;
|
||||
uint32_t bootsel_deadline_ms;
|
||||
|
|
@ -41,7 +41,7 @@ bool probe_management_vendor_control(uint8_t rhport, uint8_t stage,
|
|||
request->bRequest != static_cast<uint8_t>(Operation::kBootselReboot) ||
|
||||
request->wValue != kRequestValue || request->wIndex != kRequestIndex ||
|
||||
request->wLength != kRequestHeaderSize) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#if SWITCH2_PROBE_HUB && !SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
return rhport == 0 &&
|
||||
usb_configuration_management_vendor_control(rhport, stage, request);
|
||||
#else
|
||||
|
|
@ -53,7 +53,7 @@ bool probe_management_vendor_control(uint8_t rhport, uint8_t stage,
|
|||
// Any short OUT leaves nonzero reserved/CRC bytes and fails decoding.
|
||||
memset(transfer.envelope, 0xff, sizeof(transfer.envelope));
|
||||
transfer.pending = native_hub_control_xfer(
|
||||
rhport, request, transfer.envelope, sizeof(transfer.envelope));
|
||||
rhport, request, transfer.envelope, sizeof(transfer.envelope), false);
|
||||
return transfer.pending;
|
||||
}
|
||||
if (stage == CONTROL_STAGE_DATA) {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Core 0: native root management plus private BOOTSEL on root and children.
|
||||
// Non-hub probes retain their private BOOTSEL-only management surface.
|
||||
// Core 0: private BOOTSEL on native root/children, plus full root management
|
||||
// only outside the neutral experiment. Non-hub probes remain BOOTSEL-only.
|
||||
bool probe_management_vendor_control(uint8_t rhport, uint8_t stage,
|
||||
const tusb_control_request_t* request);
|
||||
// Core 0: service the existing reboot delay only after a validated status ACK.
|
||||
|
|
|
|||
|
|
@ -62,5 +62,9 @@ static const uint8_t probe_hid_report_descriptors[PROBE_CONTROLLER_COUNT][100] =
|
|||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
PROBE_HID_DESCRIPTOR(0x07u),
|
||||
#endif
|
||||
#if SWITCH2_PROBE_HUB && PROBE_CONTROLLER_COUNT == 4
|
||||
PROBE_HID_DESCRIPTOR(0x08u),
|
||||
PROBE_HID_DESCRIPTOR(0x07u),
|
||||
#endif
|
||||
};
|
||||
#undef PROBE_HID_DESCRIPTOR
|
||||
|
|
|
|||
|
|
@ -7,14 +7,23 @@
|
|||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "model.h"
|
||||
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
#if defined(SWITCH_PICO_SWITCH2_USB_BRIDGE) || SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
#include "bootsel.h"
|
||||
#endif
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
#include "controller_input.h"
|
||||
#else
|
||||
#elif !SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
#include "platform/pico/bootsel_button_sample.h"
|
||||
#include "button_test.h"
|
||||
#endif
|
||||
#if SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
#include "platform/pico/system_clock.h"
|
||||
#if !defined(SWITCH2_PROBE_USB_INIT) || !defined(SWITCH2_PROBE_MEMORY)
|
||||
#error "Neutral hub requires native USB protocol initialization and captured stick calibration"
|
||||
#endif
|
||||
#endif
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/sync.h"
|
||||
#include "hardware/uart.h"
|
||||
|
|
@ -75,7 +84,7 @@ typedef struct {
|
|||
#ifdef SWITCH2_PROBE_TRACE_NATIVE_INPUT
|
||||
uint32_t last_native_trace_ms;
|
||||
#endif
|
||||
#else
|
||||
#elif !SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
probe_button_state button_test;
|
||||
uint32_t last_button_ms;
|
||||
bool button_sample_error;
|
||||
|
|
@ -116,6 +125,13 @@ static void gate_join_shoulders(uint8_t instance, uint8_t report_id,
|
|||
#endif
|
||||
|
||||
int probe_debug_printf(const char* format, ...) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
// Native-hub producers and the UART consumer all run on Core0 foreground.
|
||||
// Neither USB IRQ nor the Core1 observer accesses this ring. Masking IRQs
|
||||
// across a message copy prevents completion service and can lose the next
|
||||
// address's token; do not turn a diagnostic into USB backpressure.
|
||||
hard_assert(get_core_num() == 0 && __get_current_exception() == 0);
|
||||
#endif
|
||||
char message[512];
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
|
|
@ -123,39 +139,45 @@ int probe_debug_printf(const char* format, ...) {
|
|||
va_end(args);
|
||||
if (result <= 0) return result;
|
||||
const size_t size = (size_t)result < sizeof(message) ? (size_t)result : sizeof(message) - 1;
|
||||
#if !SWITCH2_PROBE_HUB
|
||||
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();
|
||||
#elif defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
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];
|
||||
const bool queued = LOG_CAPACITY - (log_written - log_read) >= size;
|
||||
if (queued) {
|
||||
const size_t offset = log_written % LOG_CAPACITY;
|
||||
const size_t first = size < LOG_CAPACITY - offset ? size : LOG_CAPACITY - offset;
|
||||
memcpy(log_bytes + offset, message, first);
|
||||
memcpy(log_bytes, message + first, size - first);
|
||||
log_written += (uint32_t)size;
|
||||
log_dropped += (uint32_t)result - (uint32_t)size;
|
||||
} 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
|
||||
#if !SWITCH2_PROBE_HUB
|
||||
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);
|
||||
#elif defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
native_hub_trace_phase(trace_parent);
|
||||
#endif
|
||||
return result;
|
||||
return queued ? result : -1;
|
||||
}
|
||||
|
||||
static void drain_log(void) {
|
||||
while (uart_is_writable(uart0)) {
|
||||
#if !SWITCH2_PROBE_HUB
|
||||
const uint32_t interrupts = save_and_disable_interrupts();
|
||||
#endif
|
||||
if (log_read == log_written) {
|
||||
#if !SWITCH2_PROBE_HUB
|
||||
restore_interrupts(interrupts);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
const char value = log_bytes[log_read++ % LOG_CAPACITY];
|
||||
#if !SWITCH2_PROBE_HUB
|
||||
restore_interrupts(interrupts);
|
||||
#endif
|
||||
uart_putc_raw(uart0, value);
|
||||
}
|
||||
}
|
||||
|
|
@ -427,7 +449,7 @@ static void consume_bulk_packet(probe_usb_controller* controller, const uint8_t*
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
#if !defined(SWITCH_PICO_SWITCH2_USB_BRIDGE) && !SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
static void button_test_task(probe_usb_controller* controller, uint32_t now) {
|
||||
probe_protocol_state* protocol = &controller->protocol;
|
||||
const bool ready = probe_transport_mounted(controller->instance) &&
|
||||
|
|
@ -679,7 +701,7 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage,
|
|||
const tusb_control_request_t* request) {
|
||||
if (stage == CONTROL_STAGE_SETUP)
|
||||
log_packet("VENDOR_CONTROL", rhport, 0, (const uint8_t*)request, sizeof(*request));
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
#if defined(SWITCH_PICO_SWITCH2_USB_BRIDGE) || SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
if (probe_management_vendor_control(rhport, stage, request))
|
||||
return true;
|
||||
#endif
|
||||
|
|
@ -776,16 +798,30 @@ int main(void) {
|
|||
#endif
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
probe_controller_input_clock_init();
|
||||
#elif SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
system_clock_initialize();
|
||||
#endif
|
||||
stdio_init_all();
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
probe_debug_printf("\n[PROBE] " PROBE_JOYCON_PRODUCT " Bluetooth-to-USB controller/native mouse bridge\n");
|
||||
#elif SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
probe_debug_printf("\n[PROBE] Neutral native USB hub transport-only experiment: %u pair(s), %u children\n",
|
||||
PROBE_CONTROLLER_COUNT / 2, PROBE_CONTROLLER_COUNT);
|
||||
#else
|
||||
probe_debug_printf("\n[PROBE] " PROBE_JOYCON_PRODUCT " USB enumeration recorder\n");
|
||||
#endif
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#if PROBE_CONTROLLER_COUNT == 4
|
||||
probe_debug_printf("[PROBE] NATIVE_HUB: two pairs in A_R/A_L/B_R/B_L order; each HID0/vendor1 EP1/2; no shoulder gate\n");
|
||||
for (uint8_t instance = 0; instance < PROBE_CONTROLLER_COUNT; ++instance)
|
||||
probe_debug_printf("[PROBE] CHILD slot=%u pair=%c side=%c pid=%04x report=%02x\n",
|
||||
instance + 1, 'A' + instance / 2,
|
||||
probe_model_is_left(instance) ? 'L' : 'R',
|
||||
probe_model_pid(instance), probe_model_report_id(instance));
|
||||
#else
|
||||
probe_debug_printf("[PROBE] NATIVE_HUB: device1 right PID2066, device2 left PID2067; each HID0/vendor1 EP1/2; no shoulder gate\n");
|
||||
#endif
|
||||
#endif
|
||||
#if SWITCH2_PROBE_COMPOSITE
|
||||
#ifdef SWITCH2_PROBE_JOIN_CHORD_GATE
|
||||
probe_debug_printf("[PROBE] JOIN_CHORD_GATE enabled: physical L+R required; no synthesized presses or USB initialization\n");
|
||||
|
|
@ -804,7 +840,8 @@ int main(void) {
|
|||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; selected Wii IR/MotionPlus source enabled\n");
|
||||
#elif SWITCH2_BRIDGE_FULL_INPUT
|
||||
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; one supported gamepad feeds the native R/L pair\n");
|
||||
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; up to %u supported gamepad(s) feed %u native R/L pair(s)\n",
|
||||
PROBE_CONTROLLER_COUNT / 2, PROBE_CONTROLLER_COUNT / 2);
|
||||
#else
|
||||
probe_debug_printf("[PROBE] UART0 GP0=TX, 115200 8N1; %u selected Joy-Con Bluetooth source(s)\n",
|
||||
PROBE_CONTROLLER_COUNT);
|
||||
|
|
@ -833,12 +870,15 @@ int main(void) {
|
|||
probe_debug_printf("[PROBE] Wii IR drives native mouse movement; buttons retain profile mapping; MotionPlus bias learns in background\n");
|
||||
probe_debug_printf("[PROBE] Hold BOOTSEL2s for pairing; Wii cue feedback uses bounded ERM patterns, not HD audio waveforms\n");
|
||||
#elif SWITCH2_BRIDGE_FULL_INPUT
|
||||
probe_debug_printf("[PROBE] Full gamepad controls on R/L; IMU mask=%u; Wii bias learns without startup settling\n",
|
||||
(unsigned)SWITCH2_BRIDGE_IMU_TARGET_MASK);
|
||||
probe_debug_printf("[PROBE] Full gamepad controls on %u pair(s); IMU side mask=%u; Wii bias learns without startup settling\n",
|
||||
PROBE_CONTROLLER_COUNT / 2, (unsigned)SWITCH2_BRIDGE_IMU_TARGET_MASK);
|
||||
probe_debug_printf("[PROBE] Hold BOOTSEL 2s for Bluetooth pairing (never clears pairings); cues use source capabilities\n");
|
||||
#else
|
||||
probe_debug_printf("[PROBE] Live Joy-Con buttons/stick/native mouse; hold BOOTSEL 2s for Bluetooth pairing (never clears pairings)\n");
|
||||
#endif
|
||||
#elif SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
probe_debug_printf("[PROBE] Neutral captured-calibration reports only; physical BOOTSEL input disabled; no Bluetooth, mouse/IMU samples or motor cue acknowledgements\n");
|
||||
probe_debug_printf("[PROBE] Private software BOOTSEL on root/children enabled; profile/configuration management disabled\n");
|
||||
#else
|
||||
probe_debug_printf("[PROBE] Manual input test: hold BOOTSEL for SL+SR, release for neutral; no controller forwarding\n");
|
||||
#endif
|
||||
|
|
@ -858,10 +898,12 @@ int main(void) {
|
|||
uint32_t last_heartbeat = 0;
|
||||
while (true) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_RADIO_POLL);
|
||||
#endif
|
||||
probe_controller_input_task();
|
||||
#endif
|
||||
#if defined(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
native_hub_trace_phase(NATIVE_HUB_TRACE_PHASE_USB_TASK);
|
||||
#endif
|
||||
|
|
@ -877,7 +919,7 @@ int main(void) {
|
|||
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
|
||||
#if defined(SWITCH_PICO_SWITCH2_USB_BRIDGE) || SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
probe_bootsel_task(now);
|
||||
#endif
|
||||
#ifdef SWITCH2_PROBE_USB_INIT
|
||||
|
|
@ -888,7 +930,7 @@ int main(void) {
|
|||
for (uint8_t instance = 0; instance < PROBE_CONTROLLER_COUNT; ++instance) {
|
||||
#ifdef SWITCH_PICO_SWITCH2_USB_BRIDGE
|
||||
controller_input_task(&controllers[instance], now);
|
||||
#else
|
||||
#elif !SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
button_test_task(&controllers[instance], now);
|
||||
#endif
|
||||
#if !defined(SWITCH2_PROBE_JOIN_CHORD_GATE) || SWITCH2_PROBE_HUB
|
||||
|
|
|
|||
|
|
@ -23,6 +23,18 @@
|
|||
#error "Native hub and composite USB backends are mutually exclusive"
|
||||
#endif
|
||||
|
||||
#ifndef SWITCH2_PROBE_NEUTRAL_INPUT
|
||||
#define SWITCH2_PROBE_NEUTRAL_INPUT 0
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_NEUTRAL_INPUT != 0 && SWITCH2_PROBE_NEUTRAL_INPUT != 1
|
||||
#error "SWITCH2_PROBE_NEUTRAL_INPUT must be 0 or 1"
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_NEUTRAL_INPUT && (!SWITCH2_PROBE_HUB || defined(SWITCH_PICO_SWITCH2_USB_BRIDGE))
|
||||
#error "Neutral input is only supported by the standalone native hub"
|
||||
#endif
|
||||
|
||||
#ifndef SWITCH2_PROBE_JOYCON_LEFT
|
||||
#define SWITCH2_PROBE_JOYCON_LEFT 0
|
||||
#endif
|
||||
|
|
@ -33,13 +45,20 @@
|
|||
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
#if SWITCH2_PROBE_JOYCON_LEFT
|
||||
#error "Dual-controller primary must be Joy-Con 2 (R)"
|
||||
#error "Multi-controller primary must be Joy-Con 2 (R)"
|
||||
#endif
|
||||
#ifndef PROBE_CONTROLLER_COUNT
|
||||
#define PROBE_CONTROLLER_COUNT 2
|
||||
#endif
|
||||
#if PROBE_CONTROLLER_COUNT != 2
|
||||
#error "Dual-controller output requires two controller instances"
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#if PROBE_CONTROLLER_COUNT != 2 && PROBE_CONTROLLER_COUNT != 4
|
||||
#error "Native hub requires two or four controller instances"
|
||||
#endif
|
||||
#if PROBE_CONTROLLER_COUNT == 4 && !SWITCH2_PROBE_NEUTRAL_INPUT && !SWITCH2_BRIDGE_FULL_INPUT
|
||||
#error "Two-pair hub requires full-gamepad input or explicit neutral transport"
|
||||
#endif
|
||||
#elif PROBE_CONTROLLER_COUNT != 2
|
||||
#error "Composite output requires two controller instances"
|
||||
#endif
|
||||
#else
|
||||
#ifndef PROBE_CONTROLLER_COUNT
|
||||
|
|
@ -66,11 +85,11 @@
|
|||
#define PROBE_IMU_DATA_OFFSET 16u
|
||||
#endif
|
||||
|
||||
// Instance zero is the standalone model or the dual-controller right function.
|
||||
// In composite and native hub modes, instance one is the independent left side.
|
||||
// Instance zero is the standalone model or the first pair's right function.
|
||||
// Composite/hub instances alternate right/left, with later pairs following.
|
||||
static inline bool probe_model_is_left(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
return instance == 1;
|
||||
return (instance & 1u) != 0;
|
||||
#else
|
||||
(void)instance;
|
||||
return SWITCH2_PROBE_JOYCON_LEFT != 0;
|
||||
|
|
|
|||
|
|
@ -14,11 +14,15 @@
|
|||
#if !SWITCH2_PROBE_HUB || SWITCH2_BRIDGE_WII_INPUT
|
||||
#error "A full gamepad source requires the native R/L USB hub"
|
||||
#endif
|
||||
static_assert(PROBE_CONTROLLER_COUNT == 2);
|
||||
static_assert(PROBE_CONTROLLER_COUNT == 2 || PROBE_CONTROLLER_COUNT == 4);
|
||||
static_assert(BLUEPAD32_NATIVE_PAIR_COUNT == PROBE_CONTROLLER_COUNT / 2);
|
||||
extern "C" int probe_debug_printf(const char* format, ...);
|
||||
#ifndef SWITCH2_BRIDGE_IMU_TARGET_MASK
|
||||
#define SWITCH2_BRIDGE_IMU_TARGET_MASK 3
|
||||
#endif
|
||||
#ifndef SWITCH2_BRIDGE_SECOND_SOURCE_AUTO
|
||||
#define SWITCH2_BRIDGE_SECOND_SOURCE_AUTO 1
|
||||
#endif
|
||||
static_assert(SWITCH2_BRIDGE_IMU_TARGET_MASK >= 1 && SWITCH2_BRIDGE_IMU_TARGET_MASK <= 3);
|
||||
|
||||
namespace {
|
||||
|
|
@ -29,6 +33,10 @@ constexpr uint32_t kOutputDeadlineUs = 100000;
|
|||
constexpr uint8_t kSourceAddress[] = {SWITCH2_BRIDGE_SOURCE_ADDRESS_BYTES};
|
||||
static_assert(sizeof(kSourceAddress) == 6);
|
||||
#endif
|
||||
#if PROBE_CONTROLLER_COUNT == 4 && !SWITCH2_BRIDGE_SECOND_SOURCE_AUTO
|
||||
constexpr uint8_t kSecondSourceAddress[] = {SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS_BYTES};
|
||||
static_assert(sizeof(kSecondSourceAddress) == 6);
|
||||
#endif
|
||||
|
||||
struct Child {
|
||||
bool enabled = false;
|
||||
|
|
@ -51,15 +59,18 @@ struct Child {
|
|||
uint32_t committed_ticks = 0;
|
||||
};
|
||||
Child g_children[PROBE_CONTROLLER_COUNT];
|
||||
Bluepad32NativeGamepadSnapshot g_source;
|
||||
ControllerProfileTransformResult g_mapped;
|
||||
ProbeNativeMotion g_motion;
|
||||
bool g_active;
|
||||
bool g_evaluated;
|
||||
uint32_t g_evaluated_ms;
|
||||
uint32_t g_profile_generation;
|
||||
struct Pair {
|
||||
Bluepad32NativeGamepadSnapshot source{};
|
||||
ControllerProfileTransformResult mapped{};
|
||||
ProbeNativeMotion motion;
|
||||
bool active = false;
|
||||
bool evaluated = false;
|
||||
uint32_t evaluated_ms = 0;
|
||||
uint32_t profile_generation = 0;
|
||||
int sensor_status = -1;
|
||||
};
|
||||
Pair g_pairs[BLUEPAD32_NATIVE_PAIR_COUNT];
|
||||
uint32_t g_report_token;
|
||||
int g_sensor_status = -1;
|
||||
bool g_clock_started;
|
||||
uint32_t g_clock_us;
|
||||
uint32_t g_clock_ticks;
|
||||
|
|
@ -111,11 +122,12 @@ int16_t negate_axis(int16_t value) {
|
|||
return value == INT16_MIN ? INT16_MAX : static_cast<int16_t>(-value);
|
||||
}
|
||||
|
||||
void native_motion_axes(const int32_t source[3], float scale, float output[3]) {
|
||||
void native_motion_axes(ControllerProfileNativeJoyconLayout layout, const int32_t source[3],
|
||||
float scale, float output[3]) {
|
||||
// Undo rotate_solo_joycon's horizontal SDL normalization, then apply the
|
||||
// existing upright native mount [X,-Z,Y]. Rotate accel and gyro together.
|
||||
output[1] = -static_cast<float>(source[2]) * scale;
|
||||
switch (g_mapped.native_joycon_layout) {
|
||||
switch (layout) {
|
||||
case ControllerProfileNativeJoyconLayout::kLeftSolo:
|
||||
output[0] = static_cast<float>(source[1]) * scale;
|
||||
output[2] = -static_cast<float>(source[0]) * scale;
|
||||
|
|
@ -133,11 +145,12 @@ void native_motion_axes(const int32_t source[3], float scale, float output[3]) {
|
|||
|
||||
void pack_controls(uint8_t instance) {
|
||||
Child& child = g_children[instance];
|
||||
const Pair& pair = g_pairs[instance / 2];
|
||||
child.input = {};
|
||||
child.input.serial = g_source.state_generation;
|
||||
if (!g_active || !child.calibrated) return;
|
||||
child.input.serial = pair.source.state_generation;
|
||||
if (!pair.active || !child.calibrated) return;
|
||||
const bool left = probe_model_is_left(instance);
|
||||
const auto layout = g_mapped.native_joycon_layout;
|
||||
const auto layout = pair.mapped.native_joycon_layout;
|
||||
const bool solo = layout != ControllerProfileNativeJoyconLayout::kPaired;
|
||||
// Leave both USB identities in place. The existing inactive-input protocol
|
||||
// path emits neutral reports for the unselected child.
|
||||
|
|
@ -145,7 +158,7 @@ void pack_controls(uint8_t instance) {
|
|||
child.input.active = true;
|
||||
child.input.native_status = 0x30; // Host feature status is gated per model in main.
|
||||
child.input.mouse_surface = 0xff; // No optical sensor, clicks, or invented movement.
|
||||
const ControllerState& state = g_mapped.state;
|
||||
const ControllerState& state = pair.mapped.state;
|
||||
if (left) {
|
||||
child.input.buttons[0] = static_cast<uint8_t>(
|
||||
((solo ? state.button_east : state.dpad_down) ? 0x01 : 0) |
|
||||
|
|
@ -153,7 +166,7 @@ void pack_controls(uint8_t instance) {
|
|||
((solo ? state.button_south : state.dpad_left) ? 0x04 : 0) |
|
||||
((solo ? state.button_west : state.dpad_up) ? 0x08 : 0) |
|
||||
(state.button_left_shoulder ? 0x10 : 0) |
|
||||
(state.left_trigger != 0 && state.left_trigger >= g_mapped.left_trigger_digital_threshold ? 0x20 : 0) |
|
||||
(state.left_trigger != 0 && state.left_trigger >= pair.mapped.left_trigger_digital_threshold ? 0x20 : 0) |
|
||||
(state.button_select ? 0x40 : 0) | (state.button_left_stick ? 0x80 : 0));
|
||||
child.input.buttons[1] = static_cast<uint8_t>(
|
||||
(state.button_capture ? 0x01 : 0) |
|
||||
|
|
@ -166,7 +179,7 @@ void pack_controls(uint8_t instance) {
|
|||
((solo ? state.button_north : state.button_west) ? 0x04 : 0) |
|
||||
((solo ? state.button_east : state.button_north) ? 0x08 : 0) |
|
||||
(state.button_right_shoulder ? 0x10 : 0) |
|
||||
(state.right_trigger != 0 && state.right_trigger >= g_mapped.right_trigger_digital_threshold ? 0x20 : 0) |
|
||||
(state.right_trigger != 0 && state.right_trigger >= pair.mapped.right_trigger_digital_threshold ? 0x20 : 0) |
|
||||
(state.button_start ? 0x40 : 0) |
|
||||
((solo ? state.button_left_stick : state.button_right_stick) ? 0x80 : 0));
|
||||
child.input.buttons[1] = static_cast<uint8_t>(
|
||||
|
|
@ -189,66 +202,84 @@ void pack_controls(uint8_t instance) {
|
|||
child.input.stick[2] = static_cast<uint8_t>(y >> 4);
|
||||
}
|
||||
|
||||
void lose_source(uint32_t now_ms) {
|
||||
if (g_active) {
|
||||
Bluepad32SlotSnapshot inactive{};
|
||||
(void)controller_profile_runtime_transform(g_source.slot, inactive, now_ms, AdapterUsbMode::kSwitch);
|
||||
g_motion.reset();
|
||||
for (uint8_t i = 0; i < PROBE_CONTROLLER_COUNT; ++i) {
|
||||
discard_output(g_children[i]);
|
||||
bluepad32_input_backend_native_sample_cancel(i);
|
||||
}
|
||||
g_sensor_status = -1;
|
||||
void reset_pair_output(uint8_t pair_index) {
|
||||
Pair& pair = g_pairs[pair_index];
|
||||
pair.motion.reset();
|
||||
pair.sensor_status = -1;
|
||||
for (uint8_t instance = pair_index * 2; instance < pair_index * 2 + 2; ++instance) {
|
||||
discard_output(g_children[instance]);
|
||||
bluepad32_input_backend_native_sample_cancel(instance);
|
||||
}
|
||||
g_active = false;
|
||||
for (Child& child : g_children) child.input = {};
|
||||
}
|
||||
|
||||
void refresh(uint32_t now_ms) {
|
||||
void lose_source(uint8_t pair_index, uint32_t now_ms) {
|
||||
Pair& pair = g_pairs[pair_index];
|
||||
if (pair.active) {
|
||||
// A physical slot may already belong to the other pair by the time
|
||||
// this pair observes its loss. Never clear that source's slot-local
|
||||
// macro/Shift state; its new connection epoch retired our old state.
|
||||
bool slot_reassigned = false;
|
||||
for (uint8_t other = 0; other < BLUEPAD32_NATIVE_PAIR_COUNT; ++other) {
|
||||
if (other == pair_index) continue;
|
||||
Bluepad32NativeGamepadSnapshot current;
|
||||
bluepad32_input_backend_native_snapshot(other, ¤t);
|
||||
if (current.controller.active && current.slot == pair.source.slot) {
|
||||
slot_reassigned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!slot_reassigned) {
|
||||
Bluepad32SlotSnapshot inactive{};
|
||||
(void)controller_profile_runtime_transform(pair.source.slot, inactive, now_ms, AdapterUsbMode::kSwitch);
|
||||
}
|
||||
reset_pair_output(pair_index);
|
||||
}
|
||||
pair.active = false;
|
||||
pair.evaluated = false;
|
||||
for (uint8_t instance = pair_index * 2; instance < pair_index * 2 + 2; ++instance)
|
||||
g_children[instance].input = {};
|
||||
}
|
||||
|
||||
void refresh(uint8_t pair_index, uint32_t now_ms) {
|
||||
Pair& pair = g_pairs[pair_index];
|
||||
Bluepad32NativeGamepadSnapshot source;
|
||||
bluepad32_input_backend_native_snapshot(&source);
|
||||
bluepad32_input_backend_native_snapshot(pair_index, &source);
|
||||
// Snapshot first: source receipt timestamps must not be ahead of this clock.
|
||||
const uint32_t now_us = time_us_32();
|
||||
advance_clock(now_us);
|
||||
if (!source.controller.active || source.slot >= BLUEPAD32_INPUT_BACKEND_SLOT_COUNT ||
|
||||
now_us - source.received_us >= kInputDeadlineUs) {
|
||||
lose_source(now_ms);
|
||||
g_source = source;
|
||||
g_evaluated = false;
|
||||
lose_source(pair_index, now_ms);
|
||||
pair.source = source;
|
||||
return;
|
||||
}
|
||||
const bool changed_connection = !g_active || source.slot != g_source.slot ||
|
||||
source.controller.connection_generation != g_source.controller.connection_generation;
|
||||
const bool changed_connection = !pair.active || source.slot != pair.source.slot ||
|
||||
source.controller.connection_generation != pair.source.controller.connection_generation;
|
||||
const uint32_t profile_generation = profile_service_database_generation();
|
||||
// Both polls and both peeks in a paired output round share one profile and
|
||||
// motion evaluation. A real publication in the same millisecond still wins.
|
||||
if (!changed_connection && g_evaluated && g_evaluated_ms == now_ms &&
|
||||
profile_generation == g_profile_generation &&
|
||||
source.state_generation == g_source.state_generation && source.received_us == g_source.received_us &&
|
||||
source.accel_sequence == g_source.accel_sequence && source.gyro_sequence == g_source.gyro_sequence &&
|
||||
source.accel_received_us == g_source.accel_received_us && source.gyro_received_us == g_source.gyro_received_us &&
|
||||
source.accel_valid == g_source.accel_valid && source.gyro_valid == g_source.gyro_valid &&
|
||||
source.track_stationary_bias == g_source.track_stationary_bias) return;
|
||||
// This pair's polls and peeks share one profile and motion evaluation. A
|
||||
// real publication in the same millisecond still wins, independently of
|
||||
// the other pair's source updates and endpoint backpressure.
|
||||
if (!changed_connection && pair.evaluated && pair.evaluated_ms == now_ms &&
|
||||
profile_generation == pair.profile_generation &&
|
||||
source.state_generation == pair.source.state_generation && source.received_us == pair.source.received_us &&
|
||||
source.accel_sequence == pair.source.accel_sequence && source.gyro_sequence == pair.source.gyro_sequence &&
|
||||
source.accel_received_us == pair.source.accel_received_us && source.gyro_received_us == pair.source.gyro_received_us &&
|
||||
source.accel_valid == pair.source.accel_valid && source.gyro_valid == pair.source.gyro_valid &&
|
||||
source.track_stationary_bias == pair.source.track_stationary_bias) return;
|
||||
if (changed_connection) {
|
||||
lose_source(now_ms);
|
||||
g_motion.reset();
|
||||
for (Child& child : g_children) discard_output(child);
|
||||
probe_debug_printf("[PROBE] Native gamepad source active in slot %u\n", source.slot);
|
||||
lose_source(pair_index, now_ms);
|
||||
probe_debug_printf("[PROBE] Native gamepad pair %u source active in slot %u\n", pair_index, source.slot);
|
||||
}
|
||||
g_source = source;
|
||||
g_active = true;
|
||||
g_evaluated = true;
|
||||
g_evaluated_ms = now_ms;
|
||||
pair.source = source;
|
||||
pair.active = true;
|
||||
pair.evaluated = true;
|
||||
pair.evaluated_ms = now_ms;
|
||||
// Store the generation observed before transforming: a concurrent storage
|
||||
// publication must invalidate this result rather than bless an older profile.
|
||||
g_profile_generation = profile_generation;
|
||||
const auto previous_layout = g_mapped.native_joycon_layout;
|
||||
g_mapped = controller_profile_runtime_transform(source.slot, source.controller, now_ms, AdapterUsbMode::kSwitch);
|
||||
if (g_mapped.native_joycon_layout != previous_layout) {
|
||||
g_motion.reset();
|
||||
for (Child& child : g_children) discard_output(child);
|
||||
g_sensor_status = -1;
|
||||
}
|
||||
pair.profile_generation = profile_generation;
|
||||
const auto previous_layout = pair.mapped.native_joycon_layout;
|
||||
pair.mapped = controller_profile_runtime_transform(source.slot, source.controller, now_ms, AdapterUsbMode::kSwitch);
|
||||
if (pair.mapped.native_joycon_layout != previous_layout) reset_pair_output(pair_index);
|
||||
ControllerProfileRuntimeProfileChangeEvent feedback{};
|
||||
if (controller_profile_runtime_take_initial_profile_indication(source.slot, &feedback) ||
|
||||
controller_profile_runtime_take_profile_change(source.slot, &feedback)) {
|
||||
|
|
@ -262,30 +293,37 @@ void refresh(uint32_t now_ms) {
|
|||
sample.gyro_sequence = source.gyro_sequence;
|
||||
sample.accel_us = source.accel_received_us;
|
||||
sample.gyro_us = source.gyro_received_us;
|
||||
native_motion_axes(source.accel_q13, 1.0f / 8192.0f, sample.accel_g);
|
||||
native_motion_axes(source.gyro_q10, 1.0f / 1024.0f, sample.gyro_dps);
|
||||
g_motion.update(now_us, source.controller.connection_generation, sample,
|
||||
native_motion_axes(pair.mapped.native_joycon_layout, source.accel_q13, 1.0f / 8192.0f, sample.accel_g);
|
||||
native_motion_axes(pair.mapped.native_joycon_layout, source.gyro_q10, 1.0f / 1024.0f, sample.gyro_dps);
|
||||
pair.motion.update(now_us, source.controller.connection_generation, sample,
|
||||
source.track_stationary_bias ? ProbeNativeMotionBias::kTrackStationary :
|
||||
ProbeNativeMotionBias::kAlreadyCalibrated);
|
||||
const int status = !sensors_fresh(g_source, now_us) ? 0 : g_motion.ready() ? 2 : 1;
|
||||
if (status != g_sensor_status) {
|
||||
g_sensor_status = status;
|
||||
probe_debug_printf("[PROBE] Native gamepad IMU %s\n", status == 2 ? "ready" :
|
||||
const int status = !sensors_fresh(source, now_us) ? 0 : pair.motion.ready() ? 2 : 1;
|
||||
if (status != pair.sensor_status) {
|
||||
pair.sensor_status = status;
|
||||
probe_debug_printf("[PROBE] Native gamepad pair %u IMU %s\n", pair_index, status == 2 ? "ready" :
|
||||
status == 1 ? "waiting for a usable acceleration sample" : "waiting for supported fresh sensors");
|
||||
}
|
||||
for (uint8_t i = 0; i < PROBE_CONTROLLER_COUNT; ++i) {
|
||||
for (uint8_t instance = pair_index * 2; instance < pair_index * 2 + 2; ++instance) {
|
||||
// Latest-only: a blocked endpoint never queues obsolete controls/IMU.
|
||||
g_children[i].pending_token = 0;
|
||||
pack_controls(i);
|
||||
g_children[instance].pending_token = 0;
|
||||
pack_controls(instance);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void probe_native_gamepad_input_init() {
|
||||
#if SWITCH2_BRIDGE_SOURCE_AUTO
|
||||
bluepad32_input_backend_select_native_source(nullptr);
|
||||
bluepad32_input_backend_select_native_source(0, nullptr);
|
||||
#else
|
||||
bluepad32_input_backend_select_native_source(kSourceAddress);
|
||||
bluepad32_input_backend_select_native_source(0, kSourceAddress);
|
||||
#endif
|
||||
#if PROBE_CONTROLLER_COUNT == 4
|
||||
#if SWITCH2_BRIDGE_SECOND_SOURCE_AUTO
|
||||
bluepad32_input_backend_select_native_source(1, nullptr);
|
||||
#else
|
||||
bluepad32_input_backend_select_native_source(1, kSecondSourceAddress);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -319,20 +357,21 @@ void probe_native_gamepad_input_set_native_stream(uint8_t instance, bool enabled
|
|||
void probe_native_gamepad_input_poll(uint8_t instance, uint32_t now_ms, probe_controller_input* out) {
|
||||
if (!out) return;
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) { *out = {}; return; }
|
||||
refresh(now_ms);
|
||||
refresh(instance / 2, now_ms);
|
||||
*out = g_children[instance].input;
|
||||
}
|
||||
|
||||
uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_t now_ms, uint8_t report[63]) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !report) return 0;
|
||||
refresh(now_ms);
|
||||
refresh(instance / 2, now_ms);
|
||||
const Pair& pair = g_pairs[instance / 2];
|
||||
Child& child = g_children[instance];
|
||||
if (!child.enabled || !child.input.active) return 0;
|
||||
const uint32_t now_us = time_us_32();
|
||||
const bool motion_ready = (SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << instance)) != 0 &&
|
||||
g_motion.ready() && sensors_fresh(g_source, now_us) &&
|
||||
(!child.have_committed_motion || child.committed_accel_sequence != g_source.accel_sequence ||
|
||||
child.committed_gyro_sequence != g_source.gyro_sequence);
|
||||
const bool motion_ready = (SWITCH2_BRIDGE_IMU_TARGET_MASK & (1u << (instance & 1u))) != 0 &&
|
||||
pair.motion.ready() && sensors_fresh(pair.source, now_us) &&
|
||||
(!child.have_committed_motion || child.committed_accel_sequence != pair.source.accel_sequence ||
|
||||
child.committed_gyro_sequence != pair.source.gyro_sequence);
|
||||
if (child.pending_token && (now_us - child.pending_us >= kOutputDeadlineUs ||
|
||||
child.pending_motion != motion_ready)) child.pending_token = 0;
|
||||
if (!child.pending_token) {
|
||||
|
|
@ -340,7 +379,7 @@ uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_
|
|||
memset(child.pending_report, 0, sizeof(child.pending_report));
|
||||
child.pending_report[0] = child.counter;
|
||||
// Source battery level and the virtual controller's USB power are separate.
|
||||
const unsigned battery_level = (static_cast<unsigned>(g_source.battery) * 9u + 127u) / 255u;
|
||||
const unsigned battery_level = (static_cast<unsigned>(pair.source.battery) * 9u + 127u) / 255u;
|
||||
child.pending_report[1] = static_cast<uint8_t>((battery_level << 2) | 0x01u);
|
||||
memcpy(child.pending_report + 2, child.input.buttons, sizeof(child.input.buttons));
|
||||
child.pending_report[4] = 7;
|
||||
|
|
@ -351,11 +390,11 @@ uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_
|
|||
const uint32_t elapsed = child.have_committed_motion ? child.pending_ticks - child.committed_ticks : 1;
|
||||
const uint16_t wire_elapsed = static_cast<uint16_t>(elapsed <= 0xfff ? elapsed : 1);
|
||||
child.pending_motion = motion_ready && probe_native_imu_pack(
|
||||
g_motion.quaternion(), g_motion.acceleration(), static_cast<uint16_t>(child.pending_ticks & 0xfff),
|
||||
pair.motion.quaternion(), pair.motion.acceleration(), static_cast<uint16_t>(child.pending_ticks & 0xfff),
|
||||
wire_elapsed, 0, child.pending_report + probe_model_imu_data_offset(instance));
|
||||
if (child.pending_motion) child.pending_report[probe_model_imu_length_offset(instance)] = 30;
|
||||
child.pending_accel_sequence = g_source.accel_sequence;
|
||||
child.pending_gyro_sequence = g_source.gyro_sequence;
|
||||
child.pending_accel_sequence = pair.source.accel_sequence;
|
||||
child.pending_gyro_sequence = pair.source.gyro_sequence;
|
||||
child.pending_us = now_us;
|
||||
child.pending_token = ++g_report_token;
|
||||
}
|
||||
|
|
@ -366,20 +405,21 @@ uint32_t probe_native_gamepad_input_peek_native_report(uint8_t instance, uint32_
|
|||
bool probe_native_gamepad_input_commit_native_report(uint8_t instance, uint32_t token) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !token) return false;
|
||||
Child& child = g_children[instance];
|
||||
const Pair& pair = g_pairs[instance / 2];
|
||||
// Profile edits/activation need no physical publication to retire a token.
|
||||
if (!child.enabled || !child.input.active || !g_active || child.pending_token != token ||
|
||||
profile_service_database_generation() != g_profile_generation) return false;
|
||||
if (!child.enabled || !child.input.active || !pair.active || child.pending_token != token ||
|
||||
profile_service_database_generation() != pair.profile_generation) return false;
|
||||
// Check the live source even when the caller did not poll after a disconnect.
|
||||
Bluepad32NativeGamepadSnapshot source;
|
||||
bluepad32_input_backend_native_snapshot(&source);
|
||||
bluepad32_input_backend_native_snapshot(instance / 2, &source);
|
||||
const uint32_t now_us = time_us_32();
|
||||
if (!source.controller.active || source.slot != g_source.slot ||
|
||||
source.controller.connection_generation != g_source.controller.connection_generation ||
|
||||
source.state_generation != g_source.state_generation ||
|
||||
source.accel_sequence != g_source.accel_sequence || source.gyro_sequence != g_source.gyro_sequence ||
|
||||
source.accel_received_us != g_source.accel_received_us || source.gyro_received_us != g_source.gyro_received_us ||
|
||||
source.accel_valid != g_source.accel_valid || source.gyro_valid != g_source.gyro_valid ||
|
||||
source.track_stationary_bias != g_source.track_stationary_bias ||
|
||||
if (!source.controller.active || source.slot != pair.source.slot ||
|
||||
source.controller.connection_generation != pair.source.controller.connection_generation ||
|
||||
source.state_generation != pair.source.state_generation ||
|
||||
source.accel_sequence != pair.source.accel_sequence || source.gyro_sequence != pair.source.gyro_sequence ||
|
||||
source.accel_received_us != pair.source.accel_received_us || source.gyro_received_us != pair.source.gyro_received_us ||
|
||||
source.accel_valid != pair.source.accel_valid || source.gyro_valid != pair.source.gyro_valid ||
|
||||
source.track_stationary_bias != pair.source.track_stationary_bias ||
|
||||
now_us - source.received_us >= kInputDeadlineUs || now_us - child.pending_us >= kOutputDeadlineUs ||
|
||||
(child.pending_motion && !sensors_fresh(source, now_us))) return false;
|
||||
child.pending_token = 0;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@
|
|||
#include "controller_input.h"
|
||||
|
||||
#if SWITCH2_BRIDGE_FULL_INPUT
|
||||
// Core 0 only. One coherent profile/motion evaluation feeds both native children.
|
||||
// Core 0 only. Each source pair shares one coherent profile/motion evaluation.
|
||||
// Child instances remain A_R, A_L, B_R, B_L; transport state is child-local.
|
||||
void probe_native_gamepad_input_init();
|
||||
void probe_native_gamepad_input_set_stick_calibration(uint8_t instance, const uint8_t calibration[9]);
|
||||
void probe_native_gamepad_input_set_native_stream(uint8_t instance, bool enabled);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,20 @@ set(PICO_MBEDTLS_CONFIG_FILE "${SWITCH2_USB_PROBE_DIR}/mbedtls_config.h")
|
|||
option(SWITCH2_PROBE_COMPOSITE
|
||||
"Experiment: independent right and left Joy-Con 2 functions on one USB port" OFF)
|
||||
option(SWITCH2_PROBE_HUB "Native R/L devices on the built-in SIO USB hub" OFF)
|
||||
set(SWITCH2_PROBE_PAIR_COUNT "1" CACHE STRING "Native hub pair count: 1 or 2")
|
||||
set_property(CACHE SWITCH2_PROBE_PAIR_COUNT PROPERTY STRINGS 1 2)
|
||||
option(SWITCH2_PROBE_NEUTRAL_INPUT "Standalone hub transport experiment with neutral reports and no Bluetooth" OFF)
|
||||
if(NOT "${SWITCH2_PROBE_PAIR_COUNT}" MATCHES "^[12]$")
|
||||
message(FATAL_ERROR "SWITCH2_PROBE_PAIR_COUNT must be 1 or 2")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT AND (NOT SWITCH2_PROBE_HUB OR SWITCH_PICO_SWITCH2_USB_BRIDGE))
|
||||
message(FATAL_ERROR "Neutral transport requires the standalone native HUB, without the Bluetooth bridge")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_PAIR_COUNT GREATER 1 AND
|
||||
(NOT SWITCH2_PROBE_HUB OR
|
||||
(NOT SWITCH2_PROBE_NEUTRAL_INPUT AND NOT SWITCH2_BRIDGE_FULL_INPUT)))
|
||||
message(FATAL_ERROR "Two pairs require a native HUB with GAMEPAD/DUALSENSE input or explicit neutral transport")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_HUB AND SWITCH2_PROBE_COMPOSITE)
|
||||
message(FATAL_ERROR "Select native hub or composite, not both")
|
||||
endif()
|
||||
|
|
@ -24,15 +38,20 @@ else()
|
|||
message(FATAL_ERROR "SWITCH2_PROBE_SIDE must be LEFT or RIGHT")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
if(NOT SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_BRIDGE_WII_INPUT
|
||||
OR (NOT SWITCH2_BRIDGE_INPUT STREQUAL "JOYCON2" AND NOT SWITCH2_BRIDGE_FULL_INPUT))
|
||||
message(FATAL_ERROR "Native R/L output requires JOYCON2 input or a full-controller HUB source")
|
||||
if(NOT SWITCH2_PROBE_NEUTRAL_INPUT AND
|
||||
(NOT SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_BRIDGE_WII_INPUT
|
||||
OR (NOT SWITCH2_BRIDGE_INPUT STREQUAL "JOYCON2" AND NOT SWITCH2_BRIDGE_FULL_INPUT)))
|
||||
message(FATAL_ERROR "Native R/L output requires JOYCON2 input, a full-controller HUB source, or standalone neutral transport")
|
||||
endif()
|
||||
set(probe_composite 0)
|
||||
if(SWITCH2_PROBE_COMPOSITE)
|
||||
set(probe_composite 1)
|
||||
endif()
|
||||
set(probe_controller_count 2)
|
||||
if(SWITCH2_PROBE_HUB)
|
||||
math(EXPR probe_controller_count "2 * ${SWITCH2_PROBE_PAIR_COUNT}")
|
||||
else()
|
||||
set(probe_controller_count 2)
|
||||
endif()
|
||||
else()
|
||||
set(probe_composite 0)
|
||||
set(probe_controller_count 1)
|
||||
|
|
@ -45,20 +64,35 @@ add_compile_definitions(
|
|||
SWITCH2_PROBE_JOYCON_LEFT=${probe_joycon_left}
|
||||
SWITCH2_PROBE_COMPOSITE=${probe_composite}
|
||||
SWITCH2_PROBE_HUB=$<BOOL:${SWITCH2_PROBE_HUB}>
|
||||
SWITCH2_PROBE_NEUTRAL_INPUT=$<BOOL:${SWITCH2_PROBE_NEUTRAL_INPUT}>
|
||||
PROBE_CONTROLLER_COUNT=${probe_controller_count})
|
||||
|
||||
set(SWITCH2_BRIDGE_SOURCE_ADDRESS "" CACHE STRING
|
||||
"Primary physical Bluetooth source address (xx:xx:xx:xx:xx:xx)")
|
||||
set(SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS "" CACHE STRING
|
||||
"Secondary left physical Bluetooth source address (xx:xx:xx:xx:xx:xx)")
|
||||
"Second physical Joy-Con source, or Pair B full-gamepad source (empty selects auto)")
|
||||
set(SWITCH2_BRIDGE_SOURCE_AUTO OFF)
|
||||
set(SWITCH2_BRIDGE_SECOND_SOURCE_AUTO OFF)
|
||||
if(SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_PROBE_COMPOSITE)
|
||||
set(probe_source_fields SWITCH2_BRIDGE_SOURCE_ADDRESS)
|
||||
if(SWITCH2_BRIDGE_FULL_INPUT AND SWITCH2_BRIDGE_SOURCE_ADDRESS STREQUAL "")
|
||||
set(SWITCH2_BRIDGE_SOURCE_AUTO ON)
|
||||
set(probe_source_fields "")
|
||||
elseif((SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB) AND NOT SWITCH2_BRIDGE_FULL_INPUT)
|
||||
list(APPEND probe_source_fields SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS)
|
||||
set(probe_source_fields "")
|
||||
if(SWITCH2_BRIDGE_FULL_INPUT)
|
||||
if(SWITCH2_BRIDGE_SOURCE_ADDRESS STREQUAL "")
|
||||
set(SWITCH2_BRIDGE_SOURCE_AUTO ON)
|
||||
else()
|
||||
list(APPEND probe_source_fields SWITCH2_BRIDGE_SOURCE_ADDRESS)
|
||||
endif()
|
||||
if(probe_controller_count GREATER 2)
|
||||
if(SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS STREQUAL "")
|
||||
set(SWITCH2_BRIDGE_SECOND_SOURCE_AUTO ON)
|
||||
else()
|
||||
list(APPEND probe_source_fields SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
list(APPEND probe_source_fields SWITCH2_BRIDGE_SOURCE_ADDRESS)
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
list(APPEND probe_source_fields SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS)
|
||||
endif()
|
||||
endif()
|
||||
set(probe_source_addresses "")
|
||||
foreach(field IN LISTS probe_source_fields)
|
||||
|
|
@ -71,7 +105,7 @@ if(SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_PROBE_COMPOSITE)
|
|||
message(FATAL_ERROR "Provide ${field} as a physical six-byte Bluetooth address")
|
||||
endif()
|
||||
if(source_address IN_LIST probe_source_addresses)
|
||||
message(FATAL_ERROR "Composite physical source addresses must be distinct")
|
||||
message(FATAL_ERROR "Physical source addresses must be distinct")
|
||||
endif()
|
||||
list(APPEND probe_source_addresses "${source_address}")
|
||||
string(REPLACE ":" ",0x" ${field}_BYTES "${source_address}")
|
||||
|
|
@ -79,6 +113,7 @@ if(SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_PROBE_COMPOSITE)
|
|||
endforeach()
|
||||
endif()
|
||||
add_compile_definitions(SWITCH2_BRIDGE_SOURCE_AUTO=$<BOOL:${SWITCH2_BRIDGE_SOURCE_AUTO}>)
|
||||
add_compile_definitions(SWITCH2_BRIDGE_SECOND_SOURCE_AUTO=$<BOOL:${SWITCH2_BRIDGE_SECOND_SOURCE_AUTO}>)
|
||||
|
||||
function(switch2_usb_probe_configure target)
|
||||
set(probe_sources
|
||||
|
|
@ -101,8 +136,8 @@ function(switch2_usb_probe_configure target)
|
|||
option(SWITCH2_PROBE_TRACE_NATIVE_INPUT
|
||||
"Trace one completed native USB input report per second without changing its contents" OFF)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
if(NOT SWITCH_PICO_SWITCH2_USB_BRIDGE)
|
||||
message(FATAL_ERROR "Native input tracing requires the Bluetooth USB bridge")
|
||||
if(NOT SWITCH_PICO_SWITCH2_USB_BRIDGE AND NOT SWITCH2_PROBE_NEUTRAL_INPUT)
|
||||
message(FATAL_ERROR "Native input tracing requires the Bluetooth bridge or neutral hub experiment")
|
||||
endif()
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_TRACE_NATIVE_INPUT=1)
|
||||
endif()
|
||||
|
|
@ -161,7 +196,7 @@ function(switch2_usb_probe_configure target)
|
|||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_ZERO_NATIVE_IMU_PAYLOAD=1)
|
||||
endif()
|
||||
|
||||
foreach(prefix IN ITEMS SWITCH2_PROBE SWITCH2_PROBE_SECOND)
|
||||
foreach(prefix IN ITEMS SWITCH2_PROBE SWITCH2_PROBE_SECOND SWITCH2_PROBE_THIRD SWITCH2_PROBE_FOURTH)
|
||||
set(${prefix}_IDENTITY_FILE "" CACHE FILEPATH "64-byte matching Joy-Con 2 factory-format identity block")
|
||||
set(${prefix}_VERSION_FILE "" CACHE FILEPATH "Captured 12-byte matching Joy-Con 2 firmware-version reply")
|
||||
set(${prefix}_CONTROLLER_ADDRESS "" CACHE STRING "Advertised controller address (captured or distinct virtual identity)")
|
||||
|
|
@ -183,18 +218,25 @@ function(switch2_usb_probe_configure target)
|
|||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_USB_INIT=1)
|
||||
endif()
|
||||
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT AND NOT SWITCH2_PROBE_USB_INIT)
|
||||
message(FATAL_ERROR "Neutral transport requires SWITCH2_PROBE_USB_INIT and verified identity/calibration captures")
|
||||
endif()
|
||||
set(probe_capture_prefixes SWITCH2_PROBE)
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
list(APPEND probe_capture_prefixes SWITCH2_PROBE_SECOND)
|
||||
endif()
|
||||
if(probe_controller_count EQUAL 4)
|
||||
list(APPEND probe_capture_prefixes SWITCH2_PROBE_THIRD SWITCH2_PROBE_FOURTH)
|
||||
endif()
|
||||
set(identity_rows "")
|
||||
set(status_rows "")
|
||||
set(firmware_rows "")
|
||||
set(factory_rows "")
|
||||
set(user_calibration_rows "")
|
||||
set(controller_addresses "")
|
||||
set(controller_identities "")
|
||||
foreach(prefix IN LISTS probe_capture_prefixes)
|
||||
if(probe_joycon_left OR prefix STREQUAL "SWITCH2_PROBE_SECOND")
|
||||
if(probe_joycon_left OR prefix STREQUAL "SWITCH2_PROBE_SECOND" OR prefix STREQUAL "SWITCH2_PROBE_FOURTH")
|
||||
set(probe_model "Joy-Con 2 (L)")
|
||||
set(probe_vid_pid "7e056720")
|
||||
set(probe_firmware_type "00")
|
||||
|
|
@ -206,7 +248,7 @@ function(switch2_usb_probe_configure target)
|
|||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
foreach(field IDENTITY_FILE VERSION_FILE FACTORY_FILE USER_CALIBRATION_FILE CONTROLLER_ADDRESS)
|
||||
if(NOT ${prefix}_${field})
|
||||
message(FATAL_ERROR "Composite ${probe_model} requires ${prefix}_${field}")
|
||||
message(FATAL_ERROR "Native ${probe_model} requires ${prefix}_${field}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
|
@ -217,6 +259,10 @@ function(switch2_usb_probe_configure target)
|
|||
message(FATAL_ERROR "${prefix}_IDENTITY_FILE must contain exactly 64 bytes")
|
||||
endif()
|
||||
string(TOLOWER "${identity_hex}" identity_hex)
|
||||
if(identity_hex IN_LIST controller_identities)
|
||||
message(FATAL_ERROR "Native child factory identities must be distinct")
|
||||
endif()
|
||||
list(APPEND controller_identities "${identity_hex}")
|
||||
string(SUBSTRING "${identity_hex}" 36 8 identity_vid_pid)
|
||||
if(NOT identity_vid_pid STREQUAL probe_vid_pid)
|
||||
message(FATAL_ERROR "${prefix}_IDENTITY_FILE must match selected model ${probe_model}")
|
||||
|
|
@ -247,7 +293,7 @@ function(switch2_usb_probe_configure target)
|
|||
message(FATAL_ERROR "Provide ${prefix}_CONTROLLER_ADDRESS as a six-byte advertised Bluetooth address")
|
||||
endif()
|
||||
if(address_hex IN_LIST controller_addresses)
|
||||
message(FATAL_ERROR "Composite advertised controller addresses must be distinct")
|
||||
message(FATAL_ERROR "Advertised controller addresses must be distinct")
|
||||
endif()
|
||||
list(APPEND controller_addresses "${address_hex}")
|
||||
set(address_reversed "")
|
||||
|
|
@ -354,7 +400,9 @@ function(switch2_usb_probe_configure target)
|
|||
endif()
|
||||
pico_enable_stdio_usb(${target} 0)
|
||||
pico_enable_stdio_uart(${target} 1)
|
||||
if(SWITCH2_PROBE_HUB)
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT)
|
||||
pico_set_program_name(${target} "Native Joy-Con 2 neutral transport experiment")
|
||||
elseif(SWITCH2_PROBE_HUB)
|
||||
pico_set_program_name(${target} "Native Joy-Con 2 R and L stock USB hub bridge")
|
||||
elseif(SWITCH2_PROBE_COMPOSITE)
|
||||
pico_set_program_name(${target} "Switch 2 right and left Joy-Con composite bridge")
|
||||
|
|
@ -367,17 +415,29 @@ function(switch2_usb_probe_configure target)
|
|||
else()
|
||||
pico_set_program_name(${target} "Switch 2 USB initialization capture")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_FULL_INPUT)
|
||||
if(SWITCH2_PROBE_NEUTRAL_INPUT)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.89-native-digital-stick-trace")
|
||||
pico_set_program_version(${target} "0.97-neutral-hub-${SWITCH2_PROBE_PAIR_COUNT}pair-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.89-native-digital-stick")
|
||||
pico_set_program_version(${target} "0.97-neutral-hub-${SWITCH2_PROBE_PAIR_COUNT}pair")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_HUB AND SWITCH2_BRIDGE_FULL_INPUT)
|
||||
if(probe_controller_count GREATER 2)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.97-live-two-pair-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.97-live-two-pair")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.97-native-digital-stick-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.97-native-digital-stick")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_HUB)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.89-native-hub-profiles-trace")
|
||||
pico_set_program_version(${target} "0.97-native-hub-profiles-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.89-native-hub-profiles")
|
||||
pico_set_program_version(${target} "0.97-native-hub-profiles")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_JOIN_CHORD_GATE)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
|
|
|
|||
|
|
@ -17,16 +17,18 @@ namespace {
|
|||
constexpr size_t kSlotCount = 2;
|
||||
constexpr size_t kMaximumPayloadSize = 512;
|
||||
constexpr size_t kStorageSize = kSlotCount * FLASH_SECTOR_SIZE;
|
||||
constexpr size_t kReservedStorageSize = 2 * kStorageSize;
|
||||
constexpr size_t kReservedBankCount = PROBE_CONTROLLER_COUNT > 2 ? PROBE_CONTROLLER_COUNT : 2;
|
||||
constexpr size_t kReservedStorageSize = kReservedBankCount * kStorageSize;
|
||||
constexpr size_t kConfigurationStorageSize =
|
||||
CONFIGURATION_STORAGE_COPY_COUNT * FLASH_SECTOR_SIZE;
|
||||
constexpr size_t kConfigurationStorageOffset =
|
||||
PICO_FLASH_BANK_STORAGE_OFFSET - kConfigurationStorageSize;
|
||||
constexpr size_t kProfileStorageOffset =
|
||||
kConfigurationStorageOffset - PROFILE_STORAGE_TOTAL_SIZE;
|
||||
// Keep the original right bank adjacent to profiles; reserve the left bank below.
|
||||
// Preserve the original R/L banks; each additional child takes the next lower bank.
|
||||
constexpr uint32_t kRightStorageOffset = kProfileStorageOffset - kStorageSize;
|
||||
constexpr uint32_t kLeftStorageOffset = kRightStorageOffset - kStorageSize;
|
||||
constexpr uint32_t kReservedStorageOffset = kProfileStorageOffset - kReservedStorageSize;
|
||||
constexpr uint32_t kFlashSafeTimeoutMs = 5000;
|
||||
constexpr uint32_t kFormatVersion = 1;
|
||||
|
||||
|
|
@ -74,7 +76,7 @@ static_assert(PICO_FLASH_BANK_STORAGE_OFFSET >=
|
|||
"pairing storage offset underflows flash");
|
||||
static_assert(kLeftStorageOffset + kStorageSize == kRightStorageOffset);
|
||||
static_assert(kRightStorageOffset + kStorageSize == kProfileStorageOffset);
|
||||
static_assert(kLeftStorageOffset + kReservedStorageSize == kProfileStorageOffset);
|
||||
static_assert(kReservedStorageOffset + kReservedStorageSize == kProfileStorageOffset);
|
||||
static_assert(kProfileStorageOffset + PROFILE_STORAGE_TOTAL_SIZE ==
|
||||
kConfigurationStorageOffset);
|
||||
static_assert(kConfigurationStorageOffset + kConfigurationStorageSize ==
|
||||
|
|
@ -128,11 +130,11 @@ bool is_erased(const uint8_t *bytes, size_t size) {
|
|||
bool storage_region_available(uint32_t storage_offset) {
|
||||
const uintptr_t binary_end = reinterpret_cast<uintptr_t>(&__flash_binary_end);
|
||||
return binary_end >= XIP_BASE &&
|
||||
binary_end - XIP_BASE <= kLeftStorageOffset &&
|
||||
binary_end - XIP_BASE <= kReservedStorageOffset &&
|
||||
storage_offset % FLASH_SECTOR_SIZE == 0 &&
|
||||
storage_offset <= PICO_FLASH_SIZE_BYTES &&
|
||||
kStorageSize <= PICO_FLASH_SIZE_BYTES - storage_offset &&
|
||||
storage_offset >= kLeftStorageOffset &&
|
||||
storage_offset >= kReservedStorageOffset &&
|
||||
storage_offset + kStorageSize <= kProfileStorageOffset;
|
||||
}
|
||||
|
||||
|
|
@ -385,5 +387,9 @@ bool probe_storage_save(uint8_t instance, const uint8_t *data, size_t size) {
|
|||
|
||||
uint32_t probe_storage_offset(uint8_t instance) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) return UINT32_MAX;
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
return static_cast<uint32_t>(kRightStorageOffset - instance * kStorageSize);
|
||||
#else
|
||||
return probe_model_is_left(instance) ? kLeftStorageOffset : kRightStorageOffset;
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ bool probe_storage_load(uint8_t instance, uint8_t *output, size_t size);
|
|||
bool probe_storage_save(uint8_t instance, const uint8_t *data, size_t size);
|
||||
|
||||
// Flash-relative offset of the instance's two-sector pairing bank, or UINT32_MAX
|
||||
// for an invalid instance. The right bank remains immediately below profile
|
||||
// storage; the left bank occupies the preceding two sectors. Both are reserved
|
||||
// in every build, and load/save inspect and mutate only the selected bank.
|
||||
// for an invalid instance. Pair A's right bank remains immediately below profile
|
||||
// storage and its left bank immediately below that; standalone selects the same
|
||||
// bank for its side. Both original banks are always reserved. Four-child hubs
|
||||
// reserve two more banks below pair A, ordered pair B right then left. Firmware
|
||||
// must fit below the entire reserved range; load/save touch only the selected bank.
|
||||
uint32_t probe_storage_offset(uint8_t instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
|||
|
|
@ -85,7 +85,8 @@ static inline bool probe_transport_control_xfer(uint8_t rhport,
|
|||
const tusb_control_request_t* request,
|
||||
void* buffer, uint16_t length) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_control_xfer(rhport, request, buffer, length);
|
||||
return native_hub_control_xfer(rhport, request, buffer, length,
|
||||
(request->bmRequestType & 0x80u) != 0);
|
||||
#else
|
||||
return tud_control_xfer(rhport, request, buffer, length);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// Returns the formatted length when queued, or -1 if the whole message cannot
|
||||
// fit. A diagnostic consumer may retry later; native logging never masks IRQs.
|
||||
int probe_debug_printf(const char* format, ...);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue