Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions target/ast10x0/peripherals/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ rust_library(
"spimonitor/traits.rs",
"spimonitor/types.rs",
"uart/mod.rs",
"wdt/mod.rs",
"wdt/registers.rs",
"wdt/types.rs",
],
crate_name = "ast10x0_peripherals",
crate_root = "lib.rs",
Expand Down
1 change: 1 addition & 0 deletions target/ast10x0/peripherals/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ pub mod sgpiom;
pub mod smc;
pub mod spimonitor;
pub mod uart;
pub mod wdt;
110 changes: 110 additions & 0 deletions target/ast10x0/peripherals/wdt/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! AST10x0 watchdog peripheral driver.
//!
//! The AST10x0 provides four independent watchdog timers clocked at 1 MHz.
//! Obtain an instance via one of the `WdtRegisters` constructors (all unsafe),
//! wrap it in a [`Watchdog`], then [`start`](Watchdog::start) the timer and
//! [`feed`](Watchdog::feed) it before the window elapses.
//!
//! ```no_run
//! use peripherals::wdt::{ResetMode, Watchdog, WdtConfig, WdtRegisters};
//!
//! // SAFETY: single owner of WDT0 for the lifetime of `wdt`.
//! let mut wdt = Watchdog::new(unsafe { WdtRegisters::new_wdt0() });
//! wdt.start(WdtConfig {
//! timeout_ms: 1000,
//! reset_on_timeout: true,
//! reset_mode: ResetMode::SocSystem,
//! })
//! .unwrap();
//! wdt.feed();
//! ```

mod registers;
mod types;

pub use registers::WdtRegisters;
pub use types::{ResetMode, WdtConfig, WdtError, WDT_CLOCK_HZ};

use types::RESTART_MAGIC;

/// Blocking AST10x0 watchdog driver bound to one watchdog instance.
pub struct Watchdog {
regs: WdtRegisters,
}

impl Watchdog {
/// Wrap a watchdog register accessor in a driver.
#[must_use]
pub const fn new(regs: WdtRegisters) -> Self {
Self { regs }
}

/// Program the reload window and start counting.
///
/// The counter is loaded from the reload value and the timer is enabled with
/// the requested reset behavior. Returns [`WdtError::InvalidTimeout`] if the
/// window does not fit the 32-bit counter.
pub fn start(&mut self, config: WdtConfig) -> Result<(), WdtError> {
let ticks = config.reload_ticks()?;
let regs = self.regs.regs();

// Load the reload value, then trigger a reload so the counter starts from it.
regs.wdt004().write(|w| unsafe { w.bits(ticks) });
regs.wdt008().write(|w| unsafe { w.bits(RESTART_MAGIC) });

regs.wdt00c().write(|w| {
match config.reset_mode {
ResetMode::SocSystem => w
.rst_sys_mode()
.soc_system_ewvergated_by_reset_mask_registers(),
ResetMode::FullChip => w.rst_sys_mode().full_chip(),
ResetMode::CpuFmcOnly => w
.rst_sys_mode()
.cpufmc_only_just_reboot_firmware_no_any_other_ips_will_be_reset(),
};
if config.reset_on_timeout {
w.rst_sys_after_timeout().enable();
} else {
w.rst_sys_after_timeout().disable();
}
w.wdtenbl_sig().enable()
});

Ok(())
}

/// Reload the counter, restarting the timeout window.
pub fn feed(&mut self) {
self.regs
.regs()
.wdt008()
.write(|w| unsafe { w.bits(RESTART_MAGIC) });
}

/// Stop the watchdog by clearing its enable bit.
pub fn disable(&mut self) {
self.regs
.regs()
.wdt00c()
.modify(|_, w| w.wdtenbl_sig().disable());
}

/// Report whether the watchdog counter has reached zero at least once.
#[must_use]
pub fn is_timeout(&self) -> bool {
self.regs
.regs()
.wdt010()
.read()
.indicate_timeout()
.is_timeout_occur()
}

/// Clear the latched timeout / interrupt status.
pub fn clear_timeout(&mut self) {
self.regs.regs().wdt014().write(|w| unsafe { w.bits(0x01) });
}
}
88 changes: 88 additions & 0 deletions target/ast10x0/peripherals/wdt/registers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! AST10x0 watchdog low-level register accessor.

use ast1060_pac as device;
use core::marker::PhantomData;

/// Safe wrapper around one AST10x0 watchdog register block.
///
/// The SoC exposes four independent watchdog instances (`WDT0`–`WDT3`) at a
/// `0x80` stride; each is reached through its own constructor.
pub struct WdtRegisters {
base: *const device::wdt::RegisterBlock,
/// Prevent `Send` and `Sync`.
///
/// MMIO register blocks must not be transferred across threads or
/// shared by reference due to potential side effects and lack of
/// synchronization guarantees.
_not_send_sync: PhantomData<*const ()>,
}

