Skip to content
Merged
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
23 changes: 23 additions & 0 deletions services/orchestrator/server/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@rules_rust//rust:defs.bzl", "rust_library")
load("//target/ast10x0:defs.bzl", "TARGET_COMPATIBLE_WITH")

rust_library(
name = "orchestrator_server",
srcs = [
"src/lib.rs",
"src/runtime.rs",
],
crate_name = "openprot_orchestrator_server",
edition = "2024",
tags = ["kernel"],
target_compatible_with = TARGET_COMPATIBLE_WITH,
visibility = ["//visibility:public"],
deps = [
"//services/orchestrator/sm:orchestrator_sm",
"//services/orchestrator/timer:orchestrator_timer",
"@pigweed//pw_kernel/userspace",
],
)
19 changes: 19 additions & 0 deletions services/orchestrator/server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! Orchestrator server: the in-process runtime that drives the pure
//! [`openprot_orchestrator_sm`] state machine.
//!
//! Orchestrator-sm names timeouts as [`Event`](openprot_orchestrator_sm::Event)s
//! but owns no clock. [`TimerManager`] lives here, in the same process, and
//! multiplexes orchestrator-sm's boot and commit watchdogs onto the single
//! deadline the runtime's `object_wait` already accepts — no separate timer
//! task, no IPC on the arm/cancel path.

#![no_std]
#![forbid(unsafe_code)]

pub mod runtime;

pub use openprot_orchestrator_timer::{Full, TimerManager};
pub use runtime::BootWatchdogs;
84 changes: 84 additions & 0 deletions services/orchestrator/server/src/runtime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0

//! Kernel-clock binding for [`TimerManager`].
//!
//! [`BootWatchdogs`] instantiates the host-generic [`TimerManager`] with the
//! kernel's [`Instant`] and translates the run loop's relative boot/commit
//! windows into the absolute deadlines the manager tracks. The absolute
//! [`wait_deadline`](BootWatchdogs::wait_deadline) it returns is exactly the
//! argument the loop hands to `object_wait`; after each wake the loop drains
//! [`poll_expired`](BootWatchdogs::poll_expired) into orchestrator-sm.

use openprot_orchestrator_sm::{ComponentId, Event};
use openprot_orchestrator_timer::{Expired, Full, TimerManager};
use userspace::time::{Clock, Duration, Instant, SystemClock};

/// The orchestrator's watchdogs, driven by the kernel monotonic clock.
///
/// `N` bounds the boot watchdogs to the chain length, matching
/// [`TimerManager`].
pub struct BootWatchdogs<const N: usize> {
timers: TimerManager<Instant, ComponentId, N>,
}

impl<const N: usize> BootWatchdogs<N> {
pub const fn new() -> Self {
Self {
timers: TimerManager::new(),
}
}

/// Now plus `after`, saturating to [`Instant::MAX`] on overflow so a huge
/// window degrades to "wait indefinitely" rather than firing immediately.
fn deadline_in(after: Duration) -> Instant {
SystemClock::now()
.checked_add_duration(after)
.unwrap_or(Instant::MAX)
}

/// Arm (or re-arm) `id`'s boot watchdog to fire `after` from now. Returns
/// [`Full`] when a new component would exceed `N`; the run loop must
/// escalate rather than proceed with an unsupervised component.
pub fn arm_boot(&mut self, id: ComponentId, after: Duration) -> Result<(), Full> {
self.timers.arm_boot(id, Self::deadline_in(after))
}

/// Cancel `id`'s boot watchdog.
pub fn cancel_boot(&mut self, id: ComponentId) {
self.timers.cancel_boot(id);
}

/// Arm the commit watchdog to fire `after` from now.
pub fn arm_commit(&mut self, after: Duration) {
self.timers.arm_commit(Self::deadline_in(after));
}

/// Cancel the commit watchdog.
pub fn cancel_commit(&mut self) {
self.timers.cancel_commit();
}

/// Absolute deadline to pass to `object_wait`; [`Instant::MAX`] when nothing
/// is armed, so the loop blocks until a signal wakes it.
pub fn wait_deadline(&self) -> Instant {
self.timers.next_deadline().unwrap_or(Instant::MAX)
}

/// Pop the next watchdog due as of now, or `None`. Call in a loop after each
/// `object_wait` return to drain every deadline that has passed this tick.
pub fn poll_expired(&mut self) -> Option<Event> {
self.timers
.poll(SystemClock::now())
.map(|expired| match expired {
Expired::Boot(id) => Event::Timeout(id),
Expired::Commit => Event::CommitTimeout,
})
}
}

impl<const N: usize> Default for BootWatchdogs<N> {
fn default() -> Self {
Self::new()
}
}
94 changes: 94 additions & 0 deletions target/ast10x0/tests/orchestrator/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Licensed under the Apache-2.0 license
# SPDX-License-Identifier: Apache-2.0

load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app")
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")

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",
"//target/ast10x0:entry",
"@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_kernel/userspace",
"@pigweed//pw_log/rust:pw_log",
],
)

# Test app: drives the real orchestrator-sm core through the server runtime
# (BootWatchdogs) — arming boot watchdogs, blocking in object_wait on
# wait_deadline, and feeding poll_expired events back into the core.
# debug_shutdown(Ok|Err) reports.
rust_app(
name = "test_runtime",
srcs = ["main.rs"],
codegen_crate_name = "app_test_runtime",
edition = "2024",
system_config = ":system_config",
tags = ["kernel"],
target_compatible_with = TARGET_COMPATIBLE_WITH,
deps = [
"//services/orchestrator/config:orchestrator_config",
"//services/orchestrator/server:orchestrator_server",
"//services/orchestrator/sm:orchestrator_sm",
"@pigweed//pw_kernel/userspace",
"@pigweed//pw_log/rust:pw_log",
"@pigweed//pw_status/rust:pw_status",
"@rust_crates//:heapless",
],
)

system_image(
name = "runtime",
apps = [":test_runtime"],
kernel = ":target",
platform = "//target/ast10x0",
system_config = ":system_config",
tags = ["kernel"],
target_compatible_with = TARGET_COMPATIBLE_WITH,
)

system_image_test(
name = "runtime_test",
image = ":runtime",
target_compatible_with = TARGET_COMPATIBLE_WITH,
)

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