├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.mkdn ├── app ├── demo-nucleo-u575 │ ├── README.mkdn │ └── app.kdl ├── demo │ ├── README.mkdn │ └── app.kdl └── kbd │ ├── README.mkdn │ ├── app.kdl │ ├── basic-scanner-api │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs │ └── basic-scanner │ ├── Cargo.toml │ ├── build.rs │ ├── scanner-idl.kdl │ ├── src │ └── main.rs │ └── task.kdl ├── boards └── nucleo-g031k8.kdl ├── chips ├── stm32g031k8.kdl ├── stm32l412kb.kdl └── stm32u575zi_ns.kdl ├── doc ├── appdef.adoc ├── boarddef.adoc ├── build-env.adoc ├── chipdef.adoc └── hubake.adoc ├── drv ├── l4blinky │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ └── main.rs │ └── task.kdl ├── stm32l4-usb-api │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs ├── stm32l4-usb │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ ├── hid.rs │ │ ├── main.rs │ │ ├── protocol.rs │ │ └── usbsram.rs │ └── usbhid-idl.kdl ├── stm32xx-sys-api │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ └── lib.rs ├── stm32xx-sys │ ├── Cargo.toml │ ├── build.rs │ ├── src │ │ └── main.rs │ └── sys-idl.kdl └── uart-echo │ ├── Cargo.toml │ ├── build.rs │ └── src │ └── main.rs ├── files ├── kernel-link.x ├── task-link2.x ├── task-link3.x └── task-rlink.x ├── hubris-env.toml ├── kernel-generic-stm32g031 ├── .cargo │ └── config.toml ├── Cargo.toml ├── README.md ├── openocd.cfg └── src │ └── main.rs ├── kernel-generic-stm32l412 ├── .cargo │ └── config.toml ├── Cargo.toml ├── README.md ├── openocd.cfg └── src │ └── main.rs ├── kernel-generic-stm32u575 ├── .cargo │ └── config.toml ├── Cargo.toml ├── README.md ├── openocd.cfg └── src │ └── main.rs ├── rust-toolchain.toml ├── sys ├── build-util │ ├── Cargo.toml │ ├── README.mkdn │ ├── build.rs │ └── src │ │ └── lib.rs ├── idyll_runtime │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── kipc │ ├── Cargo.toml │ ├── README.mkdn │ └── src │ │ └── lib.rs ├── notifications │ ├── Cargo.toml │ ├── README.mkdn │ ├── build.rs │ └── src │ │ └── lib.rs ├── num-tasks │ ├── Cargo.toml │ ├── README.mkdn │ ├── build.rs │ └── src │ │ └── lib.rs ├── task-slots │ ├── Cargo.toml │ ├── README.mkdn │ ├── build.rs │ └── src │ │ └── lib.rs └── userlib │ ├── Cargo.toml │ ├── README.mkdn │ ├── build.rs │ └── src │ ├── arch │ ├── arm_m.rs │ └── fake.rs │ └── lib.rs ├── task ├── idle │ ├── Cargo.toml │ ├── README.mkdn │ └── src │ │ └── main.rs ├── minisuper │ ├── Cargo.toml │ ├── README.mkdn │ └── src │ │ └── main.rs ├── ping │ ├── Cargo.toml │ ├── README.mkdn │ ├── build.rs │ └── src │ │ └── main.rs └── pong │ ├── Cargo.toml │ ├── README.mkdn │ └── src │ └── main.rs └── tools ├── alloc ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── build ├── Cargo.toml └── src │ ├── alloc.rs │ ├── appcfg.rs │ ├── buildid.rs │ ├── bundle.rs │ ├── cargo.rs │ ├── config.rs │ ├── idl.rs │ ├── idl │ └── codegen.rs │ ├── kconfig.rs │ ├── lib.rs │ ├── main.rs │ ├── relink.rs │ └── verbose.rs └── hubake ├── Cargo.toml ├── README.mkdn └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .work/ 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "kernel-generic-*", 5 | "sys/*", 6 | "task/*", 7 | "drv/*", 8 | "tools/build", 9 | "tools/hubake", 10 | "tools/alloc", 11 | 12 | "app/kbd/basic-scanner", 13 | "app/kbd/basic-scanner-api", 14 | ] 15 | 16 | [workspace.dependencies] 17 | userlib = {path = "sys/userlib"} 18 | kipc = {path = "sys/kipc"} 19 | hubris-num-tasks = {path = "sys/num-tasks"} 20 | hubris-build = {path="tools/build"} 21 | 22 | [profile.release] 23 | debug = true 24 | lto = true 25 | opt-level = "s" 26 | -------------------------------------------------------------------------------- /README.mkdn: -------------------------------------------------------------------------------- 1 | # The _ex hubris_ project 2 | 3 | This is my attempt at making the [Hubris] kernel/platform accessible to 4 | applications that live _outside_ the main Hubris repo. (The name is bad Latin 5 | for "out of Hubris," which is where I want the applications to go, and also an 6 | abbreviation for "external Hubris.") 7 | 8 | **If you would like to see what an exhubris-built application looks like in 9 | practice, I'm maintaining a demo repository next to this one: 10 | https://github.com/cbiffle/exhubris-demo/** 11 | 12 | **Current Status:** starting to kinda work. 13 | 14 | ## Why this is necessary 15 | 16 | We developed Hubris at [Oxide] to scratch our own itch: we needed a 17 | microcontroller operating system with certain robustness properties, and we 18 | couldn't find anything off-the-shelf that met our needs. Plus, we happened to 19 | have a bunch of kernel hackers on the team. 20 | 21 | Since Hubris grew up with a single main customer (us), we developed Oxide's 22 | firmware in the Hubris repo. Sort of like traditional Unix systems that package 23 | their kernel and userland sources in one big morass. This was quick and easy for 24 | us, and arguably critical for the early stages when things were changing 25 | rapidly. But it makes it kind of hard for _anyone else_ to use Hubris. 26 | 27 | But I would _like_ other people to use Hubris. Heck, I would like to use Hubris 28 | in some of my personal projects! 29 | 30 | ## How it's going 31 | 32 | This is what I would call "hacked-up sprint code." Expect things to be in flux. 33 | 34 | That said, currently the code in this repo can: 35 | 36 | - Build a basic application using tasks implemented in this repo. 37 | - Pull in the upstream Hubris kernel and support crates. 38 | - Combine the two into a working firmware image. 39 | 40 | Currently, the tools in this repo cannot build Oxide's firmware. That will 41 | probably not change in the foreseeable future, because I've taken the 42 | opportunity to shed a lot of accumulated cruft. Big changes include: 43 | 44 | - I have removed everything that requires the use of a `nightly` Rust toolchain. 45 | This code targets the stable 1.82 toolchain (and later). 46 | 47 | - Configuration files are no longer TOML, because IMO TOML is pretty crappy for 48 | complex files containing deep configuration hierarchies. Currently, files are 49 | written in KDL. 50 | 51 | - As a result, the names and placement of things in the configuration files may 52 | be arbitrarily different. 53 | 54 | - The build system strives to be architecture-neutral and 64-bit clean, because 55 | it'd be fun to port to a Cortex-A53 or something. (Note that the Hubris kernel 56 | is still 32-bit only, so this is purely aspirational.) 57 | 58 | - I have rewritten `userlib`, the `libc`-equivalent that tasks use to interact 59 | with the Hubris kernel. The new one has some significant breaking API changes, 60 | which allowed it to become much smaller and simpler. (I also removed some API 61 | footguns.) 62 | 63 | - The implementation is incomplete and many features are missing. This is not 64 | necessarily deliberate, I'm implementing the features I need to make progress. 65 | 66 | ## Aspects of Hubris that are not likely to change 67 | 68 | I think we got a lot of things right in the Hubris design. Here are some 69 | properties that I currently don't intend to change. 70 | 71 | 1. Applications (firmware images) are constructed from a collection of 72 | mutually-isolated separately-compiled executables called _tasks_. 73 | 74 | 2. The set of tasks in an application is defined at design time. Tasks cannot be 75 | created or destroyed, only stopped and (re)started. 76 | 77 | 3. Tasks communicate with one another only via _messages._ 78 | 79 | 4. All tasks are unprivileged (in the hardware sense). 80 | 81 | 5. One task plays the role of _supervisor,_ which is responsible for handling 82 | faults/crashes in other tasks, starting and stopping them, etc. The 83 | supervisor has access to some additional syscalls but is otherwise no more 84 | powerful than any other task. 85 | 86 | 6. All drivers are tasks. No driver code runs in the kernel or in privileged 87 | mode. 88 | 89 | 7. The kernel's main jobs are to divide the CPU between tasks, to enforce 90 | protection boundaries between tasks (and between the tasks and the kernel 91 | itself), to transfer messages between tasks, and to route interrupts to tasks 92 | as notifications. 93 | 94 | 8. The unit of distribution is the _complete firmware image._ Individual tasks 95 | cannot be updated in the field, which ensures that you've tested the actual 96 | combination of code you're shipping. (We use A/B image slots in flash to 97 | handle online updates, accepting a new complete firmware image and then 98 | atomically rebooting into it.) 99 | 100 | 9. Hubris itself is written in Rust, and it is easiest to write Hubris tasks in 101 | Rust. It is theoretically possible to write Hubris tasks in other languages, 102 | because the kernel ABI is fairly well specified. 103 | 104 | Hubris in its currently envisioned form will probably not meet your needs if 105 | they include: 106 | 107 | - Dynamic tasks created or destroyed at runtime, particularly with code 108 | downloaded into the live system; 109 | - Firmware updates to individual tasks rather than the cohesive system; 110 | - Certain interrupt response situations with very low required latency. (Though 111 | we do pretty okay.) 112 | 113 | [Hubris]: https://hubris.oxide.computer/ 114 | [Oxide]: https://oxide.computer/ 115 | -------------------------------------------------------------------------------- /app/demo-nucleo-u575/README.mkdn: -------------------------------------------------------------------------------- 1 | # Demo for the NUCLEO-U575ZI board 2 | 3 | This is a very basic demo for the STM32U575. 4 | -------------------------------------------------------------------------------- /app/demo-nucleo-u575/app.kdl: -------------------------------------------------------------------------------- 1 | // Demo Application Definition - NUCLEO-U575 edition 2 | 3 | // App name is used for output files etc. It must be the first non-blank 4 | // non-comment line in the file. 5 | app demo-nucleo-u575 6 | 7 | // Currently the board information is inlined. It would be nice to refer to an 8 | // external file, like we already do for chip definitions. 9 | board nucleo-u575zi { 10 | chip "proj:chips/stm32u575zi_ns.kdl" 11 | } 12 | 13 | // Instructions for building the kernel. 14 | kernel { 15 | workspace-crate kernel-generic-stm32u575 16 | stack-size 640 17 | features clock-160mhz-hsi16 clock-hsi48-on pwr-vddusb-valid 18 | } 19 | 20 | task super { 21 | workspace-crate minisuper 22 | stack-size 192 23 | priority 0 24 | } 25 | 26 | task sys { 27 | workspace-crate drv-stm32xx-sys 28 | stack-size 192 29 | priority 1 30 | uses-peripheral rcc 31 | uses-peripheral gpios 32 | } 33 | 34 | // IPC server. 35 | task pong { 36 | workspace-crate pong 37 | stack-size 256 38 | priority 2 39 | } 40 | 41 | // IPC generator. 42 | task ping { 43 | workspace-crate ping 44 | stack-size 256 45 | priority 3 46 | uses-task pong 47 | uses-task sys 48 | config { 49 | led-pin C7 50 | } 51 | } 52 | 53 | // Idle task. This must be the lowest priority task. 54 | task idle { 55 | workspace-crate idle 56 | stack-size 128 57 | priority 4 58 | } 59 | -------------------------------------------------------------------------------- /app/demo/README.mkdn: -------------------------------------------------------------------------------- 1 | # demo app 2 | 3 | This is a skeleton app used to test the build system. 4 | -------------------------------------------------------------------------------- /app/demo/app.kdl: -------------------------------------------------------------------------------- 1 | // Demo Application Definition 2 | 3 | // App name is used for output files etc. It must be the first non-blank 4 | // non-comment line in the file. 5 | app demo 6 | 7 | // Selects a specific target board, which implicitly picks up things like the 8 | // CPU type. 9 | board "proj:boards/nucleo-g031k8.kdl" 10 | 11 | // Instructions for building the kernel. 12 | kernel { 13 | workspace-crate kernel-generic-stm32g031 14 | stack-size 544 15 | features clock-64mhz-hsi16 16 | } 17 | 18 | task super { 19 | workspace-crate minisuper 20 | stack-size 128 21 | priority 0 22 | } 23 | 24 | task sys { 25 | workspace-crate drv-stm32xx-sys 26 | stack-size 256 27 | priority 1 28 | uses-peripheral rcc 29 | uses-peripheral gpios 30 | } 31 | 32 | task echo { 33 | workspace-crate uart-echo 34 | stack-size 256 35 | priority 2 36 | uses-task sys 37 | uses-peripheral usart2 { 38 | irq irq usart-irq 39 | } 40 | config { 41 | uart-clock-hz 16_000_000 42 | baud-rate 9_600 43 | pins { 44 | - name=A2 af=1 45 | - name=A3 af=1 46 | } 47 | } 48 | } 49 | 50 | // IPC server. 51 | task pong { 52 | workspace-crate pong 53 | stack-size 160 54 | priority 2 55 | } 56 | 57 | // IPC generator. 58 | task ping { 59 | workspace-crate ping 60 | stack-size 256 61 | priority 3 62 | uses-task pong 63 | uses-task sys 64 | config { 65 | led-pin C6 66 | } 67 | } 68 | 69 | // Idle task. This must be the lowest priority task. 70 | task idle { 71 | workspace-crate idle 72 | stack-size 32 73 | priority 4 74 | } 75 | -------------------------------------------------------------------------------- /app/kbd/README.mkdn: -------------------------------------------------------------------------------- 1 | # Hubris? In my keyboard? 2 | 3 | It's more likely than you think. 4 | 5 | This is a basic USB HID keyboard implementation built with Hubris. 6 | -------------------------------------------------------------------------------- /app/kbd/app.kdl: -------------------------------------------------------------------------------- 1 | // STM32L4-based keyboard app. 2 | // 3 | // The keyboard bits consist of three cooperating tasks. 4 | // 5 | // 1. usbhid manages the USB device hardware and protocol state machine. 6 | // 2. scanner manages the physical key matrix 7 | // 3. keyboard converts this into a logical key matrix and posts the results to 8 | // usbhid. 9 | 10 | app kbd 11 | 12 | // Currently the board information is inlined. It would be nice to refer to an 13 | // external file, like we already do for chip definitions. 14 | board fourk { 15 | chip "proj:chips/stm32l412kb.kdl" 16 | } 17 | 18 | // Instructions for building the kernel. 19 | kernel { 20 | workspace-crate kernel-generic-stm32l412 21 | features clock-80mhz-hsi16 clock-hsi48-on pwr-vddusb-valid 22 | stack-size 640 23 | } 24 | 25 | task super { 26 | workspace-crate minisuper 27 | stack-size 256 28 | priority 0 29 | } 30 | 31 | task sys { 32 | workspace-crate drv-stm32xx-sys 33 | stack-size 224 34 | priority 1 35 | uses-peripheral rcc 36 | uses-peripheral gpios 37 | } 38 | 39 | task usbhid { 40 | workspace-crate drv-stm32l4-usb 41 | stack-size 328 42 | priority 2 43 | uses-task sys 44 | uses-peripheral crs 45 | uses-peripheral usbfs_and_sram { 46 | irq irq usb-irq 47 | } 48 | config { 49 | on_event "scanner#usb_event_ready" 50 | on_report "scanner#usb_report_needed" 51 | } 52 | } 53 | 54 | task scanner { 55 | workspace-crate kbd-basic-scanner 56 | stack-size 512 57 | priority 3 58 | uses-task sys 59 | uses-task usbhid 60 | 61 | // The GPIOs are aliased into both this task and SYS, because this task 62 | // needs high-rate low-latency access to perform matrix scanning. In 63 | // practice, sharing the GPIO blocks in this way is unlikely to cause 64 | // problems (famous last words?) 65 | uses-peripheral gpios 66 | 67 | // While we're at it, we need a hardware timer to do more precise sleeps 68 | // than the kernel offers. 69 | uses-peripheral tim2 { 70 | irq irq tim-irq 71 | } 72 | 73 | notification usb_event_ready 74 | notification usb_report_needed 75 | 76 | config { 77 | rows { 78 | - A0 79 | - A1 80 | } 81 | cols { 82 | - B0 83 | - B1 84 | } 85 | } 86 | } 87 | 88 | // Idle task. This must be the lowest priority task. 89 | task idle { 90 | workspace-crate idle 91 | stack-size 128 92 | priority 5 93 | features insomniac 94 | } 95 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kbd-basic-scanner-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | hubpack = "0.1.2" 8 | idyll_runtime = { version = "0.1.0", path = "../../../sys/idyll_runtime" } 9 | serde = { version = "1.0.215", default-features = false, features = ["derive"] } 10 | userlib.workspace = true 11 | 12 | [build-dependencies] 13 | hubris-build.workspace = true 14 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner-api/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | println!("cargo::rerun-if-changed=../basic-scanner/scanner-idl.kdl"); 5 | let iface = hubris_build::idl::load_interface("../basic-scanner/scanner-idl.kdl").unwrap(); 6 | let client = hubris_build::idl::codegen::generate_client(&iface).unwrap(); 7 | let client = hubris_build::idl::codegen::format_code(&client); 8 | let mut outpath = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 9 | outpath.push("generated_client.rs"); 10 | 11 | std::fs::write(&outpath, client).unwrap(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | include!(concat!(env!("OUT_DIR"), "/generated_client.rs")); 4 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kbd-basic-scanner" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | chip-stm32l412kb = ["stm32-metapac/stm32l412kb"] 8 | 9 | [dependencies] 10 | drv-stm32xx-sys-api = { version = "0.1.0", path = "../../../drv/stm32xx-sys-api" } 11 | drv-stm32l4-usb-api = { version = "0.1.0", path = "../../../drv/stm32l4-usb-api" } 12 | hubris-task-slots = { version = "0.1.0", path = "../../../sys/task-slots" } 13 | hubris-notifications = { path = "../../../sys/notifications" } 14 | userlib = {workspace = true, features = ["no-panic-messages"]} 15 | heapless = "0.8.0" 16 | hubpack = "0.1.2" 17 | idyll_runtime = { version = "0.1.0", path = "../../../sys/idyll_runtime" } 18 | serde = { version = "1.0.215", default-features = false, features = ["derive"] } 19 | stm32-metapac = "15.0.0" 20 | 21 | [build-dependencies] 22 | hubris-build.workspace = true 23 | hubris-build-util = { version = "0.1.0", path = "../../../sys/build-util" } 24 | serde = { version = "1.0.215", features = ["derive"] } 25 | 26 | [package.metadata.hubris.auto-features] 27 | chip = true 28 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::path::PathBuf; 3 | 4 | use serde::Deserialize; 5 | 6 | fn main() { 7 | let outpath = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 8 | 9 | println!("cargo::rerun-if-changed=scanner-idl.kdl"); 10 | let iface = hubris_build::idl::load_interface("scanner-idl.kdl").unwrap(); 11 | let server = hubris_build::idl::codegen::generate_server(&iface).unwrap(); 12 | let server = hubris_build::idl::codegen::format_code(&server); 13 | let genserver_path = outpath.join("generated_server.rs"); 14 | 15 | std::fs::write(&genserver_path, server).unwrap(); 16 | 17 | let config: Config = hubris_build_util::get_task_config(); 18 | 19 | let mut f = std::fs::File::create(outpath.join("config.rs")).unwrap(); 20 | writeln!(f, "pub(crate) mod config {{").unwrap(); 21 | 22 | writeln!(f, "use drv_stm32xx_sys_api::Port;").unwrap(); 23 | 24 | for (array, src) in [("ROWS", &config.rows), ("COLS", &config.cols)] { 25 | let n = src.len(); 26 | writeln!(f, "pub const {array}: [(Port, u8); {n}] = [").unwrap(); 27 | for pinname in src { 28 | let (port, pin) = parse_pin_name(pinname); 29 | writeln!(f, " (Port::{port}, {pin}),").unwrap(); 30 | } 31 | writeln!(f, "];").unwrap(); 32 | } 33 | 34 | writeln!(f, "pub const ROW_COUNT: usize = {};", config.rows.len()).unwrap(); 35 | writeln!(f, "pub const COL_COUNT: usize = {};", config.cols.len()).unwrap(); 36 | 37 | writeln!(f, "}}").unwrap(); 38 | drop(f); 39 | } 40 | 41 | #[derive(Deserialize)] 42 | struct Config { 43 | rows: Vec, 44 | cols: Vec, 45 | } 46 | 47 | fn parse_pin_name(name: &str) -> (&str, u8) { 48 | let (port, pin) = name.split_at(1); 49 | 50 | (port, pin.parse().unwrap()) 51 | } 52 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner/scanner-idl.kdl: -------------------------------------------------------------------------------- 1 | // Keyboard scanner IPC interface. 2 | 3 | interface scanner 4 | 5 | // Operations: 6 | 7 | method pop_event { 8 | doc "Collects one event from the pending queue, if there are any." 9 | 10 | operation 0 11 | 12 | result Option 13 | 14 | // If the scanner task crashes, we just want clients to try again; they'll 15 | // discover an empty queue and move on. 16 | auto-retry 17 | } 18 | 19 | enum KeyState { 20 | rust-derive Copy Clone Debug 21 | 22 | case Up 23 | case Down 24 | } 25 | 26 | struct KeyEvent { 27 | rust-derive Copy Clone Debug 28 | 29 | field state KeyState 30 | field row u8 31 | field col u8 32 | } 33 | -------------------------------------------------------------------------------- /app/kbd/basic-scanner/task.kdl: -------------------------------------------------------------------------------- 1 | // Ping task definition. 2 | 3 | server "pong" 4 | -------------------------------------------------------------------------------- /boards/nucleo-g031k8.kdl: -------------------------------------------------------------------------------- 1 | board nucleo-g031k8 2 | 3 | chip "proj:chips/stm32g031k8.kdl" 4 | -------------------------------------------------------------------------------- /chips/stm32g031k8.kdl: -------------------------------------------------------------------------------- 1 | chip STM32G031K8 2 | target-triple thumbv6m-none-eabi 3 | vector-table-size 0xC0 4 | probe-rs-name STM32G031K8Tx 5 | 6 | compatible { 7 | - STM32G031K8 // flash size + pin count 8 | - STM32G031x8 // flash size only 9 | - STM32G031 // subfamily only 10 | - STM32G0 // family only 11 | - arm-cortex-m0plus // microprocessor generic name 12 | } 13 | 14 | memory { 15 | region vectors { 16 | base 0x0800_0000 17 | size 0x110 // enlarged to accommodate header 18 | read 19 | } 20 | region flash { 21 | base 0x0800_0110 22 | size 0xFEF0 23 | read 24 | execute 25 | } 26 | region ram { 27 | base 0x2000_0000 28 | size 8192 29 | read 30 | write 31 | } 32 | } 33 | 34 | peripheral usart1 { 35 | base 0x4001_3800 36 | size 0x400 37 | irq irq 27 38 | } 39 | 40 | peripheral usart2 { 41 | base 0x4000_4400 42 | size 0x400 43 | irq irq 28 // note: conflicts with LPUART2 44 | } 45 | 46 | peripheral rcc { 47 | base 0x4002_1000 48 | size 0x400 49 | irq irq 4 50 | } 51 | 52 | peripheral gpios { 53 | base 0x5000_0000 54 | size 0x2000 55 | } 56 | -------------------------------------------------------------------------------- /chips/stm32l412kb.kdl: -------------------------------------------------------------------------------- 1 | chip STM32L412KB 2 | target-triple thumbv7em-none-eabihf 3 | probe-rs-name STM32L412KBTx 4 | 5 | compatible { 6 | - STM32L412KB 7 | - STM32L412xB 8 | - STM32L412 9 | - STM32L4 10 | - arm-cortex-m4f 11 | - arm-cortex-m4 12 | } 13 | 14 | // L412 lacks I2C4 and thus has a slightly smaller vector table than some other 15 | // entries in the series. 16 | vector-table-size 0x18C 17 | 18 | memory { 19 | region vectors { 20 | base 0x0800_0000 21 | size 0x1E4 // enlarged to accommodate header 22 | read 23 | } 24 | region flash { 25 | base 0x0800_01E4 26 | size 0x1FE1C // 128 kiB - 0x1E4 27 | read 28 | execute 29 | } 30 | region ram { 31 | base 0x2000_0000 32 | size 40_960 33 | read 34 | write 35 | } 36 | region usbram { 37 | base 0x4000_6C00 38 | size 1024 39 | read 40 | write 41 | } 42 | } 43 | 44 | peripheral tim2 { 45 | base 0x4000_0000 46 | size 0x400 47 | irq irq 28 48 | } 49 | 50 | peripheral crs { 51 | base 0x4000_6000 52 | size 0x400 53 | } 54 | 55 | peripheral usbfs_and_sram { 56 | base 0x4000_6800 57 | size 0x800 58 | irq irq 67 59 | } 60 | 61 | peripheral pwr { 62 | base 0x4000_7000 63 | size 0x400 64 | } 65 | 66 | peripheral rcc { 67 | base 0x4002_1000 68 | size 0x400 69 | } 70 | 71 | // This includes ports A-H. Note that ports E/F/G are not implemented on the 72 | // STM32L4, but are included in this region anyway. This has proven to be fine 73 | // in practice; we won't try to access them, and if we did it'd likely trap. 74 | peripheral gpios { 75 | base 0x4800_0000 76 | size 0x2000 77 | } 78 | -------------------------------------------------------------------------------- /chips/stm32u575zi_ns.kdl: -------------------------------------------------------------------------------- 1 | chip STM32U575ZI-NS 2 | target-triple thumbv8m.main-none-eabihf 3 | probe-rs-name STM32U575ZITx 4 | 5 | compatible { 6 | - STM32U575ZI 7 | - STM32U575xI 8 | - STM32U575 9 | - STM32U5 10 | - arm-cortex-m33 11 | } 12 | 13 | // L412 lacks I2C4 and thus has a slightly smaller vector table than some other 14 | // entries in the series. 15 | vector-table-size 0x274 16 | 17 | memory { 18 | region vectors { 19 | base 0x0800_0000 20 | size 0x2CC // enlarged to accommodate header 21 | read 22 | } 23 | region flash { 24 | base 0x0800_02CC 25 | size 0x1FFD34 // 2048 kiB - 0x2CC 26 | read 27 | execute 28 | } 29 | region ram { 30 | base 0x2000_0000 31 | size 0xC_0000 // 768 kiB 32 | read 33 | write 34 | } 35 | } 36 | 37 | peripheral tim2 { 38 | base 0x4000_0000 39 | size 0x400 40 | } 41 | 42 | peripheral crs { 43 | base 0x4000_6000 44 | size 0x400 45 | } 46 | 47 | peripheral pwr { 48 | base 0x4602_0800 49 | size 0x400 50 | } 51 | 52 | peripheral rcc { 53 | base 0x4602_0C00 54 | size 0x400 55 | } 56 | 57 | peripheral gpios { 58 | base 0x4202_0000 59 | size 0x2800 60 | } 61 | 62 | peripheral usb_otg_fs { 63 | base 0x4204_0000 64 | size 0x8_0000 65 | irq irq 73 66 | } 67 | -------------------------------------------------------------------------------- /doc/appdef.adoc: -------------------------------------------------------------------------------- 1 | :toc: 2 | :toc-placement!: 3 | 4 | = App Definition Format 5 | 6 | An app definition file, or "appdef," defines the contents of a firmware image. 7 | 8 | toc::[] 9 | 10 | == Basics 11 | 12 | Appdefs are written in https://kdl.dev/[KDL2]. 13 | 14 | To distinguish an appdef from other KDL files, we require the first non-comment 15 | non-whitespace node in the file to be `app`. 16 | 17 | Here is a simple example. 18 | 19 | ---- 20 | app simple-example 21 | 22 | board my-awesome-pcb { 23 | chip stm32f407vg 24 | } 25 | 26 | kernel { 27 | workspace-crate my-kernel 28 | stack-size 1000 29 | } 30 | 31 | task idle { 32 | workspace-crate task-idle 33 | stack-size 128 34 | } 35 | ---- 36 | 37 | == `app` (required) 38 | 39 | `app NAME` 40 | 41 | - `NAME` is the name of this firmware image, and determines (among other things) 42 | the output filename for a build. 43 | 44 | == `board` (required) 45 | 46 | Defines the board being targeted by this image. The board can be defined two 47 | different ways. First, in a separate file: 48 | 49 | ---- 50 | board PATH 51 | ---- 52 | 53 | ...where `PATH` is a prefixed path to a boarddef KDL file. Currently the only 54 | supported prefix is `proj:`, which causes the rest of the path to be interpreted 55 | from the project root. 56 | 57 | The second option is to write the boarddef inline: 58 | 59 | ---- 60 | board NAME { 61 | chip CHIPNAME 62 | } 63 | ---- 64 | 65 | This can be useful for simple or one-off boards. 66 | 67 | See the `boarddef` docs for specifics on its contents. 68 | 69 | 70 | == `kernel` (required) 71 | 72 | ---- 73 | kernel { 74 | stack-size BYTES 75 | // optional attributes and children go here 76 | } 77 | ---- 78 | 79 | Specifies how to build the kernel. 80 | 81 | Attributes: 82 | 83 | - `stack-size BYTES` gives the kernel's stack size in bytes. 84 | - `features NAME...` (optional) adds its arguments as Cargo features to the 85 | kernel's build configuration. 86 | - `no-default-features` (optional) switches off the kernel crate's default Cargo 87 | features. 88 | - `target TRIPLE` (optional) overrides the target triple used to build the 89 | kernel. 90 | - `toolchain NAME` (optional) overrides the toolchain version used to build the 91 | kernel. Note that dotted version numbers need to be quoted: `"1.85"` 92 | 93 | === `kernel > workspace-crate` 94 | 95 | See <>. 96 | 97 | === `kernel > git-crate` 98 | 99 | See <>. 100 | 101 | == `task` 102 | 103 | ---- 104 | task NAME { 105 | // children and attributes go here 106 | } 107 | ---- 108 | 109 | Adds a task to the firmware image, and specifies how to build and configure it. 110 | 111 | Attributes: 112 | 113 | - `stack-size BYTES` gives the task's stack size in bytes. 114 | - `priority P` sets the task's scheduling priority to `P`, where 0 is the most 115 | important. 116 | - `wait-for-reinit` (optional) tells the kernel not to start this task 117 | automatically on boot, and instead waits for the supervisor task to do it. 118 | - `features NAME...` (optional) adds its arguments as Cargo features to the 119 | task's build configuration. 120 | - `no-default-features` (optional) switches off the task crate's default Cargo 121 | features. 122 | - `target TRIPLE` (optional) overrides the target triple used to build this 123 | task. 124 | - `toolchain NAME` (optional) overrides the toolchain version used to build this 125 | task. Note that dotted version numbers need to be quoted: `"1.85"` 126 | 127 | === `task > workspace-crate` 128 | 129 | See <>. 130 | 131 | === `task > git-crate` 132 | 133 | See <>. 134 | 135 | === `task > uses-peripheral` 136 | 137 | ---- 138 | uses-peripheral PERIPHERAL 139 | 140 | uses-peripheral PERIPHERAL { 141 | irq IRQNAME NOTIFICATIONNAME 142 | } 143 | ---- 144 | 145 | Indicates that a task uses a memory-mapped peripheral from the chipdef, causing 146 | it to be mapped into the task's address space. 147 | 148 | Attributes: 149 | 150 | - `irq IRQNAME NOTIFICATIONNAME` (optional, repeated): binds the IRQ called 151 | `IRQNAME` in the chipdef to a notification for this task named 152 | `NOTIFICATIONNAME`. 153 | 154 | === `task > uses-task` 155 | 156 | `uses-task TASKNAME` 157 | 158 | Indicates an IPC relationship, where this task acts as a client of the task 159 | named `TASKNAME`. 160 | 161 | === `task > config` 162 | 163 | See <>. 164 | 165 | === `task > notification` 166 | 167 | `notification NAME` 168 | 169 | Defines a named notification bit for use by software. 170 | 171 | Note that you do not need to use `notification` to declare bits used in 172 | `uses-peripheral > irq`. Those are implicitly declared by being used. 173 | 174 | === `task > assign-section` 175 | 176 | `assign-section SECTIONNAME REGIONNAME` 177 | 178 | Maps the ELF section `SECTIONNAME` in the task to the memory area named 179 | `REGIONNAME` in the chipdef. This can be used to place portions of a task's 180 | memory in controlled locations, such as auxiliary SRAMs. 181 | 182 | == Shared nodes 183 | 184 | [[workspace-crate]] 185 | === `workspace-crate` 186 | 187 | `workspace-crate PACKAGENAME` 188 | 189 | Specifies that a crate should be sourced from the workspace containing this 190 | appcfg. 191 | 192 | 193 | [[git-crate]] 194 | === `git-crate` 195 | 196 | ---- 197 | git-crate { 198 | repo REPOSITORY 199 | package PACKAGENAME 200 | rev REV 201 | } 202 | ---- 203 | 204 | Specifies that a crate should be sourced from an external Git repository. 205 | 206 | Conflicts with `workspace-crate`. 207 | 208 | Attributes: 209 | 210 | - `repo REPOSITORY` gives the URL of the repository. 211 | - `package PACKAGENAME` gives the package (crate) name to build from the 212 | repository. 213 | - `rev REV` specifies the git-rev of the version to use. 214 | 215 | [[config]] 216 | === `config` 217 | 218 | ---- 219 | config { 220 | // arbitrary data goes here 221 | } 222 | ---- 223 | 224 | Provides arbitrary user-defined configuration data to a build. The contents of 225 | the `config` node use a subset of KDL that can be converted to JSON for easy 226 | sharing. Details to come (see examples in the repo for now). 227 | -------------------------------------------------------------------------------- /doc/boarddef.adoc: -------------------------------------------------------------------------------- 1 | :toc: 2 | :toc-placement!: 3 | 4 | = Board Definition Format 5 | 6 | A board definition file, or "boarddef," defines parameters common to all 7 | applications that target a particular board. 8 | 9 | This is currently very minimal. 10 | 11 | toc::[] 12 | 13 | == Basics 14 | 15 | Boarddefs are written in https://kdl.dev/[KDL2]. 16 | 17 | To distinguish a boarddef from other KDL files, when writing a boarddef in its 18 | own file, we require the first non-comment non-whitespace node in the file to be 19 | `board`. 20 | 21 | Here is a simple example. 22 | 23 | ---- 24 | board my-awesome-pcb 25 | 26 | chip "proj:chips/stm32f407vg.kdl" 27 | ---- 28 | 29 | It's also possible to write a boarddef _inline_ inside a chipdef, for one-off 30 | boards. In this case this restriction is lifted. 31 | 32 | 33 | == `board` (required) 34 | 35 | `board NAME` 36 | 37 | - `NAME` is the name of this board. 38 | 39 | == `chip` (required) 40 | 41 | ---- 42 | chip PATH 43 | ---- 44 | 45 | Defines the SoC or chip on this board. References a `chipdef` file. 46 | 47 | `PATH` can refer to different namespaces based on a prefix. Currently, the only 48 | defined prefix is `proj:`, which causes the rest of the path to be interpreted 49 | relative to the current project root. 50 | 51 | As an alternative, it's possible to write the chipdef inline, in which case it 52 | would look like this: 53 | 54 | ---- 55 | chip NAME { 56 | contents 57 | } 58 | ---- 59 | 60 | ...where `contents` is the body of a chipdef file, except for the initial `chip` 61 | line. 62 | -------------------------------------------------------------------------------- /doc/build-env.adoc: -------------------------------------------------------------------------------- 1 | :toc: 2 | :toc-placement!: 3 | 4 | = Build Script Environment 5 | 6 | This specifies the information passed into task and kernel builds, for use by 7 | build scripts. 8 | 9 | toc::[] 10 | 11 | == Basics 12 | 13 | Cargo supports a notion of "features," but treats them as purely additive 14 | boolean flags. This means it has no way to describe a feature with a numeric 15 | value, mutually exclusive features, or arbitrary structured configuration data. 16 | Because all of these things are quite common in embedded development, we had to 17 | add an alternative. 18 | 19 | The build system passes metadata to task and kernel builds through environment 20 | variables. This is not quite as much of a hack as it might sound; Cargo actually 21 | has support for declaring dependencies on the content of environment variables, 22 | so we still get reliable incremental builds. 23 | 24 | Because we like determinism, but environment variables are a great source of 25 | hidden state, we want to avoid leaking bits of the user's environment into the 26 | build. The strategy: 27 | 28 | - Define the set of environment variables that a Hubris task or kernel can 29 | depend on and expect reliable results (this document). 30 | 31 | - Strip out conflicting variables from the environment when executing build 32 | tasks. (Concretely, any environment variables starting with `HUBRIS_` will be 33 | replaced or removed.) 34 | 35 | === Depending on a variable 36 | 37 | The easiest way to use the contents of these variables is through the 38 | `hubris-build-util` crate, which provides accessor functions that automatically 39 | emit the proper Cargo directives. 40 | 41 | If you choose to do it by hand in a build script, remember that if the results 42 | of your build depend on the contents of some environment variable called `XXX`, 43 | your build script _must_ emit a directive to stdout of the form: 44 | 45 | ---- 46 | cargo::rerun-if-env-changed=XXX 47 | ---- 48 | 49 | This will cause Cargo to record the current contents of the environment variable 50 | in your crate's dependency information, ensuring that your crate will be rebuilt 51 | if the contents change. 52 | 53 | 54 | === Encoding 55 | 56 | Currently there are three different encodings in use (JSON, RON, and plain 57 | text). This is for historical reasons and could be improved. 58 | 59 | == Variables available everywhere 60 | 61 | === `HUBRIS_PROJECT_ROOT` 62 | 63 | Contains the path of the current "project root," the directory containing the 64 | `hubris-env.toml` file. 65 | 66 | This variable is set by `hubake` and is passed through the build system 67 | unmodified. 68 | 69 | 70 | == Variables available to tasks 71 | 72 | === `HUBRIS_CHIP_COMPAT` 73 | 74 | Contains a comma-separated list of the `compatible` strings provided by the 75 | chipdef, in order from most specific to least, with no whitespace added. 76 | 77 | Note that the `compatible` strings are also passed to the compiler as 78 | `cfg(hubris_chip="...")`, which is generally a more convenient way to make code 79 | conditional on chip models. 80 | 81 | === `HUBRIS_NOTIFICATIONS` 82 | 83 | Contains a RON-encoded list of strings giving the notification bit names for the 84 | current task, ordered from bit 0 counting up. 85 | 86 | === `HUBRIS_TASKS` 87 | 88 | NOTE: for most purposes it's better to use the `HUBRIS_TASK_INFO_xxx` variables. 89 | 90 | Contains a comma-separated list of task names, in index order, with no added 91 | whitespace. This is used to, among other things, generate the task count and 92 | name enumeration in the `hubris_num_tasks` crate. 93 | 94 | In general, the `HUBRIS_TASK_INFO_xxx` variables are a more powerful and 95 | easier-to-extend alternative. However, if you need to indicate a dependency on 96 | the _complete set_ of tasks declared, such that code will be rebuilt if tasks 97 | are reordered, renamed, added, or removed, then `HUBRIS_TASKS` is the right 98 | thing to use. 99 | 100 | === `HUBRIS_TASK_CONFIG` 101 | 102 | Contains a JSON-encoded representation of the task's `config` stanza from the 103 | appdef. 104 | 105 | === `HUBRIS_TASK_INFO_xxx` 106 | 107 | Each variable `HUBRIS_TASK_INFO_xxx` contains information on the task named 108 | `xxx`, encoded as a JSON object. The following keys are currently defined; more 109 | may be added at any time. 110 | 111 | - `index` (integer): index of this task in the application. 112 | - `notifications` (list of string): this task's notification bit names, starting 113 | from bit 0. 114 | 115 | This is preferred over the older `HUBRIS_TASKS` variable because it's easier to 116 | extend, and allows for more granular build dependencies: a task can indicate a 117 | build-time dependency on the index or notifications of some task `foo` _only,_ 118 | whereas depending on `HUBRIS_TASKS` means any change to task naming or order 119 | forces a rebuild. 120 | 121 | If you want to rebuild if a task is added (or renamed), use the `HUBRIS_TASKS` 122 | variable. 123 | 124 | === `HUBRIS_TASK_SLOTS` 125 | 126 | Contains a JSON-encoded object. The keys are the names of task slots defined in 127 | the current task. The values are the indices of the bound tasks. 128 | 129 | == Variables available to kernels 130 | 131 | === `HUBRIS_IMAGE_ID` 132 | 133 | Contains a decimal 64-bit integer generated from an unspecified hash over some 134 | of the build inputs. 135 | 136 | This is intended to serve only one purpose: making it easier for Humility to 137 | detect that an image has not already been flashed, without necessarily having to 138 | read all of flash. The Hubris kernel deposits the contents of this integer (in 139 | binary form) in a symbol called `HUBRIS_IMAGE_ID`. 140 | 141 | This ID _is not_ guaranteed to change if the build inputs change. It should not 142 | be relied upon to distinguish builds from one another. 143 | 144 | === `HUBRIS_KCONFIG` 145 | 146 | Contains a RON-encoded structure conveying information about the tasks and 147 | memory layout of the current application. This is consumed by the kernel build 148 | script to generate tables. 149 | 150 | The `build-kconfig` crate in upstream Hubris is the authoritative definition of 151 | the schema. 152 | 153 | 154 | == Variables used by Cargo 155 | 156 | === `CARGO_ENCODED_RUSTFLAGS` 157 | 158 | The `RUSTFLAGS` environment variable can't handle the syntax of fancier compiler 159 | flags like `--check-cfg`. The `CARGO_ENCODED_RUSTFLAGS` variable is a hack to 160 | avoid this. Cargo processes the contents of this variable and expands it into 161 | compiler flags. Hopefully the actual `RUSTFLAGS` will eventually be fixed to 162 | support the full breadth of syntax options. 163 | 164 | The build system uses `CARGO_ENCODED_RUSTFLAGS` to submit the combination of 165 | app-level and component-level build configuration to Cargo. 166 | -------------------------------------------------------------------------------- /doc/chipdef.adoc: -------------------------------------------------------------------------------- 1 | = Chip Definition Format 2 | 3 | A chip definition file, or "chipdef," defines the features of a microcontroller 4 | system-on-a-chip in a way that can be reused across boards. 5 | 6 | == Basics 7 | 8 | Chipdefs are written in https://kdl.dev/[KDL2]. 9 | 10 | To distinguish a chipdef from other KDL files, when putting a chipdef in its own 11 | file, we require the first non-comment non-whitespace node in the file to be 12 | `chip`. It's also possible to write a chipdef _inline_ within a boarddef, in 13 | which case this restriction is lifted. 14 | 15 | 16 | == `chip` (required) 17 | 18 | `chip NAME` 19 | 20 | - `NAME` is the name given to this chip definition. It can be more or less 21 | arbitrary, and isn't currently used for very much. 22 | 23 | == `target-triple` (required) 24 | 25 | `target-triple TRIPLE` 26 | 27 | - `TRIPLE` is the target triple that should be used when compiling code for this 28 | chip, by default. 29 | 30 | == `vector-table-size` (required) 31 | 32 | `vector-table-size BYTES` 33 | 34 | - `BYTES` is an integer giving the number of bytes to reserve for this chip's 35 | vector table at the start of flash. 36 | 37 | == `probe-rs-name` 38 | 39 | `probe-rs-name NAME` 40 | 41 | - `NAME` is the name the `probe_rs` crate uses to refer to this chip. This is 42 | required to get full Humility support. 43 | 44 | == `compatible` 45 | 46 | ---- 47 | compatible { 48 | - FIRST 49 | - SECOND 50 | // and so forth 51 | } 52 | ---- 53 | 54 | `compatible` provides a list of strings (`FIRST`, `SECOND`, ... in the example) 55 | that code can use to match on this chip, using the `hubris_chip` `cfg`. They 56 | should be listed from most specific to least specific. 57 | 58 | Example: 59 | 60 | ---- 61 | compatible { 62 | - STM32G031K8 // flash size + pin count 63 | - STM32G031x8 // flash size only 64 | - STM32G031 // subfamily only 65 | - STM32G0 // family only 66 | - arm-cortex-m0plus // microprocessor generic name 67 | } 68 | ---- 69 | 70 | == `memory` 71 | 72 | ---- 73 | memory { 74 | // children go here 75 | } 76 | ---- 77 | 78 | The `memory` section defines the chip's physical address space layout. 79 | 80 | === `region` 81 | 82 | ---- 83 | region NAME { 84 | base ADDRESS 85 | size BYTES 86 | // attributes go here 87 | } 88 | ---- 89 | 90 | Defines an allocatable region of address space. Three values of `NAME` are 91 | special: 92 | 93 | - `vectors` is where the vector table gets placed. 94 | - `ram` is used for task RAM images and stacks, including the kernel stack. 95 | - `flash` is used for task and kernel code. 96 | 97 | Attributes: 98 | 99 | - `base ADDRESS`: gives the start of the region 100 | - `size BYTES`: gives the region length in bytes 101 | - `read` (optional): marks the region as potentially readable. 102 | - `write` (optional): marks the region as potentially writable. 103 | - `execute` (optional): marks the region as potentially executable. 104 | 105 | == `peripheral` 106 | 107 | ---- 108 | peripheral NAME { 109 | base ADDRESS 110 | size BYTES 111 | // optional attributes 112 | } 113 | ---- 114 | 115 | Defines a memory-mapped peripheral that can be mapped into tasks. 116 | 117 | Attributes: 118 | 119 | - `base ADDRESS`: gives the base address of the peripheral. 120 | - `size BYTES`: gives the size in bytes of the region to map into a task that 121 | uses this peripheral. 122 | - `irq NAME NUMBER` (optional): associates this peripheral with a system IRQ 123 | `NUMBER`, and gives it the name `NAME` for reference by tasks. For peripherals 124 | with only one IRQ, the current convention is to use the name `irq`. 125 | -------------------------------------------------------------------------------- /doc/hubake.adoc: -------------------------------------------------------------------------------- 1 | = The `hubake` build tool 2 | 3 | `hubake` provides a frontend to the other Hubris build tools. It's intended to 4 | operate something like `rustup`: you can use a single version of `hubake` across 5 | Hubris projects that depend on different kernel and build tool versions. 6 | 7 | ``hubake``'s name is an homage to `bitbake` from OpenEmbedded/Yocto. 8 | 9 | == Getting `hubake` 10 | 11 | `hubake` lives in the exhubris repo. It's intended to be installed in your 12 | user's normal `cargo install` directory, though if you'd like to place it 13 | somewhere else, you can do as you wish. 14 | 15 | To install it in the typical location, run: 16 | 17 | ---- 18 | cargo install --git https://github.com/cbiffle/exhubris \ 19 | hubake \ 20 | --locked 21 | ---- 22 | 23 | (The backslashes help to wrap the commandline for presentation in this page; you 24 | can remove them to put it all on one line if you want.) 25 | 26 | The `--locked` flag ensures that the tool is built with the package versions 27 | listed in the `exhubris` repo, which is what `hubake` is tested against. It is 28 | probably safe to omit if you'd prefer. 29 | 30 | == An example session 31 | 32 | Here's what you might see looking over someone's shoulder as they use `hubake` 33 | in a newly checked out project. 34 | 35 | ---- 36 | $ hubake setup 37 | checking setup in project root: /home/cbiffle/proj/exhubris/exttest 38 | system: git https://github.com/cbiffle/exhubris 39 | rev: 696ecfe9197a27712a7f65deb901274f5553370d 40 | building tool for rev, this may take a bit... 41 | setup complete 42 | 43 | $ hubake build app/demo/app.kdl 44 | ...normal build system output follows... 45 | ---- 46 | 47 | == Project root (`hubris-env.toml`) 48 | 49 | `hubake` operates in terms of _projects._ A project is stored in a directory 50 | that contains file called `hubris-env.toml`. This directory is called the 51 | _project root_ (since it can contain other directories). 52 | 53 | The location of the project root is passed into the build tools, and they will 54 | derive certain other paths from it (to be described elsewhere in the build 55 | system docs). 56 | 57 | `hubris-env.toml` controls the behavior of `hubake` for that project. Its role 58 | is very similar to `rust-toolchain.toml` in a Rust project: it ensures that 59 | anyone working in the project has the same version of the tools. 60 | 61 | Most projects' `hubris-env.toml` will look something like this: 62 | 63 | [source=toml] 64 | ---- 65 | [system] 66 | source = "git" 67 | repo = "http://github.com/cbiffle/exhubris" 68 | rev = "696ecfe9197a27712a7f65deb901274f5553370d" 69 | ---- 70 | 71 | This specifies that the project's version of the exhubris tooling comes from 72 | `cbiffle/exhubris` at a specific git rev. 73 | 74 | === `[system]` section 75 | 76 | ==== `source` 77 | 78 | Defines how to obtain the build tools. There are currently two values defined: 79 | 80 | - `git`: check out the build tools from a git repo. 81 | - `here`: use build tools from inside the project itself. This is mostly useful 82 | for demo projects inside `exhubris`, but could also be used to replace or 83 | intercept the build tools if desired. 84 | 85 | ==== `repo` 86 | 87 | URL or path of a git repo containing the build tools. Useful only when 88 | `source="git"`. 89 | 90 | ==== `rev` 91 | 92 | Revision of the build tools from the repo, in `git-rev` format. Useful only when 93 | `source="git"`. 94 | 95 | CAUTION: for reproduceable builds, this should probably be a literal hexadecimal 96 | git hash. More readable alternatives like a tag name can work, but if the tag 97 | changes, you won't be able to reproduce the results. A branch name is almost 98 | certainly a bad idea. 99 | 100 | 101 | == `hubake` operation 102 | 103 | Whenever `hubake` is run, it walks up parent directories until it finds a 104 | `hubris-env.toml` file. Currently it is willing to cross filesystem boundaries 105 | during this process, but that may change. 106 | 107 | TIP: The parent directory walk is performed _textually_ by removing components 108 | from the path to the current working directory. This means the behavior will be 109 | different if you're doing something very odd with filesystem links. This is 110 | deliberate, since it's guaranteed to terminate. 111 | 112 | `hubake` then reads the `hubris-env.toml` and looks for corresponding build 113 | tools. If a matching copy is found, it will be used. Otherwise, a new copy will 114 | be built and stored. 115 | 116 | Build tools are stored in the `hubris` directory in your user's "data 117 | directory," which is different on different operating systems: 118 | 119 | - Linux and other XDG-style Unix: `$HOME/.local/share/hubris` (can be overridden 120 | by the `$XDG_DATA_HOME` environment variable) 121 | - Mac: `$HOME/Library/Application Support/hubris` 122 | - Windows: `C:\Users\YOURNAME\AppData\Roaming\hubris` 123 | 124 | CAUTION: The contents of this directory are subject to change. Please don't make 125 | assumptions about their layout. 126 | 127 | Once the tools are located, `hubake` executes the `hubris-build` executable they 128 | contain, forwarding all command line arguments to it. 129 | 130 | === Building tools 131 | 132 | If `hubake` decides that tools are not available, or if you've forced a rebuild 133 | (see `setup -f` below), `hubake` will use Cargo to obtain and build the tools at 134 | the correct version. 135 | 136 | By default, Cargo's output is suppressed during this process. The only output is 137 | a message from `hubake` saying something like 138 | 139 | `Building tools for rev, please wait...` 140 | 141 | === Bootstrapping: `setup` 142 | 143 | While most `hubake` command lines are forwarded through to a particular version 144 | of the tools, there is one exception: `setup`. 145 | 146 | `hubake setup` is handled internally and serves to validate the current project 147 | layout and make sure the appropriate tools are installed. 148 | 149 | `hubake setup -f` can be used to "force" setup, reinstalling tools even if 150 | there's an existing copy. 151 | 152 | This means that the Hubris tools themselves (the ones called _by_ `hubake`) 153 | cannot have a `setup` subcommand. 154 | 155 | === Typical commands 156 | 157 | The precise set of commands available through `hubake` can change with different 158 | versions of the tools, but the following commands should generally be available. 159 | 160 | For an exact listing of commands provided by your tools version, run `hubake 161 | help`. For details on any specific command `xx`, run `hubake xx --help`. 162 | 163 | ==== `hubake build` 164 | 165 | Example: 166 | 167 | `hubake build myapp/app.toml` 168 | 169 | Loads an appcfg from a provided path and builds the app. 170 | 171 | `--help` shows the actual set of options available. 172 | 173 | Typical options may include: 174 | 175 | - `--cargo-verbose` enables verbose output from Cargo, e.g. listing all the 176 | build steps. 177 | - `-o PATH` controls where the build archive is written. By default it's written 178 | to `appname-build.zip` in the project root, where `appname` is the name 179 | defined in the appcfg. 180 | 181 | == Build system interface 182 | 183 | `hubake` defines a very simple interface to the build tools, in the hopes that 184 | we won't have to modify it very often. 185 | 186 | Once `hubake` locates the appropriate version of the build tools (more below), 187 | it... 188 | 189 | - Sets the environment variable `HUBRIS_PROJECT_ROOT` to the path where it 190 | located the `hubris-env.toml` file. 191 | - Calls the build tool executable, forwarding all command line arguments and 192 | stdin/stdout/stderr. 193 | - Returns its exit code. 194 | 195 | TIP: Unix users may recognize this as `exec`. `hubake` does not currently use 196 | `exec` because it's not portable to Windows. 197 | 198 | === Finding the build tools with `source="here"` 199 | 200 | When the `hubris-env.toml` declares `system.source="here"`, `hubake` will locate 201 | a Cargo package in the current workspace named `hubris-build`, and then run an 202 | executable from that package, which must also be named `hubris-build`. 203 | 204 | This is more or less literally: 205 | 206 | ---- 207 | cargo run -p hubris-build --bin hubris-build -- ARGS 208 | ---- 209 | 210 | === Finding the build tools with `source="git"` 211 | 212 | When the `hubris-env.toml` declares `system.source="git"`, `hubake` defers to 213 | `cargo install` in `--git` mode. The repository named must contain a package 214 | `hubris-build` that contains an executable `hubris-build`. This executable will 215 | be installed to a ``hubake``-specific path, _not_ into your bin directory. 216 | 217 | `hubake` uses ``cargo install``'s install tracking features to avoid rebuilding 218 | the tools if they're already installed. (This works by maintaining a small 219 | metadata file next to the binary in our data directory.) To force reinstall, use 220 | `hubake setup -f`. 221 | 222 | 223 | == Design rationale 224 | 225 | The fundamental goals of `hubake` are: 226 | 227 | 1. To provide a concise and consistent command for building and manipulating 228 | Hubris projects. 229 | 2. To provide consistent, reproduceable results in any given project, 230 | _independent of the version of `hubake` that's installed._ 231 | 3. To not require updating very often. 232 | 233 | Goal 1 is met by suggesting that users install `hubake` globally, so they need 234 | only type `hubake` in any project. (Users may decide to do it differently, of 235 | course.) 236 | 237 | Goal 2 is the reason for the `hubris-env.toml` file, providing an exact pinning 238 | of the build tools version. 239 | 240 | Goal 3 is the reason for keeping the `hubake` interface to the build tools 241 | simple. We should only need to update `hubake` if that interface changes (or if 242 | it turns out to contain some unforeseen bug). 243 | -------------------------------------------------------------------------------- /drv/l4blinky/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "l4blinky" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | # It is annoying that the chip features are specified down to the package level, 8 | # but, that is how the stm32-metapac do. 9 | chip-stm32l412kb = ["stm32-metapac/stm32l412kb"] 10 | 11 | [dependencies] 12 | drv-stm32xx-sys-api = { version = "0.1.0", path = "../stm32xx-sys-api" } 13 | hubris-notifications = { version = "0.1.0", path = "../../sys/notifications" } 14 | hubris-task-slots = { version = "0.1.0", path = "../../sys/task-slots" } 15 | stm32-metapac = { version = "15.0.0" } 16 | userlib = { workspace = true, features = ["no-panic"] } 17 | 18 | [build-dependencies] 19 | serde_json = "1.0.133" 20 | 21 | [package.metadata.hubris.auto-features] 22 | chip = true 23 | -------------------------------------------------------------------------------- /drv/l4blinky/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::io::Write; 3 | 4 | use serde_json::{Map, Value}; 5 | 6 | fn main() { 7 | println!("cargo::rerun-if-env-changed=HUBRIS_TASK_CONFIG"); 8 | let Ok(configstr) = std::env::var("HUBRIS_TASK_CONFIG") else { 9 | panic!("Missing config for task!"); 10 | }; 11 | let config: Map = serde_json::from_str(&configstr).unwrap(); 12 | 13 | let mut out = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 14 | out.push("task_config.rs"); 15 | 16 | let mut f = std::fs::File::create(&out).unwrap(); 17 | 18 | writeln!(f, "pub(crate) mod config {{").unwrap(); 19 | writeln!(f, "use drv_stm32xx_sys_api::Port;").unwrap(); 20 | 21 | let pins = config["pins"].as_array().unwrap(); 22 | let npins = pins.len(); 23 | writeln!(f, "pub const PINS: [(Port, u8); {npins}] = [").unwrap(); 24 | for pinname in pins { 25 | let (port, pin) = parse_pin_name(pinname.as_str().unwrap()); 26 | writeln!(f, " (Port::{port}, {pin}),").unwrap(); 27 | } 28 | writeln!(f, "];").unwrap(); 29 | writeln!(f, "}}").unwrap(); 30 | } 31 | 32 | fn parse_pin_name(name: &str) -> (&str, u8) { 33 | let (port, pin) = name.split_at(1); 34 | 35 | (port, pin.parse().unwrap()) 36 | } 37 | -------------------------------------------------------------------------------- /drv/l4blinky/src/main.rs: -------------------------------------------------------------------------------- 1 | //! STM32L4 blinker. It's annoying how system-specific this is right now. Should 2 | //! fix that eventually. 3 | 4 | #![no_std] 5 | #![no_main] 6 | 7 | use userlib as _; 8 | use hubris_task_slots::SLOTS; 9 | use drv_stm32xx_sys_api::Stm32Sys as Sys; 10 | 11 | #[export_name = "main"] 12 | fn main() -> ! { 13 | let sys = Sys::from(SLOTS.sys); 14 | 15 | // Make all our pins outputs. 16 | for (port, pin) in config::PINS { 17 | sys.set_pin_output(port, pin); 18 | } 19 | 20 | let start = userlib::sys_get_timer().now; 21 | const INTERVAL: u64 = 500; 22 | let mut next = start + INTERVAL; 23 | loop { 24 | for (port, pin) in config::PINS { 25 | sys.toggle_pin(port, pin); 26 | userlib::sys_set_timer(Some(next), hubris_notifications::TIMER); 27 | userlib::sys_recv_notification(hubris_notifications::TIMER); 28 | next += INTERVAL; 29 | } 30 | } 31 | } 32 | 33 | include!(concat!(env!("OUT_DIR"), "/task_config.rs")); 34 | -------------------------------------------------------------------------------- /drv/l4blinky/task.kdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | // The task name here is mostly just used to talk about the task. 33 | task "uart-echo" 34 | 35 | // Since this targets a specific processor architecture and is not very 36 | // customizable, there's a default stack size that is likely to hold across all 37 | // uses. 38 | stack-size 160 39 | 40 | build { 41 | // Enable chip-specific Cargo features that we wind up passing through to the 42 | // metapac. 43 | // 44 | // This also ensures that this task is not used on some random other processor, 45 | // because the match is required to be exhaustive. 46 | match "chip" { 47 | "STM32G031K8" { 48 | features "chip-stm32g031k8" 49 | } 50 | // When you need some other STM32G0, add it here, and also in Cargo.toml, 51 | // and also in src/main.rs... sometimes I hate the rust embedded ecosystem 52 | } 53 | } 54 | 55 | // We have a required dependence on a task we call 'sys'. An 56 | // application may bind it to a task of a different name, of 57 | // course. 58 | uses-task "sys" 59 | 60 | uses-peripheral "usart2" 61 | 62 | config { 63 | type "object"; unevaluatedProperties false 64 | properties { 65 | uart-clock-hz { 66 | type "number"; minimum 1; maximum 0xFFFF_FFFF 67 | } 68 | baud-rate { 69 | type "number"; minimum 1; maximum 0xFFFF_FFFF 70 | } 71 | pins { 72 | type "array"; minItems 2; maxItems 2; uniqueItems true 73 | items { 74 | type "object"; unevaluatedProperties false 75 | properties { 76 | name { 77 | type "string"; pattern "^[A-E][0-9]{1,2}$" 78 | } 79 | af { 80 | type "number"; minimum 0; maximum 7 81 | } 82 | } 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /drv/stm32l4-usb-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drv-stm32l4-usb-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | hubpack = "0.1.2" 8 | idyll_runtime = { version = "0.1.0", path = "../../sys/idyll_runtime" } 9 | serde = { version = "1.0.215", default-features = false, features = ["derive"] } 10 | userlib.workspace = true 11 | zerocopy = "0.8.12" 12 | 13 | [build-dependencies] 14 | hubris-build.workspace = true 15 | -------------------------------------------------------------------------------- /drv/stm32l4-usb-api/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | println!("cargo::rerun-if-changed=../stm32l4-usb/usbhid-idl.kdl"); 5 | let iface = hubris_build::idl::load_interface("../stm32l4-usb/usbhid-idl.kdl").unwrap(); 6 | let client = hubris_build::idl::codegen::generate_client(&iface).unwrap(); 7 | let client = hubris_build::idl::codegen::format_code(&client); 8 | let mut outpath = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 9 | outpath.push("generated_client.rs"); 10 | 11 | std::fs::write(&outpath, client).unwrap(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /drv/stm32l4-usb-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | include!(concat!(env!("OUT_DIR"), "/generated_client.rs")); 4 | -------------------------------------------------------------------------------- /drv/stm32l4-usb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drv-stm32l4-usb" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | chip-stm32l412kb = ["stm32-metapac/stm32l412kb"] 8 | 9 | [dependencies] 10 | idyll_runtime = { version = "0.1.0", path = "../../sys/idyll_runtime" } 11 | userlib = { workspace = true, features = ["no-panic"] } 12 | stm32-metapac = "15.0.0" 13 | hubpack = "0.1.2" 14 | serde = { version = "1.0.215", default-features = false, features = ["derive"] } 15 | drv-stm32xx-sys-api = { version = "0.1.0", path = "../stm32xx-sys-api" } 16 | hubris-task-slots = { version = "0.1.0", path = "../../sys/task-slots" } 17 | hubris-notifications = { version = "0.1.0", path = "../../sys/notifications" } 18 | cortex-m = { version = "0.7.7", features = ["inline-asm"] } 19 | zerocopy = "0.8.11" 20 | zerocopy-derive = "0.8.11" 21 | num-traits = { version = "0.2.19", default-features = false } 22 | num-derive = "0.4.2" 23 | smart-default = "0.7.1" 24 | utf16_literal = "0.2.1" 25 | 26 | [build-dependencies] 27 | hubris-build-util = { version = "0.1.0", path = "../../sys/build-util" } 28 | serde = { version = "1.0.215", features = ["derive"] } 29 | hubris-build.workspace = true 30 | 31 | [[bin]] 32 | name = "drv-stm32l4-usb" 33 | test = false 34 | bench = false 35 | 36 | [package.metadata.hubris.auto-features] 37 | chip = true 38 | -------------------------------------------------------------------------------- /drv/stm32l4-usb/build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::path::PathBuf; 3 | 4 | use serde::Deserialize; 5 | 6 | fn main() { 7 | let outpath = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 8 | 9 | println!("cargo::rerun-if-changed=usbhid-idl.kdl"); 10 | let iface = hubris_build::idl::load_interface("usbhid-idl.kdl").unwrap(); 11 | let server = hubris_build::idl::codegen::generate_server(&iface).unwrap(); 12 | let server = hubris_build::idl::codegen::format_code(&server); 13 | let genserver_path = outpath.join("generated_server.rs"); 14 | 15 | std::fs::write(&genserver_path, server).unwrap(); 16 | 17 | let config: Config = hubris_build_util::get_task_config(); 18 | let Some((task_name, not_name)) = config.on_event.split_once('#') else { 19 | panic!("keyboard signal name should be 'task#notification' but was: {}", 20 | config.on_event); 21 | }; 22 | let Some(evt_task_info) = hubris_build_util::get_task_info(task_name) else { 23 | panic!("keyboard signal name references unknown task {task_name}"); 24 | }; 25 | let Some(evt_mask) = evt_task_info.notification_mask(not_name) else { 26 | panic!("keyboard signal name references unknown notification {not_name}"); 27 | }; 28 | 29 | let Some((task_name, not_name)) = config.on_report.split_once('#') else { 30 | panic!("keyboard signal name should be 'task#notification' but was: {}", 31 | config.on_report); 32 | }; 33 | let Some(report_task_info) = hubris_build_util::get_task_info(task_name) else { 34 | panic!("keyboard signal name references unknown task {task_name}"); 35 | }; 36 | let Some(report_mask) = evt_task_info.notification_mask(not_name) else { 37 | panic!("keyboard signal name references unknown notification {not_name}"); 38 | }; 39 | 40 | let mut config_out = std::fs::File::create(outpath.join("usb_config.rs")).unwrap(); 41 | writeln!(config_out, "const EVENT_TASK_INDEX: u16 = {};", evt_task_info.get_index()).unwrap(); 42 | writeln!(config_out, "const EVENT_NOTIFICATION_MASK: u32 = {};", evt_mask).unwrap(); 43 | writeln!(config_out, "const REPORT_TASK_INDEX: u16 = {};", report_task_info.get_index()).unwrap(); 44 | writeln!(config_out, "const REPORT_NOTIFICATION_MASK: u32 = {};", report_mask).unwrap(); 45 | drop(config_out); 46 | } 47 | 48 | #[derive(Deserialize)] 49 | struct Config { 50 | on_event: String, 51 | on_report: String, 52 | } 53 | -------------------------------------------------------------------------------- /drv/stm32l4-usb/src/hid.rs: -------------------------------------------------------------------------------- 1 | use num_derive::FromPrimitive; 2 | use smart_default::SmartDefault; 3 | use zerocopy::little_endian::U16; 4 | use zerocopy_derive::{Immutable, IntoBytes, Unaligned}; 5 | 6 | #[derive(Copy, Clone, Debug, FromPrimitive, IntoBytes, Unaligned, Immutable)] 7 | #[repr(u8)] 8 | pub enum HidClassDescriptorType { 9 | Hid = 0x21, 10 | Report = 0x22, 11 | Physical = 0x33, 12 | } 13 | 14 | #[derive(Copy, Clone, Debug, FromPrimitive)] 15 | #[repr(u8)] 16 | pub enum HidRequestCode { 17 | GetReport = 1, 18 | GetIdle = 2, 19 | GetProtocol = 3, 20 | SetReport = 9, 21 | SetIdle = 0xA, 22 | SetProtocol = 0xB, 23 | } 24 | 25 | #[derive(Clone, Debug, IntoBytes, Unaligned, Immutable, SmartDefault)] 26 | #[repr(C)] 27 | pub struct HidDescriptor { 28 | #[default = 9] 29 | pub length: u8, 30 | #[default(HidClassDescriptorType::Hid)] 31 | pub type_: HidClassDescriptorType, 32 | pub hid_version: U16, 33 | pub country_code: u8, 34 | #[default = 1] 35 | pub num_descriptors: u8, 36 | pub descriptor_type: u8, 37 | pub descriptor_length: U16, 38 | } 39 | 40 | #[derive(Copy, Clone, Debug, Default)] 41 | pub enum OutKind { 42 | #[default] 43 | SetReport 44 | } 45 | -------------------------------------------------------------------------------- /drv/stm32l4-usb/src/protocol.rs: -------------------------------------------------------------------------------- 1 | use num_derive::FromPrimitive; 2 | use num_traits::FromPrimitive as _; 3 | use smart_default::SmartDefault; 4 | use utf16_literal::utf16; 5 | use zerocopy::{little_endian::U16, IntoBytes}; 6 | use zerocopy_derive::{FromBytes, IntoBytes, Immutable, Unaligned}; 7 | 8 | use crate::usbsram; 9 | 10 | #[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Immutable, Unaligned)] 11 | #[repr(C)] 12 | pub struct SetupPacket { 13 | pub request_type: RequestType, 14 | pub request: u8, 15 | pub value: U16, 16 | pub index: U16, 17 | pub length: U16, 18 | } 19 | 20 | #[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, Unaligned, Immutable)] 21 | #[repr(transparent)] 22 | pub struct RequestType(u8); 23 | 24 | impl RequestType { 25 | pub fn data_phase_direction(self) -> Dir { 26 | if self.0 & 0x80 == 0 { 27 | Dir::HostToDevice 28 | } else { 29 | Dir::DeviceToHost 30 | } 31 | } 32 | 33 | pub fn type_(self) -> RequestTypeType { 34 | match (self.0 >> 5) & 0b11 { 35 | 0 => RequestTypeType::Standard, 36 | 1 => RequestTypeType::Class, 37 | 2 => RequestTypeType::Vendor, 38 | _ => RequestTypeType::Reserved, 39 | } 40 | } 41 | 42 | pub fn recipient(self) -> Recipient { 43 | match self.0 & 0x1F { 44 | 0 => Recipient::Device, 45 | 1 => Recipient::Interface, 46 | 2 => Recipient::Endpoint, 47 | 3 => Recipient::Other, 48 | x => Recipient::Reserved(x), 49 | } 50 | } 51 | } 52 | 53 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 54 | pub enum Dir { 55 | HostToDevice, 56 | DeviceToHost, 57 | } 58 | 59 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 60 | pub enum RequestTypeType { 61 | Standard = 0, 62 | Class = 1, 63 | Vendor = 2, 64 | Reserved = 3, 65 | } 66 | 67 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 68 | pub enum Recipient { 69 | Device, 70 | Interface, 71 | Endpoint, 72 | Other, 73 | Reserved(u8), 74 | } 75 | 76 | #[derive(Copy, Clone, Debug, FromPrimitive, IntoBytes, Unaligned, Immutable)] 77 | #[repr(u8)] 78 | enum DescriptorType { 79 | Device = 1, 80 | Configuration = 2, 81 | String = 3, 82 | Interface = 4, 83 | Endpoint = 5, 84 | } 85 | 86 | #[derive(Copy, Clone, Debug, FromPrimitive)] 87 | #[repr(u8)] 88 | pub enum StdRequestCode { 89 | GetStatus = 0, 90 | ClearFeature = 1, 91 | SetFeature = 3, 92 | SetAddress = 5, 93 | GetDescriptor = 6, 94 | SetDescriptor = 7, 95 | GetConfiguration = 8, 96 | SetConfiguration = 9, 97 | GetInterface = 10, 98 | SetInterface = 11, 99 | SynchFrame = 12, 100 | } 101 | 102 | #[derive(Clone, Debug, IntoBytes, Unaligned, SmartDefault, Immutable)] 103 | #[repr(C)] 104 | struct DeviceDescriptor { 105 | #[default = 18] 106 | length: u8, 107 | #[default(DescriptorType::Device)] 108 | type_: DescriptorType, 109 | #[default(U16::new(0x0101))] 110 | usb_version: U16, 111 | device_class: u8, 112 | device_subclass: u8, 113 | device_protocol: u8, 114 | max_packet_size0: u8, 115 | vendor: U16, 116 | product: U16, 117 | device_version: U16, 118 | manufacturer_string: u8, 119 | product_string: u8, 120 | serial_string: u8, 121 | num_configurations: u8, 122 | } 123 | 124 | #[derive(Clone, Debug, IntoBytes, Unaligned, SmartDefault, Immutable)] 125 | #[repr(C)] 126 | struct ConfigDescriptor { 127 | #[default = 9] 128 | length: u8, 129 | #[default(DescriptorType::Configuration)] 130 | type_: DescriptorType, 131 | total_length: U16, 132 | num_interfaces: u8, 133 | configuration_value: u8, 134 | configuration_string: u8, 135 | attributes: u8, 136 | max_power: u8, 137 | } 138 | 139 | 140 | pub fn prepare_descriptor( 141 | setup: &SetupPacket, 142 | offset: usize, 143 | ) -> Option { 144 | let dtype = DescriptorType::from_u16(setup.value.get() >> 8)?; 145 | let idx = setup.value.get() as u8; 146 | 147 | let xxx = |desc| { 148 | usbsram::write_bytes(offset, desc); 149 | Some(desc.len()) 150 | }; 151 | 152 | match (dtype, idx) { 153 | (DescriptorType::Device, 0) => xxx(DeviceDescriptor { 154 | max_packet_size0: 64, 155 | vendor: U16::new(0xdead), 156 | product: U16::new(0xbeef), 157 | device_version: U16::new(0x3_14), 158 | manufacturer_string: 1, 159 | product_string: 2, 160 | serial_string: 3, 161 | num_configurations: 1, 162 | ..DeviceDescriptor::default() 163 | }.as_bytes()), 164 | (DescriptorType::Configuration, 0) => xxx(CompoundConfig { 165 | config: ConfigDescriptor { 166 | total_length: U16::new(core::mem::size_of::() as u16), 167 | num_interfaces: 1, 168 | configuration_value: 1, 169 | configuration_string: 2, 170 | attributes: 0x80, 171 | max_power: 250, 172 | ..ConfigDescriptor::default() 173 | }, 174 | iface: InterfaceDescriptor { 175 | interface_number: 0, 176 | alternate_setting: 0, 177 | num_endpoints: 1, 178 | interface_class: 3, 179 | interface_subclass: 1, 180 | interface_protocol: 1, 181 | interface_string: 2, 182 | ..InterfaceDescriptor::default() 183 | }, 184 | hid: crate::hid::HidDescriptor { 185 | hid_version: U16::new(0x0101), 186 | country_code: 0, 187 | descriptor_type: 0x22, 188 | descriptor_length: U16::new(62), 189 | ..crate::hid::HidDescriptor::default() 190 | }, 191 | ep: EndpointDescriptor { 192 | endpoint_address: 0x81, 193 | attributes: 0b11, 194 | max_packet_size: 8.into(), 195 | interval: 1, 196 | ..EndpointDescriptor::default() 197 | }, 198 | }.as_bytes()), 199 | (DescriptorType::String, _) => { 200 | let s: &[u16] = match idx { 201 | 0 => &[0x0409], // en_US 202 | 1 => utf16!("🦶HUBRIS"), 203 | 2 => utf16!("Test Device 🎉"), 204 | 3 => utf16!("🦀"), 205 | _ => return None, 206 | }; 207 | Some(write_str(offset, s)) 208 | } 209 | _ => None, 210 | } 211 | } 212 | 213 | fn write_str(offset: usize, data: &[u16]) -> usize { 214 | let len = data.len() * 2 + 2; 215 | usbsram::write8(offset, len as u8); 216 | usbsram::write8(offset + 1, DescriptorType::String as u8); 217 | for (i, hw) in data.iter().enumerate() { 218 | usbsram::write16(offset + 2 + 2 * i, *hw); 219 | } 220 | len 221 | } 222 | 223 | #[derive(Clone, Debug, IntoBytes, Unaligned, Default, Immutable)] 224 | #[repr(C)] 225 | struct CompoundConfig { 226 | config: ConfigDescriptor, 227 | iface: InterfaceDescriptor, 228 | hid: crate::hid::HidDescriptor, 229 | ep: EndpointDescriptor, 230 | } 231 | 232 | #[derive(Clone, Debug, IntoBytes, Unaligned, SmartDefault, Immutable)] 233 | #[repr(C)] 234 | struct InterfaceDescriptor { 235 | #[default = 9] 236 | length: u8, 237 | #[default(DescriptorType::Interface)] 238 | type_: DescriptorType, 239 | interface_number: u8, 240 | alternate_setting: u8, 241 | num_endpoints: u8, 242 | interface_class: u8, 243 | interface_subclass: u8, 244 | interface_protocol: u8, 245 | interface_string: u8, 246 | } 247 | 248 | #[derive(Clone, Debug, IntoBytes, Unaligned, SmartDefault, Immutable)] 249 | #[repr(C)] 250 | struct EndpointDescriptor { 251 | #[default = 7] 252 | length: u8, 253 | #[default(DescriptorType::Endpoint)] 254 | type_: DescriptorType, 255 | endpoint_address: u8, 256 | attributes: u8, 257 | max_packet_size: U16, 258 | interval: u8, 259 | } 260 | -------------------------------------------------------------------------------- /drv/stm32l4-usb/src/usbsram.rs: -------------------------------------------------------------------------------- 1 | //! USB SRAM access and layout. 2 | //! 3 | //! Currently I'm not using the linker to manage USB SRAM. Instead, I'm treating 4 | //! it as a fixed layout, which is as follows: 5 | //! 6 | //! ```text 7 | //! 000 buffer descriptor table 8 | //! 040 tx buffer 0 9 | //! 080 rx buffer 0 10 | //! 0c0 tx buffer 1 11 | //! 100 rx buffer 1 12 | //! 140 end of allocated space 13 | //! ``` 14 | 15 | use core::ptr::{read_volatile, write_volatile}; 16 | 17 | use zerocopy::{FromBytes, Immutable, IntoBytes}; 18 | use zerocopy_derive::{FromBytes, Immutable, IntoBytes}; 19 | 20 | #[derive(FromBytes, IntoBytes, Immutable)] 21 | #[repr(C)] 22 | struct BufferDescriptor { 23 | txaddr: u16, 24 | txcount: u16, 25 | rxaddr: u16, 26 | rxcount: u16, 27 | } 28 | 29 | pub fn fill_buffer_descriptor_table(usb: &stm32_metapac::usb::Usb) { 30 | // Initially clear the buffer descriptor table, which conveniently also 31 | // indicates to the hardware that no buffers are available. 32 | for i in (0..0x40).step_by(2) { 33 | write16(i, 0); 34 | } 35 | 36 | // Initialize the buffers to a known value to make debugging, etc. easier. 37 | for i in (0x40..0x140).step_by(2) { 38 | write16(i, 0xDEAD); 39 | } 40 | 41 | // Override the first two buffer descriptor table entries for our expected 42 | // two endpoints. 43 | for i in 0..2 { 44 | let rx_bytes = 0x40; 45 | let (bl_size, num_block) = if rx_bytes < 62 { 46 | debug_assert!(rx_bytes >= 2); 47 | (0, rx_bytes as u16 >> 1) 48 | } else { 49 | (1, rx_bytes as u16 / 32 - 1) 50 | }; 51 | write(size_of::() * i, &BufferDescriptor { 52 | txaddr: u16::try_from(get_ep_tx_offset(i)).unwrap(), 53 | txcount: 0, 54 | rxaddr: u16::try_from(get_ep_rx_offset(i)).unwrap(), 55 | rxcount: (num_block << 10) | (bl_size << 15), 56 | }); 57 | } 58 | 59 | // And load the table address into the peripheral. 60 | usb.btable().write(|w| w.set_btable(0)); 61 | } 62 | 63 | pub fn get_ep_rx_offset(ep: usize) -> usize { 64 | 0x80 + ep * 0x80 65 | } 66 | 67 | pub fn get_ep_tx_offset(ep: usize) -> usize { 68 | 0x40 + ep * 0x80 69 | } 70 | 71 | pub fn set_ep_tx_count(ep: usize, count: u16) { 72 | write16(2 + 8 * ep, count); 73 | } 74 | 75 | const SRAM_BASE: usize = 0x4000_6c00; 76 | const SRAM_SIZE: usize = 1024; 77 | 78 | pub fn read16(addr: usize) -> u16 { 79 | debug_assert!(addr < SRAM_SIZE - 1); 80 | 81 | unsafe { 82 | read_volatile((SRAM_BASE + addr) as *const u16) 83 | } 84 | } 85 | 86 | pub fn write16(addr: usize, value: u16) { 87 | debug_assert!(addr < SRAM_SIZE - 1); 88 | 89 | unsafe { 90 | write_volatile((SRAM_BASE + addr) as *mut u16, value) 91 | } 92 | } 93 | 94 | fn read8(addr: usize) -> u8 { 95 | debug_assert!(addr < SRAM_SIZE); 96 | 97 | unsafe { 98 | read_volatile((SRAM_BASE + addr) as *const u8) 99 | } 100 | } 101 | 102 | pub fn write8(addr: usize, value: u8) { 103 | debug_assert!(addr < SRAM_SIZE); 104 | 105 | // This uses a read-modify-write after determining that an 8-bit write 106 | // affects the byte next to it. siiiiigh. Only 8-bit _reads_ appear to be 107 | // safe. 108 | let word_addr = addr & !1; 109 | 110 | let v = read16(word_addr); 111 | write16(word_addr, if addr & 1 == 0 { 112 | (v & 0xFF00) | u16::from(value) 113 | } else { 114 | (v & 0xFF) | u16::from(value) << 8 115 | }); 116 | } 117 | 118 | pub fn write_bytes(mut addr: usize, mut data: &[u8]) { 119 | debug_assert!(addr < SRAM_SIZE); 120 | debug_assert!(addr + data.len() <= SRAM_SIZE); 121 | 122 | // Handle misaligned initial byte. 123 | if addr & 1 != 0 { 124 | let Some((byte, rest)) = data.split_first() else { 125 | // Empty input? Sure, why not. 126 | return; 127 | }; 128 | write8(addr, *byte); 129 | addr += 1; 130 | data = rest; 131 | } 132 | 133 | // Write as much as possible in 16-bit chunks. 134 | for d in data.chunks(2) { 135 | if let Ok(pair) = <[u8; 2]>::try_from(d) { 136 | let halfword = u16::from_le_bytes(pair); 137 | write16(addr, halfword); 138 | addr += 2; 139 | } else { 140 | // One-byte final chunk. 141 | write8(addr, d[0]); 142 | addr += 1; // should be final, but, hey 143 | } 144 | } 145 | } 146 | 147 | pub fn write(addr: usize, value: &T) 148 | where T: IntoBytes + Immutable, 149 | { 150 | write_bytes(addr, value.as_bytes()); 151 | } 152 | 153 | pub fn read_bytes(mut addr: usize, mut data: &mut [u8]) { 154 | debug_assert!(addr < SRAM_SIZE); 155 | debug_assert!(addr + data.len() <= SRAM_SIZE); 156 | 157 | // Handle misaligned initial byte. 158 | if addr & 1 != 0 { 159 | let Some((byte, rest)) = data.split_first_mut() else { 160 | // Empty input? Sure, why not. 161 | return; 162 | }; 163 | *byte = read8(addr); 164 | addr += 1; 165 | data = rest; 166 | } 167 | 168 | // Read as much as possible in 16-bit chunks. 169 | for d in data.chunks_mut(2) { 170 | if let Ok(pair) = <&mut [u8; 2]>::try_from(&mut *d) { 171 | *pair = read16(addr).to_le_bytes(); 172 | addr += 2; 173 | } else { 174 | // One-byte final chunk. 175 | d[0] = read8(addr); 176 | addr += 1; // should be final, but, hey 177 | } 178 | } 179 | } 180 | 181 | pub fn read(addr: usize) -> T 182 | where T: Sized + FromBytes + IntoBytes, 183 | { 184 | let mut value = T::new_zeroed(); 185 | read_bytes(addr, value.as_mut_bytes()); 186 | value 187 | } 188 | 189 | -------------------------------------------------------------------------------- /drv/stm32l4-usb/usbhid-idl.kdl: -------------------------------------------------------------------------------- 1 | // USBHID driver interface. 2 | 3 | interface usb-hid 4 | 5 | // Operations: 6 | 7 | method enqueue_report { 8 | doc "Makes a report ready to transmit when requested by the host." 9 | 10 | operation 0 11 | arg endpoint u8 12 | lease report u8 { 13 | read 14 | } 15 | 16 | // true if enqueued, false if the previous report is still pending. 17 | result "Result" 18 | } 19 | 20 | method get_event { 21 | doc "Retrieves the pending event from the USB stack, if any." 22 | 23 | operation 1 24 | 25 | result Option 26 | auto-retry 27 | } 28 | 29 | enum EnqueueError { 30 | on-task-death Died 31 | 32 | case Died 33 | 34 | rust-derive Copy Clone Debug 35 | } 36 | 37 | enum UsbEvent { 38 | case Reset 39 | case Configured 40 | case ReportDescriptorNeeded { 41 | field length u16 42 | } 43 | 44 | rust-derive Copy Clone Debug 45 | } 46 | -------------------------------------------------------------------------------- /drv/stm32xx-sys-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drv-stm32xx-sys-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | hubpack = "0.1.2" 8 | serde = { version = "1.0.215", default-features = false, features = ["derive"] } 9 | userlib.workspace = true 10 | 11 | [build-dependencies] 12 | hubris-build.workspace = true 13 | -------------------------------------------------------------------------------- /drv/stm32xx-sys-api/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | println!("cargo::rerun-if-changed=../stm32xx-sys/sys-idl.kdl"); 5 | let iface = hubris_build::idl::load_interface("../stm32xx-sys/sys-idl.kdl").unwrap(); 6 | let client = hubris_build::idl::codegen::generate_client(&iface).unwrap(); 7 | let client = hubris_build::idl::codegen::format_code(&client); 8 | let mut outpath = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 9 | outpath.push("generated_client.rs"); 10 | 11 | std::fs::write(&outpath, client).unwrap(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /drv/stm32xx-sys-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | include!(concat!(env!("OUT_DIR"), "/generated_client.rs")); 4 | -------------------------------------------------------------------------------- /drv/stm32xx-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drv-stm32xx-sys" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | chip-stm32g031k8 = ["stm32-metapac/stm32g031k8"] 8 | chip-stm32l412kb = ["stm32-metapac/stm32l412kb"] 9 | chip-stm32u575zi = ["stm32-metapac/stm32u575zi"] 10 | 11 | [dependencies] 12 | cfg-if = "1.0.0" 13 | hubpack = "0.1.2" 14 | idyll_runtime = { version = "0.1.0", path = "../../sys/idyll_runtime" } 15 | serde = { version = "1.0.215", default-features = false, features = ["derive"] } 16 | stm32-metapac = "15.0.0" 17 | userlib = {workspace = true, features = ["no-panic"]} 18 | 19 | [[bin]] 20 | name = "drv-stm32xx-sys" 21 | test = false 22 | bench = false 23 | 24 | [build-dependencies] 25 | hubris-build.workspace = true 26 | 27 | [package.metadata.hubris.auto-features] 28 | chip = true 29 | -------------------------------------------------------------------------------- /drv/stm32xx-sys/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | println!("cargo::rerun-if-changed=sys-idl.kdl"); 5 | let iface = hubris_build::idl::load_interface("sys-idl.kdl").unwrap(); 6 | let client = hubris_build::idl::codegen::generate_server(&iface).unwrap(); 7 | let client = hubris_build::idl::codegen::format_code(&client); 8 | let mut outpath = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 9 | outpath.push("generated_server.rs"); 10 | 11 | std::fs::write(&outpath, client).unwrap(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /drv/stm32xx-sys/src/main.rs: -------------------------------------------------------------------------------- 1 | //! STM32xx generic "sys" implementation 2 | //! 3 | //! This crate is intended to provide a driver handling RCC+GPIO on all STM32 4 | //! variants. In practice, it needs a bit of code added to support each new 5 | //! family. 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | use core::mem::MaybeUninit; 11 | 12 | use cfg_if::cfg_if; 13 | use stm32_metapac::gpio::vals::{Idr, Moder, Odr, Pupdr}; 14 | use idyll_runtime::Meta; 15 | use userlib::ReplyFaultReason; 16 | 17 | #[export_name = "main"] 18 | fn main() -> ! { 19 | let mut incoming = [MaybeUninit::uninit(); STM_32_SYS_BUFFER_SIZE]; 20 | let rcc = stm32_metapac::RCC; 21 | 22 | // Turn on clock to the GPIO ports. 23 | cfg_if! { 24 | if #[cfg(hubris_chip = "STM32L412")] { 25 | rcc.ahb2enr().modify(|r| r.0 |= 0b1000_1111); 26 | } else if #[cfg(hubris_chip = "STM32G031")] { 27 | rcc.gpioenr().modify(|r| r.0 |= 0b101111); 28 | } else if #[cfg(hubris_chip = "STM32U575")] { 29 | rcc.ahb2enr1().modify(|r| r.0 |= 0b11_1111_1111); 30 | } else { 31 | compile_error!("unimplemented target chip"); 32 | } 33 | } 34 | 35 | loop { 36 | idyll_runtime::dispatch(&mut Server, &mut incoming); 37 | } 38 | } 39 | 40 | fn convert_pin(pin: u8) -> usize { 41 | usize::from(pin) & 0xF 42 | } 43 | 44 | fn screen_af(af: Function) -> Result { 45 | let af = af as u8; 46 | if cfg!(hubris_chip = "STM32G0") { 47 | // Parts that only support AF0-7 48 | if af > 7 { 49 | return Err(ReplyFaultReason::BadMessageContents); 50 | } 51 | } 52 | 53 | Ok(af) 54 | } 55 | 56 | struct Server; 57 | 58 | impl Stm32Sys for Server { 59 | fn set_pin_output(&mut self, _: Meta, port:Port, pin:u8) -> Result<(),ReplyFaultReason> { 60 | let gpio = get_port(port)?; 61 | gpio.moder().modify(|v| v.set_moder(convert_pin(pin), Moder::OUTPUT)); 62 | Ok(()) 63 | } 64 | 65 | fn set_pin_high(&mut self, _: Meta, port:Port, pin:u8) -> Result<(),ReplyFaultReason> { 66 | let gpio = get_port(port)?; 67 | gpio.bsrr().write(|v| { 68 | v.set_bs(convert_pin(pin), true); 69 | }); 70 | Ok(()) 71 | } 72 | 73 | fn set_pin_low(&mut self, _: Meta, port:Port, pin:u8) -> Result<(),ReplyFaultReason> { 74 | let gpio = get_port(port)?; 75 | gpio.bsrr().write(|v| { 76 | v.set_br(convert_pin(pin), true); 77 | }); 78 | Ok(()) 79 | } 80 | 81 | fn toggle_pin(&mut self, _: Meta, port:Port, pin:u8) -> Result<(),ReplyFaultReason> { 82 | let gpio = get_port(port)?; 83 | let pin = convert_pin(pin); 84 | let state = gpio.odr().read().odr(pin) == Odr::HIGH; 85 | gpio.bsrr().write(|v| { 86 | if state { 87 | v.set_br(pin, true); 88 | } else { 89 | v.set_bs(pin, true); 90 | } 91 | }); 92 | 93 | Ok(()) 94 | } 95 | 96 | fn set_pin_alternate_mode(&mut self, _: Meta, port:Port, pin:u8, function:Function) -> Result<(),ReplyFaultReason> { 97 | let pin = convert_pin(pin); 98 | let gpio = get_port(port)?; 99 | let af = screen_af(function)?; 100 | gpio.afr(if pin < 8 { 0 } else { 1 }).modify(|r| r.set_afr(pin & 0b111, af)); 101 | gpio.moder().modify(|v| v.set_moder(pin, Moder::ALTERNATE)); 102 | 103 | Ok(()) 104 | } 105 | 106 | fn set_pin_analog(&mut self, _: Meta, port:Port, pin:u8) -> Result<(),ReplyFaultReason> { 107 | let pin = convert_pin(pin); 108 | let gpio = get_port(port)?; 109 | gpio.moder().modify(|v| v.set_moder(pin, Moder::ANALOG)); 110 | 111 | Ok(()) 112 | } 113 | 114 | fn set_pin_input(&mut self, _: Meta, port:Port, pin:u8) -> Result<(),ReplyFaultReason> { 115 | let gpio = get_port(port)?; 116 | gpio.moder().modify(|v| v.set_moder(convert_pin(pin), Moder::INPUT)); 117 | Ok(()) 118 | } 119 | 120 | fn is_pin_high(&mut self, _: Meta, port:Port, pin:u8) -> Result { 121 | let gpio = get_port(port)?; 122 | let idr = gpio.idr().read(); 123 | Ok(idr.idr(usize::from(pin & 0xF)) == Idr::HIGH) 124 | } 125 | 126 | fn set_pin_pull(&mut self, _: Meta, port:Port, pin:u8, pull: Option) -> Result<(),ReplyFaultReason> { 127 | let gpio = get_port(port)?; 128 | gpio.pupdr().modify(|v| { 129 | v.set_pupdr(convert_pin(pin), match pull { 130 | Some(Pull::Up) => Pupdr::PULLUP, 131 | Some(Pull::Down) => Pupdr::PULLDOWN, 132 | None => Pupdr::FLOATING, 133 | }); 134 | }); 135 | gpio.moder().modify(|v| v.set_moder(convert_pin(pin), Moder::INPUT)); 136 | Ok(()) 137 | } 138 | 139 | fn enable_clock(&mut self, _: Meta, peripheral:PeripheralName) -> Result<(),ReplyFaultReason> { 140 | let bits = peripheral as u16; 141 | let bit_no = usize::from(bits & 0x1F); 142 | let reg_no = bits >> 5; 143 | let rcc = stm32_metapac::RCC; 144 | 145 | cfg_if! { 146 | if #[cfg(hubris_chip = "STM32L412")] { 147 | match reg_no { 148 | 0 => rcc.ahb1enr().modify(|r| r.0 |= 1 << bit_no), 149 | 1 => rcc.ahb2enr().modify(|r| r.0 |= 1 << bit_no), 150 | 2 => rcc.ahb3enr().modify(|r| r.0 |= 1 << bit_no), 151 | 3 => rcc.apb1enr1().modify(|r| r.0 |= 1 << bit_no), 152 | 4 => rcc.apb1enr2().modify(|r| r.0 |= 1 << bit_no), 153 | _ => rcc.apb2enr().modify(|r| r.0 |= 1 << bit_no), 154 | } 155 | } else if #[cfg(hubris_chip = "STM32G031")] { 156 | match reg_no { 157 | 0 => rcc.gpioenr().modify(|r| r.0 |= 1 << bit_no), 158 | 1 => rcc.ahbenr().modify(|r| r.0 |= 1 << bit_no), 159 | 2 => rcc.apbenr1().modify(|r| r.0 |= 1 << bit_no), 160 | _ => rcc.apbenr2().modify(|r| r.0 |= 1 << bit_no), 161 | } 162 | } else if #[cfg(hubris_chip = "STM32U575")] { 163 | match reg_no { 164 | 0 => rcc.ahb1enr().modify(|r| r.0 |= 1 << bit_no), 165 | 1 => rcc.ahb2enr1().modify(|r| r.0 |= 1 << bit_no), 166 | 2 => rcc.ahb2enr2().modify(|r| r.0 |= 1 << bit_no), 167 | 3 => rcc.ahb3enr().modify(|r| r.0 |= 1 << bit_no), 168 | 4 => rcc.apb1enr1().modify(|r| r.0 |= 1 << bit_no), 169 | 5 => rcc.apb1enr2().modify(|r| r.0 |= 1 << bit_no), 170 | 6 => rcc.apb2enr().modify(|r| r.0 |= 1 << bit_no), 171 | _ => rcc.apb3enr().modify(|r| r.0 |= 1 << bit_no), 172 | } 173 | } else { 174 | compile_error!("unsupported chip family"); 175 | } 176 | } 177 | 178 | Ok(()) 179 | } 180 | 181 | fn set_pins_output( 182 | &mut self, 183 | _: Meta, 184 | port: Port, 185 | mask: u16, 186 | ) -> Result<(),ReplyFaultReason> { 187 | let gpio = get_port(port)?; 188 | gpio.moder().modify(|w| { 189 | for i in 0..16 { 190 | if mask & (1 << i) == 0 { 191 | continue; 192 | } 193 | w.set_moder(i, Moder::OUTPUT); 194 | } 195 | }); 196 | Ok(()) 197 | } 198 | 199 | fn read_pins( 200 | &mut self, 201 | _: Meta, 202 | port: Port, 203 | ) -> Result { 204 | let gpio = get_port(port)?; 205 | Ok(gpio.idr().read().0 as u16) 206 | } 207 | } 208 | 209 | /// Returns the GPIO port corresponding to `port`, if implemented on this chip. 210 | /// If not implemented, returns a fault. 211 | fn get_port(port: Port) -> Result { 212 | match port { 213 | Port::A => Ok(stm32_metapac::GPIOA), 214 | Port::B => Ok(stm32_metapac::GPIOB), 215 | Port::C => Ok(stm32_metapac::GPIOC), 216 | Port::D => Ok(stm32_metapac::GPIOD), 217 | 218 | #[cfg(hubris_chip = "STM32G031")] 219 | Port::F => Ok(stm32_metapac::GPIOF), 220 | 221 | #[cfg(hubris_chip = "STM32L412")] 222 | Port::H => Ok(stm32_metapac::GPIOH), 223 | 224 | _ => Err(ReplyFaultReason::BadMessageContents), 225 | } 226 | } 227 | 228 | include!(concat!(env!("OUT_DIR"), "/generated_server.rs")); 229 | -------------------------------------------------------------------------------- /drv/stm32xx-sys/sys-idl.kdl: -------------------------------------------------------------------------------- 1 | // STM32 core driver IPC interface for STM32L4. 2 | 3 | interface stm32-sys 4 | 5 | // Common types for talking about pins: 6 | 7 | enum Port { 8 | doc "A GPIO port name." 9 | 10 | case A 11 | case B 12 | case C 13 | case D 14 | // E not implemented by any supported parts yet 15 | case F 16 | // G not implemented by any supported parts yet 17 | case H 18 | // I and later not implemented by any supported parts yet 19 | 20 | rust-derive Copy Clone Debug 21 | } 22 | 23 | enum Function { 24 | doc "An alternate function index for a pin." 25 | 26 | case AF0 27 | case AF1 28 | case AF2 29 | case AF3 30 | case AF4 31 | case AF5 32 | case AF6 33 | case AF7 34 | 35 | case AF8 36 | case AF9 37 | case AF10 38 | case AF11 39 | case AF12 40 | case AF13 41 | case AF14 42 | case AF15 43 | 44 | rust-derive Copy Clone Debug 45 | } 46 | 47 | enum Pull { 48 | doc "Possible settings for pin pull resistors." 49 | 50 | case Up 51 | case Down 52 | 53 | rust-derive Copy Clone Debug 54 | } 55 | 56 | // Operations: 57 | 58 | method set_pin_output { 59 | doc "Configures a pin into output mode." 60 | 61 | operation 0 62 | arg port Port 63 | arg pin u8 64 | auto-retry 65 | } 66 | 67 | method set_pins_output { 68 | doc "Configures a pin into output mode." 69 | 70 | operation 9 71 | arg port Port 72 | arg mask u16 73 | auto-retry 74 | } 75 | 76 | method set_pin_high { 77 | doc "Sets a pin's output state to high. This won't have any immediate \ 78 | effect unless the pin is in output mode." 79 | 80 | operation 1 81 | arg port Port 82 | arg pin u8 83 | auto-retry 84 | } 85 | 86 | method set_pin_low { 87 | doc "Sets a pin's output state to low. This won't have any immediate \ 88 | effect unless the pin is in output mode." 89 | 90 | operation 2 91 | arg port Port 92 | arg pin u8 93 | auto-retry 94 | } 95 | 96 | method toggle_pin { 97 | doc "Inverts a pin's output state. This won't have any immediate effect \ 98 | unless the pin is in output mode." 99 | 100 | operation 3 101 | arg port Port 102 | arg pin u8 103 | auto-retry 104 | } 105 | 106 | method set_pin_alternate_mode { 107 | doc "Configures a pin into Alternate mode, selecting a specific alternate \ 108 | function index." 109 | 110 | operation 4 111 | arg port Port 112 | arg pin u8 113 | arg function Function 114 | auto-retry 115 | } 116 | 117 | method set_pin_input { 118 | doc "Configures a pin into input mode." 119 | 120 | operation 6 121 | arg port Port 122 | arg pin u8 123 | auto-retry 124 | } 125 | 126 | method set_pin_analog { 127 | doc "Configures a pin into Analog mode, putting it in a high impedance \ 128 | state." 129 | 130 | operation 11 131 | arg port Port 132 | arg pin u8 133 | auto-retry 134 | } 135 | 136 | method is_pin_high { 137 | doc "Senses the current level of a pin." 138 | 139 | operation 7 140 | arg port Port 141 | arg pin u8 142 | result bool 143 | auto-retry 144 | } 145 | 146 | method read_pins { 147 | doc "Senses the current level of pins on a port." 148 | 149 | operation 10 150 | arg port Port 151 | result u16 152 | auto-retry 153 | } 154 | 155 | method set_pin_pull { 156 | doc "Configures pull resistors on a pin." 157 | 158 | operation 8 159 | arg port Port 160 | arg pin u8 161 | arg pull Option 162 | auto-retry 163 | } 164 | 165 | method enable_clock { 166 | doc "Enables the clock signal to a peripheral." 167 | 168 | operation 5 169 | arg peripheral PeripheralName 170 | auto-retry 171 | } 172 | 173 | on-cfg hubris_chip { 174 | is STM32G031 { 175 | enum PeripheralName { 176 | doc "An enumeration of all RCC-controlled peripherals on the chip." 177 | 178 | case Usart2 0b10_10001 179 | 180 | rust-derive Copy Clone Debug 181 | } 182 | } 183 | is STM32L412 { 184 | enum PeripheralName { 185 | doc "An enumeration of all RCC-controlled peripherals on the chip." 186 | 187 | // Bitwise value is: 188 | // bits 4:0: bit index in control registers 189 | // bits 7:5: register index: 190 | // 000 AHB1ENR 191 | // 001 AHB2ENR 192 | // 010 AHB3ENR 193 | // 011 APB1ENR1 194 | // 100 APB1ENR2 195 | // 101 APB2ENR 196 | 197 | case Tim2 0b011_00000 // APB1ENR1 bit 0 198 | case Crs 0b011_11000 // APB1ENR1 bit 24 199 | case UsbFs 0b011_11010 // APB1ENR1 bit 26 200 | 201 | rust-derive Copy Clone Debug 202 | } 203 | } 204 | is STM32U575 { 205 | enum PeripheralName { 206 | doc "An enumeration of all RCC-controlled peripherals on the chip." 207 | 208 | // Bitwise value is: 209 | // bits 4:0: bit index in control registers 210 | // bits 7:5: register index: 211 | // 000 AHB1ENR 212 | // 001 AHB2ENR1 213 | // 010 AHB2ENR2 214 | // 011 AHB3ENR 215 | // 100 APB1ENR1 216 | // 101 APB1ENR2 217 | // 110 APB2ENR 218 | // 111 APB3ENR 219 | 220 | case UsbOtg 0b001_01110 221 | case Crs 0b100_11000 222 | 223 | rust-derive Copy Clone Debug 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /drv/uart-echo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uart-echo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | # It is annoying that the chip features are specified down to the package level, 8 | # but, that is how the stm32-metapac do. 9 | chip-stm32g031k8 = ["stm32-metapac/stm32g031k8"] 10 | 11 | [dependencies] 12 | drv-stm32xx-sys-api = { version = "0.1.0", path = "../stm32xx-sys-api" } 13 | hubris-notifications = { version = "0.1.0", path = "../../sys/notifications" } 14 | hubris-task-slots = { version = "0.1.0", path = "../../sys/task-slots" } 15 | stm32-metapac = { version = "15.0.0" } 16 | userlib = { workspace = true, features = ["no-panic"] } 17 | 18 | [build-dependencies] 19 | hubris-build-util = { version = "0.1.0", path = "../../sys/build-util" } 20 | serde_json = "1.0.133" 21 | 22 | [package.metadata.hubris.auto-features] 23 | chip = true 24 | -------------------------------------------------------------------------------- /drv/uart-echo/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::io::Write; 3 | 4 | use serde_json::{Map, Value}; 5 | 6 | fn main() { 7 | let config: Map = hubris_build_util::get_task_config(); 8 | 9 | let mut out = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 10 | out.push("task_config.rs"); 11 | 12 | let mut f = std::fs::File::create(&out).unwrap(); 13 | 14 | writeln!(f, "pub(crate) mod config {{").unwrap(); 15 | writeln!(f, "use drv_stm32xx_sys_api::{{Port, Function}};").unwrap(); 16 | 17 | writeln!(f, "pub const UART_CLOCK_HZ: u32 = {};", config["uart-clock-hz"]).unwrap(); 18 | writeln!(f, "pub const BAUD_RATE: u32 = {};", config["baud-rate"]).unwrap(); 19 | 20 | writeln!(f, "pub const PINS: [(Port, u8, Function); 2] = [").unwrap(); 21 | for pinaf in config["pins"].as_array().unwrap() { 22 | let (port, pin) = parse_pin_name(pinaf["name"].as_str().unwrap()); 23 | writeln!(f, " (Port::{port}, {pin}, Function::AF{}),", pinaf["af"].as_u64().unwrap()).unwrap(); 24 | } 25 | writeln!(f, "];").unwrap(); 26 | writeln!(f, "}}").unwrap(); 27 | } 28 | 29 | fn parse_pin_name(name: &str) -> (&str, u8) { 30 | let (port, pin) = name.split_at(1); 31 | 32 | (port, pin.parse().unwrap()) 33 | } 34 | -------------------------------------------------------------------------------- /drv/uart-echo/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Aggressively minimal UART echo demo for the stm32g0 nucleo board. 2 | //! 3 | //! This brings up USART2 on PA2/3 at 9600 baud, assuming a central clock 4 | //! frequency of 16 MHz. It then echoes whatever it receives, forever. 5 | //! 6 | //! The raw sends to sys here are pretty gross. 7 | 8 | #![no_std] 9 | #![no_main] 10 | 11 | use userlib as _; 12 | use hubris_task_slots::SLOTS; 13 | use drv_stm32xx_sys_api::{Stm32Sys as Sys, PeripheralName}; 14 | 15 | /// Counter for viewing in the debugger. 16 | #[no_mangle] 17 | static mut CHARS_SENT: u32 = 0; 18 | 19 | #[export_name = "main"] 20 | fn main() -> ! { 21 | let sys = Sys::from(SLOTS.sys); 22 | 23 | // Turn on USART2 24 | sys.enable_clock(PeripheralName::Usart2); 25 | 26 | // Initialize UART 27 | let uart = stm32_metapac::USART2; 28 | 29 | uart.brr().write(|w| { 30 | w.set_brr((config::UART_CLOCK_HZ / config::BAUD_RATE) as u16); 31 | }); 32 | 33 | uart.cr1().write(|w| { 34 | w.set_rxneie(true); 35 | w.set_re(true); 36 | w.set_te(true); 37 | w.set_ue(true); 38 | }); 39 | 40 | // Set pin A2+A3 to USART2 41 | for (port, pin, af) in config::PINS { 42 | sys.set_pin_alternate_mode(port, pin, af); 43 | } 44 | 45 | loop { 46 | // Enable the UART's IRQ output to reach our notification bit. 47 | userlib::sys_enable_irq(hubris_notifications::USART_IRQ); 48 | // Block waiting for that notification bit to be set. 49 | userlib::sys_recv_notification(hubris_notifications::USART_IRQ); 50 | 51 | // Transfer all pending characters. 52 | while uart.isr().read().rxne() { 53 | let byte = uart.rdr().read().dr() & 0xFF; 54 | uart.tdr().write(|w| w.set_dr(byte)); 55 | unsafe { 56 | CHARS_SENT = CHARS_SENT.wrapping_add(1); 57 | } 58 | } 59 | } 60 | } 61 | 62 | include!(concat!(env!("OUT_DIR"), "/task_config.rs")); 63 | -------------------------------------------------------------------------------- /files/kernel-link.x: -------------------------------------------------------------------------------- 1 | /* Provides information about the memory layout of the device */ 2 | /* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ 3 | INCLUDE memory.x 4 | 5 | /* # Entry point = reset vector */ 6 | ENTRY(Reset); 7 | EXTERN(__RESET_VECTOR); /* depends on the `Reset` symbol */ 8 | 9 | /* # Exception vectors */ 10 | /* This is effectively weak aliasing at the linker level */ 11 | /* The user can override any of these aliases by defining the corresponding symbol themselves (cf. 12 | the `exception!` macro) */ 13 | EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ 14 | 15 | EXTERN(DefaultHandler); 16 | 17 | PROVIDE(NonMaskableInt = DefaultHandler); 18 | EXTERN(HardFaultTrampoline); 19 | PROVIDE(MemoryManagement = DefaultHandler); 20 | PROVIDE(BusFault = DefaultHandler); 21 | PROVIDE(UsageFault = DefaultHandler); 22 | PROVIDE(SecureFault = DefaultHandler); 23 | PROVIDE(SVCall = DefaultHandler); 24 | PROVIDE(DebugMonitor = DefaultHandler); 25 | PROVIDE(PendSV = DefaultHandler); 26 | PROVIDE(SysTick = DefaultHandler); 27 | 28 | PROVIDE(DefaultHandler = DefaultHandler_); 29 | PROVIDE(HardFault = HardFault_); 30 | 31 | /* # Interrupt vectors */ 32 | EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ 33 | 34 | /* # Pre-initialization function */ 35 | /* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, 36 | then the function this points to will be called before the RAM is initialized. */ 37 | PROVIDE(__pre_init = DefaultPreInit); 38 | 39 | /* # Sections */ 40 | SECTIONS 41 | { 42 | /* ### Vector table */ 43 | .vector_table ORIGIN(VECTORS) : 44 | { 45 | __start_vector = .; 46 | /* Initial Stack Pointer (SP) value */ 47 | LONG(_stack_start); 48 | 49 | /* Reset vector */ 50 | KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ 51 | __reset_vector = .; 52 | 53 | /* Exceptions */ 54 | KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ 55 | __eexceptions = .; 56 | 57 | /* Device specific interrupts */ 58 | KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ 59 | } > VECTORS 60 | 61 | __vector_size = SIZEOF(.vector_table); 62 | /* Header containing data needed by the bootloader. We specify 63 | _HUBRIS_IMAGE_HEADER_SIZE and _HUBRIS_IMAGE_HEADER_ALIGN in memory.x at 64 | build time, then reserve enough space for the header here in the linker 65 | script. 66 | */ 67 | .header : 68 | { 69 | ASSERT(. == ALIGN(_HUBRIS_IMAGE_HEADER_ALIGN), "error: header alignment is invalid"); 70 | HEADER = .; 71 | . = . + _HUBRIS_IMAGE_HEADER_SIZE; 72 | } > VECTORS 73 | 74 | /* ### .text */ 75 | .text : ALIGN(4) 76 | { 77 | _stext = .; 78 | __stext = .; 79 | /* place these 2 close to each other or the `b` instruction will fail to link */ 80 | *(.PreResetTrampoline); 81 | *(.Reset); 82 | 83 | *(.text .text.*); 84 | 85 | /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, 86 | so must be placed close to it. */ 87 | *(.HardFaultTrampoline); 88 | *(.HardFault.*); 89 | . = ALIGN(4); 90 | __etext = .; 91 | } > FLASH 92 | 93 | /* ### .rodata */ 94 | .rodata __etext : ALIGN(4) 95 | { 96 | __srodata = .; 97 | *(.rodata .rodata.*); 98 | /* We move this into a special section so we can ensure it is always 99 | included in the build */ 100 | KEEP(*(.hubris_id)); 101 | /* 4-byte align the end (VMA) of this section. 102 | This is required by LLD to ensure the LMA of the following .data 103 | section will have the correct alignment. */ 104 | . = ALIGN(4); 105 | __erodata = .; 106 | } > FLASH 107 | 108 | .stack (NOLOAD) : ALIGN(8) { 109 | _stack_base = .; 110 | . = ORIGIN(STACK) + LENGTH(STACK); 111 | _stack_start = .; 112 | } >STACK 113 | 114 | /* ## Sections in RAM */ 115 | /* ### .data */ 116 | .data : ALIGN(4) 117 | { 118 | . = ALIGN(4); 119 | __sdata = .; 120 | *(.data .data.*); 121 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 122 | } > RAM AT>FLASH 123 | /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to 124 | * use the .data loading mechanism by pushing __edata. Note: do not change 125 | * output region or load region in those user sections! */ 126 | . = ALIGN(4); 127 | __edata = .; 128 | 129 | /* LMA of .data */ 130 | __sidata = LOADADDR(.data); 131 | 132 | /* ### .gnu.sgstubs 133 | This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ 134 | /* Security Attribution Unit blocks must be 32 bytes aligned. */ 135 | /* Note that this pads the FLASH usage to 32 byte alignment. */ 136 | .gnu.sgstubs : { 137 | . = ALIGN(32); 138 | __veneer_base = .; 139 | *(.gnu.sgstubs*) 140 | . = ALIGN(32); 141 | __veneer_limit = .; 142 | } > FLASH 143 | 144 | /* 145 | * Fill the remaining flash space with a known value 146 | */ 147 | /* 148 | .fill : ALIGN(1) { 149 | . = (ORIGIN(FLASH) + LENGTH(FLASH)); 150 | } > FLASH =0xffffffff 151 | */ 152 | 153 | /* ### .bss */ 154 | .bss (NOLOAD) : ALIGN(4) 155 | { 156 | . = ALIGN(4); 157 | __sbss = .; 158 | *(.bss .bss.*); 159 | *(COMMON); /* Uninitialized C statics */ 160 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 161 | } > RAM 162 | /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to 163 | * use the .bss zeroing mechanism by pushing __ebss. Note: do not change 164 | * output region or load region in those user sections! */ 165 | . = ALIGN(4); 166 | __ebss = .; 167 | 168 | /* ### .uninit */ 169 | .uninit (NOLOAD) : ALIGN(4) 170 | { 171 | . = ALIGN(4); 172 | __suninit = .; 173 | *(.uninit .uninit.*); 174 | . = ALIGN(4); 175 | __euninit = .; 176 | } > RAM 177 | 178 | /* Place the heap right after `.uninit` in RAM */ 179 | PROVIDE(__sheap = __euninit); 180 | 181 | /* ## .got */ 182 | /* Dynamic relocations are unsupported. This section is only used to detect relocatable code in 183 | the input files and raise an error if relocatable code is found */ 184 | .got (NOLOAD) : 185 | { 186 | KEEP(*(.got .got.*)); 187 | } 188 | 189 | /* ## Discarded sections */ 190 | /DISCARD/ : 191 | { 192 | /* Unused exception related info that only wastes space */ 193 | *(.ARM.exidx); 194 | *(.ARM.exidx.*); 195 | *(.ARM.extab.*); 196 | } 197 | } 198 | 199 | INCLUDE device.x 200 | 201 | /* Do not exceed this mark in the error messages below | */ 202 | /* # Alignment checks */ 203 | ASSERT(ORIGIN(FLASH) % 4 == 0, " 204 | ERROR(cortex-m-rt): the start of the FLASH region must be 4-byte aligned"); 205 | 206 | ASSERT(ORIGIN(RAM) % 4 == 0, " 207 | ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); 208 | 209 | ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " 210 | BUG(cortex-m-rt): .data is not 4-byte aligned"); 211 | 212 | ASSERT(__sidata % 4 == 0, " 213 | BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); 214 | 215 | ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " 216 | BUG(cortex-m-rt): .bss is not 4-byte aligned"); 217 | 218 | ASSERT(__sheap % 4 == 0, " 219 | BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); 220 | 221 | /* # Position checks */ 222 | 223 | /* ## .vector_table */ 224 | ASSERT(__reset_vector == ADDR(.vector_table) + 0x8, " 225 | BUG(cortex-m-rt): the reset vector is missing"); 226 | 227 | ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " 228 | BUG(cortex-m-rt): the exception vectors are missing"); 229 | 230 | ASSERT(SIZEOF(.vector_table) > 0x40, " 231 | ERROR(cortex-m-rt): The interrupt vectors are missing. 232 | Possible solutions, from most likely to less likely: 233 | - Link to a svd2rust generated device crate 234 | - Check that you actually use the device/hal/bsp crate in your code 235 | - Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency 236 | may be enabling it) 237 | - Supply the interrupt handlers yourself. Check the documentation for details."); 238 | 239 | /* ## .text */ 240 | ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " 241 | ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section 242 | Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); 243 | 244 | ASSERT(_stext + SIZEOF(.text) < ORIGIN(FLASH) + LENGTH(FLASH), " 245 | ERROR(cortex-m-rt): The .text section must be placed inside the FLASH memory. 246 | Set _stext to an address smaller than 'ORIGIN(FLASH) + LENGTH(FLASH)'"); 247 | 248 | /* # Other checks */ 249 | ASSERT(SIZEOF(.got) == 0, " 250 | ERROR(cortex-m-rt): .got section detected in the input object files 251 | Dynamic relocations are not supported. If you are linking to C code compiled using 252 | the 'cc' crate then modify your build script to compile the C code _without_ 253 | the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); 254 | 255 | /* So let's talk about vector table alignment: 256 | * ARMv8m section B3.30 257 | * In a PE with a configurable vector table base, the vector table is naturally-aligned to a power of two, with an 258 | * alignment value that is: 259 | * - A minimum of 128 bytes. 260 | * - Greater than or equal to (Number of Exceptions supported x4) 261 | * ARMv7m section B1.5.3 262 | * The Vector table must be naturally aligned to a power of two whose alignment value is greater than or equal to 263 | * (Number of Exceptions supported x 4), with a minimum alignmentof 128 bytes. 264 | * 265 | * Annoyingly this means that vector table alignment is device specific. 266 | * This is also a nice catch-22: we need to know the size of the vector 267 | * table to calculate the alignment but we need to know the alignment 268 | * before we know the size. 269 | * 270 | * The best we can do right now is use a specified alignment and 271 | * indicate if it is wrong 272 | */ 273 | ASSERT(ADDR(.vector_table) % (1 << LOG2CEIL(SIZEOF(.vector_table))) == 0, " 274 | Vector table alignment too small for number of exception entires. Increase 275 | the alignment to the next power of two"); 276 | 277 | 278 | /* Do not exceed this mark in the error messages above | */ 279 | -------------------------------------------------------------------------------- /files/task-link2.x: -------------------------------------------------------------------------------- 1 | /* Linker script for a temporary link operation, used to measure task size 2 | * (but without flash fill) */ 3 | INCLUDE memory.x 4 | 5 | ENTRY(_start); 6 | 7 | SECTIONS 8 | { 9 | PROVIDE(_stack_start = ORIGIN(STACK) + LENGTH(STACK)); 10 | 11 | /* ### .text */ 12 | .text : { 13 | _stext = .; 14 | *(.text.start*); /* try and pull start symbol to beginning */ 15 | *(.text .text.*); 16 | . = ALIGN(4); 17 | __etext = .; 18 | } > FLASH =0xdededede 19 | 20 | /* ### .rodata */ 21 | .rodata : ALIGN(4) 22 | { 23 | *(.rodata .rodata.*); 24 | 25 | /* 4-byte align the end (VMA) of this section. 26 | This is required by LLD to ensure the LMA of the following .data 27 | section will have the correct alignment. */ 28 | . = ALIGN(4); 29 | __erodata = .; 30 | } > FLASH 31 | 32 | /* 33 | * Sections in RAM 34 | * 35 | * NOTE: the userlib runtime assumes that these sections 36 | * are 4-byte aligned and padded to 4-byte boundaries. 37 | */ 38 | .data : ALIGN(4) { 39 | . = ALIGN(4); 40 | __sdata = .; 41 | *(.data .data.*); 42 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 43 | __edata = .; 44 | } > RAM AT>FLASH 45 | 46 | /* LMA of .data */ 47 | __sidata = LOADADDR(.data); 48 | 49 | .stack (NOLOAD) : ALIGN(4) 50 | { 51 | . += LENGTH(STACK); 52 | } > STACK 53 | 54 | .bss (NOLOAD) : ALIGN(4) 55 | { 56 | . = ALIGN(4); 57 | __sbss = .; 58 | *(.bss .bss.*); 59 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 60 | __ebss = .; 61 | } > RAM 62 | 63 | .uninit (NOLOAD) : ALIGN(4) 64 | { 65 | . = ALIGN(4); 66 | *(.uninit .uninit.*); 67 | . = ALIGN(4); 68 | /* Place the heap right after `.uninit` */ 69 | __sheap = .; 70 | } > RAM 71 | 72 | /* ## .task_slot_table */ 73 | /* Table of TaskSlot instances and their names. Used to resolve task 74 | dependencies during packaging. */ 75 | .task_slot_table (INFO) : { 76 | . = .; 77 | KEEP(*(.task_slot_table)); 78 | } 79 | 80 | /* ## .caboose_pos_table */ 81 | /* Table of CaboosePos instances and their names. Used to record caboose 82 | position during packaging. */ 83 | .caboose_pos_table (INFO) : { 84 | . = .; 85 | KEEP(*(.caboose_pos_table)); 86 | } 87 | 88 | .hubris_abi_version (INFO) : { 89 | . = .; 90 | KEEP(*(.hubris_abi_version)); 91 | } 92 | 93 | /* ## .idolatry */ 94 | .idolatry (INFO) : { 95 | . = .; 96 | KEEP(*(.idolatry)); 97 | } 98 | 99 | /* ## Discarded sections */ 100 | /DISCARD/ : 101 | { 102 | /* Unused exception related info that only wastes space */ 103 | *(.ARM.exidx); 104 | *(.ARM.exidx.*); 105 | *(.ARM.extab.*); 106 | *(.got .got.*); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /files/task-link3.x: -------------------------------------------------------------------------------- 1 | /* Linker script for a temporary link operation, used to measure task size 2 | * (but without flash fill) */ 3 | INCLUDE memory.x 4 | 5 | ENTRY(_start); 6 | 7 | SECTIONS 8 | { 9 | PROVIDE(_stack_start = ORIGIN(STACK) + LENGTH(STACK)); 10 | 11 | /* ### .text */ 12 | .text : { 13 | _stext = .; 14 | *(.text.start*); /* try and pull start symbol to beginning */ 15 | *(.text .text.*); 16 | . = ALIGN(4); 17 | __etext = .; 18 | } > FLASH =0xdededede 19 | 20 | /* ### .rodata */ 21 | .rodata : ALIGN(4) 22 | { 23 | *(.rodata .rodata.*); 24 | 25 | /* 4-byte align the end (VMA) of this section. 26 | This is required by LLD to ensure the LMA of the following .data 27 | section will have the correct alignment. */ 28 | . = ALIGN(4); 29 | __erodata = .; 30 | } > FLASH 31 | 32 | /* 33 | * Sections in RAM 34 | * 35 | * NOTE: the userlib runtime assumes that these sections 36 | * are 4-byte aligned and padded to 4-byte boundaries. 37 | */ 38 | .data : ALIGN(4) { 39 | . = ALIGN(4); 40 | __sdata = .; 41 | *(.data .data.*); 42 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 43 | __edata = .; 44 | } > RAM AT>FLASH 45 | 46 | /* LMA of .data */ 47 | __sidata = LOADADDR(.data); 48 | 49 | .fill (LOADADDR(.data) + SIZEOF(.data)) : AT(LOADADDR(.data) + SIZEOF(.data)) 50 | { 51 | . = ORIGIN(FLASH) + LENGTH(FLASH); 52 | } >FLASH =0xffffffff 53 | 54 | .stack (NOLOAD) : ALIGN(4) 55 | { 56 | . += LENGTH(STACK); 57 | } > STACK 58 | 59 | .bss (NOLOAD) : ALIGN(4) 60 | { 61 | . = ALIGN(4); 62 | __sbss = .; 63 | *(.bss .bss.*); 64 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 65 | __ebss = .; 66 | } > RAM 67 | 68 | .uninit (NOLOAD) : ALIGN(4) 69 | { 70 | . = ALIGN(4); 71 | *(.uninit .uninit.*); 72 | . = ALIGN(4); 73 | /* Place the heap right after `.uninit` */ 74 | __sheap = .; 75 | } > RAM 76 | 77 | /* ## .task_slot_table */ 78 | /* Table of TaskSlot instances and their names. Used to resolve task 79 | dependencies during packaging. */ 80 | .task_slot_table (INFO) : { 81 | . = .; 82 | KEEP(*(.task_slot_table)); 83 | } 84 | 85 | /* ## .caboose_pos_table */ 86 | /* Table of CaboosePos instances and their names. Used to record caboose 87 | position during packaging. */ 88 | .caboose_pos_table (INFO) : { 89 | . = .; 90 | KEEP(*(.caboose_pos_table)); 91 | } 92 | 93 | .hubris_abi_version (INFO) : { 94 | . = .; 95 | KEEP(*(.hubris_abi_version)); 96 | } 97 | 98 | /* ## .idolatry */ 99 | .idolatry (INFO) : { 100 | . = .; 101 | KEEP(*(.idolatry)); 102 | } 103 | 104 | /* ## Discarded sections */ 105 | /DISCARD/ : 106 | { 107 | /* Unused exception related info that only wastes space */ 108 | *(.ARM.exidx); 109 | *(.ARM.exidx.*); 110 | *(.ARM.extab.*); 111 | *(.got .got.*); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /files/task-rlink.x: -------------------------------------------------------------------------------- 1 | /* Linker script for a relocatable task 2 | * This should be used with the `-r` linker flag */ 3 | ENTRY(_start); 4 | 5 | SECTIONS 6 | { 7 | /* ### .text */ 8 | .text : { 9 | _stext = .; 10 | *(.text.start*); /* try and pull start symbol to beginning */ 11 | *(.text .text.*); 12 | . = ALIGN(4); 13 | __etext = .; 14 | } 15 | 16 | /* ### .rodata */ 17 | .rodata : ALIGN(4) 18 | { 19 | *(.rodata .rodata.*); 20 | 21 | /* 4-byte align the end (VMA) of this section. 22 | This is required by LLD to ensure the LMA of the following .data 23 | section will have the correct alignment. */ 24 | . = ALIGN(4); 25 | __erodata = .; 26 | } 27 | 28 | /* 29 | * Sections in RAM 30 | * 31 | * NOTE: the userlib runtime assumes that these sections 32 | * are 4-byte aligned and padded to 4-byte boundaries. 33 | */ 34 | .data : ALIGN(4) { 35 | . = ALIGN(4); 36 | __sdata = .; 37 | *(.data .data.*); 38 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 39 | __edata = .; 40 | } 41 | 42 | /* LMA of .data */ 43 | __sidata = LOADADDR(.data); 44 | 45 | .bss (NOLOAD) : ALIGN(4) { 46 | . = ALIGN(4); 47 | __sbss = .; 48 | *(.bss .bss.*); 49 | . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ 50 | __ebss = .; 51 | } 52 | 53 | .uninit (NOLOAD) : ALIGN(4) { 54 | . = ALIGN(4); 55 | *(.uninit .uninit.*); 56 | . = ALIGN(4); 57 | /* Place the heap right after `.uninit` */ 58 | __sheap = .; 59 | } 60 | 61 | /* ## .task_slot_table */ 62 | /* Table of TaskSlot instances and their names. Used to resolve task 63 | dependencies during packaging. */ 64 | .task_slot_table (INFO) : { 65 | . = .; 66 | KEEP(*(.task_slot_table)); 67 | } 68 | 69 | /* ## .caboose_pos_table */ 70 | /* Table of CaboosePos instances and their names. Used to record caboose 71 | position during packaging. */ 72 | .caboose_pos_table (INFO) : { 73 | . = .; 74 | KEEP(*(.caboose_pos_table)); 75 | } 76 | 77 | .hubris_abi_version (INFO) : { 78 | . = .; 79 | KEEP(*(.hubris_abi_version)); 80 | } 81 | 82 | /* ## .idolatry */ 83 | .idolatry (INFO) : { 84 | . = .; 85 | KEEP(*(.idolatry)); 86 | } 87 | 88 | /* ## Discarded sections */ 89 | /DISCARD/ : 90 | { 91 | /* Unused exception related info that only wastes space */ 92 | *(.ARM.exidx); 93 | *(.ARM.exidx.*); 94 | *(.ARM.extab.*); 95 | *(.got .got.*); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /hubris-env.toml: -------------------------------------------------------------------------------- 1 | [system] 2 | source = "here" 3 | -------------------------------------------------------------------------------- /kernel-generic-stm32g031/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv6m-none-eabi" 3 | 4 | [target.thumbv6m-none-eabi] 5 | rustflags = ["-C", "link-arg=-Ttask-rlink.x"] 6 | 7 | [env] 8 | HUBRIS_TARGET = "thumbv6m-none-eabi" 9 | -------------------------------------------------------------------------------- /kernel-generic-stm32g031/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel-generic-stm32g031" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | clock-64mhz-hsi16 = [] 8 | 9 | [dependencies] 10 | cortex-m = { version = "0.7.7", features = ["inline-asm"] } 11 | cortex-m-rt = "0.7.5" 12 | stm32-metapac = {version = "15.0.0", features = ["rt", "stm32g031k8"]} 13 | 14 | [dependencies.hubris-kern] 15 | package = "kern" 16 | git = "https://github.com/cbiffle/hubris-fork.git" 17 | branch = "cbiffle/exhubris-fixes" 18 | features = ["nano", "stack-watermark"] 19 | -------------------------------------------------------------------------------- /kernel-generic-stm32g031/README.md: -------------------------------------------------------------------------------- 1 | # generic stm32g031 kernel 2 | 3 | This is a generic Hubris kernel wrapper / startup routine that fires up the 4 | kernel on an stm32g031-series MCU at 16 MHz. 5 | -------------------------------------------------------------------------------- /kernel-generic-stm32g031/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink.cfg] 2 | transport select hla_swd 3 | source [find target/stm32g0x.cfg] 4 | #reset_config srst_only 5 | -------------------------------------------------------------------------------- /kernel-generic-stm32g031/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use stm32_metapac::{self as device, flash::vals::Latency, rcc::vals::{Pllm, Plln, Pllp, Pllq, Pllr, Pllsrc, Sw}}; 5 | 6 | use cortex_m_rt::entry; 7 | 8 | #[entry] 9 | fn main() -> ! { 10 | let ticks_per_ms = if cfg!(feature = "clock-64mhz-hsi16") { 11 | let flash = device::FLASH; 12 | let rcc = device::RCC; 13 | // We come out of reset at 16 MHz on HSI. We would like to be running at 64 14 | // MHz. 15 | // 16 | // This implies that we need to boost the clock speed by 4 using the PLL. 17 | // 18 | // Note: to achieve this, we must be in voltage range 1 (required for 19 | // frequencies above 16 MHz). The part appears to reset into voltage 20 | // range 1, so, that was easy. 21 | // 22 | // The PLL's input frequency can go up to 16 MHz, and its internal VCO must 23 | // be between 64 and 344 MHz (in voltage range 1). Its R-tap is the one that 24 | // feeds the CPU and buses, so we need the R output to be 64 MHz. The R 25 | // divisor is limited to integers between 2 and 8 (inclusive), so, we can 26 | // get what we want by 27 | // 28 | // - Setting the PLL input divisor (VCO multiplier) to 8x for an fVCO of 29 | // 128 MHz. 30 | // - Setting the R divisor to /2 for a 64 MHz output. 31 | // - Leaving the P and Q taps off. 32 | 33 | // Adjust our wait states to reflect our target voltage + freq combination. 34 | // At 64 MHz we'll need 2 wait states. We come out of reset at 0 wait 35 | // states, so we must override it. 36 | // 37 | // Note: the SVD apparently incorrectly models an undocumented reserved bit 38 | // in this register (bit 18), so DO NOT use `write` or `reset`. If you clear 39 | // bit 18, bad shit will happen to you -- it interferes with debugger 40 | // access. 41 | flash.acr().modify(|w| { 42 | w.set_prften(true); 43 | w.set_latency(Latency::WS2); 44 | }); 45 | while flash.acr().read().latency() != Latency::WS2 { 46 | // spin 47 | } 48 | 49 | // Fire up the PLL. 50 | // First, set up our divisors. 51 | rcc.pllcfgr().modify(|w| { 52 | // Source input from HSI16. 53 | w.set_pllsrc(Pllsrc::HSI); 54 | // Do not divide the input. 55 | w.set_pllm(Pllm::DIV1); 56 | // Multiply that by 8 in the VCO for 128 MHz. 57 | w.set_plln(Plln::MUL8); 58 | // Configure the R-tap to 64 MHz output by dividing by 2. Configure P 59 | // and Q to valid configurations while we're at it. 60 | w.set_pllr(Pllr::DIV2); 61 | w.set_pllp(Pllp::DIV2); 62 | w.set_pllq(Pllq::DIV2); 63 | // But we only actually turn the R tap on. 64 | w.set_pllren(true); 65 | }); 66 | // Switch it on at the RCC and wait for it to come up. RCC should still be 67 | // at its reset value (and doesn't have any weird undocumented bits), so 68 | // we don't need to RMW it. 69 | rcc.cr().write(|w| { 70 | w.set_pllon(true); 71 | }); 72 | while !rcc.cr().read().pllrdy() { 73 | // spin 74 | } 75 | // Now switch over to it. 76 | rcc.cfgr().write(|w| { 77 | w.set_sw(Sw::PLL1_R); 78 | }); 79 | while rcc.cfgr().read().sws() != Sw::PLL1_R { 80 | // spin 81 | } 82 | 83 | 64_000 84 | } else { 85 | 16_000 86 | }; 87 | 88 | unsafe { hubris_kern::startup::start_kernel(ticks_per_ms) } 89 | } 90 | -------------------------------------------------------------------------------- /kernel-generic-stm32l412/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | rustflags = ["-C", "link-arg=-Ttask-rlink.x"] 6 | 7 | [env] 8 | HUBRIS_TARGET = "thumbv7em-none-eabihf" 9 | -------------------------------------------------------------------------------- /kernel-generic-stm32l412/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel-generic-stm32l412" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | clock-80mhz-hsi16 = [] 8 | clock-hsi48-on = [] 9 | 10 | pwr-vddusb-valid = [] 11 | 12 | # Rather specialized kernel profiling via fixed GPIOs. 13 | # Probably of limited usefulness in general. 14 | kernel-profiling = [] 15 | 16 | [dependencies] 17 | cfg-if = "1.0.0" 18 | cortex-m = { version = "0.7.7", features = ["inline-asm"] } 19 | cortex-m-rt = "0.7.5" 20 | #hubris-kern = {package = "kern", git = "https://github.com/oxidecomputer/hubris.git", features = ["nano"]} 21 | stm32l4 = { version = "0.15.1", features = ["stm32l412"] } 22 | 23 | [dependencies.hubris-kern] 24 | package = "kern" 25 | git = "https://github.com/cbiffle/hubris-fork.git" 26 | branch = "cbiffle/exhubris-fixes" 27 | features = ["nano", "stack-watermark"] 28 | -------------------------------------------------------------------------------- /kernel-generic-stm32l412/README.md: -------------------------------------------------------------------------------- 1 | # generic stm32l412 kernel 2 | 3 | This is a generic Hubris kernel wrapper / startup routine that fires up the 4 | kernel on an stm32l412-series MCU. Currently this leaves the clock at its 5 | default 4 MHz, resulting in a slo boi. 6 | -------------------------------------------------------------------------------- /kernel-generic-stm32l412/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink.cfg] 2 | source [find target/stm32l4x.cfg] 3 | -------------------------------------------------------------------------------- /kernel-generic-stm32l412/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use stm32l4::stm32l412 as device; 5 | 6 | use cortex_m_rt::entry; 7 | 8 | cfg_if::cfg_if! { 9 | if #[cfg(feature = "clock-80mhz-hsi16")] { 10 | fn clock_setup(p: &mut device::Peripherals) { 11 | 12 | // Scale the CPU up to our max frequency of 80MHz. 13 | // 14 | // We come out of reset at 4MHz on MSI. We'd like to move over to 15 | // HSI16 for greater accuracy, and then raise that to 80 with the 16 | // PLL. 17 | // 18 | // HSI16 is already in a suitable range for the PLL, but could be 19 | // further divided if it enables us to improve (reduce) VCO 20 | // frequency. However, the CPU runs on the PLL's R-tap, and the 21 | // R-tap has very few divisor options. This means our VCO frequency 22 | // is the constrained part. The easiest choice without using 23 | // fractional mode is a div-by-4 from a VCO frequency of 320MHz -- 24 | // div-by-2 and div-by-6 would each put the frequency out of range. 25 | // (This part does not have an adjustable VCO input frequency 26 | // range.) 27 | // 28 | // 16MHz input * 20 = 320MHz VCO 29 | // 320MHz VCO / 4 = 80MHz clock 30 | // 31 | // That gives us DIVN1=20, DIVP1=3. 32 | // 33 | // We'll turn off the Q and R taps for now. 34 | 35 | // Getting to 80MHz requires the CPU to be volted at its highest setting of 36 | // VOS1. It _appears_ to come out of reset at VOS1. So that was easy. 37 | 38 | // Turn on HSI16. 39 | p.RCC.cr.modify(|_, w| w.hsion().set_bit()); 40 | while !p.RCC.cr.read().hsirdy().bit() {} 41 | 42 | // Configure PLL1. 43 | p.RCC.pllcfgr.write(|w| unsafe { 44 | w.pllpdiv().bits(0) 45 | .pllr().bits(0b01) 46 | .pllren().set_bit() 47 | .pllq().bits(0b11) 48 | .pllqen().clear_bit() 49 | .pllp().clear_bit() 50 | .pllpen().clear_bit() 51 | .plln().bits(20) 52 | .pllm().bits(0) 53 | .pllsrc().bits(0b10) 54 | }); 55 | // Switch on PLL1. 56 | p.RCC.cr.modify(|_, w| w.pllon().set_bit()); 57 | while !p.RCC.cr.read().pllrdy().bit() {} 58 | 59 | // Adjust Flash wait states for target frequency. 60 | p.FLASH.acr.modify(|_, w| unsafe { w.latency().bits(0b100) }); 61 | while p.FLASH.acr.read().latency() != 0b100 {} 62 | 63 | // Adjust bus clock dividers for target frequency - no changes should be 64 | // needed. 65 | 66 | // Switch CPU frequency. 67 | p.RCC.cfgr.modify(|_, w| unsafe { w.sw().bits(0b11) }); 68 | while p.RCC.cfgr.read().sws() != 0b11 {} 69 | 70 | // Enable flash prefetching to compensate for wait states. 71 | p.FLASH.acr.modify(|_, w| w.prften().set_bit()); 72 | 73 | // k cool 74 | 75 | } 76 | const CYCLES_PER_MS: u32 = 80_000; 77 | } else { 78 | fn clock_setup(p: &mut device::Peripherals) { 79 | // Nothing to see here 80 | } 81 | const CYCLES_PER_MS: u32 = 4_000; 82 | } 83 | } 84 | 85 | #[entry] 86 | fn main() -> ! { 87 | let mut p = unsafe { device::Peripherals::steal() }; 88 | clock_setup(&mut p); 89 | 90 | if cfg!(feature = "clock-hsi48-on") { 91 | // Turn on HSI48. 92 | p.RCC.crrcr.modify(|_, w| { 93 | w.hsi48on().set_bit() 94 | }); 95 | while !p.RCC.crrcr.read().hsi48rdy().bit() { 96 | // spin 97 | } 98 | // Use HSI48 as the 48MHz reference. 99 | p.RCC.ccipr.modify(|_, w| w.clk48sel().bits(0b00)); 100 | } 101 | 102 | if cfg!(feature = "pwr-vddusb-valid") { 103 | // Enable clock to the PWR unit so we can talk to it. 104 | p.RCC.apb1enr1.modify(|_, w| w.pwren().set_bit()); 105 | cortex_m::asm::dsb(); 106 | // Tell PWR that we expect VDDUSB to be available and valid. 107 | p.PWR.cr2.modify(|_, w| w.usv().set_bit()); 108 | } 109 | 110 | if cfg!(feature = "kernel-profiling") { 111 | // Set up PA0 and PA1 to pulse on kernel events. 112 | p.RCC.ahb2enr.modify(|_, w| w.gpioaen().set_bit()); 113 | cortex_m::asm::dsb(); 114 | p.GPIOA.moder.modify(|_, w| { 115 | w.moder0().output() 116 | .moder1().output() 117 | }); 118 | static EVENTS_TABLE: hubris_kern::profiling::EventsTable = hubris_kern::profiling::EventsTable { 119 | syscall_enter: |n| { 120 | let gpioa = unsafe { &*device::GPIOA::ptr() }; 121 | gpioa.bsrr.write(|w| { 122 | w.bs0().set_bit(); 123 | if n & 4 == 0 { 124 | w.br1().set_bit(); 125 | } else { 126 | w.bs1().set_bit(); 127 | } 128 | w 129 | }); 130 | }, 131 | syscall_exit: || { 132 | let gpioa = unsafe { &*device::GPIOA::ptr() }; 133 | gpioa.bsrr.write(|w| w.br0().set_bit().br1().set_bit()); 134 | }, 135 | secondary_syscall_enter: || { 136 | }, 137 | secondary_syscall_exit: || { 138 | }, 139 | isr_enter: || { 140 | }, 141 | isr_exit: || { 142 | }, 143 | timer_isr_enter: || (), 144 | timer_isr_exit: || (), 145 | context_switch: |_| { 146 | }, 147 | }; 148 | hubris_kern::profiling::configure_events_table(&EVENTS_TABLE); 149 | } 150 | 151 | unsafe { hubris_kern::startup::start_kernel(CYCLES_PER_MS) } 152 | } 153 | -------------------------------------------------------------------------------- /kernel-generic-stm32u575/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "thumbv7em-none-eabihf" 3 | 4 | [target.thumbv7em-none-eabihf] 5 | rustflags = ["-C", "link-arg=-Ttask-rlink.x"] 6 | 7 | [env] 8 | HUBRIS_TARGET = "thumbv7em-none-eabihf" 9 | -------------------------------------------------------------------------------- /kernel-generic-stm32u575/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel-generic-stm32u575" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | clock-160mhz-hsi16 = [] 8 | clock-hsi48-on = [] 9 | pwr-vddusb-valid = [] 10 | 11 | # Rather specialized kernel profiling via fixed GPIOs. 12 | # Probably of limited usefulness in general. 13 | kernel-profiling = [] 14 | 15 | [dependencies] 16 | cfg-if = "1.0.0" 17 | cortex-m = { version = "0.7.7", features = ["inline-asm"] } 18 | cortex-m-rt = "0.7.5" 19 | stm32-metapac = { version = "15.0.0", features = ["rt", "stm32u575zi"] } 20 | 21 | [dependencies.hubris-kern] 22 | package = "kern" 23 | git = "https://github.com/cbiffle/hubris-fork.git" 24 | branch = "cbiffle/exhubris-fixes" 25 | features = ["stack-watermark"] 26 | -------------------------------------------------------------------------------- /kernel-generic-stm32u575/README.md: -------------------------------------------------------------------------------- 1 | # generic stm32u575 kernel 2 | 3 | This is a generic Hubris kernel wrapper / startup routine that fires up the 4 | kernel on an stm32u575-series MCU. Currently this leaves the clock at its 5 | default 4 MHz, resulting in a slo boi. 6 | -------------------------------------------------------------------------------- /kernel-generic-stm32u575/openocd.cfg: -------------------------------------------------------------------------------- 1 | source [find interface/stlink.cfg] 2 | source [find target/stm32u5x.cfg] 3 | -------------------------------------------------------------------------------- /kernel-generic-stm32u575/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use stm32_metapac as pac; 5 | use stm32_metapac::{pwr::vals::Vos, rcc::vals::{Iclksel, Plldiv, Pllm, Pllmboost, Plln, Pllrge, Pllsrc, Sw}}; 6 | use cortex_m_rt::entry; 7 | 8 | fn clock_setup() -> u32 { 9 | if cfg!(feature = "clock-160mhz-hsi16") { 10 | // Turn on the HSI16 clock source. 11 | pac::RCC.cr().modify(|w| w.set_hsion(true)); 12 | while !pac::RCC.cr().read().hsion() { 13 | // spin 14 | } 15 | 16 | // Configure PLL1 to use HSI16 as its input clock source, and set up the 17 | // EPOD booster divider. Interestingly, ST says to do this _before_ 18 | // configuring the PLL, because it generates a clock that's used behind 19 | // the scenes. 20 | pac::RCC.pll1cfgr().modify(|w| { 21 | w.set_pllmboost(Pllmboost::DIV1); 22 | w.set_pllsrc(Pllsrc::HSI); 23 | }); 24 | 25 | pac::RCC.ahb3enr().modify(|w| w.set_pwren(true)); 26 | cortex_m::asm::dsb(); 27 | 28 | // Transition to power Range 1 and enable the EPOD boost, which is 29 | // evidently required at speeds over 55 MHz. 30 | pac::PWR.vosr().modify(|w| { 31 | w.set_vos(Vos::RANGE1); 32 | w.set_boosten(true); 33 | }); 34 | 35 | // Wait until the VOS switch completes. 36 | while !pac::PWR.vosr().read().vosrdy() { 37 | // spin 38 | } 39 | 40 | // Wait until the booster is live. 41 | while !pac::PWR.vosr().read().boostrdy() { 42 | // spin 43 | } 44 | 45 | // The ICACHE appears to be pretty damn important on this part, when 46 | // wait states are enabled. There does not appear to be a separate flash 47 | // accelerator, so without the ICACHE, all we get is 48 | // next-sequential-line prefetching. In testing this causes a delay loop 49 | // that leaves its flash line to take dramatically longer than one which 50 | // is line-aligned. 51 | // 52 | // Fortunately turning on the ICACHE appears to be pretty easy. A cache 53 | // invalidation operation is kicked off at reset; wait for it to confirm 54 | // completion: 55 | while !pac::ICACHE.sr().read().bsyendf() { 56 | // spin 57 | } 58 | // And then turn it on: 59 | pac::ICACHE.cr().modify(|w| w.set_en(true)); 60 | 61 | // Add a wait state to flash accesses to prepare for 160 MHz. 62 | pac::FLASH.acr().modify(|w| w.set_latency(4)); 63 | while pac::FLASH.acr().read().latency() != 4 { 64 | // spin 65 | } 66 | 67 | // Enable the flash prefetcher, since we have wait states now. This is 68 | // not as critical as it was on ART accelerator parts, but is described 69 | // as speeding up cache fills. 70 | pac::FLASH.acr().modify(|w| w.set_prften(true)); 71 | 72 | // Configure PLL1. It's getting 16 MHz from HSI16; we're gonna do the 73 | // following math: 74 | pac::RCC.pll1cfgr().modify(|w| { 75 | w.set_pllfracen(false); 76 | // Input frequency (16 MHz) is already in range, we don't have to 77 | // divide the input. 78 | w.set_pllm(Pllm::DIV1); 79 | w.set_pllrge(Pllrge::FREQ_8TO16MHZ); 80 | // Enable the R tap. 81 | w.set_pllren(true); 82 | }); 83 | pac::RCC.pll1divr().modify(|w| { 84 | // Multiply reference clock by 16 to produce a VCO frequency of 85 | // 160 MHz. 86 | w.set_plln(Plln::MUL10); 87 | // Don't divide that on the R tap, so we get 160 MHz out. 88 | w.set_pllr(Plldiv::DIV1); 89 | }); 90 | 91 | // Turn it on and wait! 92 | pac::RCC.cr().modify(|w| { 93 | w.set_pllon(0, true); 94 | }); 95 | while pac::RCC.cr().read().pllrdy(0) { 96 | // spin 97 | } 98 | 99 | // Switch the system clock over to PLL1R. 100 | pac::RCC.cfgr1().modify(|w| w.set_sw(Sw::PLL1_R)); 101 | while pac::RCC.cfgr1().read().sws() != Sw::PLL1_R { 102 | // spin 103 | } 104 | 105 | // Alright, we should now be at 160 MHz. 106 | 160_000 107 | } else { 108 | // Nothing to see here 109 | 4_000 110 | } 111 | } 112 | 113 | #[entry] 114 | fn main() -> ! { 115 | let cycles_per_ms = clock_setup(); 116 | 117 | if cfg!(feature = "clock-hsi48-on") { 118 | // Turn on HSI48. 119 | pac::RCC.cr().modify(|w| { 120 | w.set_hsi48on(true); 121 | }); 122 | while !pac::RCC.cr().read().hsi48rdy() { 123 | // spin 124 | } 125 | // Use HSI48 as the 48MHz reference source. 126 | pac::RCC.ccipr1().modify(|w| w.set_iclksel(Iclksel::HSI48)); 127 | } 128 | 129 | if cfg!(feature = "pwr-vddusb-valid") { 130 | // Enable clock to the PWR unit so we can talk to it. 131 | pac::RCC.ahb3enr().modify(|w| w.set_pwren(true)); 132 | cortex_m::asm::dsb(); 133 | // Tell PWR that we expect VDDUSB to be available and valid. 134 | pac::PWR.svmcr().modify(|w| w.set_usv(true)); 135 | } 136 | 137 | unsafe { hubris_kern::startup::start_kernel(cycles_per_ms) } 138 | } 139 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.82" 3 | targets = [ "thumbv6m-none-eabi", "thumbv7em-none-eabihf", "thumbv8m.main-none-eabihf" ] 4 | profile = "minimal" 5 | components = [ "rustfmt", "rust-analyzer" ] 6 | -------------------------------------------------------------------------------- /sys/build-util/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubris-build-util" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | 8 | [dependencies] 9 | indexmap = "2.7.0" 10 | serde = { version = "1.0.215", features = ["derive"] } 11 | serde_json = "1.0.133" 12 | -------------------------------------------------------------------------------- /sys/build-util/README.mkdn: -------------------------------------------------------------------------------- 1 | # `hubris-build-util` 2 | 3 | Utility crate for build scripts in Hubris tasks. Provides access to the build 4 | environment data, which allows application introspection. 5 | -------------------------------------------------------------------------------- /sys/build-util/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | let names = std::env::var("HUBRIS_TASKS").unwrap(); 5 | let task_count = names.split(',').count(); 6 | 7 | let mut path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 8 | path.push("task_count.txt"); 9 | std::fs::write(path, format!("{task_count}")).unwrap(); 10 | 11 | println!("cargo::rerun-if-env-changed=HUBRIS_TASKS"); 12 | } 13 | -------------------------------------------------------------------------------- /sys/build-util/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate allows build scripts for Hubris task to perform application 2 | //! introspection. 3 | 4 | use indexmap::IndexSet; 5 | use serde::{Deserialize, de::DeserializeOwned, Serialize}; 6 | 7 | /// Retrieved information about a named task. 8 | #[derive(Serialize, Deserialize)] 9 | pub struct TaskInfo { 10 | /// Index of the task in the application, used to create `TaskId`s. 11 | index: usize, 12 | /// The application's defined notification bits, ordered starting from zero. 13 | notifications: IndexSet, 14 | } 15 | 16 | impl TaskInfo { 17 | pub fn get_index(&self) -> usize { 18 | self.index 19 | } 20 | 21 | pub fn notification_mask(&self, name: &str) -> Option { 22 | self.notifications.get_index_of(name).map(|i| 1 << i) 23 | } 24 | } 25 | 26 | /// Retrieves the info record for a named task from the build environment. 27 | /// 28 | /// Returns `None` if the task does not exist. 29 | /// 30 | /// This will emit a Cargo build script directive to stdout, ensuring that your 31 | /// build script will be re-run if the task's info record changes. 32 | pub fn get_task_info(name: &str) -> Option { 33 | let varname = format!("HUBRIS_TASK_INFO_{name}"); 34 | let text = std::env::var(&varname).ok()?; 35 | println!("cargo::rerun-if-env-changed={varname}"); 36 | Some(serde_json::from_str(&text).expect("bad JSON from build system")) 37 | } 38 | 39 | /// Collects the `config` section for the current task, as JSON, and 40 | /// deserializes it into a `T`. 41 | pub fn get_task_config() -> T 42 | where T: DeserializeOwned, 43 | { 44 | let text = std::env::var("HUBRIS_TASK_CONFIG") 45 | .expect("required task config section not provided"); 46 | println!("cargo::rerun-if-env-changed=HUBRIS_TASK_CONFIG"); 47 | serde_json::from_str(&text).expect("config failed to deserialize (is probably wrong)") 48 | } 49 | 50 | /// Collects the chip configuration information from the build environment and 51 | /// exposes it as a set of `hubris_chip` cfgs. 52 | pub fn export_chip_cfg() { 53 | println!("cargo::rerun-if-env-changed=HUBRIS_CHIP_COMPAT"); 54 | println!("cargo::rustc-check-cfg=cfg(hubris_chip, values(any()))"); 55 | if let Ok(text) = std::env::var("HUBRIS_CHIP_COMPAT") { 56 | for compat_name in text.split(',') { 57 | println!("cargo::rustc-cfg=hubris_chip=\"{compat_name}\""); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sys/idyll_runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "idyll_runtime" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | userlib.workspace = true 8 | zerocopy = "0.8.11" 9 | 10 | [lib] 11 | test = false 12 | bench = false 13 | -------------------------------------------------------------------------------- /sys/kipc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kipc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | 8 | [dependencies] 9 | userlib.workspace = true 10 | hubris-abi = {package = "abi", git = "https://github.com/cbiffle/hubris-fork.git", branch = "cbiffle/exhubris-fixes" } 11 | ssmarshal = {version = "1.0.0", default-features = false} 12 | 13 | [lib] 14 | test = false 15 | bench = false 16 | doctest = false 17 | -------------------------------------------------------------------------------- /sys/kipc/README.mkdn: -------------------------------------------------------------------------------- 1 | # `kipc` interface stubs 2 | 3 | This crate provides stubs for the kernel's `kipc` interface for the supervisor. 4 | -------------------------------------------------------------------------------- /sys/kipc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! API for the kernel's `kipc` interface. This should only be used from 2 | //! supervisor tasks. 3 | 4 | #![no_std] 5 | 6 | use core::{num::NonZeroUsize, sync::atomic::Ordering}; 7 | 8 | use hubris_abi::Kipcnum; 9 | pub use hubris_abi::TaskState; 10 | 11 | /// Reads the scheduling/fault status of the task with the given index. 12 | /// 13 | /// This is powerful, but relatively expensive, because deserializing 14 | /// `TaskState` is somewhat complex. To just detect that a task has faulted, see 15 | /// `find_faulted_task`. 16 | pub fn read_task_status(task_index: usize) -> TaskState { 17 | let mut response = [0; size_of::()]; 18 | let (_, len) = userlib::sys_send_to_kernel( 19 | Kipcnum::ReadTaskStatus as u16, 20 | &(task_index as u32).to_le_bytes(), 21 | &mut response, 22 | &mut [], 23 | ); 24 | match ssmarshal::deserialize(&response[..len]) { 25 | Ok((state, _)) => state, 26 | Err(_) => panic!(), 27 | } 28 | } 29 | 30 | /// Scans tasks from `task_index` looking for a task that has failed. Returns 31 | /// its index if found, or `None` if it hits the end of the table. 32 | /// 33 | /// This operation is unable to detect that task 0 failed, which is deliberate: 34 | /// it's intended to be called from task 0, the supervisor. 35 | pub fn find_faulted_task(task_index: usize) -> Option { 36 | let mut response = [0; 4]; 37 | let (_, _len) = userlib::sys_send_to_kernel( 38 | Kipcnum::FindFaultedTask as u16, 39 | &(task_index as u32).to_le_bytes(), 40 | &mut response, 41 | &mut [], 42 | ); 43 | let i = u32::from_le_bytes(response); 44 | NonZeroUsize::new(i as usize) 45 | } 46 | 47 | /// Requests that the task at a given index be reinitialized and optionally 48 | /// started. 49 | /// 50 | /// `new_state` controls whether the task gets set to runnable after 51 | /// reinitialization. 52 | pub fn reinitialize_task(task_index: usize, new_state: NewState) { 53 | let mut msg = [0; 5]; 54 | msg[..4].copy_from_slice(&(task_index as u32).to_le_bytes()); 55 | msg[4] = new_state as u8; 56 | 57 | let _ = userlib::sys_send_to_kernel( 58 | Kipcnum::RestartTask as u16, 59 | &msg, 60 | &mut [], 61 | &mut [], 62 | ); 63 | } 64 | 65 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 66 | pub enum NewState { 67 | Halted = 0, 68 | Runnable = 1, 69 | } 70 | 71 | pub fn reset() -> ! { 72 | userlib::sys_send_to_kernel(Kipcnum::Reset as u16, &[], &mut [], &mut []); 73 | // The kernel does not return from this, but we currently have no way of 74 | // indicating that, so... 75 | loop { 76 | core::sync::atomic::compiler_fence(Ordering::SeqCst); 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /sys/notifications/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubris-notifications" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | 8 | [dependencies] 9 | userlib.workspace = true 10 | 11 | [lib] 12 | test = false 13 | bench = false 14 | doctest = false 15 | 16 | [build-dependencies] 17 | convert_case = "0.6.0" 18 | ron = "0.8.1" 19 | -------------------------------------------------------------------------------- /sys/notifications/README.mkdn: -------------------------------------------------------------------------------- 1 | # `hubris-notifications` 2 | 3 | This crate exposes the build system's binding of the current task's notification 4 | bits as compile-time constants. 5 | -------------------------------------------------------------------------------- /sys/notifications/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::io::Write; 3 | 4 | use convert_case::{Case, Casing as _}; 5 | 6 | fn main() { 7 | println!("cargo::rerun-if-env-changed=HUBRIS_NOTIFICATIONS"); 8 | if let Ok(text) = std::env::var("HUBRIS_NOTIFICATIONS") { 9 | let names: Vec = ron::from_str(&text).unwrap(); 10 | 11 | let mut path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 12 | path.push("notifications.rs"); 13 | 14 | let mut f = std::fs::File::create(path).unwrap(); 15 | 16 | for (i, name) in names.into_iter().enumerate() { 17 | let const_name = name.to_case(Case::ScreamingSnake); 18 | writeln!(f, "/// Bit assignment for notification \"{name}\"").unwrap(); 19 | writeln!(f, "pub const {const_name}: u32 = 1 << {i};").unwrap(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sys/notifications/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Exposes the current task's binding of notification bits. 2 | 3 | #![no_std] 4 | 5 | include!(concat!(env!("OUT_DIR"), "/notifications.rs")); 6 | -------------------------------------------------------------------------------- /sys/num-tasks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubris-num-tasks" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | 8 | [dependencies] 9 | 10 | [lib] 11 | test = false 12 | bench = false 13 | doctest = false 14 | -------------------------------------------------------------------------------- /sys/num-tasks/README.mkdn: -------------------------------------------------------------------------------- 1 | # `hubris-num-tasks` 2 | 3 | This trivial crate lets a Hubris task determine the number of tasks in the 4 | application, as a compile-time constant. 5 | 6 | Note that depending on this crate will cause your code to get rebuilt if the set 7 | of tasks in the application changes, including changes that don't alter the 8 | count. 9 | -------------------------------------------------------------------------------- /sys/num-tasks/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | fn main() { 4 | let names = std::env::var("HUBRIS_TASKS").unwrap(); 5 | let task_count = names.split(',').count(); 6 | 7 | let mut path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 8 | path.push("task_count.txt"); 9 | std::fs::write(path, format!("{task_count}")).unwrap(); 10 | 11 | println!("cargo::rerun-if-env-changed=HUBRIS_TASKS"); 12 | } 13 | -------------------------------------------------------------------------------- /sys/num-tasks/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Exposes the number of tasks in the current Hubris application as a 2 | //! compile-time constant, for sizing tables and the like. 3 | 4 | #![no_std] 5 | 6 | /// Number of tasks in the current application. 7 | pub const NUM_TASKS: usize = include!(concat!(env!("OUT_DIR"), "/task_count.txt")); 8 | -------------------------------------------------------------------------------- /sys/task-slots/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubris-task-slots" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | 8 | [dependencies] 9 | userlib.workspace = true 10 | 11 | [lib] 12 | test = false 13 | bench = false 14 | doctest = false 15 | 16 | [build-dependencies] 17 | ron = "0.8.1" 18 | -------------------------------------------------------------------------------- /sys/task-slots/README.mkdn: -------------------------------------------------------------------------------- 1 | # `hubris-task-slots` 2 | 3 | This crate exposes the current task's `uses-task` bindings as compile-time 4 | `TaskId` constants. 5 | -------------------------------------------------------------------------------- /sys/task-slots/build.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, path::PathBuf}; 2 | use std::io::Write; 3 | 4 | fn main() { 5 | println!("cargo::rerun-if-env-changed=HUBRIS_TASK_SLOTS"); 6 | if let Ok(text) = std::env::var("HUBRIS_TASK_SLOTS") { 7 | let map: BTreeMap = ron::from_str(&text).unwrap(); 8 | 9 | let mut path = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 10 | path.push("task_slots.rs"); 11 | 12 | let mut f = std::fs::File::create(path).unwrap(); 13 | 14 | writeln!(f, "pub struct Slots {{").unwrap(); 15 | for name in map.keys() { 16 | writeln!(f, " pub {name}: userlib::TaskId,").unwrap(); 17 | } 18 | writeln!(f, "}}").unwrap(); 19 | 20 | writeln!(f, "/// Tasks bound in the current task.").unwrap(); 21 | writeln!(f, "pub const SLOTS: Slots = Slots {{").unwrap(); 22 | for (name, index) in map { 23 | writeln!(f, " {name}: userlib::TaskId::gen0({index}),").unwrap(); 24 | } 25 | writeln!(f, "}};").unwrap(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /sys/task-slots/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Exposes the current task's binding of logical task names to application 2 | //! indices. 3 | 4 | #![no_std] 5 | 6 | include!(concat!(env!("OUT_DIR"), "/task_slots.rs")); 7 | -------------------------------------------------------------------------------- /sys/userlib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "userlib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | default = [] 8 | 9 | # Any panic! in the final task binary will cause a linker failure. This can be 10 | # used to ensure that all panics are proven unreachable by the compiler. This 11 | # causes the panic message control features to become irrelevant, since panic 12 | # messages can no longer occur. 13 | no-panic = [] 14 | 15 | # The message, file, line, and column of any panic! in the task will be ignored, 16 | # and replaced with the string "PANIC". This ensures that integer formatting and 17 | # Display/Debug impls are not compiled into the task (unless used elsewhere) 18 | # which saves a lot of space. 19 | no-panic-messages = [] 20 | 21 | [dependencies] 22 | bitflags = "2.6.0" 23 | cfg-if = "1.0.0" 24 | 25 | [target."thumbv8m.main-none-eabihf".dependencies] 26 | cortex-m = {version = "0.7", features = ["inline-asm"]} 27 | 28 | [target.thumbv7em-none-eabihf.dependencies] 29 | cortex-m = {version = "0.7", features = ["inline-asm"]} 30 | 31 | [target.thumbv6m-none-eabi.dependencies] 32 | cortex-m = {version = "0.7", features = ["inline-asm"]} 33 | 34 | [lib] 35 | test = false 36 | bench = false 37 | doctest = false 38 | -------------------------------------------------------------------------------- /sys/userlib/README.mkdn: -------------------------------------------------------------------------------- 1 | # exhubris userlib 2 | 3 | This is a task library for interfacing with the Hubris kernel, compatible with 4 | the original Hubris ABI but rewritten to be more compact and efficient. 5 | -------------------------------------------------------------------------------- /sys/userlib/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | fn main() { 4 | println!("cargo::rustc-check-cfg=cfg(hubris_target, values(any()))"); 5 | let target = std::env::var("TARGET").unwrap(); 6 | println!("cargo::rerun-if-env-changed=TARGET"); 7 | println!("cargo::rustc-cfg=hubris_target=\"{target}\""); 8 | 9 | // It's surprisingly hard to get the package version as a _byte string_ at 10 | // compile time, so, we'll do it the hard way. 11 | let version = std::env::var("CARGO_PKG_VERSION").unwrap(); 12 | let n = version.len(); 13 | let versionfile = format!(" 14 | #[link_section = \".hubris_abi_version\"] 15 | #[used] 16 | static HUBRIS_API_VERSION: [u8; {n}+1] = *b\"{version}\0\"; 17 | "); 18 | let outdir = std::env::var("OUT_DIR").unwrap(); 19 | std::fs::write( 20 | Path::new(&outdir).join("hubris_abi_version.rs"), 21 | versionfile, 22 | ).unwrap(); 23 | } 24 | -------------------------------------------------------------------------------- /task/idle/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "idle" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | userlib = {workspace = true, features = ["no-panic"]} 8 | 9 | [features] 10 | insomniac = [] 11 | 12 | [[bin]] 13 | name = "idle" 14 | test = false 15 | bench = false 16 | -------------------------------------------------------------------------------- /task/idle/README.mkdn: -------------------------------------------------------------------------------- 1 | # idle task 2 | 3 | This task just pauses the CPU waiting for interrupts. At least one interrupt, 4 | the timer interrupt, will eventually arrive. This is useful for reducing power 5 | consumption when there's no work to be done. 6 | 7 | The idle task is designed to be generic and should work for most applications on 8 | most CPUs. You can replace it if you'd like to do some work at idle, or if you 9 | have specific needs for setting the processor into a low power state. 10 | 11 | **Note:** there must be at least one task in an application that is runnable at 12 | all times. The idle task is one way of achieving this. If you forget to include 13 | an idle task, your application will crash --- generally right away. 14 | 15 | 16 | -------------------------------------------------------------------------------- /task/idle/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use userlib as _; 5 | 6 | #[export_name = "main"] 7 | fn main() -> ! { 8 | loop { 9 | if cfg!(feature = "insomniac") { 10 | // It may be useful, on some systems, to stop the CPU from entering 11 | // low power mode. Usually this is done because low power mode 12 | // interrupts debugging. 13 | // 14 | // This is not an empty block because that would reduce to `loop 15 | // {}`, which the compiler tends to replace with a trap instruction. 16 | core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); 17 | } else { 18 | userlib::idle(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /task/minisuper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minisuper" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | userlib = {workspace = true, features = ["no-panic"]} 8 | hubris-num-tasks.workspace = true 9 | kipc.workspace = true 10 | 11 | [[bin]] 12 | name = "minisuper" 13 | test = false 14 | bench = false 15 | -------------------------------------------------------------------------------- /task/minisuper/README.mkdn: -------------------------------------------------------------------------------- 1 | # `minisuper`: minimal supervisor implementation 2 | 3 | `minisuper` is a minimal supervisory task for Hubris applications. It can only 4 | do one thing, which is restart tasks that crash. (This is enough for a lot of 5 | applications!) 6 | 7 | `minisuper` is designed with the following goals: 8 | 9 | - Build with `no-panic`. The supervisor task must not crash, and so the 10 | supervisor executable should not contain any panic sites. 11 | 12 | - Compile to as little machine code as practical (currently a smidge over 256 13 | bytes). 14 | 15 | In addition to being an abbreviation for "minimal supervisor," `minisuper` is 16 | a slang name for small markets or general stores in Mexico, continuing the 17 | tradition of naming supervisory components in Spanish since `jefe`. 18 | -------------------------------------------------------------------------------- /task/minisuper/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Minimal supervisor task. 2 | 3 | #![no_std] 4 | #![no_main] 5 | 6 | /// This is a fixed detail of the kernel-supervisor interface: the kernel pokes 7 | /// bit 0 on fault. 8 | const FAULT_NOTIFICATION: u32 = 1; 9 | 10 | #[export_name = "main"] 11 | fn main() -> ! { 12 | loop { 13 | userlib::sys_recv_notification(FAULT_NOTIFICATION); 14 | 15 | // Something has died. Or, someone has posted a notification to us 16 | // in an attempt to convince us that something has died. Either way. 17 | 18 | // find_faulted_task(i) scans for the first task at an index greater 19 | // than or equal to i that has failed. Since we are task 0, we start out 20 | // with i=1, skipping ourselves. 21 | 22 | let mut next_task = 1; 23 | while let Some(fault_index) = kipc::find_faulted_task(next_task) { 24 | let fault_index = usize::from(fault_index); 25 | kipc::reinitialize_task(fault_index, kipc::NewState::Runnable); 26 | // Keep moving. Because of the API guarantees on the `fault_index` 27 | // value from the kernel, we can use wrapping add to advance this 28 | // without risk of it actually wrapping, to avoid a panic when 29 | // overflow checks are enabled. 30 | next_task = fault_index.wrapping_add(1); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /task/ping/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ping" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | drv-stm32xx-sys-api = { version = "0.1.0", path = "../../drv/stm32xx-sys-api" } 8 | hubris-task-slots = { version = "0.1.0", path = "../../sys/task-slots" } 9 | userlib = {workspace = true, features = ["no-panic"]} 10 | 11 | [build-dependencies] 12 | hubris-build-util = { version = "0.1.0", path = "../../sys/build-util" } 13 | serde_json = "1.0.134" 14 | -------------------------------------------------------------------------------- /task/ping/README.mkdn: -------------------------------------------------------------------------------- 1 | # Basic IPC generator 2 | 3 | This task spams a counterpart, `pong`, with IPC messages. This is intended to 4 | demonstrate that the IPC facilities work, and shouldn't be included in a real 5 | application. 6 | -------------------------------------------------------------------------------- /task/ping/build.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::io::Write; 3 | 4 | use serde_json::{Map, Value}; 5 | 6 | fn main() { 7 | let config: Map = hubris_build_util::get_task_config(); 8 | 9 | let mut out = PathBuf::from(std::env::var("OUT_DIR").unwrap()); 10 | out.push("task_config.rs"); 11 | 12 | let mut f = std::fs::File::create(&out).unwrap(); 13 | 14 | writeln!(f, "pub(crate) mod config {{").unwrap(); 15 | writeln!(f, "use drv_stm32xx_sys_api::Port;").unwrap(); 16 | 17 | let (port, pin) = parse_pin_name(config["led-pin"].as_str().unwrap()); 18 | writeln!(f, "pub const PIN: (Port, u8) = (Port::{port}, {pin});").unwrap(); 19 | writeln!(f, "}}").unwrap(); 20 | } 21 | 22 | fn parse_pin_name(name: &str) -> (&str, u8) { 23 | let (port, pin) = name.split_at(1); 24 | 25 | (port, pin.parse().unwrap()) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /task/ping/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A task that spams its counterpart, pong, with IPC messages. 2 | //! 3 | //! This task exists to test the build system and IPC implementation. You 4 | //! probably don't want to include it in your application, particularly because 5 | //! it doesn't sleep. 6 | 7 | #![no_std] 8 | #![no_main] 9 | 10 | use hubris_task_slots::SLOTS; 11 | use drv_stm32xx_sys_api::Stm32Sys as Sys; 12 | 13 | #[export_name = "main"] 14 | fn main() -> ! { 15 | let sys = Sys::from(SLOTS.sys); 16 | sys.set_pin_output(config::PIN.0, config::PIN.1); 17 | 18 | let mut pong = SLOTS.pong; 19 | 20 | // Arbitrarily chosen operation code: 21 | let ping_op: u16 = 1; 22 | // Very important message: 23 | let message = b"I am the lizard king"; 24 | 25 | // We do not expect a response. 26 | let mut incoming = []; 27 | // We don't lease any memory. 28 | let mut leases = []; 29 | 30 | // Record the current time so we can start our delay loop properly. 31 | let mut next_send = userlib::sys_get_timer().now; 32 | 33 | const INTERVAL: u64 = 5; 34 | let mut send_count = 0; 35 | 36 | loop { 37 | userlib::sys_set_timer(Some(next_send), 1); 38 | 39 | // The proper thing to do, when waiting for a timer, is to sleep waiting 40 | // for notifications _and then check the time._ Otherwise other tasks 41 | // can wake you up by posting. 42 | loop { 43 | userlib::sys_recv_notification(1); 44 | let now = userlib::sys_get_timer().now; 45 | if now >= next_send { 46 | next_send += INTERVAL; 47 | break; 48 | } 49 | } 50 | 51 | let r = userlib::sys_send( 52 | pong, 53 | ping_op, 54 | message, 55 | &mut incoming, 56 | &mut leases, 57 | ); 58 | 59 | match r { 60 | Ok((_retval, _response_len)) => { 61 | // TODO consider panicking here if the results are wrong, to 62 | // help test the syscall interface? 63 | send_count += 1; 64 | } 65 | Err(dead) => { 66 | // Update generation to handle pong crash 67 | pong = pong.with_generation(dead.new_generation()); 68 | } 69 | } 70 | 71 | if send_count == 100 { 72 | send_count = 0; 73 | sys.toggle_pin(config::PIN.0, config::PIN.1); 74 | } 75 | } 76 | } 77 | 78 | include!(concat!(env!("OUT_DIR"), "/task_config.rs")); 79 | -------------------------------------------------------------------------------- /task/pong/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pong" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | userlib = {workspace = true, features = ["no-panic"]} 8 | -------------------------------------------------------------------------------- /task/pong/README.mkdn: -------------------------------------------------------------------------------- 1 | # IPC server 2 | 3 | This task sends do-nothing replies to short IPC messages, and is intended as a 4 | counterpart to `ping` for testing IPC mechanisms. 5 | -------------------------------------------------------------------------------- /task/pong/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use userlib::{sys_panic, sys_recv_msg_open, sys_reply, ResponseCode}; 5 | 6 | #[no_mangle] 7 | static mut MESSAGE_COUNT: u32 = 0; 8 | 9 | #[export_name = "main"] 10 | fn main() -> ! { 11 | let mut buffer = [core::mem::MaybeUninit::uninit(); 32]; 12 | loop { 13 | let rm = sys_recv_msg_open(&mut buffer); 14 | unsafe { 15 | MESSAGE_COUNT = MESSAGE_COUNT.wrapping_add(1); 16 | } 17 | sys_reply(rm.sender, ResponseCode::SUCCESS, &[]); 18 | 19 | // Periodically crash this task to test both supervisor restarts, and 20 | // IPC client handling of dead codes. 21 | if unsafe { MESSAGE_COUNT } % 10 == 0 { 22 | sys_panic(b""); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/alloc/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /tools/alloc/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.4.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "2.6.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 16 | 17 | [[package]] 18 | name = "cfg-if" 19 | version = "1.0.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 22 | 23 | [[package]] 24 | name = "comfy-table" 25 | version = "7.1.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" 28 | dependencies = [ 29 | "crossterm", 30 | "strum", 31 | "strum_macros", 32 | "unicode-width", 33 | ] 34 | 35 | [[package]] 36 | name = "crossterm" 37 | version = "0.28.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 40 | dependencies = [ 41 | "bitflags", 42 | "crossterm_winapi", 43 | "parking_lot", 44 | "rustix", 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "crossterm_winapi" 50 | version = "0.9.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 53 | dependencies = [ 54 | "winapi", 55 | ] 56 | 57 | [[package]] 58 | name = "errno" 59 | version = "0.3.10" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 62 | dependencies = [ 63 | "libc", 64 | "windows-sys", 65 | ] 66 | 67 | [[package]] 68 | name = "heck" 69 | version = "0.5.0" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 72 | 73 | [[package]] 74 | name = "hubris-region-alloc" 75 | version = "0.1.0" 76 | dependencies = [ 77 | "comfy-table", 78 | ] 79 | 80 | [[package]] 81 | name = "libc" 82 | version = "0.2.168" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" 85 | 86 | [[package]] 87 | name = "linux-raw-sys" 88 | version = "0.4.14" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 91 | 92 | [[package]] 93 | name = "lock_api" 94 | version = "0.4.12" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 97 | dependencies = [ 98 | "autocfg", 99 | "scopeguard", 100 | ] 101 | 102 | [[package]] 103 | name = "parking_lot" 104 | version = "0.12.3" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 107 | dependencies = [ 108 | "lock_api", 109 | "parking_lot_core", 110 | ] 111 | 112 | [[package]] 113 | name = "parking_lot_core" 114 | version = "0.9.10" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 117 | dependencies = [ 118 | "cfg-if", 119 | "libc", 120 | "redox_syscall", 121 | "smallvec", 122 | "windows-targets", 123 | ] 124 | 125 | [[package]] 126 | name = "proc-macro2" 127 | version = "1.0.92" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 130 | dependencies = [ 131 | "unicode-ident", 132 | ] 133 | 134 | [[package]] 135 | name = "quote" 136 | version = "1.0.37" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 139 | dependencies = [ 140 | "proc-macro2", 141 | ] 142 | 143 | [[package]] 144 | name = "redox_syscall" 145 | version = "0.5.7" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 148 | dependencies = [ 149 | "bitflags", 150 | ] 151 | 152 | [[package]] 153 | name = "rustix" 154 | version = "0.38.42" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 157 | dependencies = [ 158 | "bitflags", 159 | "errno", 160 | "libc", 161 | "linux-raw-sys", 162 | "windows-sys", 163 | ] 164 | 165 | [[package]] 166 | name = "rustversion" 167 | version = "1.0.18" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 170 | 171 | [[package]] 172 | name = "scopeguard" 173 | version = "1.2.0" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 176 | 177 | [[package]] 178 | name = "smallvec" 179 | version = "1.13.2" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 182 | 183 | [[package]] 184 | name = "strum" 185 | version = "0.26.3" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 188 | 189 | [[package]] 190 | name = "strum_macros" 191 | version = "0.26.4" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 194 | dependencies = [ 195 | "heck", 196 | "proc-macro2", 197 | "quote", 198 | "rustversion", 199 | "syn", 200 | ] 201 | 202 | [[package]] 203 | name = "syn" 204 | version = "2.0.90" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 207 | dependencies = [ 208 | "proc-macro2", 209 | "quote", 210 | "unicode-ident", 211 | ] 212 | 213 | [[package]] 214 | name = "unicode-ident" 215 | version = "1.0.14" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 218 | 219 | [[package]] 220 | name = "unicode-width" 221 | version = "0.2.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 224 | 225 | [[package]] 226 | name = "winapi" 227 | version = "0.3.9" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 230 | dependencies = [ 231 | "winapi-i686-pc-windows-gnu", 232 | "winapi-x86_64-pc-windows-gnu", 233 | ] 234 | 235 | [[package]] 236 | name = "winapi-i686-pc-windows-gnu" 237 | version = "0.4.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 240 | 241 | [[package]] 242 | name = "winapi-x86_64-pc-windows-gnu" 243 | version = "0.4.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 246 | 247 | [[package]] 248 | name = "windows-sys" 249 | version = "0.59.0" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 252 | dependencies = [ 253 | "windows-targets", 254 | ] 255 | 256 | [[package]] 257 | name = "windows-targets" 258 | version = "0.52.6" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 261 | dependencies = [ 262 | "windows_aarch64_gnullvm", 263 | "windows_aarch64_msvc", 264 | "windows_i686_gnu", 265 | "windows_i686_gnullvm", 266 | "windows_i686_msvc", 267 | "windows_x86_64_gnu", 268 | "windows_x86_64_gnullvm", 269 | "windows_x86_64_msvc", 270 | ] 271 | 272 | [[package]] 273 | name = "windows_aarch64_gnullvm" 274 | version = "0.52.6" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 277 | 278 | [[package]] 279 | name = "windows_aarch64_msvc" 280 | version = "0.52.6" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 283 | 284 | [[package]] 285 | name = "windows_i686_gnu" 286 | version = "0.52.6" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 289 | 290 | [[package]] 291 | name = "windows_i686_gnullvm" 292 | version = "0.52.6" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 295 | 296 | [[package]] 297 | name = "windows_i686_msvc" 298 | version = "0.52.6" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 301 | 302 | [[package]] 303 | name = "windows_x86_64_gnu" 304 | version = "0.52.6" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 307 | 308 | [[package]] 309 | name = "windows_x86_64_gnullvm" 310 | version = "0.52.6" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 313 | 314 | [[package]] 315 | name = "windows_x86_64_msvc" 316 | version = "0.52.6" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 319 | -------------------------------------------------------------------------------- /tools/alloc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubris-region-alloc" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | serde = { version = "1", features = ["derive"] } 8 | 9 | [dev-dependencies] 10 | comfy-table = "7.1.3" 11 | -------------------------------------------------------------------------------- /tools/build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubris-build" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cargo_metadata = "0.18.1" 8 | clap = { version = "4.5.20", features = ["derive"] } 9 | comfy-table = "7.1.3" 10 | goblin = { version = "0.9.2", features = ["elf32", "elf64", "endian_fd", "std"], default-features = false } 11 | indexmap = { version = "2.6.0", features = ["serde"] } 12 | itertools = "0.13.0" 13 | kdl = "6.3.3" 14 | miette = { version = "7.2", features = ["fancy"] } 15 | size = "0.4.1" 16 | strsim = "0.11.1" 17 | 18 | #hubris-build-kconfig = {package = "build-kconfig", git = "https://github.com/oxidecomputer/hubris.git"} 19 | hubris-build-kconfig = {package = "build-kconfig", git = "https://github.com/cbiffle/hubris-fork.git", branch = "cbiffle/exhubris-fixes"} 20 | ron = "0.8.1" 21 | rangemap = "1.5.1" 22 | ihex = "3.0.0" 23 | tempdir = "0.3.7" 24 | serde_json = "1.0.133" 25 | serde = { version = "1.0.215", features = ["derive"] } 26 | quote = "1.0.37" 27 | prettyplease = "0.2.25" 28 | syn = "2.0.89" 29 | convert_case = "0.6.0" 30 | proc-macro2 = "1.0.92" 31 | lazy_static = "1.5.0" 32 | zip = "2.2.1" 33 | toml = { version = "0.8.19", features = ["preserve_order"] } 34 | object = { version = "0.36.5", features = ["write"] } 35 | hubris-region-alloc = { version = "0.1.0", path = "../alloc" } 36 | thiserror = "2.0.6" 37 | blake3 = "1.5.5" 38 | -------------------------------------------------------------------------------- /tools/build/src/alloc.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, ops::{RangeInclusive, Range}}; 2 | use indexmap::IndexMap; 3 | use hubris_region_alloc::{Mem, TaskInfo, TaskName}; 4 | use miette::bail; 5 | use serde::Serialize; 6 | 7 | use crate::{appcfg::{KernelDef, RegionDef, Spanned}, SizeRule, TargetSpec}; 8 | 9 | #[derive(Clone, Debug, Default, Serialize)] 10 | pub struct Allocations { 11 | pub tasks: IndexMap>, 12 | pub kernel: KernelAllocation, 13 | } 14 | 15 | impl Allocations { 16 | pub fn by_region(&self) -> BTreeMap<&str, IndexMap<&str, &TaskAllocation>> { 17 | let mut pivot: BTreeMap<_, IndexMap<_, _>> = BTreeMap::new(); 18 | for (task_name, ta) in &self.tasks { 19 | for (region_name, alloc) in ta { 20 | pivot.entry(region_name.as_str()) 21 | .or_default() 22 | .insert(task_name.as_str(), alloc); 23 | } 24 | } 25 | pivot 26 | } 27 | } 28 | 29 | #[derive(Clone, Debug, Serialize)] 30 | pub struct KernelAllocation { 31 | pub by_region: BTreeMap>, 32 | pub stack: Range, 33 | } 34 | 35 | impl Default for KernelAllocation { 36 | fn default() -> Self { 37 | Self { by_region: Default::default(), stack: 0..0 } 38 | } 39 | } 40 | 41 | #[derive(Clone, Debug, Serialize)] 42 | pub struct TaskAllocation { 43 | pub requested: u64, 44 | pub base: u64, 45 | pub sizes: Vec, 46 | } 47 | 48 | impl TaskAllocation { 49 | pub fn calculate_size(&self) -> u64 { 50 | self.sizes.iter().sum() 51 | } 52 | 53 | pub fn range_inclusive(&self) -> RangeInclusive { 54 | let size = self.calculate_size(); 55 | self.base..=self.base + (size - 1) 56 | } 57 | } 58 | 59 | pub fn allocate_space( 60 | target_spec: &TargetSpec, 61 | available: &IndexMap>, 62 | tasks: &BTreeMap, 63 | kernel: &KernelDef, 64 | ) -> miette::Result { 65 | let mut available = available.iter().map(|(name, def)| { 66 | let base = *def.value().base.value(); 67 | (Mem(name.clone()), base..base + def.value().size.value()) 68 | }).collect::>(); 69 | let mut results = Allocations::default(); 70 | for (region_name, region) in &mut available { 71 | let is_ram = region_name.0.eq_ignore_ascii_case("RAM"); 72 | // Derive some region characteristics. 73 | let orig_addr = region.start; 74 | let mut addr = target_spec.align_before_allocation(orig_addr); 75 | // Carve stack space out of the RAM region. 76 | if is_ram { 77 | addr = target_spec.align_for_stack(addr); 78 | results.kernel.stack = addr..addr + kernel.stack_size.value(); 79 | addr += kernel.stack_size.value(); 80 | addr = target_spec.align_for_stack(addr); 81 | } 82 | if orig_addr != addr { 83 | eprintln!("warning: adjusted region {region_name} base up to {addr:#x} from {orig_addr:#x}"); 84 | } 85 | 86 | region.start = addr; 87 | } 88 | 89 | match &target_spec.size_rule { 90 | SizeRule::PowerOfTwo => { 91 | let granule = target_spec.alloc_minimum.next_power_of_two(); 92 | let round = granule - 1; 93 | available.values_mut().for_each(|range| { 94 | range.start = (range.start.next_multiple_of(granule) + round) / granule; 95 | range.end = (range.end + round) / granule; 96 | }); 97 | let tasks_granule = tasks.iter().map(|(name, info)| { 98 | (name.clone(), TaskInfo { 99 | regs_avail: info.regs_avail, 100 | reqs: info.reqs.iter() 101 | .map(|(mem, size)| (mem.clone(), (size + round) / granule)) 102 | .collect(), 103 | }) 104 | }).collect::>(); 105 | let Some((aresult, leftover)) = hubris_region_alloc::allocate(&tasks_granule, &available) else { 106 | bail!("can't fit app in memory"); 107 | }; 108 | for (name, info) in tasks { 109 | let ta = results.tasks.entry(name.0.clone()).or_default(); 110 | for (region, alloc) in &aresult.tasks[name].regions { 111 | ta.insert(region.0.clone(), TaskAllocation { 112 | requested: info.reqs[region], 113 | base: alloc.1 * granule, 114 | sizes: alloc.2.iter().map(|i| (1 << i) * granule).collect(), 115 | }); 116 | } 117 | 118 | } 119 | results.kernel.by_region = leftover.into_iter().map(|(name, range)| { 120 | (name, range.start * granule .. range.end * granule) 121 | }).collect(); 122 | } 123 | SizeRule::MultipleOf(granule) => { 124 | let round = granule - 1; 125 | available.values_mut().for_each(|range| { 126 | range.start = (range.start + round) / granule; 127 | range.end = (range.end + round) / granule; 128 | }); 129 | for (name, info) in tasks { 130 | let ta = results.tasks.entry(name.0.clone()).or_default(); 131 | for (mem, orig_byte_size) in &info.reqs { 132 | let granule_size = (orig_byte_size + round) / granule; 133 | let byte_size = granule_size * granule; 134 | 135 | let av = available.get_mut(mem).unwrap(); 136 | let base = av.start * granule; 137 | av.start += granule_size; 138 | 139 | ta.insert(mem.0.clone(), TaskAllocation { 140 | requested: *orig_byte_size, 141 | base, 142 | sizes: vec![byte_size], 143 | }); 144 | } 145 | } 146 | 147 | results.kernel.by_region = available.into_iter().map(|(n, range)| { 148 | (n, range.start * granule .. range.end * granule) 149 | }).collect(); 150 | } 151 | } 152 | 153 | Ok(results) 154 | } 155 | 156 | 157 | -------------------------------------------------------------------------------- /tools/build/src/buildid.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use serde::Serialize; 4 | 5 | /// Incrementally constructs the `HUBRIS_IMAGE_ID` value used to 6 | /// probabilistically distinguish builds. 7 | #[derive(Clone, Default, Debug)] 8 | pub struct BuildId { 9 | hasher: blake3::Hasher, 10 | } 11 | 12 | impl BuildId { 13 | /// Creates a new default `BuildId`. 14 | pub fn new() -> Self { 15 | Self { 16 | hasher: blake3::Hasher::new() 17 | } 18 | } 19 | 20 | /// Appends `data` to the internal hash. 21 | pub fn eat(&mut self, data: &[u8]) { 22 | self.hasher.update(data); 23 | } 24 | 25 | /// Serializes `something` to JSON and appends that to the internal hash. 26 | pub fn hash(&mut self, something: &impl Serialize) { 27 | serde_json::to_writer(&mut self.hasher, something).unwrap(); 28 | } 29 | 30 | /// Generates 64 bits of ID from the internal hash. 31 | /// 32 | /// This destroys `self` so you don't accidentally feed it more data that 33 | /// won't be included in the result. 34 | pub fn finish(self) -> u64 { 35 | let mut bytes = [0; 8]; 36 | self.hasher.finalize_xof().read_exact(&mut bytes).unwrap(); 37 | u64::from_le_bytes(bytes) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tools/build/src/bundle.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::{BTreeMap, BTreeSet}, io::Write as _, path::Path}; 2 | 3 | use indexmap::{IndexMap, IndexSet}; 4 | use serde::Serialize; 5 | 6 | use crate::appcfg::{self, AppDef}; 7 | 8 | #[derive(Debug, thiserror::Error)] 9 | #[non_exhaustive] 10 | pub enum BundleError { 11 | #[error("underlying I/O operation failed")] 12 | Io(#[from] std::io::Error), 13 | #[error("could not update ZIP file")] 14 | Zip(#[from] zip::result::ZipError), 15 | #[error("error reading ELF file from previous build step")] 16 | Goblin(#[from] goblin::error::Error), 17 | #[error("error writing combined ELF file")] 18 | Object(#[from] object::write::Error), 19 | 20 | #[error("build output contains a mix of big- and little-endian objects")] 21 | MixedEndian, 22 | #[error("build output contains a mix of 32- and 64-bit objects")] 23 | MixedPointerWidth, 24 | #[error("build output contains a mix of OS ABI values")] 25 | MixedOsAbi, 26 | #[error("build output contains a mix of ABI version values")] 27 | MixedAbiVersion, 28 | #[error("build output contains a mix of machine types")] 29 | MixedMachine, 30 | } 31 | 32 | pub fn make_bundle( 33 | app: &AppDef, 34 | bindir: &Path, 35 | outpath: &Path, 36 | ) -> Result<(), BundleError> { 37 | let f = std::fs::File::create(outpath)?; 38 | let mut z = zip::ZipWriter::new(f); 39 | let opts = zip::write::SimpleFileOptions::default(); 40 | 41 | z.set_comment("hubris build archive v9"); 42 | 43 | { 44 | z.start_file("app.toml", opts)?; 45 | 46 | let tasks_toml = app.tasks.iter().map(|(name, task)| { 47 | (name.clone(), TaskToml { 48 | notifications: task.notifications.clone(), 49 | }) 50 | }).collect(); 51 | 52 | let app_toml = AppToml { 53 | name: app.name.value().clone(), 54 | target: app.board.chip.target_triple.value().clone(), 55 | board: app.board.name.value().clone(), 56 | kernel: KernelToml { 57 | name: match app.kernel.package_source.value() { 58 | appcfg::PackageSource::WorkspaceCrate { name } => name.clone(), 59 | appcfg::PackageSource::GitCrate { name, .. } => name.clone(), 60 | }, 61 | }, 62 | tasks: tasks_toml, 63 | }; 64 | let toml_text = toml::to_string(&app_toml).unwrap(); 65 | writeln!(z, "# stub TOML generated because Humility expects it")?; 66 | z.write_all(toml_text.as_bytes())?; 67 | } 68 | 69 | let mut collected_segments = BTreeMap::new(); 70 | 71 | // We expect these properties to be consistent across the ELF files, but we 72 | // collect them into sets to find out if they aren't. 73 | let mut little_endian = BTreeSet::new(); 74 | let mut is_64 = BTreeSet::new(); 75 | let mut elf_machine = BTreeSet::new(); 76 | let mut elf_os_abi = BTreeSet::new(); 77 | let mut elf_abi_version = BTreeSet::new(); 78 | 79 | let mut kernel_entry = None; 80 | 81 | if let Some(prs_name) = &app.board.chip.probe_rs_name { 82 | let text = ron::to_string(&FlashRon { 83 | chip: Some(prs_name.value().clone()), 84 | }).unwrap(); 85 | z.start_file("img/flash.ron", opts) 86 | ?; 87 | z.write_all(text.as_bytes())?; 88 | } 89 | 90 | let names = app.tasks.keys() 91 | .map(|s| s.as_str()) 92 | .chain(std::iter::once("kernel")); 93 | 94 | for name in names { 95 | let is_the_kernel = name == "kernel"; 96 | 97 | let archive_path = if is_the_kernel { 98 | "elf/kernel".to_string() 99 | } else { 100 | format!("elf/task/{name}") 101 | }; 102 | 103 | let elfpath = bindir.join(name); 104 | let elf_bytes = std::fs::read(&elfpath) 105 | ?; 106 | z.start_file(archive_path, opts) 107 | ?; 108 | z.write_all(&elf_bytes)?; 109 | 110 | let elf = goblin::elf::Elf::parse(&elf_bytes)?; 111 | little_endian.insert(elf.little_endian); 112 | is_64.insert(elf.is_64); 113 | elf_machine.insert(elf.header.e_machine); 114 | elf_os_abi.insert(elf.header.e_ident[goblin::elf::header::EI_OSABI]); 115 | elf_abi_version.insert(elf.header.e_ident[goblin::elf::header::EI_ABIVERSION]); 116 | 117 | for phdr in &elf.program_headers { 118 | if phdr.p_type != goblin::elf::program_header::PT_LOAD { 119 | continue; 120 | } 121 | if phdr.p_filesz == 0 { 122 | continue; 123 | } 124 | let start = usize::try_from(phdr.p_offset).unwrap(); 125 | let end = start + usize::try_from(phdr.p_filesz).unwrap(); 126 | let segment_bytes = elf_bytes[start..end].to_vec(); 127 | collected_segments.insert( 128 | phdr.p_paddr, 129 | segment_bytes, 130 | ); 131 | } 132 | 133 | if is_the_kernel { 134 | kernel_entry = Some(elf.entry); 135 | } 136 | } 137 | 138 | let base_addr = *collected_segments.first_key_value().unwrap().0; 139 | let (_final_addr, flattened) = collected_segments.into_iter() 140 | .fold((None, vec![]), |(last_addr, mut flattened), (addr, bytes)| { 141 | if let Some(la) = last_addr { 142 | let gap_size = addr - la; 143 | let new_len = flattened.len() + usize::try_from(gap_size).unwrap(); 144 | flattened.resize(new_len, 0xFF); 145 | } 146 | let n = bytes.len(); 147 | flattened.extend(bytes); 148 | 149 | (Some(addr + u64::try_from(n).unwrap()), flattened) 150 | }); 151 | 152 | if little_endian.len() == 2 { 153 | return Err(BundleError::MixedEndian); 154 | } 155 | let little_endian = little_endian.pop_last().unwrap(); 156 | if is_64.len() == 2 { 157 | return Err(BundleError::MixedPointerWidth); 158 | } 159 | let is_64 = is_64.pop_last().unwrap(); 160 | if elf_os_abi.len() == 2 { 161 | return Err(BundleError::MixedOsAbi); 162 | } 163 | let elf_os_abi = elf_os_abi.pop_last().unwrap(); 164 | if elf_abi_version.len() == 2 { 165 | return Err(BundleError::MixedAbiVersion); 166 | } 167 | let elf_abi_version = elf_abi_version.pop_last().unwrap(); 168 | if elf_machine.len() == 2 { 169 | return Err(BundleError::MixedMachine); 170 | } 171 | let elf_machine = elf_machine.pop_last().unwrap(); 172 | 173 | let mut final_buf = vec![]; 174 | let mut w = object::write::elf::Writer::new( 175 | if little_endian { 176 | object::Endianness::Little 177 | } else { 178 | object::Endianness::Big 179 | }, 180 | is_64, 181 | &mut final_buf, 182 | ); 183 | w.reserve_file_header(); 184 | w.reserve_program_headers(1); 185 | let offset = w.reserve(flattened.len(), 8); 186 | let _index = w.reserve_section_index(); 187 | let name = w.add_section_name(b".sec1"); 188 | w.reserve_shstrtab_section_index(); 189 | w.reserve_shstrtab(); 190 | w.reserve_section_headers(); 191 | 192 | w.write_file_header(&object::write::elf::FileHeader { 193 | os_abi: elf_os_abi, 194 | abi_version: elf_abi_version, 195 | e_type: object::elf::ET_REL, 196 | e_machine: elf_machine, 197 | e_entry: kernel_entry.unwrap(), 198 | e_flags: 0, 199 | })?; 200 | w.write_align_program_headers(); 201 | let len64 = u64::try_from(flattened.len()).unwrap(); 202 | w.write_program_header(&object::write::elf::ProgramHeader { 203 | p_type: object::elf::PT_LOAD, 204 | p_flags: object::elf::PF_R, 205 | p_offset: u64::try_from(offset).unwrap(), 206 | p_vaddr: base_addr, 207 | p_paddr: base_addr, 208 | p_filesz: len64, 209 | p_memsz: len64, 210 | p_align: 0, 211 | }); 212 | 213 | w.write_align(8); 214 | assert_eq!(w.len(), offset); 215 | w.write(&flattened); 216 | w.write_shstrtab(); 217 | w.write_null_section_header(); 218 | 219 | w.write_section_header(&object::write::elf::SectionHeader { 220 | name: Some(name), 221 | sh_type: object::elf::SHT_PROGBITS, 222 | sh_flags: (object::elf::SHF_WRITE | object::elf::SHF_ALLOC) as u64, 223 | sh_addr: base_addr, 224 | sh_offset: u64::try_from(offset).unwrap(), 225 | sh_size: len64, 226 | sh_link: 0, 227 | sh_info: 0, 228 | sh_addralign: 1, 229 | sh_entsize: 0, 230 | }); 231 | 232 | w.write_shstrtab_section_header(); 233 | 234 | assert_eq!(w.reserved_len(), w.len()); 235 | 236 | z.start_file("img/final.elf", opts) 237 | ?; 238 | z.write_all(&final_buf)?; 239 | 240 | drop(z); 241 | Ok(()) 242 | } 243 | 244 | #[derive(Serialize)] 245 | pub struct AppToml { 246 | pub target: String, 247 | pub name: String, 248 | pub board: String, 249 | 250 | pub kernel: KernelToml, 251 | 252 | pub tasks: IndexMap, 253 | } 254 | 255 | #[derive(Serialize)] 256 | pub struct KernelToml { 257 | pub name: String 258 | } 259 | 260 | #[derive(Serialize)] 261 | pub struct TaskToml { 262 | pub notifications: IndexSet, 263 | } 264 | 265 | 266 | #[derive(Serialize)] 267 | pub struct FlashRon { 268 | pub chip: Option, 269 | } 270 | 271 | -------------------------------------------------------------------------------- /tools/build/src/cargo.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use miette::{bail, Context as _, IntoDiagnostic as _}; 4 | 5 | use crate::{appcfg::{BuildMethod, BuildPlan}, cmd_with_clean_env}; 6 | 7 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 8 | pub enum LinkStyle { 9 | Partial, 10 | Full, 11 | } 12 | 13 | #[allow(clippy::too_many_arguments)] 14 | pub fn do_cargo_build( 15 | linker_script_text: &str, 16 | plan: &BuildPlan, 17 | link_style: LinkStyle, 18 | targetroot: &Path, 19 | tmpdir: &Path, 20 | outdir: &Path, 21 | product_name: &str, 22 | cargo_verbose: bool, 23 | build_verbose: bool, 24 | ) -> miette::Result<()> { 25 | let comma_features = itertools::join(&plan.cargo_features, ","); 26 | if build_verbose { 27 | let mut rows = vec![ 28 | ("Product", product_name), 29 | ("Package", &plan.package_name), 30 | ("Binary", &plan.bin_name), 31 | ("Target", &plan.target_triple), 32 | ("Default Features", if plan.default_features { "true" } else { "false" }), 33 | ("Features", &comma_features), 34 | ]; 35 | if let Some(tc) = &plan.toolchain_override { 36 | rows.push(("Toolchain Override", tc)); 37 | } 38 | let joined_rustflags = plan.rustflags.join("\n"); 39 | rows.push(("RUSTFLAGS", &joined_rustflags)); 40 | for (k, v) in &plan.smuggled_env { 41 | rows.push((k, v)); 42 | } 43 | crate::verbose::simple_table(rows); 44 | } else { 45 | crate::verbose::banner(format!("building component: {product_name}")); 46 | } 47 | 48 | let mut cmd = cmd_with_clean_env("cargo"); 49 | 50 | if let Some(tc) = &plan.toolchain_override { 51 | cmd.arg(format!("+{tc}")); 52 | } 53 | 54 | let linker_script_copy = tmpdir.join("link.x"); 55 | std::fs::write(&linker_script_copy, linker_script_text).into_diagnostic()?; 56 | 57 | let mut rustflags = vec![ 58 | format!("-Clink-arg=-L{}", tmpdir.display()), 59 | format!("-Clink-arg=-T{}", linker_script_copy.display()), 60 | ]; 61 | match link_style { 62 | LinkStyle::Partial => rustflags.push("-Clink-arg=-r".to_string()), 63 | LinkStyle::Full => (), 64 | } 65 | rustflags.extend(plan.rustflags.iter().cloned()); 66 | 67 | cmd.env("CARGO_ENCODED_RUSTFLAGS", rustflags.join("\u{001f}")); 68 | 69 | let product_path = match &plan.method { 70 | BuildMethod::CargoWorkspaceBuild => { 71 | cmd.args(["build", "--release"]); 72 | cmd.args(["-p", &plan.package_name, "--bin", &plan.bin_name]); 73 | 74 | let mut outloc = targetroot.join(&plan.target_triple); 75 | outloc.push("release"); 76 | outloc.push(&plan.bin_name); 77 | outloc 78 | } 79 | BuildMethod::CargoInstallGit { repo, rev } => { 80 | cmd.args(["install", "--locked", "--no-track", "--force"]); 81 | cmd.arg(&plan.package_name); 82 | cmd.args(["--bin", &plan.bin_name]); 83 | cmd.args(["--git", repo]); 84 | cmd.args(["--rev", rev]); 85 | 86 | let installroot = tmpdir.join(format!("{product_name}.cargo-install")); 87 | let targetdir = tmpdir.join(format!("{product_name}.cargo-target")); 88 | 89 | cmd.arg("--root"); 90 | cmd.arg(&installroot); 91 | 92 | let bindir = installroot.join("bin"); 93 | let binpath = bindir.join(&plan.bin_name); 94 | 95 | // Extend the PATH to add our output directory so that cargo won't 96 | // unconditionally warn about every build, sigh. 97 | match std::env::var_os("PATH") { 98 | Some(path) => { 99 | let mut parts = std::env::split_paths(&path).collect::>(); 100 | parts.push(bindir.clone()); 101 | let new_path = std::env::join_paths(parts).into_diagnostic()?; 102 | cmd.env("PATH", new_path); 103 | } 104 | None => { 105 | cmd.env("PATH", &bindir); 106 | } 107 | } 108 | 109 | cmd.env("CARGO_TARGET_DIR", targetdir); 110 | 111 | binpath 112 | } 113 | }; 114 | 115 | if cargo_verbose { 116 | cmd.arg("--verbose"); 117 | } 118 | 119 | cmd.args(["--target", &plan.target_triple]); 120 | 121 | if !plan.default_features { 122 | cmd.arg("--no-default-features"); 123 | } 124 | if !plan.cargo_features.is_empty() { 125 | cmd.args(["--features", &comma_features]); 126 | } 127 | 128 | for (k, v) in &plan.smuggled_env { 129 | cmd.env(k, v); 130 | } 131 | 132 | let status = cmd.status().into_diagnostic()?; 133 | if !status.success() { 134 | bail!("failed to build, see output"); 135 | } 136 | 137 | std::fs::remove_file(linker_script_copy).into_diagnostic()?; 138 | 139 | let final_path = outdir.join(product_name); 140 | std::fs::copy(&product_path, &final_path).into_diagnostic() 141 | .with_context(|| format!("copying {} to {}", product_path.display(), final_path.display()))?; 142 | 143 | Ok(()) 144 | } 145 | 146 | 147 | -------------------------------------------------------------------------------- /tools/build/src/kconfig.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{btree_map, BTreeMap, BTreeSet}; 2 | 3 | use hubris_build_kconfig as kconfig; 4 | use miette::IntoDiagnostic as _; 5 | 6 | use crate::{appcfg::AppDef, relink::BuiltTask}; 7 | 8 | /// Generates kernel configuration for a given build of an app. 9 | /// 10 | /// `app` gives the static appcfg, while `built_tasks` describes the actual 11 | /// build output, including allocation decisions and sizes. 12 | /// 13 | /// This function does not access the filesystem or parse ELF files; everything 14 | /// it needs to know is in its arguments. 15 | pub fn generate_kconfig( 16 | app: &AppDef, 17 | built_tasks: &[BuiltTask], 18 | ) -> miette::Result { 19 | let shared_regions = app.board.chip.peripherals.iter() 20 | .map(|(name, pdef)| { 21 | let pdef = pdef.value(); 22 | 23 | (name.clone(), kconfig::RegionConfig { 24 | base: *pdef.base.value() as u32, 25 | size: *pdef.size.value() as u32, 26 | attributes: kconfig::RegionAttributes { 27 | read: true, 28 | write: true, 29 | execute: false, 30 | special_role: Some(kconfig::SpecialRole::Device), 31 | }, 32 | }) 33 | }) 34 | .collect(); 35 | 36 | let mut kconfig = kconfig::KernelConfig { 37 | tasks: vec![], 38 | shared_regions, 39 | irqs: BTreeMap::new(), 40 | }; 41 | for (i, task) in app.tasks.values().enumerate() { 42 | for (pname, puse) in &task.peripherals { 43 | let periphdef = &app.board.chip.peripherals[pname]; 44 | let interrupts = &periphdef.value().interrupts; 45 | 46 | for (pirqname, notname) in &puse.value().interrupts { 47 | let irqnum = interrupts[pirqname]; 48 | 49 | match kconfig.irqs.entry(irqnum) { 50 | btree_map::Entry::Vacant(v) => { 51 | v.insert(kconfig::InterruptConfig { 52 | task_index: i, 53 | notification: 1 << task.notifications.get_index_of(notname).unwrap(), 54 | }); 55 | } 56 | btree_map::Entry::Occupied(_) => { 57 | panic!("internal inconsistency: interrupt {irqnum} defined in multiple places"); 58 | } 59 | } 60 | } 61 | } 62 | } 63 | 64 | let mut used_shared_regions = BTreeSet::new(); 65 | 66 | for task in built_tasks { 67 | let mut config = kconfig::TaskConfig { 68 | owned_regions: BTreeMap::new(), 69 | shared_regions: BTreeSet::new(), 70 | entry_point: kconfig::OwnedAddress { 71 | region_name: task.entry.region.clone(), 72 | offset: u32::try_from(task.entry.offset).into_diagnostic()?, 73 | }, 74 | initial_stack: kconfig::OwnedAddress { 75 | region_name: task.initial_stack_pointer.region.clone(), 76 | offset: u32::try_from(task.initial_stack_pointer.offset).into_diagnostic()?, 77 | }, 78 | priority: *app.tasks[&task.name].priority.value(), 79 | start_at_boot: !app.tasks[&task.name].wait_for_reinit, 80 | }; 81 | 82 | for (name, reg) in &task.owned_regions { 83 | let mem = &app.board.chip.memory[name].value(); 84 | 85 | config.owned_regions.insert(name.clone(), kconfig::MultiRegionConfig { 86 | base: u32::try_from(reg.base).into_diagnostic()?, 87 | sizes: reg.sizes.iter().map(|n| u32::try_from(*n) 88 | .into_diagnostic()) 89 | .collect::>>()?, 90 | attributes: kconfig::RegionAttributes { 91 | read: mem.read, 92 | write: mem.write, 93 | execute: mem.execute, 94 | special_role: None, // TODO 95 | }, 96 | }); 97 | } 98 | 99 | for name in app.tasks[&task.name].peripherals.keys() { 100 | used_shared_regions.insert(name); 101 | config.shared_regions.insert(name.clone()); 102 | } 103 | 104 | kconfig.tasks.push(config); 105 | } 106 | 107 | kconfig.shared_regions.retain(|name, _| used_shared_regions.contains(name)); 108 | 109 | Ok(kconfig) 110 | } 111 | -------------------------------------------------------------------------------- /tools/build/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod appcfg; 2 | pub mod alloc; 3 | pub mod idl; 4 | pub mod config; 5 | pub mod bundle; 6 | pub mod buildid; 7 | pub mod relink; 8 | pub mod cargo; 9 | pub mod verbose; 10 | pub mod kconfig; 11 | 12 | use std::{ffi::OsStr, path::PathBuf, process::Command}; 13 | 14 | use cargo_metadata::Package; 15 | use miette::{bail, miette, IntoDiagnostic as _}; 16 | use serde::Serialize; 17 | 18 | #[derive(Clone, Debug, Serialize)] 19 | pub struct BuildEnv { 20 | /// release of toolchain 21 | pub release: String, 22 | /// sysroot for toolchain obtained from rustup. 23 | pub sysroot: PathBuf, 24 | /// Host triple --- the triple used for tools that run on the host, rather 25 | /// than being cross-compiled. 26 | pub host_triple: String, 27 | /// The location we have inferred for the linker. 28 | pub linker_path: PathBuf, 29 | } 30 | 31 | pub fn determine_build_env() -> miette::Result { 32 | let sysroot = Command::new("rustc") 33 | .args(["--print", "sysroot"]) 34 | .output().into_diagnostic()?; 35 | if !sysroot.status.success() { 36 | panic!("Could not find execute rustc to get sysroot"); 37 | } 38 | let sysroot = PathBuf::from(std::str::from_utf8(&sysroot.stdout).into_diagnostic()?.trim()); 39 | 40 | let host = Command::new(sysroot.join("bin").join("rustc")) 41 | .arg("-vV") 42 | .output().into_diagnostic()?; 43 | if !host.status.success() { 44 | panic!("Could not execute rustc to get host"); 45 | } 46 | let output = std::str::from_utf8(&host.stdout).into_diagnostic()?; 47 | let host_triple = output.lines() 48 | .find_map(|line| line.strip_prefix("host: ")) 49 | .ok_or_else(|| miette!("Could not get host from rustc"))? 50 | .to_string(); 51 | let release = output.lines() 52 | .find_map(|line| line.strip_prefix("release: ")) 53 | .ok_or_else(|| miette!("Could not get release from rustc"))? 54 | .to_string(); 55 | 56 | let mut linker_path = sysroot.clone(); 57 | linker_path.extend([ 58 | "lib", 59 | "rustlib", 60 | &host_triple, 61 | "bin", 62 | "gcc-ld", 63 | "ld.lld", 64 | ]); 65 | if !std::fs::exists(&linker_path).into_diagnostic()? { 66 | bail!("linker not available at: {}", linker_path.display()); 67 | } 68 | 69 | Ok(BuildEnv { 70 | release, 71 | sysroot, 72 | host_triple, 73 | linker_path, 74 | }) 75 | } 76 | 77 | pub enum BuildablePackage { 78 | WorkspaceCrate { 79 | name: String, 80 | /// The entire package metadata record, proving that we found it (and 81 | /// providing useful contents). 82 | package: Package, 83 | /// The cargo arguments to build the crate. 84 | cargo_args: Vec, 85 | /// The path where we expect to find the result. 86 | product_path: PathBuf, 87 | }, 88 | } 89 | 90 | #[derive(Copy, Clone, Debug, Serialize)] 91 | pub enum SizeRule { 92 | /// Allocations must be a power-of-two in size. 93 | PowerOfTwo, 94 | /// Allocations must be an integer multiple of this number. 95 | MultipleOf(u64), 96 | } 97 | 98 | #[derive(Clone, Debug, Serialize)] 99 | pub struct TargetSpec { 100 | /// Rule determining legal allocation sizes on this target. 101 | pub size_rule: SizeRule, 102 | /// Minimum allocation size on this target. 103 | pub alloc_minimum: u64, 104 | /// Stack alignment requirement in bytes. 105 | pub stack_align: u64, 106 | /// BFD architecture name. 107 | pub bfd_name: String, 108 | /// Number of MPU regions available. 109 | pub region_count: usize, 110 | } 111 | 112 | impl TargetSpec { 113 | pub fn align_before_allocation(&self, addr: u64) -> u64 { 114 | addr.next_multiple_of(self.alloc_minimum) 115 | } 116 | 117 | pub fn align_for_allocation_size(&self, addr: u64, size: u64) -> u64 { 118 | let size = u64::max(size, self.alloc_minimum); 119 | match self.size_rule { 120 | SizeRule::PowerOfTwo => { 121 | addr.next_multiple_of(size) 122 | } 123 | SizeRule::MultipleOf(n) => { 124 | addr.next_multiple_of(n) 125 | } 126 | } 127 | } 128 | 129 | pub fn align_to_next_larger_boundary(&self, addr: u64) -> u64 { 130 | match self.size_rule { 131 | SizeRule::PowerOfTwo => { 132 | addr + (1 << addr.trailing_zeros()) 133 | } 134 | SizeRule::MultipleOf(n) => { 135 | addr.next_multiple_of(n) 136 | } 137 | } 138 | } 139 | 140 | pub fn round_allocation_size(&self, size: u64) -> u64 { 141 | let size = u64::max(size, self.alloc_minimum); 142 | match self.size_rule { 143 | SizeRule::PowerOfTwo => { 144 | size.next_power_of_two() 145 | } 146 | SizeRule::MultipleOf(n) => { 147 | size.next_multiple_of(n) 148 | } 149 | } 150 | } 151 | 152 | pub fn align_for_stack(&self, addr: u64) -> u64 { 153 | addr.next_multiple_of(self.stack_align) 154 | } 155 | } 156 | 157 | pub fn get_target_spec(triple: &str) -> Option { 158 | match triple { 159 | "thumbv6m-none-eabi" => Some(TargetSpec { 160 | size_rule: SizeRule::PowerOfTwo, 161 | alloc_minimum: 32, 162 | stack_align: 8, 163 | bfd_name: "armelf".to_string(), 164 | region_count: 8 - 1, 165 | }), 166 | "thumbv7em-none-eabihf" => Some(TargetSpec { 167 | size_rule: SizeRule::PowerOfTwo, 168 | alloc_minimum: 32, 169 | stack_align: 8, 170 | bfd_name: "armelf".to_string(), 171 | region_count: 8 - 1, 172 | }), 173 | "thumbv8m.main-none-eabihf" => Some(TargetSpec { 174 | size_rule: SizeRule::MultipleOf(32), 175 | alloc_minimum: 32, 176 | stack_align: 8, 177 | bfd_name: "armelf".to_string(), 178 | region_count: 8 - 1, 179 | }), 180 | _ => None, 181 | } 182 | } 183 | 184 | /// Creates a `Command` to run `command`, but with environment conflicts 185 | /// removed. 186 | /// 187 | /// This will prevent the resulting `Command` from inheriting any environment 188 | /// variables starting with `HUBRIS_`, except for those explicitly set after 189 | /// this returns, and `HUBRIS_PROJECT_ROOT`. 190 | /// 191 | /// This is intended to help avoid confusing state leaks from the user's shell 192 | /// environment into builds. 193 | pub fn cmd_with_clean_env(command: impl AsRef) -> Command { 194 | let mut cmd = Command::new(command); 195 | 196 | for (name, _value) in std::env::vars() { 197 | if name == "HUBRIS_PROJECT_ROOT" { 198 | continue; 199 | } 200 | if name.starts_with("HUBRIS_") { 201 | cmd.env_remove(name); 202 | } 203 | } 204 | 205 | cmd 206 | } 207 | -------------------------------------------------------------------------------- /tools/build/src/verbose.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | use comfy_table::{Attribute, Cell, CellAlignment}; 4 | use indexmap::IndexMap; 5 | use rangemap::RangeSet; 6 | use size::Size; 7 | 8 | use crate::{alloc::TaskAllocation, appcfg::AppDef}; 9 | 10 | pub fn simple_table<'a>(content: impl IntoIterator) { 11 | let mut table = comfy_table::Table::new(); 12 | table.load_preset(comfy_table::presets::UTF8_FULL); 13 | table.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS); 14 | table.apply_modifier(comfy_table::modifiers::UTF8_SOLID_INNER_BORDERS); 15 | table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); 16 | 17 | for row in content { 18 | table.add_row([row.0, row.1]); 19 | } 20 | 21 | println!(); 22 | println!(); 23 | println!("{table}"); 24 | println!(); 25 | } 26 | 27 | pub fn banner(content: impl core::fmt::Display) { 28 | let mut table = comfy_table::Table::new(); 29 | table.load_preset(comfy_table::presets::UTF8_FULL); 30 | table.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS); 31 | table.set_content_arrangement(comfy_table::ContentArrangement::Dynamic); 32 | 33 | table.add_row([content]); 34 | 35 | println!(); 36 | println!(); 37 | println!("{table}"); 38 | println!(); 39 | } 40 | 41 | pub fn print_allocations( 42 | app: &AppDef, 43 | task_allocs: &BTreeMap<&str, IndexMap<&str, &TaskAllocation>>, 44 | kernel_allocs: &BTreeMap>, 45 | ) { 46 | let mut table = comfy_table::Table::new(); 47 | table.load_preset(comfy_table::presets::NOTHING); 48 | table.set_header(["MEMORY", "OWNER", "START", "END", "SIZE", "WASTE"]); 49 | table.column_mut(2).unwrap().set_cell_alignment(CellAlignment::Right); 50 | table.column_mut(3).unwrap().set_cell_alignment(CellAlignment::Right); 51 | table.column_mut(4).unwrap().set_cell_alignment(CellAlignment::Right); 52 | table.column_mut(5).unwrap().set_cell_alignment(CellAlignment::Right); 53 | for region_name in app.board.chip.memory.keys() { 54 | let mut regrows = vec![]; 55 | let mut total = 0; 56 | let mut loss = 0; 57 | let mut last = None; 58 | 59 | if let Some(regallocs) = task_allocs.get(region_name.as_str()) { 60 | let mut regallocs = regallocs.iter().collect::>(); 61 | regallocs.sort_by_key(|(_name, ta)| ta.base); 62 | 63 | for (task_name, talloc) in regallocs { 64 | let base = talloc.base; 65 | let req_size = talloc.requested; 66 | 67 | if let Some(last) = last { 68 | if base != last { 69 | let pad_size = base - last; 70 | total += pad_size; 71 | loss += pad_size; 72 | regrows.push([ 73 | Cell::new(region_name).dim(), 74 | Cell::new("-pad-").dim(), 75 | Cell::new(format!("{:#x}", last)).dim(), 76 | Cell::new(format!("{:#x}", base - 1)).dim(), 77 | Cell::new(Size::from_bytes(pad_size)).dim(), 78 | Cell::new(Size::from_bytes(pad_size)).dim(), 79 | ]); 80 | } 81 | } 82 | let size = talloc.sizes.iter().sum::(); 83 | let internal_pad = size - req_size; 84 | regrows.push([ 85 | Cell::new(region_name), 86 | Cell::new(task_name), 87 | Cell::new(format!("{:#x}", base)), 88 | Cell::new(format!("{:#x}", base + size - 1)), 89 | Cell::new(Size::from_bytes(size)), 90 | Cell::new(Size::from_bytes(internal_pad)), 91 | ]); 92 | total += size; 93 | loss += internal_pad; 94 | 95 | last = Some(base + size); 96 | } 97 | } 98 | if let Some(kallocs) = kernel_allocs.get(region_name) { 99 | for kalloc in kallocs.iter() { 100 | let size = kalloc.end - kalloc.start; 101 | regrows.push([ 102 | Cell::new(region_name), 103 | Cell::new("kernel"), 104 | Cell::new(format!("{:#x}", kalloc.start)), 105 | Cell::new(format!("{:#x}", kalloc.end - 1)), 106 | Cell::new(Size::from_bytes(size)), 107 | Cell::new(Size::from_bytes(0)), 108 | ]); 109 | total += size; 110 | } 111 | } 112 | 113 | regrows.sort_by(|a, b| a[2].content().cmp(&b[2].content())); 114 | 115 | if !regrows.is_empty() { 116 | table.add_rows(regrows); 117 | table.add_row([ 118 | Cell::new(region_name).bold().underlined(), 119 | Cell::new("(total)").bold().underlined(), 120 | Cell::new("").underlined(), 121 | Cell::new("").underlined(), 122 | Cell::new(Size::from_bytes(total)).bold().underlined(), 123 | Cell::new(Size::from_bytes(loss)).bold().underlined(), 124 | ]); 125 | } 126 | } 127 | println!("{table}"); 128 | } 129 | 130 | trait CellExt { 131 | fn dim(self) -> Self; 132 | fn bold(self) -> Self; 133 | fn underlined(self) -> Self; 134 | } 135 | 136 | impl CellExt for Cell { 137 | fn dim(self) -> Self { 138 | self.add_attribute(Attribute::Dim) 139 | } 140 | fn bold(self) -> Self { 141 | self.add_attribute(Attribute::Bold) 142 | } 143 | fn underlined(self) -> Self { 144 | self.add_attribute(Attribute::Underlined) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tools/hubake/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hubake" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.94" 8 | base64 = "0.22.1" 9 | dirs = "5.0.1" 10 | serde = { version = "1.0.215", features = ["derive"] } 11 | toml = "0.8.19" 12 | -------------------------------------------------------------------------------- /tools/hubake/README.mkdn: -------------------------------------------------------------------------------- 1 | # `hubake`: façade build tool for Hubris 2 | 3 | `hubake` is intended to be installed either globally or per-user (using e.g. 4 | `cargo install`). It serves as a façade for whatever version of the Hubris build 5 | tools a specific project requires. 6 | -------------------------------------------------------------------------------- /tools/hubake/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{path::{Path, PathBuf}, process::Command}; 2 | 3 | use anyhow::{bail, Context as _, anyhow}; 4 | use base64::Engine; 5 | use serde::Deserialize; 6 | 7 | fn main() -> anyhow::Result<()> { 8 | let args = std::env::args().skip(1).collect::>(); 9 | 10 | match args.first().map(|s| s.as_str()) { 11 | Some("help" | "--help" | "-h") => { 12 | println!("\ 13 | hubake \n\ 14 | \n\ 15 | where is one of:\n\ 16 | setup [-f] []\n\ 17 | \n\ 18 | Sets up the Hubris tools for the precise version needed for a project.\n\ 19 | The project is defined by a hubris-env.toml file, which also marks its\n\ 20 | root directory. If is not given as an argument, hubake will\n\ 21 | search from the current directory up.\n\ 22 | \n\ 23 | -f --force-reinstall rebuild tools even if they appear OK\n\ 24 | \n\ 25 | help\n\ 26 | \n\ 27 | Shows this message and exits with an error code."); 28 | std::process::exit(1); 29 | } 30 | Some("setup") => { 31 | let mut given_root = None; 32 | std::env::current_dir() 33 | .context("can't get current directory?")?; 34 | let mut force_reinstall = false; 35 | for arg in &args[1..] { 36 | match arg.as_str() { 37 | "--force-install" | "-f" => force_reinstall = true, 38 | _ => { 39 | if given_root.is_some() { 40 | bail!("too many arguments to setup command"); 41 | } else { 42 | given_root = Some(PathBuf::from(arg)); 43 | } 44 | } 45 | } 46 | } 47 | 48 | let storage = prepare_storage_dir()?; 49 | let given_root = if let Some(r) = given_root { 50 | r 51 | } else { 52 | std::env::current_dir() 53 | .context("can't get current directory?")? 54 | }; 55 | let project_root = find_project_root(&given_root)?; 56 | println!("checking setup in project root: {}", project_root.display()); 57 | let config = load_config(&project_root)?; 58 | 59 | let _cmd = prepare_run_command(&project_root, &config, &storage, force_reinstall, true)?; 60 | 61 | println!("setup complete"); 62 | } 63 | _ => { 64 | // passthrough 65 | let storage = prepare_storage_dir()?; 66 | let given_root = std::env::current_dir() 67 | .context("can't get current directory?")?; 68 | let project_root = find_project_root(&given_root)?; 69 | let config = load_config(&project_root)?; 70 | let mut cmd = prepare_run_command(&project_root, &config, &storage, false, false)?; 71 | 72 | cmd.args(args); 73 | 74 | cmd.env("HUBRIS_PROJECT_ROOT", &project_root); 75 | 76 | let status = cmd.status()?; 77 | if status.success() { 78 | return Ok(()); 79 | } else { 80 | std::process::exit(2); 81 | } 82 | } 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | fn prepare_run_command( 89 | project_root: &Path, 90 | config: &Config, 91 | storage: &Path, 92 | force_reinstall: bool, 93 | be_chatty: bool, 94 | ) -> anyhow::Result { 95 | let strat = match &config.system { 96 | SystemConfig::Git { repo, rev } => { 97 | if be_chatty { 98 | println!("system: git {repo}"); 99 | println!(" rev: {rev}"); 100 | } 101 | 102 | let toolsdir = storage.join("git") 103 | .join(base64::prelude::BASE64_STANDARD.encode(repo)) 104 | .join(rev); 105 | let binary_path = toolsdir.join("bin").join("hubris-build"); 106 | 107 | ExecStrategy::CargoInstall { 108 | binary_path, 109 | flags_if_needed: vec![ 110 | "--root".to_string(), 111 | toolsdir.display().to_string(), 112 | "--git".to_string(), 113 | repo.clone(), 114 | "--locked".to_string(), 115 | "--rev".to_string(), 116 | rev.clone(), 117 | "hubris-build".to_string(), 118 | "--bin".to_string(), 119 | "hubris-build".to_string(), 120 | 121 | ], 122 | } 123 | } 124 | SystemConfig::Here => { 125 | if be_chatty { 126 | println!("system: in project root"); 127 | } 128 | ExecStrategy::DirectRun { 129 | package_name: "hubris-build".to_string(), 130 | bin_name: "hubris-build".to_string(), 131 | } 132 | } 133 | }; 134 | 135 | let toolcmd = match strat { 136 | ExecStrategy::CargoInstall { binary_path, flags_if_needed } => { 137 | if !force_reinstall && std::fs::exists(&binary_path) 138 | .with_context(|| format!("can't check existence of tool at {}", binary_path.display()))? 139 | { 140 | if be_chatty { 141 | println!("tool already built"); 142 | } 143 | } else { 144 | if force_reinstall { 145 | println!("skipped checking tool install, --force-reinstall set"); 146 | } 147 | println!("building tool for rev, this may take a bit..."); 148 | let mut cmd = Command::new("cargo"); 149 | cmd.args(["install", "-q", "-f"]); 150 | for arg in flags_if_needed { 151 | cmd.arg(arg); 152 | } 153 | let stat = cmd.status().context("cargo build failed, see output")?; 154 | if stat.success() { 155 | println!("Installed tool"); 156 | } else { 157 | bail!("cargo build failed, see output"); 158 | } 159 | } 160 | 161 | Command::new(binary_path) 162 | } 163 | ExecStrategy::DirectRun { package_name, bin_name } => { 164 | let mut cmd = Command::new("cargo"); 165 | cmd.current_dir(project_root); 166 | cmd.args(["run", "-q", "-p", &package_name, "--bin", &bin_name]); 167 | cmd.arg("--"); 168 | cmd 169 | } 170 | }; 171 | Ok(toolcmd) 172 | } 173 | 174 | enum ExecStrategy { 175 | CargoInstall { 176 | binary_path: PathBuf, 177 | flags_if_needed: Vec, 178 | }, 179 | DirectRun { 180 | package_name: String, 181 | bin_name: String, 182 | }, 183 | } 184 | 185 | fn prepare_storage_dir() -> anyhow::Result { 186 | let path = dirs::data_dir().ok_or_else(|| anyhow!("can't get data directory for user?"))? 187 | .join("hubris"); 188 | 189 | std::fs::create_dir_all(&path) 190 | .with_context(|| format!("can't create storage directory at path: {}", path.display()))?; 191 | 192 | let git_dir = path.join("git"); 193 | std::fs::create_dir_all(&git_dir) 194 | .with_context(|| format!("can't create storage directory at path: {}", git_dir.display()))?; 195 | 196 | Ok(path) 197 | } 198 | 199 | fn find_project_root(orig_path: impl AsRef) -> anyhow::Result { 200 | let orig_path = orig_path.as_ref(); 201 | let mut pathbuf = orig_path.canonicalize() 202 | .with_context(|| format!("can't interpret path: {}", orig_path.display()))?; 203 | 204 | loop { 205 | let config_path = pathbuf.join("hubris-env.toml"); 206 | if std::fs::exists(&config_path).with_context(|| format!("can't check existence of path: {}", config_path.display()))? { 207 | return Ok(pathbuf); 208 | } 209 | 210 | if !pathbuf.pop() { 211 | break; 212 | } 213 | } 214 | 215 | bail!("unable to find a hubris-env.toml in {} or any parent directory", orig_path.display()) 216 | } 217 | 218 | fn load_config(root: impl AsRef) -> anyhow::Result { 219 | let root = root.as_ref(); 220 | let config_path = root.join("hubris-env.toml"); 221 | let config_text = std::fs::read_to_string(&config_path) 222 | .with_context(|| format!("can't read config file at path: {}", config_path.display()))?; 223 | 224 | toml::from_str(&config_text) 225 | .with_context(|| format!("can't parse config file at path: {}", config_path.display())) 226 | } 227 | 228 | #[derive(Deserialize, Debug)] 229 | struct Config { 230 | system: SystemConfig, 231 | } 232 | 233 | #[derive(Deserialize, Debug)] 234 | #[serde(tag = "source")] 235 | #[serde(rename_all = "kebab-case")] 236 | enum SystemConfig { 237 | Git { 238 | repo: String, 239 | rev: String, 240 | }, 241 | Here, 242 | } 243 | --------------------------------------------------------------------------------