impl WdtRegisters {
/// Create a register accessor from a raw watchdog register block pointer.
///
/// # Safety
///
/// - `base` must be a valid, non-null pointer to an AST1060 watchdog register block.
/// - The block must remain valid for the lifetime of this value.
/// - Caller must enforce exclusive (or otherwise coordinated) access to the
/// register block for the duration of use.
pub const unsafe fn new(base: *const device::wdt::RegisterBlock) -> Self {
Self {
base,
_not_send_sync: PhantomData,
}
}

/// Create a register accessor for watchdog instance 0 (`0x7e78_5000`).
///
/// # Safety
///
/// Caller must ensure exclusive access to the singleton `WDT0` peripheral is
/// coordinated for the lifetime of this value.
pub unsafe fn new_wdt0() -> Self {
// SAFETY: Caller upholds the singleton access contract.
unsafe { Self::new(device::Wdt::ptr()) }
}

/// Create a register accessor for watchdog instance 1 (`0x7e78_5080`).
///
/// # Safety
///
/// Caller must ensure exclusive access to the singleton `WDT1` peripheral is
/// coordinated for the lifetime of this value.
pub unsafe fn new_wdt1() -> Self {
// SAFETY: Caller upholds the singleton access contract.
unsafe { Self::new(device::Wdt1::ptr()) }
}

/// Create a register accessor for watchdog instance 2 (`0x7e78_5100`).
///
/// # Safety
///
/// Caller must ensure exclusive access to the singleton `WDT2` peripheral is
/// coordinated for the lifetime of this value.
pub unsafe fn new_wdt2() -> Self {
// SAFETY: Caller upholds the singleton access contract.
unsafe { Self::new(device::Wdt2::ptr()) }
}

/// Create a register accessor for watchdog instance 3 (`0x7e78_5180`).
///
/// # Safety
///
/// Caller must ensure exclusive access to the singleton `WDT3` peripheral is
/// coordinated for the lifetime of this value.
pub unsafe fn new_wdt3() -> Self {
// SAFETY: Caller upholds the singleton access contract.
unsafe { Self::new(device::Wdt3::ptr()) }
}

#[inline]
pub(crate) fn regs(&self) -> &device::wdt::RegisterBlock {
// SAFETY: Constructor guarantees a valid, non-null register block pointer.
unsafe { &*self.base }
}
}
54 changes: 54 additions & 0 deletions target/ast10x0/peripherals/wdt/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! AST10x0 watchdog types and configuration.

/// Frequency of the AST10x0 watchdog counter clock (1 MHz), so one reload tick
/// equals one microsecond.
pub const WDT_CLOCK_HZ: u32 = 1_000_000;

/// Magic value written to the restart register to reload the counter.
pub(crate) const RESTART_MAGIC: u32 = 0x0000_4755;

/// Watchdog driver errors.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WdtError {
/// The requested timeout is zero or maps to a reload value that does not fit
/// in the 32-bit counter.
InvalidTimeout,
}

/// Selects what the watchdog resets when the counter expires.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ResetMode {
/// Reset the SoC system, gated by the reset-mask registers.
#[default]
SocSystem,
/// Full-chip reset.
FullChip,
/// Reboot only the CPU/FMC firmware; other IPs keep running.
CpuFmcOnly,
}

/// Watchdog start-up configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WdtConfig {
/// Timeout window in milliseconds.
pub timeout_ms: u32,
/// Drive a hardware reset when the counter expires. When `false`, expiry
/// only latches the timeout status (and any enabled interrupt).
pub reset_on_timeout: bool,
/// What to reset when `reset_on_timeout` is set.
pub reset_mode: ResetMode,
}

impl WdtConfig {
/// Convert the configured millisecond window into counter reload ticks.
pub(crate) fn reload_ticks(&self) -> Result<u32, WdtError> {
let ticks = u64::from(self.timeout_ms) * u64::from(WDT_CLOCK_HZ / 1000);
if ticks == 0 || ticks > u64::from(u32::MAX) {
return Err(WdtError::InvalidTimeout);
}
Ok(ticks as u32)
}
}
73 changes: 73 additions & 0 deletions target/ast10x0/tests/peripherals/wdt/wdt_smoke/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image", "system_image_test")
load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen")
load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script")
load("@pigweed//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH")

system_image(
name = "wdt_smoke",
kernel = ":target",
platform = "//target/ast10x0",
system_config = ":system_config",
tags = ["kernel"],
target_compatible_with = TARGET_COMPATIBLE_WITH,
userspace = False,
)

system_image_test(
name = "wdt_smoke_test",
image = ":wdt_smoke",
target_compatible_with = TARGET_COMPATIBLE_WITH,
)

rust_binary_no_panics_test(
name = "no_panics_test",
binary = ":wdt_smoke",
tags = ["kernel"],
)

filegroup(
name = "system_config",
srcs = ["system.json5"],
)

target_codegen(
name = "codegen",
arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
system_config = ":system_config",
target_compatible_with = TARGET_COMPATIBLE_WITH,
)

target_linker_script(
name = "linker_script",
system_config = ":system_config",
tags = ["kernel"],
target_compatible_with = TARGET_COMPATIBLE_WITH,
template = "//target/ast10x0:linker_script_template",
)

rust_binary(
name = "target",
srcs = ["target.rs"],
edition = "2024",
tags = ["kernel"],
target_compatible_with = TARGET_COMPATIBLE_WITH,
deps = [
":codegen",
":linker_script",
"//hal/blocking",
"//target/ast10x0:entry",
"//target/ast10x0/peripherals",
"@ast1060_pac",
"@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m",
"@pigweed//pw_kernel/kernel",
"@pigweed//pw_kernel/subsys/console:console_backend",
"@pigweed//pw_kernel/target:target_common",
"@pigweed//pw_log/rust:pw_log",
"@rust_crates//:cortex-m-semihosting",
],
)
17 changes: 17 additions & 0 deletions target/ast10x0/tests/peripherals/wdt/wdt_smoke/system.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

// AST10x0 Kernel watchdog smoke test configuration.
{
arch: {
type: "armv7m",
vector_table_start_address: 0x00000000,
vector_table_size_bytes: 1280,
},
kernel: {
flash_start_address: 0x00000500,
flash_size_bytes: 262144,
ram_start_address: 0x00040500,
ram_size_bytes: 391936, // ends at RAM_NC boundary (0x000A0000)
},
}
Loading