diff --git a/Makefile b/Makefile index 1bb84463b0..e515364975 100644 --- a/Makefile +++ b/Makefile @@ -490,6 +490,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog + @$(MD5SUM) test.hex # test simulated boards on play.tinygo.org ifneq ($(WASM), 0) $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 diff --git a/src/examples/watchdog/main.go b/src/examples/watchdog/main.go new file mode 100644 index 0000000000..120f16cd1d --- /dev/null +++ b/src/examples/watchdog/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "machine" + "time" +) + +const ( + // CONFIG: change to true to demonstrate a second source + // that needs to keep the CPU from resetting + demoExtraSource = true +) + +const ( + watchdogSourceMain = 0 + watchdogSourceImportantRoutine = 1 +) + +func main() { + //sleep for 2 secs for console + time.Sleep(2 * time.Second) + + config := machine.WatchdogConfig{ + TimeoutMillis: 1000, + ExtraSources: 0, + } + + if demoExtraSource { + config.ExtraSources = 1 + println("demo extra source: enabled") + } else { + println("demo extra source: disabled") + } + + machine.Watchdog.Configure(config) + + // From this point the watchdog is running and Update must be + // called periodically, per the config + machine.Watchdog.Start() + + if demoExtraSource { + go importantRoutine() + } + + // This loop should complete because watchdog update is called + // every 100ms, unless the 'importantRoutine' dies. + start := time.Now() + println("asserting source 0 for 3 secs") + for i := 0; i < 30; i++ { + time.Sleep(100 * time.Millisecond) + machine.Watchdog.Update(watchdogSourceMain) + fmt.Printf("alive @ %v\n", time.Now().Sub(start)) + } + + // This loop should cause a watchdog reset after 1s since + // there is no update call. + start = time.Now() + println("entering tight loop") + for { + time.Sleep(100 * time.Millisecond) + fmt.Printf("alive @ %v\n", time.Now().Sub(start)) + } +} + +// This is an important goroutine that must keep running - if it fails, +// the watchdog will force a reset. +// +// This example dies after 1 sec, so the CPU should be reset at 2 secs +// into the first loop of main. +func importantRoutine() { + for i := 0; i < 10; i++ { + time.Sleep(100 * time.Millisecond) + machine.Watchdog.Update(watchdogSourceImportantRoutine) + } +} diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 7c4c4d07c7..c5c04badee 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -2299,3 +2299,81 @@ func checkFlashError() error { return nil } + +// Watchdog provides access to the hardware watchdog available +// in the SAMD51. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxSources is 8 + // + // SAMD51 only supports a single source in hardware, so + // the support for multiple sources is in software. + WatchdogMaxSources = 8 + + // WatchdogMaxTimeout in milliseconds (16s) + WatchdogMaxTimeout = (16384 * 1000) / 1024 // CYC16384/1024kHz +) + +type watchdogImpl struct { + // RP2040 doesn't natively support multiple sources, so + // emulate up to 8. + sourceStatus uint8 + + // Mask to detect when all sources are asserted. + sourceMask uint8 +} + +var ( + errInvalidParameter = errors.New("watchdog: invalid parameter") +) + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + if config.ExtraSources > 7 { + return errInvalidParameter + } + wd.sourceMask = (1 << (config.ExtraSources + 1)) - 1 + + // 1.024kHz clock + cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) + + // period is expressed as a power-of-two, starting at 8 / 1024ths of a second + period := uint8(0) + cfgCycles := 8 + for cfgCycles < cycles { + period++ + cfgCycles <<= 1 + + if period >= 0xB { + break + } + } + + sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + sam.WDT.CTRLA.SetBits(sam.WDT_CTRLA_ENABLE) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +// +// By default there is a single source `0`, so do +// `machine.Watchdog.Update(0)` +func (wd *watchdogImpl) Update(source int) { + wd.sourceStatus |= (1 << source) + if wd.sourceStatus&wd.sourceMask == wd.sourceMask { + // 0xA5 = magic value (see datasheet) + sam.WDT.CLEAR.Set(0xA5) + wd.sourceStatus = 0 + } +} diff --git a/src/machine/machine_nrf528xx.go b/src/machine/machine_nrf528xx.go index 019a66cf1b..7aeec84269 100644 --- a/src/machine/machine_nrf528xx.go +++ b/src/machine/machine_nrf528xx.go @@ -214,3 +214,57 @@ func twisError(val uint32) error { return errI2CBusError } + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxSources is 8 + // + // SAMD51 only supports a single source in hardware, so + // the support for multiple sources is in software. + WatchdogMaxSources = 8 + + // WatchdogMaxTimeout in milliseconds (approx 36h) + WatchdogMaxTimeout = (0xffffffff * 1000) / 32768 +) + +type watchdogImpl struct { + numSources int +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + wd.numSources = config.ExtraSources + 1 + + // 32.768kHz counter + crv := int32((int64(config.TimeoutMillis) * 32768) / 1000) + nrf.WDT.CRV.Set(uint32(crv)) + + rren := uint32(1< 7 { + return errInvalidParameter + } + wd.sourceMask = (1 << (config.ExtraSources + 1)) - 1 + + // x2 due to errata RP2040-E1 + wd.loadValue = config.TimeoutMillis * 1000 * 2 + if wd.loadValue > rp.WATCHDOG_LOAD_LOAD_Msk { + wd.loadValue = rp.WATCHDOG_LOAD_LOAD_Msk + } + + rp.WATCHDOG.CTRL.ClearBits(rp.WATCHDOG_CTRL_ENABLE) + + // Reset everything apart from ROSC and XOSC + rp.PSM.WDSEL.Set(0x0001ffff &^ (rp.PSM_WDSEL_ROSC | rp.PSM_WDSEL_XOSC)) + + // Pause watchdog during debug + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_PAUSE_DBG0 | rp.WATCHDOG_CTRL_PAUSE_DBG1 | rp.WATCHDOG_CTRL_PAUSE_JTAG) + + // Load initial counter + rp.WATCHDOG.LOAD.Set(wd.loadValue) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_ENABLE) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +// +// By default there is a single source `0`, so do +// `machine.Watchdog.Update(0)` +func (wd *watchdogImpl) Update(source int) { + wd.sourceStatus |= (1 << source) + if wd.sourceStatus&wd.sourceMask == wd.sourceMask { + rp.WATCHDOG.LOAD.Set(wd.loadValue) + wd.sourceStatus = 0 + } +} // startTick starts the watchdog tick. // cycles needs to be a divider that when applied to the xosc input, // produces a 1MHz clock. So if the xosc frequency is 12MHz, // this will need to be 12. -func (wd *watchdogType) startTick(cycles uint32) { - wd.tick.Set(cycles | rp.WATCHDOG_TICK_ENABLE) +func (wd *watchdogImpl) startTick(cycles uint32) { + rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE) } diff --git a/src/machine/watchdog.go b/src/machine/watchdog.go new file mode 100644 index 0000000000..1d65d1217a --- /dev/null +++ b/src/machine/watchdog.go @@ -0,0 +1,52 @@ +//go:build nrf52840 || nrf52833 || rp2040 || atsamd51 || atsame5x + +package machine + +// WatchdogConfig holds configuration for the watchdog timer. +type WatchdogConfig struct { + // The timeout (in milliseconds) before the watchdog fires. + // + // If the requested timeout exceeds `MaxTimeout` it will be rounded + // down. + TimeoutMillis uint32 + + // ExtraSources enables the the watchdog to only refresh when multiple + // independant sources request it. + // + // Use this if there are multiple critical loops that must all be + // functioning correctly. If any source does not call Update within + // the watchdog timeout, the CPU will reset. + // + // Source 0 is implied, so `Update(0)`` must always be called periodically + // once the watchdog is starting, setting `ExtraSources`` to `1` means that + // `Update(0)` and `Update(1)`` must both be called periodically to avoid a + // reset. + ExtraSources int +} + +// watchdog must be implemented by any platform supporting watchdog functionality +type watchdog interface { + // Configure the watchdog. + // + // This method should not be called after the watchdog is started and on + // some platforms attempting to reconfigure after starting the watchdog + // is explicitly forbidden / will not work. + Configure(config WatchdogConfig) error + + // Starts the watchdog. + Start() error + + // Update the watchdog, indicating that `source` is healthy. + Update(source int) +} + +// Ensure required public symbols var exists and meets interface spec +var ( + _ = watchdog(Watchdog) +) + +// Ensure required public constants exist +const ( + _ = WatchdogMaxSources + _ = WatchdogMaxTimeout +)