Files
micropython/ports/rp2/modmachine.c
Angus Gratton 69993daa5c
Some checks failed
JavaScript code lint and formatting with Biome / eslint (push) Has been cancelled
Check code formatting / code-formatting (push) Has been cancelled
Check spelling with codespell / codespell (push) Has been cancelled
Build docs / build (push) Has been cancelled
Check examples / embedding (push) Has been cancelled
Package mpremote / build (push) Has been cancelled
.mpy file format and tools / test (push) Has been cancelled
Build ports metadata / build (push) Has been cancelled
alif port / build_alif (alif_ae3_build) (push) Has been cancelled
cc3200 port / build (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3) (push) Has been cancelled
esp8266 port / build (push) Has been cancelled
mimxrt port / build (push) Has been cancelled
nrf port / build (push) Has been cancelled
powerpc port / build (push) Has been cancelled
qemu port / build_and_test_arm (push) Has been cancelled
qemu port / build_and_test_rv32 (push) Has been cancelled
renesas-ra port / build_renesas_ra_board (push) Has been cancelled
rp2 port / build (push) Has been cancelled
samd port / build (push) Has been cancelled
stm32 port / build_stm32 (stm32_misc_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_nucleo_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_pyb_build) (push) Has been cancelled
unix port / minimal (push) Has been cancelled
unix port / reproducible (push) Has been cancelled
unix port / standard (push) Has been cancelled
unix port / standard_v2 (push) Has been cancelled
unix port / coverage (push) Has been cancelled
unix port / coverage_32bit (push) Has been cancelled
unix port / nanbox (push) Has been cancelled
unix port / float (push) Has been cancelled
unix port / stackless_clang (push) Has been cancelled
unix port / float_clang (push) Has been cancelled
unix port / settrace (push) Has been cancelled
unix port / settrace_stackless (push) Has been cancelled
unix port / macos (push) Has been cancelled
unix port / qemu_mips (push) Has been cancelled
unix port / qemu_arm (push) Has been cancelled
unix port / qemu_riscv64 (push) Has been cancelled
webassembly port / build (push) Has been cancelled
windows port / build-vs (Debug, x64, windows-2022, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x64, windows-latest, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x86, windows-2022, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x86, windows-latest, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x64, windows-2019, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, windows-2019, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, windows-2022, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, windows-2022, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, windows-latest, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x64, windows-latest, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x86, windows-2019, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x86, windows-2019, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x86, windows-2022, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, windows-2022, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, windows-latest, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, x86, windows-latest, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, dev) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, standard) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, dev) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, standard) (push) Has been cancelled
windows port / cross-build-on-linux (push) Has been cancelled
zephyr port / build (push) Has been cancelled
Python code lint and formatting with ruff / ruff (push) Has been cancelled
rp2/modmachine: Add mutual exclusion for machine.lightsleep().
There's no specified behaviour for what should happen if both CPUs call
`lightsleep()` together, but the latest changes could cause a permanent
hang due to a race in the timer cleanup code.  Add a flag to prevent hangs
if two threads accidentally lightsleep, at least.

This allows the new lightsleep test to pass on RPI_PICO and RPI_PICO2, and
even have much tighter time deltas.  However, the test still fails on
wireless boards where the lwIP tick wakes them up too frequently.

This work was funded through GitHub Sponsors.

Signed-off-by: Angus Gratton <angus@redyak.com.au>
2025-05-12 16:30:46 +10:00

345 lines
11 KiB
C

