blob: 35c53b1fb61154fd79c2d82883f70caff63779f8 [file]
// Copyright 2025 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.
use std::collections::{BTreeMap, HashMap, HashSet};
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use crate::ArchConfigInterface;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct SystemConfig<A: ArchConfigInterface> {
pub arch: A,
#[serde(flatten)]
pub base: BaseConfig,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct BaseConfig {
pub kernel: KernelConfig,
#[serde(default)]
pub apps: Vec<AppConfig>,
#[serde(skip_deserializing)]
pub arch_crate_name: &'static str,
#[serde(skip_deserializing)]
pub userspace: bool,
#[serde(skip_deserializing)]
pub bare_interrupt_table_entries: bool,
}
impl BaseConfig {
#[must_use]
pub fn get_app(&self, app_name: &str) -> Option<&AppConfig> {
self.apps.iter().find(|a| a.name == app_name)
}
#[must_use]
pub fn get_process_and_app(&self, process_name: &str) -> Option<(&AppConfig, &ProcessConfig)> {
for app in &self.apps {
if let Some(p) = app.processes.iter().find(|pr| pr.name == process_name) {
return Some((app, p));
}
}
None
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct NvicConfig {
pub vector_table_start_address: u64,
pub vector_table_size_bytes: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Armv8MConfig {
#[serde(flatten)]
pub nvic: NvicConfig,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Armv7MConfig {
#[serde(flatten)]
pub nvic: NvicConfig,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct RiscVConfig;
use crate::mpu_validation::MpuValidationMode;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct KernelConfig {
pub flash_start_address: u64,
pub flash_size_bytes: u64,
pub ram_start_address: u64,
pub ram_size_bytes: u64,
pub interrupt_table: Option<InterruptTableConfig>,
/// MPU validation mode for memory layout compatibility checking.
/// - "strict": Fail build on any MPU issues
/// - "warn": Emit warnings but continue (default)
/// - "permissive": Silent
#[serde(default)]
pub mpu_validation: MpuValidationMode,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct InterruptTableConfig {
/// User defined table of interrupt handlers, mapping
/// IRQ to interrupt handler.
pub table: BTreeMap<String, String>,
#[serde(skip_deserializing)]
pub table_size: usize,
/// Complete table of all interrupt handlers in the system
/// composed of both user defined handlers from `table` and
/// code generated handlers.
#[serde(skip_deserializing)]
pub combined_ordered_table: BTreeMap<u32, String>,
#[serde(skip_deserializing)]
pub link_section: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ConstConfig {
Bool { name: String, value: bool },
Char { name: String, value: char },
U8 { name: String, value: u8 },
U16 { name: String, value: u16 },
U32 { name: String, value: u32 },
U64 { name: String, value: u64 },
U128 { name: String, value: u128 },
Usize { name: String, value: usize },
I8 { name: String, value: i8 },
I16 { name: String, value: i16 },
I32 { name: String, value: i32 },
I64 { name: String, value: i64 },
I128 { name: String, value: i128 },
Isize { name: String, value: isize },
String { name: String, value: String },
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct AppConfig {
pub name: String,
pub flash_size_bytes: u64,
#[serde(skip_deserializing)]
pub ram_size_bytes: u64,
pub processes: Vec<ProcessConfig>,
#[serde(default)]
pub constants: Vec<ConstConfig>,
// The following fields are calculated, not defined by a user.
// TODO: davidroth - if this becomes too un-wieldy, we should
// split the config schema from the template structs.
#[serde(skip_deserializing)]
pub flash_start_address: u64,
#[serde(skip_deserializing)]
pub ram_start_address: u64,
#[serde(skip_deserializing)]
pub start_fn_address: u64,
#[serde(skip_deserializing)]
pub initial_sp: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ProcessConfig {
pub name: String,
pub ram_size_bytes: u64,
#[serde(skip_deserializing)]
pub ram_start_address: u64,
#[serde(skip_deserializing)]
pub main_thread_name: Option<String>,
#[serde(default)]
pub memory_mappings: Vec<MemoryMapping>,
#[serde(default)]
pub objects: Vec<ObjectConfig>,
}
impl ProcessConfig {
pub fn threads(&self) -> impl Iterator<Item = &ThreadObjectConfig> {
self.objects.iter().filter_map(|object| match object {
ObjectConfig::Thread(thread) => Some(thread),
_ => None,
})
}
pub fn threads_mut(&mut self) -> impl Iterator<Item = &mut ThreadObjectConfig> {
self.objects.iter_mut().filter_map(|object| match object {
&mut ObjectConfig::Thread(ref mut thread) => Some(thread),
_ => None,
})
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MemoryMappingType {
Device,
ReadOnlyExecutable,
ReadWriteData,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct MemoryMapping {
pub name: String,
#[serde(rename = "type")]
pub ty: MemoryMappingType,
pub start_address: u64,
pub size_bytes: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ObjectConfig {
ChannelInitiator(ChannelInitiatorConfig),
ChannelHandler(ChannelHandlerConfig),
Interrupt(InterruptConfig),
Process(ProcessObjectConfig),
Thread(ThreadObjectConfig),
WaitGroup(WaitGroupConfig),
}
impl ObjectConfig {
#[must_use]
pub fn name(&self) -> &str {
match self {
ObjectConfig::ChannelInitiator(c) => &c.name,
ObjectConfig::ChannelHandler(c) => &c.name,
ObjectConfig::Interrupt(c) => &c.name,
ObjectConfig::Process(c) => &c.name,
ObjectConfig::Thread(c) => &c.name,
ObjectConfig::WaitGroup(c) => &c.name,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ProcessObjectConfig {
pub name: String,
pub linked_process: String,
#[serde(skip_deserializing)]
#[serde(default)]
pub process_object_name: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ThreadObjectConfig {
pub name: String,
pub kernel_stack_size_bytes: Option<u64>,
pub priority: Option<String>,
#[serde(skip_deserializing)]
pub stack_size_expression: String,
/// Boolean flag to tag this thread as the main thread for the
/// parent process. If only one thread is defined this can be
/// omitted.
#[serde(default)]
pub main_thread: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ChannelInitiatorConfig {
pub name: String,
pub handler_process: String,
pub handler_object_name: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ChannelHandlerConfig {
pub name: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct IrqConfig {
pub name: String,
pub number: u32,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct InterruptConfig {
pub name: String,
pub irqs: Vec<IrqConfig>,
#[serde(skip_deserializing)]
pub handlers: HashMap<u32, String>,
#[serde(skip_deserializing)]
pub object_ref_name: String,
#[serde(skip_deserializing)]
pub interrupt_signal_map: HashMap<String, String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct WaitGroupConfig {
pub name: String,
}
impl<A: ArchConfigInterface> SystemConfig<A> {
fn handler_exists(&self, process_name: &str, object_name: &str) -> bool {
for app in &self.base.apps {
for process in &app.processes {
if process.name == process_name
&& let Some(object) = process.objects.iter().find(|o| o.name() == object_name)
&& matches!(object, ObjectConfig::ChannelHandler(_))
{
return true;
}
}
}
false
}
fn check_unique_names<'a, I>(items: I, context: &str) -> Result<()>
where
I: Iterator<Item = &'a str>,
{
let mut names = HashSet::new();
for name in items {
if !names.insert(name) {
return Err(anyhow!("Duplicate name `{}` found in {}", name, context));
}
}
Ok(())
}
pub fn calculate_and_validate(&mut self) -> Result<()> {
// Before generic calculations and validations are done, let the Arch
// specific interface do its own validation and fixups.
self.arch.calculate_and_validate_config(&mut self.base)?;
for app_config in &mut self.base.apps {
for process in &mut app_config.processes {
let alignment = self.arch.get_ram_alignment(process.ram_size_bytes);
if process.ram_size_bytes % alignment != 0 {
return Err(anyhow!(
"RAM size ({}) for process {} must be aligned to {} bytes",
process.ram_size_bytes,
process.name,
alignment
));
}
for thread in &mut process.threads_mut() {
if thread.priority.is_none() {
thread.priority = Some("DEFAULT_PRIORITY".to_string());
}
thread.stack_size_expression = match thread.kernel_stack_size_bytes {
None => {
"kernel::__private::kernel_config::KernelConfig::KERNEL_STACK_SIZE_BYTES"
.to_string()
}
Some(size) => format!("{:#x}", size),
};
}
}
}
Self::check_unique_names(self.base.apps.iter().map(|a| a.name.as_str()), "apps")?;
for app_config in &self.base.apps {
for process in &app_config.processes {
Self::check_unique_names(
process.memory_mappings.iter().map(|m| m.name.as_str()),
&format!(
"memory mappings for process {} in app {}",
process.name, app_config.name
),
)?;
Self::check_unique_names(
process.objects.iter().map(|o| o.name()),
&format!(
"objects for process {} in app {}",
process.name, app_config.name
),
)?;
for object in &process.objects {
if let ObjectConfig::ChannelInitiator(initiator) = object {
let handler_process = &initiator.handler_process;
let handler_object_name = &initiator.handler_object_name;
// Check to make sure that channel objects are properly linked.
if !self.handler_exists(handler_process, handler_object_name) {
return Err(anyhow!(
"Channel initiator `{app_name}:{initiator_name}` references non-existent handler `{handler_process}`:{handler_object_name}",
app_name = app_config.name,
initiator_name = initiator.name,
));
}
} else if let ObjectConfig::Interrupt(interrupt_config) = object {
Self::check_unique_names(
interrupt_config.irqs.iter().map(|i| i.name.as_str()),
&format!("irqs for interrupt object {}", interrupt_config.name),
)?;
}
}
}
}
Ok(())
}
}