Queue correlated, volatile wake requests for the BTstack owner and expose read-only completion state. Keep the controller chord, captured identity, pairings, profiles, HD settings and existing wake burst unchanged. Add Python API and wake CLI with bounded waits, explicit failure outcomes and no automatic broadcast retries. Avoid importing SDL just to obtain fixed sensor IDs so USB automation has clean output and no SDL dependency. Windows libusb cannot control hub roots. Expose only INFO/WAKE on each native child's existing vendor Interface 1 with isolated control state; retain hub/HID drivers, descriptors and identities. Add safe sibling-aware Windows discovery and a packaged Windows libusb runtime. Document manual WinUSB binding to Interface 1 only and migrate examples to find_wake_pico. Validate with 713 tests, 10 firmware/probe builds, Windows x64 wheel resolution on Python 3.9/3.11, and a real child-interface wake burst on Linux (2.03 s, no controller, no failures). Device/configuration descriptors, serials and persistent state matched before/after. Physical Windows hardware/driver operation remains unverified on this Linux workstation.
108 lines
3.7 KiB
C++
108 lines
3.7 KiB
C++
#include "bootsel.h"
|
|
|
|
#include "model.h"
|
|
#if SWITCH2_PROBE_HUB
|
|
#include <string.h>
|
|
#include "pico/bootrom.h"
|
|
#include "usb/native_hub/native_hub.h"
|
|
#else
|
|
#include "adapter/adapter_mode_controller.h"
|
|
#endif
|
|
#include "usb/usb_configuration_management.h"
|
|
|
|
namespace {
|
|
bool bootsel_accepted;
|
|
#if SWITCH2_PROBE_HUB
|
|
constexpr uint32_t kBootselRebootDelayMs = 50;
|
|
struct BootselTransfer {
|
|
uint8_t envelope[UsbConfigurationManagement::kRequestHeaderSize];
|
|
bool pending;
|
|
bool validated;
|
|
};
|
|
// 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;
|
|
#endif
|
|
}
|
|
|
|
bool probe_management_vendor_control(uint8_t rhport, uint8_t stage,
|
|
const tusb_control_request_t* request) {
|
|
using namespace UsbConfigurationManagement;
|
|
#if SWITCH2_PROBE_HUB
|
|
if (rhport > PROBE_CONTROLLER_COUNT) return false;
|
|
BootselTransfer& transfer = bootsel_transfers[rhport];
|
|
if (stage == CONTROL_STAGE_SETUP) {
|
|
transfer.pending = false;
|
|
transfer.validated = false;
|
|
}
|
|
#endif
|
|
#if SWITCH2_PROBE_HUB && !SWITCH2_PROBE_NEUTRAL_INPUT
|
|
if (rhport != 0 &&
|
|
usb_configuration_management_child_vendor_control(rhport, stage, request)) {
|
|
return true;
|
|
}
|
|
#endif
|
|
if (request == nullptr || request->bmRequestType != 0x40 ||
|
|
request->bRequest != static_cast<uint8_t>(Operation::kBootselReboot) ||
|
|
request->wValue != kRequestValue || request->wIndex != kRequestIndex ||
|
|
request->wLength != kRequestHeaderSize) {
|
|
#if SWITCH2_PROBE_HUB && !SWITCH2_PROBE_NEUTRAL_INPUT
|
|
return rhport == 0 &&
|
|
usb_configuration_management_vendor_control(rhport, stage, request);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
#if SWITCH2_PROBE_HUB
|
|
if (stage == CONTROL_STAGE_SETUP) {
|
|
// 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), false);
|
|
return transfer.pending;
|
|
}
|
|
if (stage == CONTROL_STAGE_DATA) {
|
|
DecodedRequest decoded{};
|
|
transfer.validated = transfer.pending &&
|
|
decode_request(Operation::kBootselReboot, transfer.envelope,
|
|
sizeof(transfer.envelope), &decoded) &&
|
|
decoded.payload_size == 0;
|
|
return transfer.validated;
|
|
}
|
|
if (stage == CONTROL_STAGE_ACK && transfer.pending && transfer.validated) {
|
|
transfer.pending = false;
|
|
bootsel_accepted = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
#else
|
|
// The shared handler receives the envelope at SETUP and validates and
|
|
// dispatches it only at ACK, after the host's control transfer completes.
|
|
const bool accepted =
|
|
usb_configuration_management_vendor_control(rhport, stage, request);
|
|
if (accepted && stage == CONTROL_STAGE_ACK) {
|
|
bootsel_accepted = true;
|
|
}
|
|
return accepted;
|
|
#endif
|
|
}
|
|
|
|
void probe_bootsel_task(uint32_t now_ms) {
|
|
#if SWITCH2_PROBE_HUB
|
|
if (!bootsel_accepted) return;
|
|
if (!bootsel_delay_started) {
|
|
bootsel_delay_started = true;
|
|
bootsel_deadline_ms = now_ms + kBootselRebootDelayMs;
|
|
} else if (static_cast<int32_t>(now_ms - bootsel_deadline_ms) >= 0) {
|
|
bootsel_accepted = false;
|
|
reset_usb_boot(0, 0);
|
|
}
|
|
#else
|
|
// The native bridge does not initialize ordinary adapter-mode selection.
|
|
// A successful BOOTSEL dispatch guarantees the task takes its reboot path.
|
|
if (bootsel_accepted) {
|
|
adapter_mode_controller_task(now_ms);
|
|
}
|
|
#endif
|
|
}
|