/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020-2023 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// This file is never compiled standalone, it's included directly from
// extmod/modmachine.c via MICROPY_PY_MACHINE_INCLUDEFILE.
#include "py/mphal.h"
#include "mp_usbd.h"
#include "modmachine.h"
#include "uart.h"
#include "rp2_psram.h"
#include "rp2_flash.h"
#include "clocks_extra.h"
#include "hardware/pll.h"
#include "hardware/structs/rosc.h"
#include "hardware/structs/scb.h"
#include "hardware/structs/syscfg.h"
#include "hardware/watchdog.h"
#include "hardware/xosc.h"
#include "pico/bootrom.h"
#include "pico/stdlib.h"
#include "pico/unique_id.h"
#include "pico/runtime_init.h"
#if MICROPY_PY_NETWORK_CYW43
#include "lib/cyw43-driver/src/cyw43.h"
#endif
#define RP2_RESET_PWRON (1)
#define RP2_RESET_WDT (3)
#define MICROPY_PY_MACHINE_EXTRA_GLOBALS \
{ MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \
{ MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, \
{ MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, \
\
{ MP_ROM_QSTR(MP_QSTR_PWRON_RESET), MP_ROM_INT(RP2_RESET_PWRON) }, \
{ MP_ROM_QSTR(MP_QSTR_WDT_RESET), MP_ROM_INT(RP2_RESET_WDT) }, \
static mp_obj_t mp_machine_unique_id(void) {
pico_unique_board_id_t id;
pico_get_unique_board_id(&id);
return mp_obj_new_bytes(id.id, sizeof(id.id));
}
NORETURN static void mp_machine_reset(void) {
watchdog_reboot(0, SRAM_END, 0);
for (;;) {
__wfi();
}
}
static mp_int_t mp_machine_reset_cause(void) {
int reset_cause;
if (watchdog_caused_reboot()) {
reset_cause = RP2_RESET_WDT;
} else {
reset_cause = RP2_RESET_PWRON;
}
return reset_cause;
}
NORETURN void mp_machine_bootloader(size_t n_args, const mp_obj_t *args) {
MICROPY_BOARD_ENTER_BOOTLOADER(n_args, args);
rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB;
reset_usb_boot(0, 0);
for (;;) {
}
}
static mp_obj_t mp_machine_get_freq(void) {
return MP_OBJ_NEW_SMALL_INT(mp_hal_get_cpu_freq());
}
static void mp_machine_set_freq(size_t n_args, const mp_obj_t *args) {
mp_int_t freq = mp_obj_get_int(args[0]);
// If necessary, increase the flash divider before increasing the clock speed
const int old_freq = clock_get_hz(clk_sys);
rp2_flash_set_timing_for_freq(MAX(freq, old_freq));
if (!set_sys_clock_khz(freq / 1000, false)) {
mp_raise_ValueError(MP_ERROR_TEXT("cannot change frequency"));
}
if (n_args > 1) {
mp_int_t freq_peri = mp_obj_get_int(args[1]);
if (freq_peri != (USB_CLK_KHZ * KHZ)) {
if (freq_peri == freq) {
clock_configure(clk_peri,
0,
CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLKSRC_PLL_SYS,
freq,
freq);
} else {
mp_raise_ValueError(MP_ERROR_TEXT("peripheral freq must be 48_000_000 or the same as the MCU freq"));
}
}
}
// If clock speed was reduced, maybe we can reduce the flash divider
if (freq < old_freq) {
rp2_flash_set_timing_for_freq(freq);
}
#if MICROPY_HW_ENABLE_UART_REPL
setup_default_uart();
mp_uart_init();
#endif
#if MICROPY_HW_ENABLE_PSRAM
psram_init(MICROPY_HW_PSRAM_CS_PIN);
#endif
}
static void mp_machine_idle(void) {
MICROPY_INTERNAL_WFE(1);
}
static void alarm_sleep_callback(uint alarm_id) {
}
// Set this to 1 to enable some debug of the interrupt that woke the device
#define DEBUG_LIGHTSLEEP 0
static void mp_machine_lightsleep(size_t n_args, const mp_obj_t *args) {
mp_int_t delay_ms = 0;
bool use_timer_alarm = false;
if (n_args == 1) {
delay_ms = mp_obj_get_int(args[0]);
if (delay_ms <= 1) {
// Sleep is too small, just use standard delay.
mp_hal_delay_ms(delay_ms);
return;
}
use_timer_alarm = delay_ms < (1ULL << 32) / 1000;
if (use_timer_alarm) {
// Use timer alarm to wake.
} else {
// TODO: Use RTC alarm to wake.
mp_raise_ValueError(MP_ERROR_TEXT("sleep too long"));
}
}
const uint32_t xosc_hz = XOSC_MHZ * 1000000;
uint32_t my_interrupts = MICROPY_BEGIN_ATOMIC_SECTION();
#if MICROPY_PY_NETWORK_CYW43
if (cyw43_poll_is_pending()) {
MICROPY_END_ATOMIC_SECTION(my_interrupts);
return;
}
#endif
#if MICROPY_PY_THREAD
static bool in_lightsleep;
if (in_lightsleep) {
// The other CPU is also in machine.lightsleep()
MICROPY_END_ATOMIC_SECTION(my_interrupts);
return;
}
in_lightsleep = true;
#endif
#if MICROPY_HW_ENABLE_USBDEV
// Only disable the USB clock if a USB host has not configured the device
// or if going to DORMANT mode.
bool disable_usb = !(tud_mounted() && n_args > 0);
#else
bool disable_usb = true;
#endif
if (disable_usb) {
clock_stop(clk_usb);
}
clock_stop(clk_adc);
#if PICO_RP2350
clock_stop(clk_hstx);
#endif
// CLK_REF = XOSC
clock_configure(clk_ref, CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC, 0, xosc_hz, xosc_hz);
// CLK_SYS = CLK_REF
clock_configure(clk_sys, CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, 0, xosc_hz, xosc_hz);
// CLK_RTC = XOSC / 256
#if PICO_RP2040
clock_configure(clk_rtc, 0, CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC, xosc_hz, xosc_hz / 256);
#endif
// CLK_PERI = CLK_SYS
clock_configure(clk_peri, 0, CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, xosc_hz, xosc_hz);
// Disable PLLs.
pll_deinit(pll_sys);
if (disable_usb) {
pll_deinit(pll_usb);
}
// Disable ROSC.
rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_DISABLE << ROSC_CTRL_ENABLE_LSB;
#if DEBUG_LIGHTSLEEP
#if PICO_RP2040
uint32_t pending_intr = 0;
#else
uint32_t pending_intr[2] = { 0 };
#endif
#endif
bool alarm_armed = false;
if (n_args == 0) {
#if MICROPY_PY_NETWORK_CYW43
gpio_set_dormant_irq_enabled(CYW43_PIN_WL_HOST_WAKE, GPIO_IRQ_LEVEL_HIGH, true);
#endif
xosc_dormant();
} else {
uint32_t save_sleep_en0 = clocks_hw->sleep_en0;
uint32_t save_sleep_en1 = clocks_hw->sleep_en1;
if (use_timer_alarm) {
// Use timer alarm to wake.
clocks_hw->sleep_en0 = 0x0;
#if PICO_RP2040
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_SYS_TIMER_BITS;
#elif PICO_RP2350
clocks_hw->sleep_en1 = CLOCKS_SLEEP_EN1_CLK_REF_TICKS_BITS | CLOCKS_SLEEP_EN1_CLK_SYS_TIMER0_BITS;
#else
#error Unknown processor
#endif
hardware_alarm_claim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM);
hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, alarm_sleep_callback);
if (hardware_alarm_set_target(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, make_timeout_time_ms(delay_ms)) == PICO_OK) {
alarm_armed = true;
}
} else {
// TODO: Use RTC alarm to wake.
clocks_hw->sleep_en0 = 0x0;
clocks_hw->sleep_en1 = 0x0;
}
if (!disable_usb) {
clocks_hw->sleep_en0 |= CLOCKS_SLEEP_EN0_CLK_SYS_PLL_USB_BITS;
#if PICO_RP2040
clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_USB_USBCTRL_BITS;
#elif PICO_RP2350
clocks_hw->sleep_en1 |= CLOCKS_SLEEP_EN1_CLK_USB_BITS;
#else
#error Unknown processor
#endif
}
#if PICO_ARM
// Configure SLEEPDEEP bits on Cortex-M CPUs.
#if PICO_RP2040
scb_hw->scr |= M0PLUS_SCR_SLEEPDEEP_BITS;
#elif PICO_RP2350
scb_hw->scr |= M33_SCR_SLEEPDEEP_BITS;
#else
#error Unknown processor
#endif
#endif
// Go into low-power mode.
if (alarm_armed) {
__wfi();
#if DEBUG_LIGHTSLEEP
#if PICO_RP2040
pending_intr = nvic_hw->ispr;
#else
pending_intr[0] = nvic_hw->ispr[0];
pending_intr[1] = nvic_hw->ispr[1];
#endif
#endif
}
clocks_hw->sleep_en0 = save_sleep_en0;
clocks_hw->sleep_en1 = save_sleep_en1;
}
// Enable ROSC.
rosc_hw->ctrl = ROSC_CTRL_ENABLE_VALUE_ENABLE << ROSC_CTRL_ENABLE_LSB;
// Bring back all clocks.
runtime_init_clocks_optional_usb(disable_usb);
MICROPY_END_ATOMIC_SECTION(my_interrupts);
// Re-sync mp_hal_time_ns() counter with aon timer.
mp_hal_time_ns_set_from_rtc();
// Note: This must be done after MICROPY_END_ATOMIC_SECTION
if (use_timer_alarm) {
if (alarm_armed) {
hardware_alarm_cancel(MICROPY_HW_LIGHTSLEEP_ALARM_NUM);
}
hardware_alarm_set_callback(MICROPY_HW_LIGHTSLEEP_ALARM_NUM, NULL);
hardware_alarm_unclaim(MICROPY_HW_LIGHTSLEEP_ALARM_NUM);
#if DEBUG_LIGHTSLEEP
// Check irq.h for the list of IRQ's
// for rp2040 00000042: TIMER_IRQ_1 woke the device as expected
// 00000020: USBCTRL_IRQ woke the device (probably early)
// For rp2350 00000000:00000002: TIMER0_IRQ_1 woke the device as expected
// 00000000:00004000: USBCTRL_IRQ woke the device (probably early)
#if PICO_RP2040
mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx\n", pending_intr);
#else
mp_printf(MP_PYTHON_PRINTER, "lightsleep: pending_intr=%08lx:%08lx\n", pending_intr[1], pending_intr[0]);
#endif
#endif
}
#if MICROPY_PY_THREAD
// Clearing the flag here is atomic, and we know we're the ones who set it
// (higher up, inside the critical section)
in_lightsleep = false;
#endif
}
NORETURN static void mp_machine_deepsleep(size_t n_args, const mp_obj_t *args) {
mp_machine_lightsleep(n_args, args);
mp_machine_reset();
}