Skip to content

Commit

Permalink
samd51,rp2040,nrf528xx: implement watchdog
Browse files Browse the repository at this point in the history
  • Loading branch information
kenbell committed Aug 10, 2023
1 parent 67ec722 commit ace7b58
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions src/examples/watchdog/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
78 changes: 78 additions & 0 deletions src/machine/machine_atsamd51.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
54 changes: 54 additions & 0 deletions src/machine/machine_nrf528xx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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<<wd.numSources) - 1
nrf.WDT.RREN.Set(rren)

nrf.WDT.CONFIG.Set(nrf.WDT_CONFIG_SLEEP_Run)

return nil
}

// Starts the watchdog.
func (wd *watchdogImpl) Start() error {
nrf.WDT.TASKS_START.Set(nrf.WDT_TASKS_START_TASKS_START)
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) {
// 0x6E524635 = magic value from datasheet
nrf.WDT.RR[source].Set(0x6E524635)
}
2 changes: 1 addition & 1 deletion src/machine/machine_rp2040_clocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) {
// Must be called before any other clock function.
func (clks *clocksType) init() {
// Start the watchdog tick
watchdog.startTick(xoscFreq)
Watchdog.startTick(xoscFreq)

// Disable resus that may be enabled from previous software
clks.resus.ctrl.Set(0)
Expand Down
95 changes: 84 additions & 11 deletions src/machine/machine_rp2040_watchdog.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,97 @@ package machine

import (
"device/rp"
"runtime/volatile"
"unsafe"
"errors"
)

type watchdogType struct {
ctrl volatile.Register32
load volatile.Register32
reason volatile.Register32
scratch [8]volatile.Register32
tick volatile.Register32
// Watchdog provides access to the hardware watchdog available
// in the RP2040.
var Watchdog = &watchdogImpl{}

const (
// WatchdogMaxSources is 8
//
// RP2040 only supports a single source in hardware, so
// the support for multiple sources is in software.
WatchdogMaxSources = 8

// WatchdogMaxTimeout in milliseconds (approx 8.3s).
//
// Nominal 1us per watchdog tick, 24-bit counter,
// but due to errata two ticks consumed per 1us.
// See: Errata RP2040-E1
WatchdogMaxTimeout = (rp.WATCHDOG_LOAD_LOAD_Msk / 1000) / 2
)

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

// The value to reset the counter to on each Update
loadValue uint32
}

var watchdog = (*watchdogType)(unsafe.Pointer(rp.WATCHDOG))
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

// 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)
}
52 changes: 52 additions & 0 deletions src/machine/watchdog.go
Original file line number Diff line number Diff line change
@@ -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
)

0 comments on commit ace7b58

Please sign in to comment.