diff --git a/bricks/_common/sources.mk b/bricks/_common/sources.mk index 4448b7f08..9ae7489f4 100644 --- a/bricks/_common/sources.mk +++ b/bricks/_common/sources.mk @@ -188,6 +188,7 @@ PBIO_SRC_C = $(addprefix lib/pbio/,\ drv/uart/uart_stm32l4_ll_dma.c \ drv/usb/usb_ev3.c \ drv/usb/usb_stm32.c \ + drv/watchdog/watchdog_ev3.c \ drv/watchdog/watchdog_stm32.c \ platform/$(PBIO_PLATFORM)/platform.c \ src/angle.c \ diff --git a/lib/pbio/drv/block_device/block_device_ev3.c b/lib/pbio/drv/block_device/block_device_ev3.c index 8e1f6a669..9176d7701 100644 --- a/lib/pbio/drv/block_device/block_device_ev3.c +++ b/lib/pbio/drv/block_device/block_device_ev3.c @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -486,8 +487,8 @@ static pbio_error_t spi_begin_for_flash( spi_dev.status = SPI_STATUS_WAIT_TX | SPI_STATUS_WAIT_RX; - // TODO: pbio probably needs a framework for memory barriers and DMA cache management - __asm__ volatile ("" ::: "memory"); + // TODO: eventually needs DMA cache management + pbdrv_compiler_memory_barrier(); EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_TX, EDMA3_TRIG_MODE_EVENT); EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_RX, EDMA3_TRIG_MODE_EVENT); @@ -805,8 +806,8 @@ static pbio_error_t pbdrv_block_device_ev3_spi_begin_for_adc(const uint32_t *cmd spi_dev.status = SPI_STATUS_WAIT_TX | SPI_STATUS_WAIT_RX; - // TODO: pbio probably needs a framework for memory barriers and DMA cache management - __asm__ volatile ("" ::: "memory"); + // TODO: eventually needs DMA cache management + pbdrv_compiler_memory_barrier(); EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_TX, EDMA3_TRIG_MODE_EVENT); EDMA3EnableTransfer(SOC_EDMA30CC_0_REGS, EDMA3_CHA_SPI0_RX, EDMA3_TRIG_MODE_EVENT); diff --git a/lib/pbio/drv/reset/reset_ev3.c b/lib/pbio/drv/reset/reset_ev3.c index eecbcc7e5..140b7ae7e 100644 --- a/lib/pbio/drv/reset/reset_ev3.c +++ b/lib/pbio/drv/reset/reset_ev3.c @@ -7,36 +7,90 @@ #if PBDRV_CONFIG_RESET_EV3 +#include + +#include #include #include #include "../drv/gpio/gpio_ev3.h" +#include #include +#include +#include + +#define BOOTLOADER_UPDATE_MODE_VALUE 0x5555AAAA + +// 'Pybr' +#define RESET_REASON_FLAG_WDT 0x50796272 +// 'rboo' +#define RESET_REASON_FLAG_SOFT_RESET 0x72626f6f + +typedef struct { + // 0xffff1ff0 + uint32_t _dummy0; + // 0xffff1ff4 + // Pybricks uses this flag to determine software reset vs other resets + uint32_t reset_reason_flag; + // 0xffff1ff8 + // Have not fully investigated this, but the bootloader seems to store + // the result of (DDR) memory testing at 0xffff1ffa and 0xffff1ffb + uint32_t _bootloader_unk_ram_test; + // 0xffff1ffc + uint32_t bootloader_update_flag; +} persistent_data_t; +// This is defined as an extern variable so that its address can be specified +// in the platform.ld linker script. This means that the linker script +// contains information about *all* fixed memory locations. +// +// This lives at the very end of the ARM local RAM. +extern volatile persistent_data_t ev3_persistent_data; + +static uint32_t saved_reset_reason_flag; static const pbdrv_gpio_t poweroff_pin = PBDRV_GPIO_EV3_PIN(13, 19, 16, 6, 11); void pbdrv_reset_init(void) { - pbdrv_gpio_out_high(&poweroff_pin); + saved_reset_reason_flag = ev3_persistent_data.reset_reason_flag; + ev3_persistent_data.reset_reason_flag = RESET_REASON_FLAG_WDT; } void pbdrv_reset(pbdrv_reset_action_t action) { for (;;) { switch (action) { - case PBDRV_RESET_ACTION_RESET_IN_UPDATE_MODE: - // TODO + case PBDRV_RESET_ACTION_POWER_OFF: + pbdrv_reset_power_off(); break; - // TODO: implement case PBDRV_RESET_ACTION_RESET + case PBDRV_RESET_ACTION_RESET_IN_UPDATE_MODE: + ev3_persistent_data.bootloader_update_flag = BOOTLOADER_UPDATE_MODE_VALUE; + PBDRV_FALL_THROUGH; default: + // PBDRV_RESET_ACTION_RESET + + ev3_persistent_data.reset_reason_flag = RESET_REASON_FLAG_SOFT_RESET; + + // Poke the watchdog timer with a bad value to immediately trigger it + HWREG(SOC_TMR_1_REGS + TMR_WDTCR) = 0; break; } } } pbdrv_reset_reason_t pbdrv_reset_get_reason(void) { + if (saved_reset_reason_flag == RESET_REASON_FLAG_SOFT_RESET) { + return PBDRV_RESET_REASON_SOFTWARE; + } + if (saved_reset_reason_flag == RESET_REASON_FLAG_WDT) { + return PBDRV_RESET_REASON_WATCHDOG; + } return PBDRV_RESET_REASON_NONE; } +void pbdrv_reset_ev3_early_init(void) { + pbdrv_gpio_out_high(&poweroff_pin); +} + void pbdrv_reset_power_off(void) { pbdrv_gpio_out_low(&poweroff_pin); } diff --git a/lib/pbio/drv/reset/reset_ev3.h b/lib/pbio/drv/reset/reset_ev3.h new file mode 100644 index 000000000..fecca623c --- /dev/null +++ b/lib/pbio/drv/reset/reset_ev3.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +// The EV3 requires a GPIO pin to be set in order to stay powered on. +// We want to be able to do this as early as possible. + +#ifndef _INTERNAL_PBDRV_RESET_EV3_H_ +#define _INTERNAL_PBDRV_RESET_EV3_H_ + +void pbdrv_reset_ev3_early_init(void); + +#endif // _INTERNAL_PBDRV_RESET_EV3_H_ diff --git a/lib/pbio/drv/watchdog/watchdog_ev3.c b/lib/pbio/drv/watchdog/watchdog_ev3.c new file mode 100644 index 000000000..b054d6387 --- /dev/null +++ b/lib/pbio/drv/watchdog/watchdog_ev3.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +// Watchdog timer driver for EV3. + +#include + +#if PBDRV_CONFIG_WATCHDOG_EV3 + +#include +#include + +// The input to Timer1 is PLL0_AUXCLK which is 24 MHz +// Configure the timeout to be 3 seconds +#define WDT_TIMEOUT_SECONDS 3ull +#define WDT_PERIOD_LSB ((WDT_TIMEOUT_SECONDS * SOC_ASYNC_2_FREQ) & 0xffffffff) +#define WDT_PERIOD_MSB (((WDT_TIMEOUT_SECONDS * SOC_ASYNC_2_FREQ) >> 32) & 0xffffffff) + +void pbdrv_watchdog_init(void) { + TimerDisable(SOC_TMR_1_REGS, TMR_TIMER_BOTH); + TimerConfigure(SOC_TMR_1_REGS, TMR_CFG_64BIT_WATCHDOG); + TimerPeriodSet(SOC_TMR_1_REGS, TMR_TIMER12, WDT_PERIOD_LSB); + TimerPeriodSet(SOC_TMR_1_REGS, TMR_TIMER34, WDT_PERIOD_MSB); + TimerWatchdogActivate(SOC_TMR_1_REGS); +} + +void pbdrv_watchdog_update(void) { + TimerWatchdogReactivate(SOC_TMR_1_REGS); +} + +#endif // PBDRV_CONFIG_WATCHDOG_EV3 diff --git a/lib/pbio/include/pbdrv/compiler.h b/lib/pbio/include/pbdrv/compiler.h new file mode 100644 index 000000000..e665b1643 --- /dev/null +++ b/lib/pbio/include/pbdrv/compiler.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025 The Pybricks Authors + +#ifndef _PBDRV_COMPILER_H_ + +// Marks a switch case that intentionally falls through to the next one +#define PBDRV_FALL_THROUGH __attribute__((fallthrough)) + +// Forces the compiler to not reorder memory access around this line +#define pbdrv_compiler_memory_barrier() __asm__ volatile ("" ::: "memory") + +#endif diff --git a/lib/pbio/platform/ev3/pbdrvconfig.h b/lib/pbio/platform/ev3/pbdrvconfig.h index 233001610..4a5e10efc 100644 --- a/lib/pbio/platform/ev3/pbdrvconfig.h +++ b/lib/pbio/platform/ev3/pbdrvconfig.h @@ -89,3 +89,6 @@ #define PBDRV_CONFIG_STACK (1) #define PBDRV_CONFIG_STACK_EMBEDDED (1) + +#define PBDRV_CONFIG_WATCHDOG (1) +#define PBDRV_CONFIG_WATCHDOG_EV3 (1) diff --git a/lib/pbio/platform/ev3/platform.c b/lib/pbio/platform/ev3/platform.c index e798523e2..ff2cd5212 100644 --- a/lib/pbio/platform/ev3/platform.c +++ b/lib/pbio/platform/ev3/platform.c @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -64,8 +65,8 @@ #include "../../drv/led/led_dual.h" #include "../../drv/led/led_pwm.h" #include "../../drv/pwm/pwm_ev3.h" +#include "../../drv/reset/reset_ev3.h" #include "../../drv/uart/uart_ev3.h" -#include "../../drv/reset/reset.h" enum { LED_DEV_0_STATUS, @@ -489,6 +490,12 @@ void ev3_panic_handler(int except_type, ev3_panic_ctx *except_data) { panic_putu32(except_data->spsr); panic_puts("\r\nSystem will now reboot...\r\n"); + + // Poke the watchdog timer with a bad value to immediately trigger it + // if it has already been configured. If it has *not* been configured, + // that means we are crashing in early boot, and we let the jump back + // to the reset vector take care of rebooting the system. + HWREG(SOC_TMR_1_REGS + TMR_WDTCR) = 0; } /** @@ -663,7 +670,7 @@ void SystemInit(void) { // Must set the power enable bin before disabling the pull up on the power // pin below, otherwise the hub will power off. - pbdrv_reset_init(); + pbdrv_reset_ev3_early_init(); // Disable all pull-up/pull-down groups. HWREG(SOC_SYSCFG_1_REGS + SYSCFG1_PUPD_ENA) &= ~0xFFFFFFFF; diff --git a/lib/pbio/platform/ev3/platform.ld b/lib/pbio/platform/ev3/platform.ld index 99f84a196..4eb875f8e 100644 --- a/lib/pbio/platform/ev3/platform.ld +++ b/lib/pbio/platform/ev3/platform.ld @@ -9,13 +9,19 @@ MEMORY SRAM_PRU1 (rw) : ORIGIN = 0x80010000, LENGTH = 64K DDR_unused (rwx) : ORIGIN = 0xC0000000, LENGTH = 0x8000 DDR (rwx) : ORIGIN = 0xC0008000, LENGTH = (64M - 0x8000) - ARM_LRAM (rwx) : ORIGIN = 0xFFFF0000, LENGTH = 8K + ARM_LRAM (rwx) : ORIGIN = 0xFFFF0000, LENGTH = (8K - 16) } _minimal_stack_size = 4M; pbdrv_stack_end = ORIGIN(DDR) + LENGTH(DDR) - 4; /* Extra heap for large allocations (images, etc). */ pb_umm_heap_size = 2M; +/* +We declare this in this style rather than creating a section. +If we do create a section, the loader (U-boot) will always clear it. +This defeats what we are trying to do to determine reset causes. +*/ +ev3_persistent_data = ORIGIN(ARM_LRAM) + LENGTH(ARM_LRAM); SECTIONS {