blob: b209d7ff561fbbc9b8205989dd6410741cd93637 [file]
// Copyright 2026 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#![no_main]
#![no_std]
use pw_status::{Error, Result};
use supervisor_codegen::handle;
use userspace::entry;
use userspace::syscall::{self, ExitStatus, Signals};
use userspace::time::{self, Clock, Duration, SystemClock};
const SHUTDOWN_DRAIN_TIMEOUT: Duration = Duration::from_millis(100);
const SUPERVISOR_TIMEOUT: Duration = Duration::from_secs(15);
#[entry]
fn main() -> Result<()> {
pw_log::info!("Supervisor started");
let result = run_supervisor().inspect_err(|&err| {
pw_log::error!("Supervisor failed with error: {}", err as u32);
});
// Allow any IO buffers (console/UART) to be drained before shutting down.
let _ = time::sleep_until(SystemClock::now() + SHUTDOWN_DRAIN_TIMEOUT);
syscall::debug_shutdown(result)
}
// DOCSTAG: [tour]
/// Encapsulates the supervisor setup and event loop execution,
/// returning Ok(()) on successful game exit or a pw_status::Error on failures.
fn run_supervisor() -> Result<()> {
// Add both child processes to the WaitGroup, passing their handles as user_data
syscall::wait_group_add(
handle::MAIN_LOOP_WAIT_GROUP,
handle::UART_DRIVER_PROCESS,
Signals::JOINABLE,
handle::UART_DRIVER_PROCESS as usize,
)?;
syscall::wait_group_add(
handle::MAIN_LOOP_WAIT_GROUP,
handle::ADVENTURE_GAME_PROCESS,
Signals::JOINABLE,
handle::ADVENTURE_GAME_PROCESS as usize,
)?;
loop {
// Wait for a process to become joinable, retrying on timeout. This
// timeout could be used to pet a watchdog or the like.
let deadline = SystemClock::now() + SUPERVISOR_TIMEOUT;
let wait_result =
match syscall::object_wait(handle::MAIN_LOOP_WAIT_GROUP, Signals::READABLE, deadline) {
Err(Error::DeadlineExceeded) => continue,
res => res?,
};
let triggered_handle = wait_result.user_data as u32;
// Join the process and get it's `ExitStatus`.
let status = syscall::task_join(triggered_handle)?;
log_exit_status(triggered_handle, status);
// If the game process completed successfully, trigger clean system shutdown.
if triggered_handle == handle::ADVENTURE_GAME_PROCESS
&& matches!(status, ExitStatus::Success(_))
{
pw_log::info!("Supervisor shutting down system due to successful game exit.");
return Ok(());
}
// Always restart any exited process
pw_log::warn!(
"Process '{}' exited, restarting...",
process_name(triggered_handle) as &str
);
syscall::process_start(triggered_handle).inspect_err(|err| {
pw_log::error!(
"Failed to restart process '{}': {}",
process_name(triggered_handle) as &str,
*err as u32
);
})?;
}
}
// DOCSTAG: [tour]
/// Helper function to log the exact exit status/reason for a terminated process.
fn log_exit_status(process_handle: u32, status: ExitStatus) {
match status {
ExitStatus::Success(code) => {
pw_log::info!(
"Process '{}' exited cleanly with code {}",
process_name(process_handle) as &str,
code as u32
);
}
ExitStatus::UnhandledException(exception) => {
pw_log::error!(
"Process '{}' crashed due to unhandled exception {}",
process_name(process_handle) as &str,
exception as usize
);
}
ExitStatus::TerminatedBySyscall => {
pw_log::error!(
"Process '{}' was terminated by syscall",
process_name(process_handle) as &str
);
}
ExitStatus::TerminatedByKernel => {
pw_log::error!(
"Process '{}' was terminated by kernel",
process_name(process_handle) as &str
);
}
ExitStatus::ProcessTerminated => {
// ExitStatus::ProcessTerminated is only returned when joining a thread.
pw_log::error!(
"Process '{}' terminated with unexpected ExitStatus::ProcessTerminated",
process_name(process_handle) as &str
);
}
_ => {
pw_log::error!(
"Process '{}' exited with unknown/non-exhaustive status",
process_name(process_handle) as &str
);
}
}
}
/// Helper function to resolve a dynamic process handle to its human-readable name.
fn process_name(process_handle: u32) -> &'static str {
match process_handle {
handle::UART_DRIVER_PROCESS => "uart_driver",
handle::ADVENTURE_GAME_PROCESS => "adventure_game",
_ => "unknown",
}
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
pw_log::error!("PANIC: Supervisor panicked!");
syscall::process_exit(Error::Internal as u32);
}