Add managed reboot into USB BOOTSEL
This commit is contained in:
parent
bd5d4b3177
commit
4626b13bd8
13 changed files with 153 additions and 0 deletions
|
|
@ -29,6 +29,7 @@ enum class Call : uint8_t {
|
|||
kClearPairings,
|
||||
kPairingSnapshot,
|
||||
kWatchdogReboot,
|
||||
kBootselReboot,
|
||||
};
|
||||
|
||||
std::vector<Call> calls;
|
||||
|
|
@ -61,6 +62,9 @@ ControllerProfileConfirmationPolicy feedback_policy =
|
|||
int reboot_count = 0;
|
||||
int runtime_reset_count = 0;
|
||||
int clear_pairings_count = 0;
|
||||
int bootsel_reboot_count = 0;
|
||||
uint32_t bootsel_gpio_mask = UINT32_MAX;
|
||||
uint32_t bootsel_disable_mask = UINT32_MAX;
|
||||
bool recovery_reserved = false;
|
||||
bool abandoned_host_receive = false;
|
||||
bool abandoned_host_receive_canceled = false;
|
||||
|
|
@ -101,6 +105,9 @@ void reset_harness(AdapterRequestedMode requested_mode =
|
|||
reboot_count = 0;
|
||||
runtime_reset_count = 0;
|
||||
clear_pairings_count = 0;
|
||||
bootsel_reboot_count = 0;
|
||||
bootsel_gpio_mask = UINT32_MAX;
|
||||
bootsel_disable_mask = UINT32_MAX;
|
||||
recovery_reserved = false;
|
||||
abandoned_host_receive = false;
|
||||
abandoned_host_receive_canceled = false;
|
||||
|
|
@ -553,6 +560,29 @@ void test_auto_xinput_disconnect_reboots_to_probe() {
|
|||
"Auto Switch probe rebooted after USB unmount");
|
||||
}
|
||||
|
||||
void test_bootsel_reboot_is_delayed_and_idempotent() {
|
||||
reset_harness(AdapterRequestedMode::kAuto);
|
||||
require(adapter_reboot_to_bootsel() &&
|
||||
adapter_reboot_to_bootsel() &&
|
||||
runtime_reset_count == 1 &&
|
||||
bootsel_reboot_count == 0 && reboot_count == 0,
|
||||
"BOOTSEL request was not accepted exactly once");
|
||||
|
||||
adapter_mode_controller_task(UINT32_MAX - 25u);
|
||||
adapter_mode_controller_task(23);
|
||||
require(bootsel_reboot_count == 0,
|
||||
"BOOTSEL reboot occurred before the control-transfer guard");
|
||||
adapter_mode_controller_task(24);
|
||||
adapter_mode_controller_task(25);
|
||||
require(bootsel_reboot_count == 1 &&
|
||||
bootsel_gpio_mask == 0 &&
|
||||
bootsel_disable_mask == 0 &&
|
||||
reboot_count == 0 &&
|
||||
!adapter_reboot_for_mode_transaction(91),
|
||||
"BOOTSEL reboot was not delayed, one-shot, or isolated from "
|
||||
"the correlated watchdog path");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void configuration_service_initialize_pre_usb() {
|
||||
|
|
@ -669,6 +699,14 @@ void watchdog_reboot(uint32_t, uint32_t, uint32_t) {
|
|||
++reboot_count;
|
||||
}
|
||||
|
||||
void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask,
|
||||
uint32_t disable_interface_mask) {
|
||||
calls.push_back(Call::kBootselReboot);
|
||||
++bootsel_reboot_count;
|
||||
bootsel_gpio_mask = usb_activity_gpio_pin_mask;
|
||||
bootsel_disable_mask = disable_interface_mask;
|
||||
}
|
||||
|
||||
int main() {
|
||||
test_pre_tusb_ordering_and_configured_selection();
|
||||
test_mode_availability_has_one_stable_value();
|
||||
|
|
@ -681,5 +719,6 @@ int main() {
|
|||
test_recovery_auto_wins_host_mode_interleaving();
|
||||
test_failed_recovery_never_reboots();
|
||||
test_auto_xinput_disconnect_reboots_to_probe();
|
||||
test_bootsel_reboot_is_delayed_and_idempotent();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
6
tests/mode_native_stubs/pico/bootrom.h
Normal file
6
tests/mode_native_stubs/pico/bootrom.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask,
|
||||
uint32_t disable_interface_mask);
|
||||
|
|
@ -58,6 +58,7 @@ class FakeDevice:
|
|||
self.mode_pending_reads = 0
|
||||
self.fail_mode_status: int | None = None
|
||||
self.reboot_transaction_ids: list[int] = []
|
||||
self.bootsel_reboot_requested = False
|
||||
self.records = [
|
||||
(
|
||||
config_manager.TRANSPORT_CLASSIC,
|
||||
|
|
@ -392,6 +393,9 @@ class FakeDevice:
|
|||
assert reboot_transaction_id == self.transaction_id
|
||||
assert self.transaction_status == config_manager.STATUS_OK
|
||||
self.reboot_transaction_ids.append(reboot_transaction_id)
|
||||
elif request == config_manager.OP_BOOTSEL_REBOOT:
|
||||
assert payload == b""
|
||||
self.bootsel_reboot_requested = True
|
||||
elif request == config_manager.OP_PAIRING_REFRESH:
|
||||
self.pairing_generation += 1
|
||||
elif request == config_manager.OP_PAIRING_CLEAR:
|
||||
|
|
@ -660,6 +664,15 @@ def test_mode_envelopes_and_host_side_validation(
|
|||
config_manager.OP_REBOOT, reboot_payload
|
||||
)
|
||||
|
||||
config_manager.request_bootsel_reboot(device)
|
||||
operation, payload, encoded = device.out_requests[-1]
|
||||
assert operation == config_manager.OP_BOOTSEL_REBOOT
|
||||
assert payload == b""
|
||||
assert encoded == config_manager.encode_request(
|
||||
config_manager.OP_BOOTSEL_REBOOT
|
||||
)
|
||||
assert device.bootsel_reboot_requested
|
||||
|
||||
for transaction_id in (0, 0x80000000, True):
|
||||
with pytest.raises(config_manager.ConfigManagerError):
|
||||
config_manager.request_reboot(device, transaction_id)
|
||||
|
|
@ -1687,6 +1700,20 @@ def test_mode_cli_changes_then_noops(
|
|||
assert reenumerated.out_requests == []
|
||||
|
||||
|
||||
def test_bootsel_reboot_cli(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
device = FakeDevice()
|
||||
monkeypatch.setattr(
|
||||
config_manager, "_candidate_devices", lambda: (device,)
|
||||
)
|
||||
|
||||
assert config_manager.main(["reboot", "bootsel"]) == 0
|
||||
assert capsys.readouterr().out == "Rebooting into USB BOOTSEL mode.\n"
|
||||
assert device.bootsel_reboot_requested
|
||||
|
||||
|
||||
def test_mode_parser_accepts_all_implemented_modes() -> None:
|
||||
for mode in config_manager.REQUESTED_MODE_NAMES:
|
||||
args = config_manager.build_parser().parse_args(["mode", mode])
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ uint32_t mode_set_call_count = 0;
|
|||
uint32_t correlated_reboot_transaction_id = 0;
|
||||
uint32_t reboot_transaction_id = 0;
|
||||
uint32_t reboot_call_count = 0;
|
||||
bool bootsel_reboot_requested = false;
|
||||
bool refresh_requested = false;
|
||||
bool clear_requested = false;
|
||||
Bluepad32BackendDiagnostics current_diagnostics{};
|
||||
|
|
@ -221,6 +222,9 @@ void test_vendor_requests() {
|
|||
perform_out(Operation::kPairingRefresh, {});
|
||||
require(refresh_requested,
|
||||
"pairing refresh was not dispatched");
|
||||
perform_out(Operation::kBootselReboot, {});
|
||||
require(bootsel_reboot_requested,
|
||||
"BOOTSEL reboot request was not dispatched");
|
||||
perform_out(Operation::kPairingClear, {});
|
||||
require(clear_requested, "pairing clear was not dispatched");
|
||||
|
||||
|
|
@ -756,6 +760,11 @@ void bluepad32_input_backend_diagnostics(
|
|||
*out = current_diagnostics;
|
||||
}
|
||||
|
||||
bool adapter_reboot_to_bootsel() {
|
||||
bootsel_reboot_requested = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool adapter_host_probe_vendor_control(
|
||||
uint8_t, uint8_t, const tusb_control_request_t*) {
|
||||
return false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue