From 02be46f2335d6468e4ea3a4f1e17de74acfb21fd Mon Sep 17 00:00:00 2001 From: Ivan Velickovic Date: Tue, 18 Jun 2024 14:58:17 +1000 Subject: [PATCH 1/6] libmicrokit: remove unused define Signed-off-by: Ivan Velickovic --- libmicrokit/src/main.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/libmicrokit/src/main.c b/libmicrokit/src/main.c index 53ed337e7..4c743779b 100644 --- a/libmicrokit/src/main.c +++ b/libmicrokit/src/main.c @@ -15,8 +15,6 @@ #define INPUT_CAP 1 #define REPLY_CAP 4 -#define NOTIFICATION_BITS 57 - char _stack[4096] __attribute__((__aligned__(16))); bool passive; From bec5a771bff0938824af22193980f7872ad41888 Mon Sep 17 00:00:00 2001 From: Ivan Velickovic Date: Thu, 30 May 2024 17:14:12 +0200 Subject: [PATCH 2/6] Implement PD hierarchy Signed-off-by: Ivan Velickovic --- docs/manual.md | 51 +++++++-- libmicrokit/include/microkit.h | 45 ++++++++ libmicrokit/src/main.c | 17 ++- tool/microkit/src/main.rs | 106 ++++++++++++------ tool/microkit/src/sysxml.rs | 88 ++++++++++++++- ...than_62.xml => ch_id_greater_than_max.xml} | 0 ...han_62.xml => irq_id_greater_than_max.xml} | 0 tool/microkit/tests/test.rs | 6 +- 8 files changed, 257 insertions(+), 56 deletions(-) rename tool/microkit/tests/sdf/{ch_id_greater_than_62.xml => ch_id_greater_than_max.xml} (100%) rename tool/microkit/tests/sdf/{irq_id_greater_than_62.xml => irq_id_greater_than_max.xml} (100%) diff --git a/docs/manual.md b/docs/manual.md index 75d05079d..3a40138dc 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -97,10 +97,11 @@ This document attempts to clearly describe all of these terms, however as the co * [system](#system) * [protection domain (PD)](#pd) -* [channel](#chan) +* [channel](#channel) * [memory region](#mr) * [notification](#notification) * [protected procedure](#pp) +* [faults](#faults) ## System {#system} @@ -128,15 +129,21 @@ Microkit supports a maximum of 63 protection domains. Although a protection domain is somewhat analogous to a process, it has a considerably different program structure and life-cycle. A process on a typical operating system will have a `main` function which is invoked by the system when the process is created. When the `main` function returns the process is destroyed. -By comparison a protection domain has three entry points: `init`, `notify` and, optionally, `protected`. + +By comparison a protection domain has up to four entry points: +* `init`, `notify` which are required. +* `protected`, `fault` which are optional. When an Microkit system is booted, all PDs in the system execute the `init` entry point. The `notified` entry point will be invoked whenever the protection domain receives a *notification* on a *channel*. The `protected` entry point is invoked when a PD's *protected procedure* is called by another PD. A PD does not have to provide a protected procedure, therefore the `protected` entry point is optional. -These entry points are described in more detail in subsequent sections. +The `fault` entry point is invoked when a PD that is a child of another PD causes a fault. +A PD does not have to have child PDs, therefore the `fault` entry point is optional. + +These entry points are described in more detail in subsequent sections. **Note:** The processing of `init` entry points is **not** synchronised across protection domains. Specifically, it is possible for a high priority PD's `notified` or `protected` entry point to be called prior to the completion of a low priority PD's `init` entry point. @@ -191,7 +198,7 @@ The mapping has a number of attributes, which include: **Note:** When a memory region is mapped into multiple protection domains, the attributes used for different mapping may vary. -## Channels +## Channels {#channel} A *channel* enables two protection domains to interact using protected procedures or notifications. Each connects exactly two PDs; there are no multi-party channels. @@ -209,7 +216,7 @@ Similarly, **B** can refer to **A** via the channel identifier **42**. The system supports a maximum of 63 channels and interrupts per protection domain. -### Protected procedure +### Protected procedure {#pp} A protection domain may provide a *protected procedure* (PP) which can be invoked from another protection domain. Up to 64 words of data may be passed as arguments when calling a protected procedure. @@ -239,7 +246,7 @@ A *message* structure is returned from this function. When a PD's protected procedure is invoked, the `protected` entry point is invoked with the channel identifier and message structure passed as arguments. The `protected` entry point must return a message structure. -### Notification +### Notification {#notification} A notification is a (binary) semaphore-like synchronisation mechanism. A PD can *notify* another PD to indicate availability of data in a shared memory region if they share a channel. @@ -253,7 +260,7 @@ Unlike protected procedures, notifications can be sent in either direction on a If a PD notifies another PD, that PD will become scheduled to run (if it is not already), but the current PD does **not** block. Of course, if the notified PD has a higher priority than the current PD, then the current PD will be preempted (but not blocked) by the other PD. -## Interrupts +## Interrupts {#irqs} Hardware interrupts can be used to notify a protection domain. The system description specifies if a protection domain receives notifications for any hardware interrupt sources. @@ -268,6 +275,20 @@ Microkit does not provides timers, nor any *sleep* API. After initialisation, activity in the system is initiated by an interrupt causing a `notified` entry point to be invoked. That notified function may in turn notify or call other protection domains that cause other system activity, but eventually all activity indirectly initiated from that interrupt will complete, at which point the system is inactive again until another interrupt occurs. +## Faults {#faults} + +Faults such as an invalid memory access or illegal instruction are delivered to the seL4 kernel which then forwards them to +a designated 'fault handler'. By default, all faults caused by protection domains go to the Monitor which simply prints out +details about the fault in a debug configuration. + +When a protection domain is a child of another protection domain, the designated fault handler for the child is the parent +protection domain. + +This means that whenever a fault is caused by a child PD, it will be delivered to the parent PD instead of the monitor via the +`fault` entry point. It is then up to the parent to decide how the fault is handled. The label of the given `msginfo` can be +used to determine what kind of fault occurred. You can find more information about decoding the fault in the 'Faults' section +of the seL4 manual. + # SDK {#sdk} Microkit is distributed as a software development kit (SDK). @@ -414,6 +435,14 @@ Channel identifiers are specified in the system configuration. Acknowledge the interrupt identified by the specified channel. +## `void microkit_pd_restart(microkit_pd id, uintptr_t entry_point)` + +Restart the execution of a child protection domain with ID `id` at the given `entry_point`. +This will set the program counter of the child protection domain to `entry_point`. + +## `void microkit_pd_stop(microkit_pd id)` + +Stop the execution of the child protection domain with ID `id`. ## `microkit_msginfo microkit_msginfo_new(uint64_t label, uint16_t count)` @@ -472,6 +501,7 @@ Additionally, it supports the following child elements: * `map`: (zero or more) describes mapping of memory regions into the protection domain. * `irq`: (zero or more) describes hardware interrupt associations. * `setvar`: (zero or more) describes variable rewriting. +* `protection_domain`: (zero or more) describes a child protection domain. The `program_image` element has a single `path` attribute describing the path to an ELF file. @@ -494,6 +524,13 @@ The `setvar` element has the following attributes: * `symbol`: Name of a symbol in the ELF file. * `region_paddr`: Name of an MR. The symbol's value shall be updated to this MR's physical address. +The `protection_domain` element the same attributes as any other protection domain as well as: +* `id`: The ID of the child for the parent to refer to. + +Child protection domains have their faults handled by the parent protection domain itself instead of the Microkit monitor. +The parent protection domain is able to control the child's thread allowing it to stop it or restart it using the +Microkit API. + ## `memory_region` The `memory_region` element describes a memory region. diff --git a/libmicrokit/include/microkit.h b/libmicrokit/include/microkit.h index 076717909..86274a165 100644 --- a/libmicrokit/include/microkit.h +++ b/libmicrokit/include/microkit.h @@ -12,12 +12,14 @@ #include typedef unsigned int microkit_channel; +typedef unsigned int microkit_pd; typedef seL4_MessageInfo_t microkit_msginfo; #define MONITOR_EP 5 #define BASE_OUTPUT_NOTIFICATION_CAP 10 #define BASE_ENDPOINT_CAP 74 #define BASE_IRQ_CAP 138 +#define BASE_TCB_CAP 202 #define MICROKIT_MAX_CHANNELS 63 @@ -25,6 +27,7 @@ typedef seL4_MessageInfo_t microkit_msginfo; void init(void); void notified(microkit_channel ch); microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo); +void fault(microkit_channel ch, microkit_msginfo msginfo); extern char microkit_name[16]; /* These next three variables are so our PDs can combine a signal with the next Recv syscall */ @@ -42,6 +45,19 @@ void microkit_dbg_putc(int c); */ void microkit_dbg_puts(const char *s); +static inline void microkit_internal_crash(seL4_Error err) +{ + /* + * Currently crash be dereferencing NULL page + * + * Actually derference 'err' which means the crash reporting will have + * `err` as the fault address. A bit of a cute hack. Not a good long term + * solution but good for now. + */ + int *x = (int *)(seL4_Word) err; + *x = 0; +} + static inline void microkit_notify(microkit_channel ch) { seL4_Signal(BASE_OUTPUT_NOTIFICATION_CAP + ch); @@ -52,6 +68,35 @@ static inline void microkit_irq_ack(microkit_channel ch) seL4_IRQHandler_Ack(BASE_IRQ_CAP + ch); } +static inline void microkit_pd_restart(microkit_pd pd, seL4_Word entry_point) +{ + seL4_Error err; + seL4_UserContext ctxt = {0}; + ctxt.pc = entry_point; + err = seL4_TCB_WriteRegisters( + BASE_TCB_CAP + pd, + seL4_True, + 0, /* No flags */ + 1, /* writing 1 register */ + &ctxt + ); + + if (err != seL4_NoError) { + microkit_dbg_puts("microkit_pd_restart: error writing TCB registers\n"); + microkit_internal_crash(err); + } +} + +static inline void microkit_pd_stop(microkit_pd pd) +{ + seL4_Error err; + err = seL4_TCB_Suspend(BASE_TCB_CAP + pd); + if (err != seL4_NoError) { + microkit_dbg_puts("microkit_pd_stop: error writing TCB registers\n"); + microkit_internal_crash(err); + } +} + static inline microkit_msginfo microkit_ppcall(microkit_channel ch, microkit_msginfo msginfo) { return seL4_Call(BASE_ENDPOINT_CAP + ch, msginfo); diff --git a/libmicrokit/src/main.c b/libmicrokit/src/main.c index 4c743779b..6b9834c15 100644 --- a/libmicrokit/src/main.c +++ b/libmicrokit/src/main.c @@ -15,6 +15,9 @@ #define INPUT_CAP 1 #define REPLY_CAP 4 +#define PD_MASK 0xff +#define CHANNEL_MASK 0x3f + char _stack[4096] __attribute__((__aligned__(16))); bool passive; @@ -37,6 +40,10 @@ __attribute__((weak)) microkit_msginfo protected(microkit_channel ch, microkit_m return seL4_MessageInfo_new(0, 0, 0, 0); } +__attribute__((weak)) void fault(microkit_pd pd, microkit_msginfo msginfo) +{ +} + static void run_init_funcs(void) { size_t count = __init_array_end - __init_array_start; @@ -64,13 +71,17 @@ static void handler_loop(void) } uint64_t is_endpoint = badge >> 63; + uint64_t is_fault = (badge >> 62) & 1; + + have_reply = false; - if (is_endpoint) { + if (is_fault) { + fault(badge & PD_MASK, tag); + } else if (is_endpoint) { have_reply = true; - reply_tag = protected(badge & 0x3f, tag); + reply_tag = protected(badge & CHANNEL_MASK, tag); } else { unsigned int idx = 0; - have_reply = false; do { if (badge & 1) { notified(idx); diff --git a/tool/microkit/src/main.rs b/tool/microkit/src/main.rs index 77c8d2122..a4b4e921c 100644 --- a/tool/microkit/src/main.rs +++ b/tool/microkit/src/main.rs @@ -24,6 +24,9 @@ use util::{bytes_to_struct, struct_to_bytes, comma_sep_usize, comma_sep_u64}; // Corresponds to the IPC buffer symbol in libmicrokit and the monitor const SYMBOL_IPC_BUFFER: &str = "__sel4_ipc_buffer_obj"; +const FAULT_BADGE: u64 = 1 << 62; +const PPC_BADGE: u64 = 1 << 63; + const INPUT_CAP_IDX: u64 = 1; #[allow(dead_code)] const FAULT_EP_CAP_IDX: u64 = 2; @@ -34,6 +37,7 @@ const MONITOR_EP_CAP_IDX: u64 = 5; const BASE_OUTPUT_NOTIFICATION_CAP: u64 = 10; const BASE_OUTPUT_ENDPOINT_CAP: u64 = BASE_OUTPUT_NOTIFICATION_CAP + 64; const BASE_IRQ_CAP: u64 = BASE_OUTPUT_ENDPOINT_CAP + 64; +const BASE_TCB_CAP: u64 = BASE_IRQ_CAP + 64; const MAX_SYSTEM_INVOCATION_SIZE: u64 = util::mb(128); @@ -1194,9 +1198,8 @@ fn build_system(kernel_config: &Config, let sched_context_objs = init_system.allocate_objects(ObjectType::SchedContext, sched_context_names, Some(PD_SCHEDCONTEXT_SIZE)); let sched_context_caps: Vec = sched_context_objs.iter().map(|sc| sc.cap_addr).collect(); - let pp_protection_domains: Vec<&ProtectionDomain> = system.protection_domains.iter().filter(|pd| pd.pp).collect(); - - let pd_endpoint_names: Vec = pp_protection_domains.iter().map(|pd| format!("EP: PD={}", pd.name)).collect(); + let pds_with_endpoints: Vec<&ProtectionDomain> = system.protection_domains.iter().filter(|pd| pd.needs_ep()).collect(); + let pd_endpoint_names: Vec = pds_with_endpoints.iter().map(|pd| format!("EP: PD={}", pd.name)).collect(); let endpoint_names = [vec![format!("EP: Monitor Fault")], pd_endpoint_names].concat(); let pd_reply_names: Vec = system.protection_domains.iter().map(|pd| format!("Reply: PD={}", pd.name)).collect(); @@ -1207,11 +1210,9 @@ fn build_system(kernel_config: &Config, let pd_reply_objs = &reply_objs[1..]; let endpoint_objs = init_system.allocate_objects(ObjectType::Endpoint, endpoint_names, None); let fault_ep_endpoint_object = &endpoint_objs[0]; - let mut pp_ep_endpoint_objs: HashMap<&ProtectionDomain, &Object> = HashMap::with_capacity(pp_protection_domains.len()); - for (i, pd) in pp_protection_domains.iter().enumerate() { - // Because the first reply object is for the monitor, we map from index 1 of endpoint_objs - pp_ep_endpoint_objs.insert(pd, &endpoint_objs[1..][i]); - } + + // Because the first reply object is for the monitor, we map from index 1 of endpoint_objs + let pd_endpoint_objs: Vec<&Object> = pds_with_endpoints.iter().enumerate().map(|(i, _)| &endpoint_objs[1..][i]).collect(); let notification_names = system.protection_domains.iter().map(|pd| format!("Notification: PD={}", pd.name)).collect(); let notification_objs = init_system.allocate_objects(ObjectType::Notification, notification_names, None); @@ -1430,37 +1431,44 @@ fn build_system(kernel_config: &Config, } } - let mut invocation = Invocation::new(InvocationArgs::CnodeMint{ - cnode: system_cnode_cap, - dest_index: cap_slot, - dest_depth: system_cnode_bits, - src_root: root_cnode_cap, - src_obj: fault_ep_endpoint_object.cap_addr, - src_depth: kernel_config.cap_address_bits, - rights: Rights::All as u64, - badge: 1, - }); - invocation.repeat(system.protection_domains.len() as u32, InvocationArgs::CnodeMint{ - cnode: 0, - dest_index: 1, - dest_depth: 0, - src_root: 0, - src_obj: 0, - src_depth: 0, - rights: 0, - badge: 1, - }); - system_invocations.push(invocation); - + // Create a fault endpoint cap for each protection domain. + // For root PDs, this shall be the system fault EP endpoint object. + // For non-root PDs, this shall be the parent endpoint. let badged_fault_ep = system_cap_address_mask | cap_slot; - cap_slot += system.protection_domains.len() as u64; + for (i, pd) in system.protection_domains.iter().enumerate() { + let is_root = pd.parent.is_none(); + let fault_ep_cap; + let badge: u64; + if is_root { + fault_ep_cap = fault_ep_endpoint_object.cap_addr; + badge = i as u64 + 1; + } else { + assert!(pd.id.is_some()); + assert!(pd.parent.is_some()); + fault_ep_cap = pd_endpoint_objs[pd.parent.unwrap()].cap_addr; + badge = FAULT_BADGE | pd.id.unwrap(); + } + + let invocation = Invocation::new(InvocationArgs::CnodeMint{ + cnode: system_cnode_cap, + dest_index: cap_slot, + dest_depth: system_cnode_bits, + src_root: root_cnode_cap, + src_obj: fault_ep_cap, + src_depth: kernel_config.cap_address_bits, + rights: Rights::All as u64, + badge, + }); + system_invocations.push(invocation); + cap_slot += 1; + } let final_cap_slot = cap_slot; // Minting in the address space for (idx, pd) in system.protection_domains.iter().enumerate() { - let obj = if pd.pp { - pp_ep_endpoint_objs[pd] + let obj = if pd.needs_ep() { + pd_endpoint_objs[idx] } else { ¬ification_objs[idx] }; @@ -1544,6 +1552,30 @@ fn build_system(kernel_config: &Config, } } + // Mint access to the child TCB in the CSpace of root PDs + for (pd_idx, _) in system.protection_domains.iter().enumerate() { + for (maybe_child_idx, maybe_child_pd) in system.protection_domains.iter().enumerate() { + // Before doing anything, check if we are dealing with a child PD + if let Some(parent_idx) = maybe_child_pd.parent { + // We are dealing with a child PD, now check if the index of its parent + // matches this iteration's PD. + if parent_idx == pd_idx { + let cap_idx = BASE_TCB_CAP + maybe_child_pd.id.unwrap(); + system_invocations.push(Invocation::new(InvocationArgs::CnodeMint { + cnode: cnode_objs[pd_idx].cap_addr, + dest_index: cap_idx, + dest_depth: PD_CAP_BITS, + src_root: root_cnode_cap, + src_obj: tcb_objs[maybe_child_idx].cap_addr, + src_depth: kernel_config.cap_address_bits, + rights: Rights::All as u64, + badge: 0, + })); + } + } + } + } + for cc in &system.channels { let pd_a = &system.protection_domains[cc.pd_a]; let pd_b = &system.protection_domains[cc.pd_b]; @@ -1584,8 +1616,8 @@ fn build_system(kernel_config: &Config, // Set up the endpoint caps if pd_b.pp { let pd_a_cap_idx = BASE_OUTPUT_ENDPOINT_CAP + cc.id_a; - let pd_a_badge = (1 << 63) | cc.id_b; - let pd_b_endpoint_obj = pp_ep_endpoint_objs[pd_b]; + let pd_a_badge = PPC_BADGE | cc.id_b; + let pd_b_endpoint_obj = pd_endpoint_objs[cc.pd_b]; assert!(pd_a_cap_idx < PD_CAP_SIZE); system_invocations.push(Invocation::new(InvocationArgs::CnodeMint { @@ -1602,8 +1634,8 @@ fn build_system(kernel_config: &Config, if pd_a.pp { let pd_b_cap_idx = BASE_OUTPUT_ENDPOINT_CAP + cc.id_b; - let pd_b_badge = (1 << 63) | cc.id_a; - let pd_a_endpoint_obj = pp_ep_endpoint_objs[pd_a]; + let pd_b_badge = PPC_BADGE | cc.id_a; + let pd_a_endpoint_obj = pd_endpoint_objs[cc.pd_a]; assert!(pd_b_cap_idx < PD_CAP_SIZE); system_invocations.push(Invocation::new(InvocationArgs::CnodeMint { diff --git a/tool/microkit/src/sysxml.rs b/tool/microkit/src/sysxml.rs index 6b3fabaf6..2adae01b5 100644 --- a/tool/microkit/src/sysxml.rs +++ b/tool/microkit/src/sysxml.rs @@ -30,9 +30,10 @@ use crate::MAX_PDS; /// limited in how many IDs a Microkit protection domain has since each ID represents /// a unique bit. /// Currently the first bit is used to determine whether or not the event is a PPC -/// or notification. This means we are left with 63 bits for the ID. +/// or notification. The second bit is used to determine whether a fault occurred. +/// This means we are left with 62 bits for the ID. /// IDs start at zero. -const PD_MAX_ID: u64 = 62; +const PD_MAX_ID: u64 = 61; const PD_MAX_PRIORITY: u8 = 254; @@ -139,6 +140,8 @@ pub struct Channel { #[derive(Debug, PartialEq, Eq, Hash)] pub struct ProtectionDomain { + /// Only populated for child protection domains + pub id: Option, pub name: String, pub priority: u8, pub budget: u64, @@ -149,6 +152,13 @@ pub struct ProtectionDomain { pub maps: Vec, pub irqs: Vec, pub setvars: Vec, + /// Only used when parsing child PDs. All elements will be removed + /// once we flatten each PD and its children into one list. + pub child_pds: Vec, + pub has_children: bool, + /// Index into the total list of protection domains if a parent + /// protection domain exists + pub parent: Option, /// Location in the parsed SDF file text_pos: roxmltree::TextPos } @@ -170,11 +180,25 @@ impl SysMapPerms { } impl ProtectionDomain { - fn from_xml(xml_sdf: &XmlSystemDescription, node: &roxmltree::Node) -> Result { - check_attributes(xml_sdf, node, &["name", "priority", "pp", "budget", "period", "passive"])?; + pub fn needs_ep(&self) -> bool { + self.pp || self.has_children + } + + fn from_xml(xml_sdf: &XmlSystemDescription, node: &roxmltree::Node, is_child: bool) -> Result { + let mut attrs = vec!["name", "priority", "pp", "budget", "period", "passive"]; + if is_child { + attrs.push("id"); + } + check_attributes(xml_sdf, node, &attrs)?; let name = checked_lookup(xml_sdf, node, "name")?.to_string(); + let id = if is_child { + Some(sdf_parse_number(checked_lookup(xml_sdf, node, "id")?, node)?) + } else { + None + }; + // Default to 1000 microseconds as the budget, with the period defaulting // to being the same as the budget as well. let budget = if let Some(xml_budget) = node.attribute("budget") { @@ -206,6 +230,7 @@ impl ProtectionDomain { let mut maps = Vec::new(); let mut irqs = Vec::new(); let mut setvars = Vec::new(); + let mut child_pds = Vec::new(); let mut program_image = None; @@ -313,6 +338,7 @@ impl ProtectionDomain { vaddr: None, }) }, + "protection_domain" => child_pds.push(ProtectionDomain::from_xml(xml_sdf, &child, true)?), _ => { let pos = xml_sdf.doc.text_pos_at(child.range().start); return Err(format!("Invalid XML element '{}': {}", child.tag_name().name(), loc_string(xml_sdf, pos))); @@ -320,7 +346,10 @@ impl ProtectionDomain { } } + let has_children = !child_pds.is_empty(); + Ok(ProtectionDomain { + id, name, // This downcast is safe as we have checked that this is less than // the maximum PD priority, which fits in a u8. @@ -333,6 +362,9 @@ impl ProtectionDomain { maps, irqs, setvars, + child_pds, + has_children, + parent: None, text_pos: xml_sdf.doc.text_pos_at(node.range().start), }) } @@ -500,6 +532,48 @@ fn check_no_text(xml_sdf: &XmlSystemDescription, node: &roxmltree::Node) -> Resu Ok(()) } +fn pd_tree_to_list(xml_sdf: &XmlSystemDescription, mut root_pd: ProtectionDomain, parent: bool, idx: usize) -> Result, String> { + let mut child_ids = vec![]; + for child_pd in &root_pd.child_pds { + let child_id = child_pd.id.unwrap(); + if child_ids.contains(&child_id) { + return Err(format!("duplicate id: {} in protection domain: '{}' @ {}", child_id, root_pd.name, loc_string(xml_sdf, child_pd.text_pos))); + } + child_ids.push(child_pd.id.unwrap()); + } + + if parent { + root_pd.parent = Some(idx); + } else { + root_pd.parent = None; + } + let mut new_child_pds = vec![]; + let child_pds: Vec<_> = root_pd.child_pds.drain(0..).collect(); + for child_pd in child_pds { + new_child_pds.extend(pd_tree_to_list(xml_sdf, child_pd, true, idx)?); + } + + let mut all = vec![root_pd]; + all.extend(new_child_pds); + + Ok(all) +} + +/// Given an iterable of protection domains flatten the tree representation +/// into a flat tuple. +/// +/// In doing so the representation is changed from "Node with list of children", +/// to each node having a parent link instead. +fn pd_flatten(xml_sdf: &XmlSystemDescription, pds: Vec) -> Result, String> { + let mut all_pds = vec![]; + + for pd in pds { + all_pds.extend(pd_tree_to_list(xml_sdf, pd, false, 0)?); + } + + Ok(all_pds) +} + pub fn parse(filename: &str, xml: &str, plat_desc: &PlatformDescription) -> Result { let doc = match roxmltree::Document::parse(xml) { Ok(doc) => doc, @@ -511,7 +585,7 @@ pub fn parse(filename: &str, xml: &str, plat_desc: &PlatformDescription) -> Resu doc: &doc, }; - let mut pds = vec![]; + let mut root_pds = vec![]; let mut mrs = vec![]; let mut channels = vec![]; @@ -532,7 +606,7 @@ pub fn parse(filename: &str, xml: &str, plat_desc: &PlatformDescription) -> Resu let child_name = child.tag_name().name(); match child_name { - "protection_domain" => pds.push(ProtectionDomain::from_xml(&xml_sdf, &child)?), + "protection_domain" => root_pds.push(ProtectionDomain::from_xml(&xml_sdf, &child, false)?), "channel" => channel_nodes.push(child), "memory_region" => mrs.push(SysMemoryRegion::from_xml(&xml_sdf, &child, plat_desc)?), _ => { @@ -542,6 +616,8 @@ pub fn parse(filename: &str, xml: &str, plat_desc: &PlatformDescription) -> Resu } } + let pds = pd_flatten(&xml_sdf, root_pds)?; + for node in channel_nodes { channels.push(Channel::from_xml(&xml_sdf, &node, &pds)?); } diff --git a/tool/microkit/tests/sdf/ch_id_greater_than_62.xml b/tool/microkit/tests/sdf/ch_id_greater_than_max.xml similarity index 100% rename from tool/microkit/tests/sdf/ch_id_greater_than_62.xml rename to tool/microkit/tests/sdf/ch_id_greater_than_max.xml diff --git a/tool/microkit/tests/sdf/irq_id_greater_than_62.xml b/tool/microkit/tests/sdf/irq_id_greater_than_max.xml similarity index 100% rename from tool/microkit/tests/sdf/irq_id_greater_than_62.xml rename to tool/microkit/tests/sdf/irq_id_greater_than_max.xml diff --git a/tool/microkit/tests/test.rs b/tool/microkit/tests/test.rs index f281962c5..44ebcdc8c 100644 --- a/tool/microkit/tests/test.rs +++ b/tool/microkit/tests/test.rs @@ -138,8 +138,8 @@ mod protection_domain { } #[test] - fn test_irq_greater_than_62() { - check_error("irq_id_greater_than_62.xml", "Error: id must be < 63 on element 'irq'") + fn test_irq_greater_than_max() { + check_error("irq_id_greater_than_max.xml", "Error: id must be < 62 on element 'irq'") } #[test] @@ -174,7 +174,7 @@ mod channel { #[test] fn test_id_greater_than_max() { - check_error("ch_id_greater_than_62.xml", "Error: id must be < 63 on element 'end'") + check_error("ch_id_greater_than_max.xml", "Error: id must be < 62 on element 'end'") } #[test] From 7a5c8bbbe9a03bd9f55adfc2a3b6d26725831a1b Mon Sep 17 00:00:00 2001 From: Ivan Velickovic Date: Tue, 18 Jun 2024 14:49:39 +1000 Subject: [PATCH 3/6] tool: typo in comment fix Signed-off-by: Ivan Velickovic --- tool/microkit/src/sysxml.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/microkit/src/sysxml.rs b/tool/microkit/src/sysxml.rs index 2adae01b5..1a13d42a2 100644 --- a/tool/microkit/src/sysxml.rs +++ b/tool/microkit/src/sysxml.rs @@ -21,7 +21,7 @@ use crate::MAX_PDS; /// There are various XML parsing/deserialising libraries within the Rust eco-system /// but few seem to be concerned with giving any introspection regarding the parsed /// XML. The roxmltree project allows us to work on a lower-level than something based -/// on serde and so we can report propper user errors. +/// on serde and so we can report proper user errors. /// /// Events that come through entry points (e.g notified or protected) are given an From 73865e2e9daf7ccdb7e8725d678865a377ce35c1 Mon Sep 17 00:00:00 2001 From: Ivan Velickovic Date: Tue, 18 Jun 2024 18:14:10 +1000 Subject: [PATCH 4/6] monitor: add missing 'ERROR' prefix to logs Signed-off-by: Ivan Velickovic --- monitor/src/main.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/monitor/src/main.c b/monitor/src/main.c index 876a33dab..e300ddd63 100644 --- a/monitor/src/main.c +++ b/monitor/src/main.c @@ -464,11 +464,11 @@ static void monitor(void) puts("\n"); if (badge < MAX_PDS && pd_names[badge][0] != 0) { - puts("faulting PD: "); + puts("MON|ERROR: faulting PD: "); puts(pd_names[badge]); puts("\n"); } else { - fail("unknown/invalid badge\n"); + fail("MON|ERROR: unknown/invalid badge\n"); } seL4_UserContext regs; @@ -479,35 +479,35 @@ static void monitor(void) } // FIXME: Would be good to print the whole register set - puts("Registers: \n"); - puts("pc : "); + puts("MON|ERROR: Registers: \n"); + puts("MON|ERROR: pc : "); puthex64(regs.pc); puts("\n"); - puts("spsr : "); + puts("MON|ERROR: spsr : "); puthex64(regs.spsr); puts("\n"); - puts("x0 : "); + puts("MON|ERROR: x0 : "); puthex64(regs.x0); puts("\n"); - puts("x1 : "); + puts("MON|ERROR: x1 : "); puthex64(regs.x1); puts("\n"); - puts("x2 : "); + puts("MON|ERROR: x2 : "); puthex64(regs.x2); puts("\n"); - puts("x3 : "); + puts("MON|ERROR: x3 : "); puthex64(regs.x3); puts("\n"); - puts("x4 : "); + puts("MON|ERROR: x4 : "); puthex64(regs.x4); puts("\n"); - puts("x5 : "); + puts("MON|ERROR: x5 : "); puthex64(regs.x5); puts("\n"); - puts("x6 : "); + puts("MON|ERROR: x6 : "); puthex64(regs.x6); puts("\n"); - puts("x7 : "); + puts("MON|ERROR: x7 : "); puthex64(regs.x7); puts("\n"); @@ -522,7 +522,7 @@ static void monitor(void) seL4_Word guard_found = seL4_GetMR(seL4_CapFault_GuardMismatch_GuardFound); seL4_Word guard_bits_found = seL4_GetMR(seL4_CapFault_GuardMismatch_BitsFound); - puts("CapFault: ip="); + puts("MON|ERROR: CapFault: ip="); puthex64(ip); puts(" fault_addr="); puthex64(fault_addr); @@ -571,7 +571,7 @@ static void monitor(void) break; } case seL4_Fault_UserException: { - puts("UserException\n"); + puts("MON|ERROR: UserException\n"); break; } case seL4_Fault_VMFault: { @@ -582,7 +582,7 @@ static void monitor(void) seL4_Word ec = fsr >> 26; seL4_Word il = fsr >> 25 & 1; seL4_Word iss = fsr & 0x1ffffffUL; - puts("VMFault: ip="); + puts("MON|ERROR: VMFault: ip="); puthex64(ip); puts(" fault_addr="); puthex64(fault_addr); @@ -591,7 +591,7 @@ static void monitor(void) puts(" "); puts(is_instruction ? "(instruction fault)" : "(data fault)"); puts("\n"); - puts(" ec: "); + puts("MON|ERROR: ec: "); puthex32(ec); puts(" "); puts(ec_to_string(ec)); @@ -610,7 +610,7 @@ static void monitor(void) bool cm = (iss >> 8) & 1; bool s1ptw = (iss >> 7) & 1; bool wnr = (iss >> 6) & 1; - puts(" dfsc = "); + puts("MON|ERROR: dfsc = "); puts(data_abort_dfsc_to_string(dfsc)); puts(" ("); puthex32(dfsc); From 394f809471445f74c8187cb14859fefa057b379b Mon Sep 17 00:00:00 2001 From: Ivan Velickovic Date: Wed, 19 Jun 2024 12:44:13 +1000 Subject: [PATCH 5/6] Add hierarchy example to show off PD hierarchy Signed-off-by: Ivan Velickovic --- example/qemu_virt_aarch64/hierachy/Makefile | 63 +++++++++++++++++++ example/qemu_virt_aarch64/hierachy/crasher.c | 19 ++++++ example/qemu_virt_aarch64/hierachy/hello.c | 16 +++++ .../hierachy/hierachy.system | 17 +++++ .../qemu_virt_aarch64/hierachy/restarter.c | 58 +++++++++++++++++ 5 files changed, 173 insertions(+) create mode 100644 example/qemu_virt_aarch64/hierachy/Makefile create mode 100644 example/qemu_virt_aarch64/hierachy/crasher.c create mode 100644 example/qemu_virt_aarch64/hierachy/hello.c create mode 100644 example/qemu_virt_aarch64/hierachy/hierachy.system create mode 100644 example/qemu_virt_aarch64/hierachy/restarter.c diff --git a/example/qemu_virt_aarch64/hierachy/Makefile b/example/qemu_virt_aarch64/hierachy/Makefile new file mode 100644 index 000000000..a4c06cc4f --- /dev/null +++ b/example/qemu_virt_aarch64/hierachy/Makefile @@ -0,0 +1,63 @@ +# +# Copyright 2021, Breakaway Consulting Pty. Ltd. +# +# SPDX-License-Identifier: BSD-2-Clause +# +ifeq ($(strip $(BUILD_DIR)),) +$(error BUILD_DIR must be specified) +endif + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif + +ifeq ($(strip $(MICROKIT_CONFIG)),) +$(error MICROKIT_CONFIG must be specified) +endif + +TOOLCHAIN := aarch64-none-elf + +CPU := cortex-a53 + +CC := $(TOOLCHAIN)-gcc +LD := $(TOOLCHAIN)-ld +AS := $(TOOLCHAIN)-as +MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit + +RESTARTER_OBJS := restarter.o +CRASHER_OBJS := crasher.o +HELLO_OBJS := hello.o + +BOARD_DIR := $(MICROKIT_SDK)/board/$(MICROKIT_BOARD)/$(MICROKIT_CONFIG) + +IMAGES := restarter.elf crasher.elf hello.elf +CFLAGS := -mcpu=$(CPU) -mstrict-align -nostdlib -ffreestanding -g -O3 -Wall -Wno-unused-function -Werror -I$(BOARD_DIR)/include +LDFLAGS := -L$(BOARD_DIR)/lib +LIBS := -lmicrokit -Tmicrokit.ld + +IMAGE_FILE = $(BUILD_DIR)/loader.img +REPORT_FILE = $(BUILD_DIR)/report.txt + +all: $(IMAGE_FILE) + +$(BUILD_DIR)/%.o: %.c Makefile + $(CC) -c $(CFLAGS) $< -o $@ + +$(BUILD_DIR)/%.o: %.s Makefile + $(AS) -g -mcpu=$(CPU) $< -o $@ + +$(BUILD_DIR)/restarter.elf: $(addprefix $(BUILD_DIR)/, $(RESTARTER_OBJS)) + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(BUILD_DIR)/crasher.elf: $(addprefix $(BUILD_DIR)/, $(CRASHER_OBJS)) + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(BUILD_DIR)/hello.elf: $(addprefix $(BUILD_DIR)/, $(HELLO_OBJS)) + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(addprefix $(BUILD_DIR)/, $(IMAGES)) hierachy.system + $(MICROKIT_TOOL) hierachy.system --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) diff --git a/example/qemu_virt_aarch64/hierachy/crasher.c b/example/qemu_virt_aarch64/hierachy/crasher.c new file mode 100644 index 000000000..8600fb8ae --- /dev/null +++ b/example/qemu_virt_aarch64/hierachy/crasher.c @@ -0,0 +1,19 @@ +/* + * Copyright 2021, Breakaway Consulting Pty. Ltd. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +void init(void) +{ + int *x = 0; + microkit_dbg_puts("crasher, starting\n"); + /* Crash! */ + *x = 1; +} + +void notified(microkit_channel ch) +{ +} \ No newline at end of file diff --git a/example/qemu_virt_aarch64/hierachy/hello.c b/example/qemu_virt_aarch64/hierachy/hello.c new file mode 100644 index 000000000..493c95746 --- /dev/null +++ b/example/qemu_virt_aarch64/hierachy/hello.c @@ -0,0 +1,16 @@ +/* + * Copyright 2021, Breakaway Consulting Pty. Ltd. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +void init(void) +{ + microkit_dbg_puts("hello, world\n"); +} + +void notified(microkit_channel ch) +{ +} diff --git a/example/qemu_virt_aarch64/hierachy/hierachy.system b/example/qemu_virt_aarch64/hierachy/hierachy.system new file mode 100644 index 000000000..55bddebb6 --- /dev/null +++ b/example/qemu_virt_aarch64/hierachy/hierachy.system @@ -0,0 +1,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/qemu_virt_aarch64/hierachy/restarter.c b/example/qemu_virt_aarch64/hierachy/restarter.c new file mode 100644 index 000000000..a70756251 --- /dev/null +++ b/example/qemu_virt_aarch64/hierachy/restarter.c @@ -0,0 +1,58 @@ +/* + * Copyright 2021, Breakaway Consulting Pty. Ltd. + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#include +#include + +static uint8_t restart_count = 0; + +static char decchar(unsigned int v) +{ + return '0' + v; +} + +static void put8(uint8_t x) +{ + char tmp[4]; + unsigned i = 3; + tmp[3] = 0; + do { + uint8_t c = x % 10; + tmp[--i] = decchar(c); + x /= 10; + } while (x); + microkit_dbg_puts(&tmp[i]); +} + +void init(void) +{ + microkit_dbg_puts("restarter: starting\n"); +} + +void notified(microkit_channel ch) +{ +} + +seL4_MessageInfo_t protected(microkit_channel ch, microkit_msginfo msginfo) +{ + microkit_dbg_puts("restarter: received protected message\n"); + + return microkit_msginfo_new(0, 0); +} + +void fault(microkit_pd id, microkit_msginfo msginfo) +{ + microkit_dbg_puts("restarter: received fault message for pd: "); + put8(id); + microkit_dbg_puts("\n"); + restart_count++; + if (restart_count < 10) { + microkit_pd_restart(id, 0x200000); + microkit_dbg_puts("restarter: restarted\n"); + } else { + microkit_pd_stop(id); + microkit_dbg_puts("restarter: too many restarts - PD stopped\n"); + } +} From 3e44fab468d4bacfbeb8f74663f987eb64381f30 Mon Sep 17 00:00:00 2001 From: Ivan Velickovic Date: Wed, 19 Jun 2024 14:13:04 +1000 Subject: [PATCH 6/6] tool: more tests and fix for missing error check Signed-off-by: Ivan Velickovic --- tool/microkit/src/sysxml.rs | 8 +++-- .../tests/sdf/pd_child_missing_id.xml | 14 +++++++++ .../tests/sdf/pd_duplicate_child_id.xml | 17 ++++++++++ .../tests/sdf/pd_missing_program_image.xml | 10 ++++++ tool/microkit/tests/sdf/pd_parent_has_id.xml | 10 ++++++ tool/microkit/tests/test.rs | 31 +++++++++++++++++-- 6 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 tool/microkit/tests/sdf/pd_child_missing_id.xml create mode 100644 tool/microkit/tests/sdf/pd_duplicate_child_id.xml create mode 100644 tool/microkit/tests/sdf/pd_missing_program_image.xml create mode 100644 tool/microkit/tests/sdf/pd_parent_has_id.xml diff --git a/tool/microkit/src/sysxml.rs b/tool/microkit/src/sysxml.rs index 1a13d42a2..60cdc135e 100644 --- a/tool/microkit/src/sysxml.rs +++ b/tool/microkit/src/sysxml.rs @@ -254,7 +254,7 @@ impl ProtectionDomain { "program_image" => { check_attributes(xml_sdf, &child, &["path"])?; if program_image.is_some() { - return Err(value_error(xml_sdf, &child, "program_image must only be specified once".to_string())); + return Err(value_error(xml_sdf, node, "program_image must only be specified once".to_string())); } let program_image_path = checked_lookup(xml_sdf, &child, "path")?; @@ -346,6 +346,10 @@ impl ProtectionDomain { } } + if program_image.is_none() { + return Err(format!("Error: missing 'program_image' element on protection_domain: '{}'", name)); + } + let has_children = !child_pds.is_empty(); Ok(ProtectionDomain { @@ -537,7 +541,7 @@ fn pd_tree_to_list(xml_sdf: &XmlSystemDescription, mut root_pd: ProtectionDomain for child_pd in &root_pd.child_pds { let child_id = child_pd.id.unwrap(); if child_ids.contains(&child_id) { - return Err(format!("duplicate id: {} in protection domain: '{}' @ {}", child_id, root_pd.name, loc_string(xml_sdf, child_pd.text_pos))); + return Err(format!("Error: duplicate id: {} in protection domain: '{}' @ {}", child_id, root_pd.name, loc_string(xml_sdf, child_pd.text_pos))); } child_ids.push(child_pd.id.unwrap()); } diff --git a/tool/microkit/tests/sdf/pd_child_missing_id.xml b/tool/microkit/tests/sdf/pd_child_missing_id.xml new file mode 100644 index 000000000..4438279f9 --- /dev/null +++ b/tool/microkit/tests/sdf/pd_child_missing_id.xml @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/tool/microkit/tests/sdf/pd_duplicate_child_id.xml b/tool/microkit/tests/sdf/pd_duplicate_child_id.xml new file mode 100644 index 000000000..1b3896b88 --- /dev/null +++ b/tool/microkit/tests/sdf/pd_duplicate_child_id.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/tool/microkit/tests/sdf/pd_missing_program_image.xml b/tool/microkit/tests/sdf/pd_missing_program_image.xml new file mode 100644 index 000000000..2a7db97af --- /dev/null +++ b/tool/microkit/tests/sdf/pd_missing_program_image.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/tool/microkit/tests/sdf/pd_parent_has_id.xml b/tool/microkit/tests/sdf/pd_parent_has_id.xml new file mode 100644 index 000000000..ec6e17181 --- /dev/null +++ b/tool/microkit/tests/sdf/pd_parent_has_id.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/tool/microkit/tests/test.rs b/tool/microkit/tests/test.rs index 44ebcdc8c..823d1e7c1 100644 --- a/tool/microkit/tests/test.rs +++ b/tool/microkit/tests/test.rs @@ -19,13 +19,18 @@ const DEFAULT_KERNEL_CONFIG: sel4::Config = sel4::Config { const DEFAULT_PLAT_DESC: sysxml::PlatformDescription = sysxml::PlatformDescription::new(&DEFAULT_KERNEL_CONFIG); -fn check_error(test_name: &str, err: &str) { +fn check_error(test_name: &str, expected_err: &str) { let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); path.push("tests/sdf/"); path.push(test_name); let xml = std::fs::read_to_string(path).unwrap(); let parse_err = sysxml::parse(test_name, &xml, &DEFAULT_PLAT_DESC).unwrap_err(); - assert!(parse_err.starts_with(err)); + + if !parse_err.starts_with(expected_err) { + eprintln!("Expected error:\n{}\nGot error:\n{}\n", expected_err, parse_err); + } + + assert!(parse_err.starts_with(expected_err)); } fn check_missing(test_name: &str, attr: &str, element: &str) { @@ -82,6 +87,11 @@ mod protection_domain { check_missing("pd_missing_name.xml", "name", "protection_domain") } + #[test] + fn test_missing_program_image() { + check_error("pd_missing_program_image.xml", "Error: missing 'program_image' element on protection_domain: ") + } + #[test] fn test_missing_path() { check_missing("pd_missing_path.xml", "path", "program_image") @@ -119,7 +129,7 @@ mod protection_domain { #[test] fn test_duplicate_program_image() { - check_error("pd_duplicate_program_image.xml", "Error: program_image must only be specified once on element 'program_image': ") + check_error("pd_duplicate_program_image.xml", "Error: program_image must only be specified once on element 'protection_domain': ") } #[test] @@ -156,6 +166,21 @@ mod protection_domain { fn test_irq_invalid_trigger() { check_error("irq_invalid_trigger.xml", "Error: trigger must be either 'level' or 'edge' on element 'irq'") } + + #[test] + fn test_parent_has_id() { + check_error("pd_parent_has_id.xml", "Error: invalid attribute 'id' on element 'protection_domain': ") + } + + #[test] + fn test_child_missing_id() { + check_missing("pd_child_missing_id.xml", "id", "protection_domain") + } + + #[test] + fn test_duplicate_child_id() { + check_error("pd_duplicate_child_id.xml", "Error: duplicate id: 0 in protection domain: 'parent' @") + } } #[cfg(test)]