├── .cargo └── config.toml ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── publish_docs.yml │ └── security_audit.yml ├── .gitignore ├── .vscode └── settings.json ├── CITATION.cff ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── hermit-builtins ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── exports └── src │ ├── lib.rs │ └── math.rs ├── hermit-macro ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT └── src │ ├── lib.rs │ └── system.rs ├── img └── demo.gif ├── rust-toolchain.toml ├── rustfmt.toml ├── src ├── arch │ ├── aarch64 │ │ ├── kernel │ │ │ ├── core_local.rs │ │ │ ├── interrupts.rs │ │ │ ├── mmio.rs │ │ │ ├── mod.rs │ │ │ ├── pci.rs │ │ │ ├── processor.rs │ │ │ ├── scheduler.rs │ │ │ ├── serial.rs │ │ │ ├── start.rs │ │ │ ├── start.s │ │ │ ├── switch.rs │ │ │ └── systemtime.rs │ │ ├── mm │ │ │ ├── mod.rs │ │ │ └── paging.rs │ │ └── mod.rs │ ├── mod.rs │ ├── riscv64 │ │ ├── kernel │ │ │ ├── core_local.rs │ │ │ ├── devicetree.rs │ │ │ ├── interrupts.rs │ │ │ ├── mmio.rs │ │ │ ├── mod.rs │ │ │ ├── pci.rs │ │ │ ├── processor.rs │ │ │ ├── scheduler.rs │ │ │ ├── start.rs │ │ │ ├── switch.rs │ │ │ ├── switch.s │ │ │ └── systemtime.rs │ │ ├── mm │ │ │ ├── mod.rs │ │ │ └── paging.rs │ │ └── mod.rs │ └── x86_64 │ │ ├── kernel │ │ ├── acpi.rs │ │ ├── apic.rs │ │ ├── boot.s │ │ ├── core_local.rs │ │ ├── gdt.rs │ │ ├── interrupts.rs │ │ ├── mmio.rs │ │ ├── mod.rs │ │ ├── pci.rs │ │ ├── pic.rs │ │ ├── pit.rs │ │ ├── processor.rs │ │ ├── scheduler.rs │ │ ├── serial.rs │ │ ├── start.rs │ │ ├── switch.rs │ │ ├── syscall.rs │ │ ├── systemtime.rs │ │ └── vga.rs │ │ ├── mm │ │ ├── mod.rs │ │ └── paging.rs │ │ └── mod.rs ├── config.rs ├── console.rs ├── drivers │ ├── fs │ │ ├── mod.rs │ │ ├── virtio_fs.rs │ │ └── virtio_pci.rs │ ├── mmio.rs │ ├── mod.rs │ ├── net │ │ ├── gem.rs │ │ ├── mod.rs │ │ ├── rtl8139.rs │ │ └── virtio │ │ │ ├── mmio.rs │ │ │ ├── mod.rs │ │ │ └── pci.rs │ ├── pci.rs │ ├── virtio │ │ ├── mod.rs │ │ ├── transport │ │ │ ├── mmio.rs │ │ │ ├── mod.rs │ │ │ └── pci.rs │ │ └── virtqueue │ │ │ ├── mod.rs │ │ │ ├── packed.rs │ │ │ └── split.rs │ └── vsock │ │ ├── mod.rs │ │ └── pci.rs ├── entropy.rs ├── env.rs ├── errno.rs ├── executor │ ├── device.rs │ ├── mod.rs │ ├── network.rs │ ├── task.rs │ └── vsock.rs ├── fd │ ├── eventfd.rs │ ├── mod.rs │ ├── socket │ │ ├── mod.rs │ │ ├── tcp.rs │ │ ├── udp.rs │ │ └── vsock.rs │ └── stdio.rs ├── fs │ ├── fuse.rs │ ├── mem.rs │ ├── mod.rs │ └── uhyve.rs ├── init_cell.rs ├── io.rs ├── lib.rs ├── logging.rs ├── macros.rs ├── mm │ ├── allocator.rs │ ├── device_alloc.rs │ ├── mod.rs │ ├── physicalmem.rs │ └── virtualmem.rs ├── scheduler │ ├── mod.rs │ └── task.rs ├── shell.rs ├── synch │ ├── futex.rs │ ├── mod.rs │ ├── recmutex.rs │ └── semaphore.rs ├── syscalls │ ├── condvar.rs │ ├── entropy.rs │ ├── futex.rs │ ├── interfaces │ │ ├── generic.rs │ │ ├── mod.rs │ │ └── uhyve.rs │ ├── mmap.rs │ ├── mod.rs │ ├── processor.rs │ ├── recmutex.rs │ ├── semaphore.rs │ ├── socket.rs │ ├── spinlock.rs │ ├── system.rs │ ├── table.rs │ ├── tasks.rs │ └── timer.rs └── time.rs ├── tests ├── basic_math.rs ├── basic_mem.rs ├── basic_print.rs ├── common │ └── mod.rs ├── hermit_test_runner.py ├── measure_startup_time.rs └── thread.rs ├── typos.toml └── xtask ├── .gitignore ├── Cargo.toml └── src ├── arch.rs ├── archive.rs ├── artifact.rs ├── binutil.rs ├── build.rs ├── cargo_build.rs ├── ci ├── c.rs ├── firecracker.rs ├── firecracker_vm_config.json ├── mod.rs ├── qemu.rs ├── rftrace.snap ├── rs.rs └── uhyve.rs ├── clippy.rs ├── doc.rs └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | 4 | [target.x86_64-unknown-none] 5 | runner = "tests/hermit_test_runner.py" 6 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # First run of rustfmt 2 | 0e43f336cba7c2282e17e9aa66399ee711ff0863 3 | a307e93e239ae240fab098683941f1ffa1ae4ca6 4 | c4231e9642966cad12f64e87a797d8f75524a8e9 5 | 07355bba67db6d844e4ffff7f2225cfd28bba916 6 | b54fa4c9edeab71733d13026cda86bad7b1a3978 7 | 78ec47e23bf034f59c4e21713ae5a0737d95f2f7 8 | 24cc503aa726a70a58c93e5efdb48d270d3e1cfb 9 | cce90750ea19bda122b1887c38e401749557d22f 10 | 4a690969bc1b0e47d2015be53216a9fac96daa79 11 | ae54e54c3da8f17993502488b7a7d1b83cfeea1c 12 | 49c9dcd6d8c6513981982f1989e836e5d73fd5c7 13 | 413ae405820020dbcdfd9c4a326ae70507ac51c0 14 | 912968be44bddd563461da615e9a9af1bf94ba36 15 | 37714c0a28c5484965abf90c8bf7541b38982775 16 | b78e0e6358e2487230db8cab6c520fe308010ee4 17 | 7b160dabc924fbcd69b42917f7704c8fcf260578 18 | 317f2ac2d32a3e8fbf9bd6e08552f55e932d6e5c 19 | 626e1f1c8c0ede44cde1383fddf551bdc0680e75 20 | 68cc1796fc7b7b6984ca2e6fb261b2040861cb7a 21 | c497e40688634167671abf0ac5bd7318d7b221d3 22 | 95df25368b7157e62a8e476dd82f3626035b4a57 23 | 7845c702efb87bdeb468f6307222243bedb88e2f 24 | dfe33bbd996e6076ca5da64dedbb5c587f0028db 25 | 0cae001c90c6934974151f68fcbe401c2593ae07 26 | 2e87d545d2a2878bd5326612898ab769391d1532 27 | a903030b82a1569968ed98d578072491c4c3bc23 28 | b3ecb39a95034e168387ef4260959f44a95e518a 29 | 5a441484ac198dcd1f07552c8321adecf8c35a5c 30 | b797de2855ed7930ddb2a88fc12ce8d2643ea927 31 | 1017262730b79be4617b430cc421b318f1a2e6b5 32 | c630f65060e467a8a67588a1303b2eab7eafb6ab 33 | # The biggest rustfmt run 34 | 999613172db2aee44b2926b1c248b120df042fde 35 | fa7290cc0d1762a9463260f0cae601f1d62c2a1b 36 | c1146d043722272c96097848613264081c815165 37 | 562ea18b893b97f11cd258ed69c08f6d48fdf693 38 | a84035a74f9d354b47a7eb940307eae357c87d58 39 | cdb3577d2d4cdec996b3bad9166a2c951aa67f95 40 | # Format Cargo.toml 41 | 911be508d8693cf3f9df211dd4266b712dae43ee 42 | 49c723d72749f52c4e9b094598380096a0ace43d 43 | efc3f0a39209d1cc0fb0907842559cd0ba7f2626 44 | 3c8367339e89bb1e011c73995e01f11acdc5fab4 45 | 4029cbff5a2bda4eabb3e0e0e2a9f611b985e0a4 46 | 0ad39c85290fcdb1790f77240fb7d4693a61bb12 47 | 9e570cb1542585865d3822ced27dacf84f94e973 48 | 5b7c7255612cbfa0236dee1e5ed04630af42ed0b 49 | 4cc2e4efa3e581d0e97376ff35d4352bc0db8dee 50 | 2a702e37cbd5ec6ff1454d053974c83e1c71cc20 51 | 4255f65dbb1d9bc93442a355f34aa64b3ff25e85 52 | 16a9b8a859c4b29c6e8e7b62527e684fd236cd2c 53 | 0660b9b48aafa7c10b007c9ed373166d4b372729 54 | 6ffef254f5004176840a4ba5fa8a4ab78c9cb0b4 55 | 1980a19b283e405c12f172dda0d61f6e7392cbb3 56 | 12261cd05d69974fa470974b84fc46c73e3e4e07 57 | # Upgrade to Rust 2024 style edition 58 | 52a7dc889c0c5b44598117b96e153d00b64a2ff3 59 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | . text !filter !merge !diff 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "cargo" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | timezone: "Europe/Berlin" 10 | 11 | - package-ecosystem: "cargo" 12 | directory: "/hermit-builtins" 13 | schedule: 14 | interval: "weekly" 15 | timezone: "Europe/Berlin" 16 | 17 | - package-ecosystem: "github-actions" 18 | directory: "/" 19 | schedule: 20 | interval: "weekly" 21 | timezone: "Europe/Berlin" 22 | -------------------------------------------------------------------------------- /.github/workflows/publish_docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | publish_docs: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | - uses: mkroening/rust-toolchain-toml@main 18 | - run: rustup target add x86_64-unknown-none 19 | - name: Generate documentation 20 | run: cargo doc --target x86_64-unknown-none --package hermit-kernel 21 | - name: Generate index.html 22 | run: | 23 | cat > target/x86_64-unknown-none/doc/index.html < 25 | 26 | 27 | Redirect! 28 | 29 | 30 | 31 |

Redirect

32 | 33 | 34 | EOL 35 | - name: Deploy documentation 36 | if: success() 37 | uses: crazy-max/ghaction-github-pages@v4 38 | with: 39 | target_branch: gh-pages 40 | build_dir: target/x86_64-unknown-none/doc 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.github/workflows/security_audit.yml: -------------------------------------------------------------------------------- 1 | name: Security audit 2 | on: 3 | schedule: 4 | - cron: '0 0 * * *' 5 | jobs: 6 | security_audit: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: rustsec/audit-check@v2 11 | with: 12 | token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.buildScripts.overrideCommand": [ 3 | "cargo", 4 | "check", 5 | "--quiet", 6 | "--workspace", 7 | "--message-format=json", 8 | "--all-targets", 9 | ], 10 | // "rust-analyzer.cargo.target": "aarch64-unknown-none-softfloat", 11 | // "rust-analyzer.cargo.target": "riscv64gc-unknown-none-elf", 12 | "rust-analyzer.cargo.target": "x86_64-unknown-none", 13 | "rust-analyzer.check.overrideCommand": [ 14 | "cargo", 15 | "clippy", 16 | "-Zbuild-std=core,alloc", 17 | "--quiet", 18 | "--message-format=json", 19 | // "--target=aarch64-unknown-none-softfloat", 20 | // "--target=riscv64gc-unknown-none-elf", 21 | "--target=x86_64-unknown-none", 22 | ], 23 | "rust-analyzer.check.targets": [ 24 | "aarch64-unknown-none-softfloat", 25 | "riscv64gc-unknown-none-elf", 26 | "x86_64-unknown-none", 27 | ], 28 | } -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: The Hermit Kernel 3 | message: >- 4 | If you use this software, please cite both the article 5 | from preferred-citation and the software itself. 6 | type: software 7 | authors: 8 | - family-names: Lankes 9 | given-names: Stefan 10 | orcid: 'https://orcid.org/0000-0003-4718-2238' 11 | - family-names: Kröning 12 | given-names: Martin 13 | orcid: 'https://orcid.org/0009-0005-0622-4229' 14 | - family-names: Klimt 15 | given-names: Jonathan 16 | orcid: 'https://orcid.org/0000-0002-5980-2214' 17 | - family-names: Breitbart 18 | given-names: Jens 19 | - family-names: Finck 20 | given-names: Colin 21 | - family-names: Krebs 22 | given-names: Daniel 23 | - family-names: Schöning 24 | given-names: Simon 25 | - family-names: Schwender 26 | given-names: Jonathan 27 | - family-names: Şahin 28 | given-names: Çağatay Yiğit 29 | - family-names: Thomas 30 | given-names: Lambertz 31 | - name: The Hermit Project Developers 32 | identifiers: 33 | - type: doi 34 | value: 10.5281/zenodo.14645534 35 | description: The concept DOI of the software. 36 | - type: doi 37 | value: 10.5281/zenodo.14645535 38 | description: The versioned DOI for version 0.8.0 of the software. 39 | - type: url 40 | value: >- 41 | https://github.com/hermit-os/kernel/releases/tag/v0.8.0 42 | description: The GitHub release URL of tag v0.8.0. 43 | - type: url 44 | value: >- 45 | https://github.com/hermit-os/kernel/tree/742526984ee0094b0626ad6afddb17b11bc9caff 46 | description: >- 47 | The GitHub release URL of the commit tagged with 48 | v0.8.0. 49 | - type: url 50 | value: >- 51 | https://github.com/hermit-os/kernel/releases/tag/v0.10.0 52 | description: The GitHub release URL of tag v0.10.0. 53 | - type: url 54 | value: >- 55 | https://github.com/hermit-os/kernel/tree/791b9328b3a6b93944692c6dfa3e2f42c59672cb 56 | description: >- 57 | The GitHub release URL of the commit tagged with 58 | v0.10.0. 59 | repository-code: 'https://github.com/hermit-os/kernel' 60 | url: 'https://hermit-os.org/' 61 | abstract: 'A Rust-based, lightweight unikernel.' 62 | keywords: 63 | - unikernel 64 | - rust 65 | - virtualization 66 | - operating system 67 | - high-performance computing 68 | - cloud computing 69 | license: 70 | - MIT 71 | - Apache-2.0 72 | commit: 791b9328b3a6b93944692c6dfa3e2f42c59672cb 73 | version: 0.10.0 74 | date-released: '2025-01-18' 75 | preferred-citation: 76 | type: article 77 | title: Exploring Rust for Unikernel Development 78 | journal: >- 79 | Proceedings of the 10th Workshop on Programming Languages and Operating 80 | Systems 81 | doi: 10.1145/3365137.3365395 82 | year: 2019 83 | month: 10 84 | authors: 85 | - family-names: Lankes 86 | given-names: Stefan 87 | orcid: 'https://orcid.org/0000-0003-4718-2238' 88 | - family-names: Breitbart 89 | given-names: Jens 90 | - family-names: Pickartz 91 | given-names: Simon 92 | orcid: 'https://orcid.org/0000-0002-6316-6396' 93 | pages: 8 94 | start: 8 95 | end: 15 96 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Hermit Kernel 4 | 5 | [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://hermit-os.github.io/kernel/hermit/) 6 | ![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue) 7 | [![Zulip Badge](https://img.shields.io/badge/chat-hermit-57A37C?logo=zulip)](https://hermit.zulipchat.com/) 8 | [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14645534.svg)](https://doi.org/10.5281/zenodo.14645534) 9 | 10 | This is the kernel of the [Hermit](https://github.com/hermit-os) unikernel project. 11 | 12 | ## Requirements 13 | 14 | * [`rustup`](https://www.rust-lang.org/tools/install) 15 | 16 | ## Building the kernel 17 | 18 | Usually the kernel will be linked as static library to your applications. 19 | 20 | - **Rust applications:** Instructions can be found in the [hermit-rs](https://github.com/hermit-os/hermit-rs) repository. 21 | - **For C/C++ applications:** Instructions can be found in the [hermit-playground](https://github.com/hermit-os/hermit-playground) repository. 22 | 23 | 24 | ### Standalone static library build 25 | 26 | ```sh 27 | cargo xtask build --arch x86_64 28 | ``` 29 | 30 | On completion, the script will print the path of `libhermit.a`. 31 | If you want to build the kernel for aarch64, please replace `x86_64` by `aarch64`. 32 | If you want to build the kernel for riscv64, please use `riscv64`. 33 | 34 | ### Control the kernel messages verbosity 35 | 36 | This kernel uses the lightweight logging crate [log](https://github.com/rust-lang/log) to print kernel messages. 37 | The environment variable `HERMIT_LOG_LEVEL_FILTER` controls the verbosity. 38 | You can change it by setting it at compile time to a string matching the name of a [LevelFilter](https://docs.rs/log/0.4.8/log/enum.LevelFilter.html). 39 | If the variable is not set, or the name doesn't match, then `LevelFilter::Info` is used by default. 40 | 41 | ```sh 42 | $ HERMIT_LOG_LEVEL_FILTER=Debug cargo xtask build --arch x86_64 43 | ``` 44 | 45 | ## Credits 46 | 47 | This kernel is derived from following tutorials and software distributions: 48 | 49 | 1. Philipp Oppermann's [excellent series of blog posts][opp]. 50 | 2. Erik Kidd's [toyos-rs][kidd], which is an extension of Philipp Opermann's kernel. 51 | 3. The Rust-based teaching operating system [eduOS-rs][eduos]. 52 | 53 | [opp]: http://blog.phil-opp.com/ 54 | [kidd]: http://www.randomhacks.net/bare-metal-rust/ 55 | [eduos]: http://rwth-os.github.io/eduOS-rs/ 56 | 57 | ## License 58 | 59 | Licensed under either of 60 | 61 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 62 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 63 | 64 | at your option. 65 | 66 | ## Contribution 67 | 68 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 69 | 70 | The kernel is being developed on [hermit-os/kernel](https://github.com/hermit-os/kernel). 71 | Create your own fork, send us a pull request, and chat with us on [Zulip](https://hermit.zulipchat.com/). 72 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use std::path::{Path, PathBuf}; 4 | use std::process::Command; 5 | use std::{env, fs}; 6 | 7 | use anyhow::{Context, Result, anyhow}; 8 | use llvm_tools::LlvmTools; 9 | 10 | fn main() -> Result<()> { 11 | if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "x86_64" 12 | && env::var_os("CARGO_FEATURE_SMP").is_some() 13 | { 14 | assemble_x86_64_smp_boot()?; 15 | } 16 | 17 | Ok(()) 18 | } 19 | 20 | fn assemble_x86_64_smp_boot() -> Result<()> { 21 | let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); 22 | 23 | let boot_s = Path::new("src/arch/x86_64/kernel/boot.s"); 24 | let boot_ll = out_dir.join("boot.ll"); 25 | let boot_bc = out_dir.join("boot.bc"); 26 | let boot_bin = out_dir.join("boot.bin"); 27 | 28 | let llvm_as = binutil("llvm-as")?; 29 | let rust_lld = binutil("rust-lld")?; 30 | 31 | let assembly = fs::read_to_string(boot_s)?; 32 | 33 | let mut llvm_file = File::create(&boot_ll)?; 34 | writeln!( 35 | &mut llvm_file, 36 | r#" 37 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 38 | target triple = "x86_64-unknown-none-elf" 39 | 40 | module asm " 41 | {assembly} 42 | " 43 | "# 44 | )?; 45 | llvm_file.flush()?; 46 | drop(llvm_file); 47 | 48 | let status = Command::new(&llvm_as) 49 | .arg("-o") 50 | .arg(&boot_bc) 51 | .arg(boot_ll) 52 | .status() 53 | .with_context(|| format!("Failed to run llvm-as from {}", llvm_as.display()))?; 54 | assert!(status.success()); 55 | 56 | let status = Command::new(&rust_lld) 57 | .arg("-flavor") 58 | .arg("gnu") 59 | .arg("--section-start=.text=0x8000") 60 | .arg("--oformat=binary") 61 | .arg("-o") 62 | .arg(&boot_bin) 63 | .arg(&boot_bc) 64 | .status() 65 | .with_context(|| format!("Failed to run rust-lld from {}", rust_lld.display()))?; 66 | assert!(status.success()); 67 | 68 | println!("cargo:rerun-if-changed={}", boot_s.display()); 69 | Ok(()) 70 | } 71 | 72 | fn binutil(name: &str) -> Result { 73 | let exe = format!("{name}{}", env::consts::EXE_SUFFIX); 74 | 75 | let path = LlvmTools::new() 76 | .map_err(|err| match err { 77 | llvm_tools::Error::NotFound => anyhow!( 78 | "Could not find llvm-tools component\n\ 79 | \n\ 80 | Maybe the rustup component `llvm-tools` is missing? Install it through: `rustup component add llvm-tools`" 81 | ), 82 | err => anyhow!("{err:?}"), 83 | })? 84 | .tool(&exe) 85 | .ok_or_else(|| anyhow!("could not find {exe}"))?; 86 | 87 | Ok(path) 88 | } 89 | -------------------------------------------------------------------------------- /hermit-builtins/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /hermit-builtins/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "hermit-builtins" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "libm", 10 | ] 11 | 12 | [[package]] 13 | name = "libm" 14 | version = "0.2.15" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 17 | -------------------------------------------------------------------------------- /hermit-builtins/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hermit-builtins" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | libm = "0.2" 8 | 9 | [lib] 10 | crate-type = ["staticlib"] 11 | 12 | [workspace] 13 | -------------------------------------------------------------------------------- /hermit-builtins/exports: -------------------------------------------------------------------------------- 1 | acos 2 | acosf 3 | acosh 4 | acoshf 5 | asin 6 | asinf 7 | asinh 8 | asinhf 9 | atan 10 | atan2 11 | atan2f 12 | atanf 13 | atanh 14 | atanhf 15 | cbrt 16 | cbrtf 17 | ceil 18 | ceilf 19 | copysign 20 | copysignf 21 | cos 22 | cosf 23 | cosh 24 | coshf 25 | erf 26 | erfc 27 | erfcf 28 | erff 29 | exp 30 | exp10 31 | exp10f 32 | exp2 33 | exp2f 34 | expf 35 | expm1 36 | expm1f 37 | fabs 38 | fabsf 39 | fdim 40 | fdimf 41 | floor 42 | floorf 43 | fma 44 | fmaf 45 | fmax 46 | fmaxf 47 | fmaximum 48 | fmaximum_num 49 | fmaximumf 50 | fmaximum_numf 51 | fmin 52 | fminf 53 | fminimum 54 | fminimum_num 55 | fminimumf 56 | fminimum_numf 57 | fmod 58 | fmodf 59 | frexp 60 | frexpf 61 | hypot 62 | hypotf 63 | ilogb 64 | ilogbf 65 | j0 66 | j0f 67 | j1 68 | j1f 69 | jn 70 | jnf 71 | ldexp 72 | ldexpf 73 | lgamma 74 | lgammaf 75 | lgammaf_r 76 | lgamma_r 77 | log 78 | log10 79 | log10f 80 | log1p 81 | log1pf 82 | log2 83 | log2f 84 | logf 85 | memcmp 86 | memcpy 87 | memmove 88 | memset 89 | modf 90 | modff 91 | nan 92 | nanf 93 | nextafter 94 | nextafterf 95 | pow 96 | powf 97 | remainder 98 | remainderf 99 | remquo 100 | remquof 101 | rint 102 | rintf 103 | round 104 | roundeven 105 | roundevenf 106 | roundf 107 | scalbn 108 | scalbnf 109 | sin 110 | sincos 111 | sincosf 112 | sinf 113 | sinh 114 | sinhf 115 | sqrt 116 | sqrtf 117 | strlen 118 | tan 119 | tanf 120 | tanh 121 | tanhf 122 | tgamma 123 | tgammaf 124 | trunc 125 | truncf 126 | y0 127 | y0f 128 | y1 129 | y1f 130 | yn 131 | ynf 132 | -------------------------------------------------------------------------------- /hermit-builtins/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | 3 | pub mod math; 4 | 5 | #[panic_handler] 6 | fn panic(_info: &core::panic::PanicInfo) -> ! { 7 | loop {} 8 | } 9 | -------------------------------------------------------------------------------- /hermit-macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hermit-macro" 3 | version = "0.1.0" 4 | authors = ["Martin Kröning "] 5 | edition = "2024" 6 | description = "Proc macro implementation to defined system calls" 7 | repository = "https://github.com/hermit-os/kernel" 8 | license = "MIT OR Apache-2.0" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | proc-macro2 = "1" 15 | quote = "1" 16 | syn = { version = "2", features = ["full"] } 17 | -------------------------------------------------------------------------------- /hermit-macro/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /hermit-macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::ToTokens; 3 | use syn::parse::Nothing; 4 | use syn::parse_macro_input; 5 | 6 | macro_rules! bail { 7 | ($span:expr, $($tt:tt)*) => { 8 | return Err(syn::Error::new_spanned($span, format!($($tt)*))) 9 | }; 10 | } 11 | 12 | mod system; 13 | 14 | // The structure of this implementation is inspired by Amanieu's excellent naked-function crate. 15 | #[proc_macro_attribute] 16 | pub fn system(attr: TokenStream, item: TokenStream) -> TokenStream { 17 | parse_macro_input!(attr as Nothing); 18 | match system::system_attribute(parse_macro_input!(item)) { 19 | Ok(item) => item.into_token_stream().into(), 20 | Err(e) => e.to_compile_error().into(), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:36029b7d5a678a63b01fcbdc73a2bc732585c7bfe9e040a5337c395b7b65da24 3 | size 12062419 4 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-05-16" 3 | components = [ 4 | "llvm-tools", 5 | "rust-src", 6 | ] 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | group_imports = "StdExternalCrate" 2 | hard_tabs = true 3 | hex_literal_case = "Lower" 4 | imports_granularity = "Module" 5 | -------------------------------------------------------------------------------- /src/arch/aarch64/kernel/core_local.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::vec::Vec; 3 | use core::arch::asm; 4 | use core::cell::{Cell, RefCell, RefMut}; 5 | use core::ptr; 6 | use core::sync::atomic::Ordering; 7 | 8 | #[cfg(feature = "smp")] 9 | use hermit_sync::InterruptTicketMutex; 10 | 11 | use super::CPU_ONLINE; 12 | use super::interrupts::{IRQ_COUNTERS, IrqStatistics}; 13 | use crate::executor::task::AsyncTask; 14 | #[cfg(feature = "smp")] 15 | use crate::scheduler::SchedulerInput; 16 | use crate::scheduler::{CoreId, PerCoreScheduler}; 17 | 18 | pub(crate) struct CoreLocal { 19 | this: *const Self, 20 | /// ID of the current Core. 21 | core_id: CoreId, 22 | /// Scheduler of the current Core. 23 | scheduler: Cell<*mut PerCoreScheduler>, 24 | /// Interface to the interrupt counters 25 | irq_statistics: &'static IrqStatistics, 26 | /// Queue of async tasks 27 | async_tasks: RefCell>, 28 | /// Queues to handle incoming requests from the other cores 29 | #[cfg(feature = "smp")] 30 | pub scheduler_input: InterruptTicketMutex, 31 | } 32 | 33 | impl CoreLocal { 34 | pub fn install() { 35 | let core_id = CPU_ONLINE.0.load(Ordering::Relaxed); 36 | 37 | let irq_statistics = if core_id == 0 { 38 | static FIRST_IRQ_STATISTICS: IrqStatistics = IrqStatistics::new(); 39 | &FIRST_IRQ_STATISTICS 40 | } else { 41 | &*Box::leak(Box::new(IrqStatistics::new())) 42 | }; 43 | 44 | let this = Self { 45 | this: ptr::null_mut(), 46 | core_id, 47 | scheduler: Cell::new(ptr::null_mut()), 48 | irq_statistics, 49 | async_tasks: RefCell::new(Vec::new()), 50 | #[cfg(feature = "smp")] 51 | scheduler_input: InterruptTicketMutex::new(SchedulerInput::new()), 52 | }; 53 | let this = if core_id == 0 { 54 | take_static::take_static! { 55 | static FIRST_CORE_LOCAL: Option = None; 56 | } 57 | FIRST_CORE_LOCAL.take().unwrap().insert(this) 58 | } else { 59 | this.add_irq_counter(); 60 | Box::leak(Box::new(this)) 61 | }; 62 | this.this = ptr::from_ref(this); 63 | 64 | unsafe { 65 | asm!("msr tpidr_el1, {}", in(reg) this, options(nostack, preserves_flags)); 66 | } 67 | } 68 | 69 | #[inline] 70 | pub fn get() -> &'static Self { 71 | unsafe { 72 | let raw: *const Self; 73 | asm!("mrs {}, tpidr_el1", out(reg) raw, options(nomem, nostack, preserves_flags)); 74 | &*raw 75 | } 76 | } 77 | 78 | pub fn add_irq_counter(&self) { 79 | IRQ_COUNTERS 80 | .lock() 81 | .insert(self.core_id, self.irq_statistics); 82 | } 83 | } 84 | 85 | #[inline] 86 | pub(crate) fn core_id() -> CoreId { 87 | if cfg!(target_os = "none") { 88 | CoreLocal::get().core_id 89 | } else { 90 | 0 91 | } 92 | } 93 | 94 | #[inline] 95 | pub(crate) fn core_scheduler() -> &'static mut PerCoreScheduler { 96 | unsafe { CoreLocal::get().scheduler.get().as_mut().unwrap() } 97 | } 98 | 99 | pub(crate) fn async_tasks() -> RefMut<'static, Vec> { 100 | CoreLocal::get().async_tasks.borrow_mut() 101 | } 102 | 103 | pub(crate) fn set_core_scheduler(scheduler: *mut PerCoreScheduler) { 104 | CoreLocal::get().scheduler.set(scheduler); 105 | } 106 | 107 | pub(crate) fn increment_irq_counter(irq_no: u8) { 108 | CoreLocal::get().irq_statistics.inc(irq_no); 109 | } 110 | -------------------------------------------------------------------------------- /src/arch/aarch64/kernel/mmio.rs: -------------------------------------------------------------------------------- 1 | use hermit_sync::InterruptTicketMutex; 2 | 3 | use crate::drivers::net::virtio::VirtioNetDriver; 4 | 5 | pub(crate) fn get_network_driver() -> Option<&'static InterruptTicketMutex> { 6 | None 7 | } 8 | -------------------------------------------------------------------------------- /src/arch/aarch64/kernel/serial.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | use crate::syscalls::interfaces::serial_buf_hypercall; 4 | 5 | enum SerialInner { 6 | Uart(u32), 7 | Uhyve, 8 | } 9 | 10 | pub struct SerialPort { 11 | inner: SerialInner, 12 | } 13 | 14 | impl SerialPort { 15 | pub fn new(port_address: u32) -> Self { 16 | if crate::env::is_uhyve() { 17 | Self { 18 | inner: SerialInner::Uhyve, 19 | } 20 | } else { 21 | Self { 22 | inner: SerialInner::Uart(port_address), 23 | } 24 | } 25 | } 26 | 27 | pub fn write_buf(&mut self, buf: &[u8]) { 28 | match &mut self.inner { 29 | SerialInner::Uhyve => { 30 | serial_buf_hypercall(buf); 31 | } 32 | SerialInner::Uart(port_address) => { 33 | let port = core::ptr::with_exposed_provenance_mut::(*port_address as usize); 34 | for &byte in buf { 35 | // LF newline characters need to be extended to CRLF over a real serial port. 36 | if byte == b'\n' { 37 | unsafe { 38 | asm!( 39 | "strb w8, [{port}]", 40 | port = in(reg) port, 41 | in("x8") b'\r', 42 | options(nostack), 43 | ); 44 | } 45 | } 46 | 47 | unsafe { 48 | asm!( 49 | "strb w8, [{port}]", 50 | port = in(reg) port, 51 | in("x8") byte, 52 | options(nostack), 53 | ); 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | pub fn init(&self, _baudrate: u32) { 61 | // We don't do anything here (yet). 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/arch/aarch64/kernel/switch.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::{mem, ptr}; 3 | 4 | macro_rules! kernel_function_impl { 5 | ($kernel_function:ident($($arg:ident: $A:ident),*) { $($operands:tt)* }) => { 6 | /// Executes `f` on the kernel stack. 7 | #[allow(dead_code)] 8 | pub unsafe fn $kernel_function(f: unsafe extern "C" fn($($A),*) -> R, $($arg: $A),*) -> R { 9 | unsafe { 10 | assert!(mem::size_of::() <= mem::size_of::()); 11 | 12 | $( 13 | assert!(mem::size_of::<$A>() <= mem::size_of::()); 14 | let $arg = { 15 | let mut reg = 0usize; 16 | // SAFETY: $A is smaller than usize and directly fits in a register 17 | // Since f takes $A as argument via C calling convention, any upper bytes do not matter. 18 | ptr::write(ptr::from_mut(&mut reg).cast(), $arg); 19 | reg 20 | }; 21 | )* 22 | 23 | let ret: u64; 24 | asm!( 25 | // Switch to kernel stack 26 | "msr spsel, {l1}", 27 | 28 | // To make sure, Rust manages the stack in `f` correctly, 29 | // we keep all arguments and return values in registers 30 | // until we switch the stack back. Thus follows the sizing 31 | // requirements for arguments and return types. 32 | "blr {f}", 33 | 34 | // Switch back to user stack 35 | "msr spsel, {l0}", 36 | 37 | l0 = const 0, 38 | l1 = const 1, 39 | f = in(reg) f, 40 | 41 | $($operands)* 42 | 43 | // Return argument in x0 44 | lateout("x0") ret, 45 | 46 | clobber_abi("C"), 47 | ); 48 | 49 | // SAFETY: R is smaller than usize and directly fits in rax 50 | // Since f returns R, we can safely convert ret to R 51 | mem::transmute_copy(&ret) 52 | } 53 | } 54 | }; 55 | } 56 | 57 | kernel_function_impl!(kernel_function0() {}); 58 | 59 | kernel_function_impl!(kernel_function1(arg1: A1) { 60 | in("x0") arg1, 61 | }); 62 | 63 | kernel_function_impl!(kernel_function2(arg1: A1, arg2: A2) { 64 | in("x0") arg1, 65 | in("x1") arg2, 66 | }); 67 | 68 | kernel_function_impl!(kernel_function3(arg1: A1, arg2: A2, arg3: A3) { 69 | in("x0") arg1, 70 | in("x1") arg2, 71 | in("x2") arg3, 72 | }); 73 | 74 | kernel_function_impl!(kernel_function4(arg1: A1, arg2: A2, arg3: A3, arg4: A4) { 75 | in("x0") arg1, 76 | in("x1") arg2, 77 | in("x2") arg3, 78 | in("x3") arg4, 79 | }); 80 | 81 | kernel_function_impl!(kernel_function5(arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5) { 82 | in("x0") arg1, 83 | in("x1") arg2, 84 | in("x2") arg3, 85 | in("x3") arg4, 86 | in("x4") arg5, 87 | }); 88 | 89 | kernel_function_impl!(kernel_function6(arg1: A1, arg2: A2, arg3: A3, arg4: A4, arg5: A5, arg6: A6) { 90 | in("x0") arg1, 91 | in("x1") arg2, 92 | in("x2") arg3, 93 | in("x3") arg4, 94 | in("x4") arg5, 95 | in("x5") arg6, 96 | }); 97 | -------------------------------------------------------------------------------- /src/arch/aarch64/kernel/systemtime.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use alloc::vec::Vec; 4 | use core::arch::asm; 5 | use core::str; 6 | 7 | use hermit_dtb::Dtb; 8 | use hermit_entry::boot_info::PlatformInfo; 9 | use hermit_sync::OnceCell; 10 | use memory_addresses::arch::aarch64::{PhysAddr, VirtAddr}; 11 | use time::OffsetDateTime; 12 | 13 | use crate::arch::aarch64::mm::paging::{self, BasePageSize, PageSize, PageTableEntryFlags}; 14 | use crate::env; 15 | use crate::mm::virtualmem; 16 | 17 | static PL031_ADDRESS: OnceCell = OnceCell::new(); 18 | static BOOT_TIME: OnceCell = OnceCell::new(); 19 | 20 | const RTC_DR: usize = 0x00; 21 | const RTC_MR: usize = 0x04; 22 | const RTC_LR: usize = 0x08; 23 | const RTC_CR: usize = 0x0c; 24 | /// Interrupt mask and set register 25 | const RTC_IRQ_MASK: usize = 0x10; 26 | /// Raw interrupt status 27 | const RTC_RAW_IRQ_STATUS: usize = 0x14; 28 | /// Masked interrupt status 29 | const RTC_MASK_IRQ_STATUS: usize = 0x18; 30 | /// Interrupt clear register 31 | const RTC_IRQ_CLEAR: usize = 0x1c; 32 | 33 | #[inline] 34 | fn rtc_read(off: usize) -> u32 { 35 | let value: u32; 36 | 37 | // we have to use inline assembly to guarantee 32bit memory access 38 | unsafe { 39 | asm!("ldar {value:w}, [{addr}]", 40 | value = out(reg) value, 41 | addr = in(reg) (PL031_ADDRESS.get().unwrap().as_usize() + off), 42 | options(nostack, readonly), 43 | ); 44 | } 45 | 46 | value 47 | } 48 | 49 | pub fn init() { 50 | match env::boot_info().platform_info { 51 | PlatformInfo::Uhyve { boot_time, .. } => { 52 | PL031_ADDRESS.set(VirtAddr::zero()).unwrap(); 53 | BOOT_TIME.set(u64::try_from(boot_time.unix_timestamp_nanos() / 1000).unwrap()); 54 | info!("Hermit booted on {boot_time}"); 55 | 56 | return; 57 | } 58 | _ => { 59 | let dtb = unsafe { 60 | Dtb::from_raw(core::ptr::with_exposed_provenance( 61 | env::boot_info().hardware_info.device_tree.unwrap().get() as usize, 62 | )) 63 | .expect(".dtb file has invalid header") 64 | }; 65 | 66 | for node in dtb.enum_subnodes("/") { 67 | let parts: Vec<_> = node.split('@').collect(); 68 | 69 | if let Some(compatible) = dtb.get_property(parts.first().unwrap(), "compatible") { 70 | if str::from_utf8(compatible).unwrap().contains("pl031") { 71 | let reg = dtb.get_property(parts.first().unwrap(), "reg").unwrap(); 72 | let (slice, residual_slice) = reg.split_at(core::mem::size_of::()); 73 | let addr = PhysAddr::new(u64::from_be_bytes(slice.try_into().unwrap())); 74 | let (slice, _residual_slice) = 75 | residual_slice.split_at(core::mem::size_of::()); 76 | let size = u64::from_be_bytes(slice.try_into().unwrap()); 77 | 78 | debug!("Found RTC at {addr:p} (size {size:#X})"); 79 | 80 | let pl031_address = virtualmem::allocate_aligned( 81 | size.try_into().unwrap(), 82 | BasePageSize::SIZE.try_into().unwrap(), 83 | ) 84 | .unwrap(); 85 | PL031_ADDRESS.set(pl031_address).unwrap(); 86 | debug!("Mapping RTC to virtual address {pl031_address:p}",); 87 | 88 | let mut flags = PageTableEntryFlags::empty(); 89 | flags.device().writable().execute_disable(); 90 | paging::map::( 91 | pl031_address, 92 | addr, 93 | (size / BasePageSize::SIZE).try_into().unwrap(), 94 | flags, 95 | ); 96 | 97 | let boot_time = 98 | OffsetDateTime::from_unix_timestamp(rtc_read(RTC_DR).into()).unwrap(); 99 | info!("Hermit booted on {boot_time}"); 100 | 101 | BOOT_TIME 102 | .set(u64::try_from(boot_time.unix_timestamp_nanos() / 1000).unwrap()) 103 | .unwrap(); 104 | 105 | return; 106 | } 107 | } 108 | } 109 | } 110 | }; 111 | 112 | BOOT_TIME.set(0).unwrap(); 113 | } 114 | 115 | /// Returns the current time in microseconds since UNIX epoch. 116 | pub fn now_micros() -> u64 { 117 | *BOOT_TIME.get().unwrap() + super::processor::get_timer_ticks() 118 | } 119 | -------------------------------------------------------------------------------- /src/arch/aarch64/mm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod paging; 2 | 3 | pub fn init() { 4 | unsafe { 5 | paging::init(); 6 | } 7 | crate::mm::physicalmem::init(); 8 | crate::mm::virtualmem::init(); 9 | } 10 | 11 | pub fn init_page_tables() {} 12 | -------------------------------------------------------------------------------- /src/arch/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod kernel; 2 | pub mod mm; 3 | 4 | /// Force strict CPU ordering, serializes load and store operations. 5 | #[allow(dead_code)] 6 | #[inline(always)] 7 | pub(crate) fn memory_barrier() { 8 | use core::arch::asm; 9 | unsafe { 10 | asm!("dmb ish", options(nostack, nomem, preserves_flags),); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | //! Architecture-specific architecture abstraction. 2 | 3 | cfg_if::cfg_if! { 4 | if #[cfg(target_arch = "aarch64")] { 5 | pub(crate) mod aarch64; 6 | pub(crate) use self::aarch64::*; 7 | 8 | #[cfg(target_os = "none")] 9 | pub(crate) use self::aarch64::kernel::boot_processor_init; 10 | pub(crate) use self::aarch64::kernel::core_local; 11 | pub(crate) use self::aarch64::kernel::interrupts; 12 | pub(crate) use self::aarch64::kernel::interrupts::wakeup_core; 13 | #[cfg(feature = "pci")] 14 | pub(crate) use self::aarch64::kernel::pci; 15 | pub(crate) use self::aarch64::kernel::processor; 16 | pub(crate) use self::aarch64::kernel::processor::set_oneshot_timer; 17 | pub(crate) use self::aarch64::kernel::scheduler; 18 | #[cfg(not(feature = "common-os"))] 19 | pub(crate) use self::aarch64::kernel::switch; 20 | #[cfg(feature = "smp")] 21 | pub(crate) use self::aarch64::kernel::application_processor_init; 22 | pub(crate) use self::aarch64::kernel::{ 23 | get_processor_count, 24 | }; 25 | pub use self::aarch64::mm::paging::{BasePageSize, PageSize}; 26 | } else if #[cfg(target_arch = "x86_64")] { 27 | pub(crate) mod x86_64; 28 | pub(crate) use self::x86_64::*; 29 | 30 | pub(crate) use self::x86_64::kernel::apic::{ 31 | set_oneshot_timer, 32 | wakeup_core, 33 | }; 34 | #[cfg(all(target_os = "none", feature = "smp"))] 35 | pub(crate) use self::x86_64::kernel::application_processor_init; 36 | pub(crate) use self::x86_64::kernel::core_local; 37 | pub(crate) use self::x86_64::kernel::gdt::set_current_kernel_stack; 38 | pub(crate) use self::x86_64::kernel::interrupts; 39 | #[cfg(feature = "pci")] 40 | pub(crate) use self::x86_64::kernel::pci; 41 | pub(crate) use self::x86_64::kernel::processor; 42 | pub(crate) use self::x86_64::kernel::scheduler; 43 | pub(crate) use self::x86_64::kernel::switch; 44 | #[cfg(target_os = "none")] 45 | pub(crate) use self::x86_64::kernel::boot_processor_init; 46 | pub(crate) use self::x86_64::kernel::{ 47 | get_processor_count, 48 | }; 49 | pub use self::x86_64::mm::paging::{BasePageSize, PageSize}; 50 | #[cfg(feature = "common-os")] 51 | pub use self::x86_64::mm::create_new_root_page_table; 52 | #[cfg(feature = "common-os")] 53 | pub use self::x86_64::kernel::{load_application, jump_to_user_land}; 54 | } else if #[cfg(target_arch = "riscv64")] { 55 | pub(crate) mod riscv64; 56 | pub(crate) use self::riscv64::*; 57 | 58 | #[cfg(feature = "smp")] 59 | pub(crate) use self::riscv64::kernel::application_processor_init; 60 | #[cfg(feature = "pci")] 61 | pub(crate) use self::riscv64::kernel::pci; 62 | pub(crate) use self::riscv64::kernel::processor::{self, set_oneshot_timer, wakeup_core}; 63 | pub(crate) use self::riscv64::kernel::{ 64 | boot_processor_init, 65 | core_local, 66 | get_processor_count, 67 | interrupts, 68 | scheduler, 69 | switch, 70 | }; 71 | pub use self::riscv64::mm::paging::{BasePageSize, PageSize}; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/core_local.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::vec::Vec; 3 | use core::arch::asm; 4 | use core::cell::{Cell, RefCell, RefMut}; 5 | use core::ptr; 6 | use core::sync::atomic::Ordering; 7 | 8 | #[cfg(feature = "smp")] 9 | use hermit_sync::InterruptTicketMutex; 10 | 11 | use crate::arch::riscv64::kernel::CPU_ONLINE; 12 | use crate::executor::task::AsyncTask; 13 | #[cfg(feature = "smp")] 14 | use crate::scheduler::SchedulerInput; 15 | use crate::scheduler::{CoreId, PerCoreScheduler}; 16 | 17 | pub struct CoreLocal { 18 | /// ID of the current Core. 19 | core_id: CoreId, 20 | /// Scheduler of the current Core. 21 | scheduler: Cell<*mut PerCoreScheduler>, 22 | /// start address of the kernel stack 23 | pub kernel_stack: Cell, 24 | /// Queue of async tasks 25 | async_tasks: RefCell>, 26 | /// Queues to handle incoming requests from the other cores 27 | #[cfg(feature = "smp")] 28 | pub scheduler_input: InterruptTicketMutex, 29 | } 30 | 31 | impl CoreLocal { 32 | pub fn install() { 33 | unsafe { 34 | let raw: *const Self; 35 | asm!("mv {}, gp", out(reg) raw); 36 | debug_assert_eq!(raw, ptr::null()); 37 | 38 | let core_id = CPU_ONLINE.load(Ordering::Relaxed); 39 | 40 | let this = Self { 41 | core_id, 42 | scheduler: Cell::new(ptr::null_mut()), 43 | kernel_stack: Cell::new(0), 44 | async_tasks: RefCell::new(Vec::new()), 45 | #[cfg(feature = "smp")] 46 | scheduler_input: InterruptTicketMutex::new(SchedulerInput::new()), 47 | }; 48 | let this = if core_id == 0 { 49 | take_static::take_static! { 50 | static FIRST_CORE_LOCAL: Option = None; 51 | } 52 | FIRST_CORE_LOCAL.take().unwrap().insert(this) 53 | } else { 54 | Box::leak(Box::new(this)) 55 | }; 56 | 57 | asm!("mv gp, {}", in(reg) this); 58 | } 59 | } 60 | 61 | #[inline] 62 | pub fn get() -> &'static Self { 63 | unsafe { 64 | let raw: *const Self; 65 | asm!("mv {}, gp", out(reg) raw); 66 | debug_assert_ne!(raw, ptr::null()); 67 | &*raw 68 | } 69 | } 70 | } 71 | 72 | #[inline] 73 | pub fn core_id() -> CoreId { 74 | CoreLocal::get().core_id 75 | } 76 | 77 | #[inline] 78 | pub fn core_scheduler() -> &'static mut PerCoreScheduler { 79 | unsafe { CoreLocal::get().scheduler.get().as_mut().unwrap() } 80 | } 81 | 82 | #[inline] 83 | pub fn set_core_scheduler(scheduler: *mut PerCoreScheduler) { 84 | CoreLocal::get().scheduler.set(scheduler); 85 | } 86 | 87 | pub(crate) fn async_tasks() -> RefMut<'static, Vec> { 88 | CoreLocal::get().async_tasks.borrow_mut() 89 | } 90 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/mmio.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use hermit_sync::InterruptSpinMutex; 4 | 5 | #[cfg(feature = "gem-net")] 6 | use crate::drivers::net::gem::GEMDriver; 7 | #[cfg(not(feature = "gem-net"))] 8 | use crate::drivers::net::virtio::VirtioNetDriver; 9 | use crate::init_cell::InitCell; 10 | 11 | pub(crate) static MMIO_DRIVERS: InitCell> = InitCell::new(Vec::new()); 12 | 13 | pub(crate) enum MmioDriver { 14 | #[cfg(feature = "gem-net")] 15 | GEMNet(InterruptSpinMutex), 16 | #[cfg(not(feature = "gem-net"))] 17 | VirtioNet(InterruptSpinMutex), 18 | } 19 | 20 | impl MmioDriver { 21 | #[cfg(feature = "gem-net")] 22 | fn get_network_driver(&self) -> Option<&InterruptSpinMutex> { 23 | match self { 24 | Self::GEMNet(drv) => Some(drv), 25 | } 26 | } 27 | 28 | #[cfg(not(feature = "gem-net"))] 29 | fn get_network_driver(&self) -> Option<&InterruptSpinMutex> { 30 | match self { 31 | Self::VirtioNet(drv) => Some(drv), 32 | } 33 | } 34 | } 35 | pub(crate) fn register_driver(drv: MmioDriver) { 36 | MMIO_DRIVERS.with(|mmio_drivers| mmio_drivers.unwrap().push(drv)); 37 | } 38 | 39 | #[cfg(feature = "gem-net")] 40 | pub(crate) fn get_network_driver() -> Option<&'static InterruptSpinMutex> { 41 | MMIO_DRIVERS 42 | .get()? 43 | .iter() 44 | .find_map(|drv| drv.get_network_driver()) 45 | } 46 | 47 | #[cfg(not(feature = "gem-net"))] 48 | pub(crate) fn get_network_driver() -> Option<&'static InterruptSpinMutex> { 49 | MMIO_DRIVERS 50 | .get()? 51 | .iter() 52 | .find_map(|drv| drv.get_network_driver()) 53 | } 54 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/pci.rs: -------------------------------------------------------------------------------- 1 | use pci_types::{ConfigRegionAccess, PciAddress}; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | pub struct PciConfigRegion; 5 | 6 | impl ConfigRegionAccess for PciConfigRegion { 7 | unsafe fn read(&self, addr: PciAddress, offset: u16) -> u32 { 8 | warn!("pci_config_region.read({addr}, {offset}) called but not implemented"); 9 | todo!() 10 | } 11 | 12 | unsafe fn write(&self, addr: PciAddress, offset: u16, value: u32) { 13 | warn!("pci_config_region.write({addr}, {offset}, {value}) called but not implemented"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/start.rs: -------------------------------------------------------------------------------- 1 | use core::arch::naked_asm; 2 | use core::sync::atomic::Ordering; 3 | 4 | use fdt::Fdt; 5 | use hermit_entry::Entry; 6 | use hermit_entry::boot_info::RawBootInfo; 7 | 8 | use super::{CPU_ONLINE, CURRENT_BOOT_ID, HART_MASK, NUM_CPUS, get_dtb_ptr}; 9 | use crate::arch::riscv64::kernel::CURRENT_STACK_ADDRESS; 10 | #[cfg(not(feature = "smp"))] 11 | use crate::arch::riscv64::kernel::processor; 12 | use crate::{KERNEL_STACK_SIZE, env}; 13 | 14 | //static mut BOOT_STACK: [u8; KERNEL_STACK_SIZE] = [0; KERNEL_STACK_SIZE]; 15 | 16 | /// Entrypoint - Initialize Stack pointer and Exception Table 17 | #[unsafe(no_mangle)] 18 | #[unsafe(naked)] 19 | pub unsafe extern "C" fn _start(hart_id: usize, boot_info: Option<&'static RawBootInfo>) -> ! { 20 | // validate signatures 21 | // `_Start` is compatible to `Entry` 22 | { 23 | unsafe extern "C" fn _entry(_hart_id: usize, _boot_info: &'static RawBootInfo) -> ! { 24 | unreachable!() 25 | } 26 | pub type _Start = 27 | unsafe extern "C" fn(hart_id: usize, boot_info: Option<&'static RawBootInfo>) -> !; 28 | const _ENTRY: Entry = _entry; 29 | const _START: _Start = _start; 30 | const _PRE_INIT: _Start = pre_init; 31 | } 32 | 33 | naked_asm!( 34 | // Use stack pointer from `CURRENT_STACK_ADDRESS` if set 35 | "ld t0, {current_stack_pointer}", 36 | "beqz t0, 2f", 37 | "li t1, {top_offset}", 38 | "add t0, t0, t1", 39 | "mv sp, t0", 40 | "2:", 41 | 42 | "j {pre_init}", 43 | current_stack_pointer = sym CURRENT_STACK_ADDRESS, 44 | top_offset = const KERNEL_STACK_SIZE, 45 | pre_init = sym pre_init, 46 | ) 47 | } 48 | 49 | unsafe extern "C" fn pre_init(hart_id: usize, boot_info: Option<&'static RawBootInfo>) -> ! { 50 | CURRENT_BOOT_ID.store(hart_id as u32, Ordering::Relaxed); 51 | 52 | if CPU_ONLINE.load(Ordering::Acquire) == 0 { 53 | unsafe { 54 | env::set_boot_info(*boot_info.unwrap()); 55 | let fdt = Fdt::from_ptr(get_dtb_ptr()).expect("FDT is invalid"); 56 | // Init HART_MASK 57 | let mut hart_mask = 0; 58 | for cpu in fdt.cpus() { 59 | let hart_id = cpu.property("reg").unwrap().as_usize().unwrap(); 60 | let status = cpu.property("status").unwrap().as_str().unwrap(); 61 | 62 | if status != "disabled\u{0}" { 63 | hart_mask |= 1 << hart_id; 64 | } 65 | } 66 | NUM_CPUS.store(fdt.cpus().count().try_into().unwrap(), Ordering::Relaxed); 67 | HART_MASK.store(hart_mask, Ordering::Relaxed); 68 | } 69 | crate::boot_processor_main() 70 | } else { 71 | #[cfg(not(feature = "smp"))] 72 | { 73 | error!("SMP support deactivated"); 74 | loop { 75 | processor::halt(); 76 | } 77 | } 78 | #[cfg(feature = "smp")] 79 | crate::application_processor_main(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/switch.rs: -------------------------------------------------------------------------------- 1 | use core::arch::global_asm; 2 | 3 | global_asm!(include_str!("switch.s")); 4 | 5 | unsafe extern "C" { 6 | pub fn switch_to_task(old_stack: *mut usize, new_stack: usize); 7 | } 8 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/switch.s: -------------------------------------------------------------------------------- 1 | .section .text 2 | .global switch_to_task 3 | .global task_start 4 | .extern task_entry 5 | // .extern set_current_kernel_stack 6 | 7 | 8 | .align 16 9 | // This function should only be called if the fp registers are clean 10 | switch_to_task: 11 | // a0 = old_stack => the address to store the old rsp 12 | // a1 = new_stack => stack pointer of the new task 13 | 14 | addi sp, sp, -(31*8) 15 | sd x31, (8*30)(sp) 16 | sd x30, (8*29)(sp) 17 | sd x29, (8*28)(sp) 18 | sd x28, (8*27)(sp) 19 | sd x27, (8*26)(sp) 20 | sd x26, (8*25)(sp) 21 | sd x25, (8*24)(sp) 22 | sd x24, (8*23)(sp) 23 | sd x23, (8*22)(sp) 24 | sd x22, (8*21)(sp) 25 | sd x21, (8*20)(sp) 26 | sd x20, (8*19)(sp) 27 | sd x19, (8*18)(sp) 28 | sd x18, (8*17)(sp) 29 | sd x17, (8*16)(sp) 30 | sd x16, (8*15)(sp) 31 | sd x15, (8*14)(sp) 32 | sd x14, (8*13)(sp) 33 | sd x13, (8*12)(sp) 34 | sd x12, (8*11)(sp) 35 | sd x11, (8*10)(sp) 36 | sd x10, (8*9)(sp) 37 | sd x9, (8*8)(sp) 38 | sd x8, (8*7)(sp) 39 | sd x7, (8*6)(sp) 40 | sd x6, (8*5)(sp) 41 | sd x5, (8*4)(sp) 42 | sd x4, (8*3)(sp) 43 | //sd x3, (8*2)(sp) 44 | //sd x2, (8*1)(sp) 45 | sd x1, (8*0)(sp) 46 | 47 | //Store floating point registers 48 | //TODO: Save only when changed 49 | # fsd f0, (8*31)(sp) 50 | # fsd f1, (8*32)(sp) 51 | # fsd f2, (8*33)(sp) 52 | # fsd f3, (8*34)(sp) 53 | # fsd f4, (8*35)(sp) 54 | # fsd f5, (8*36)(sp) 55 | # fsd f6, (8*37)(sp) 56 | # fsd f7, (8*38)(sp) 57 | # fsd f8, (8*39)(sp) 58 | # fsd f9, (8*40)(sp) 59 | # fsd f10, (8*41)(sp) 60 | # fsd f11, (8*42)(sp) 61 | # fsd f12, (8*43)(sp) 62 | # fsd f13, (8*44)(sp) 63 | # fsd f14, (8*45)(sp) 64 | # fsd f15, (8*46)(sp) 65 | # fsd f16, (8*47)(sp) 66 | # fsd f17, (8*48)(sp) 67 | # fsd f18, (8*49)(sp) 68 | # fsd f19, (8*50)(sp) 69 | # fsd f20, (8*51)(sp) 70 | # fsd f21, (8*52)(sp) 71 | # fsd f22, (8*53)(sp) 72 | # fsd f23, (8*54)(sp) 73 | # fsd f24, (8*55)(sp) 74 | # fsd f25, (8*56)(sp) 75 | # fsd f26, (8*57)(sp) 76 | # fsd f27, (8*58)(sp) 77 | # fsd f28, (8*59)(sp) 78 | # fsd f29, (8*60)(sp) 79 | # fsd f30, (8*61)(sp) 80 | # fsd f31, (8*62)(sp) 81 | # frcsr t0 82 | # sd t0, (8*63)(sp) 83 | 84 | // Store current stack pointer with saved context in `_dst`. 85 | sd sp, (0)(a0) 86 | // Set stack pointer to supplied `_src`. 87 | mv sp, a1 88 | 89 | //set current kernel stack 90 | call set_current_kernel_stack 91 | 92 | # // Restore fp regs 93 | # fld f0, (8*31)(sp) 94 | # fld f1, (8*32)(sp) 95 | # fld f2, (8*33)(sp) 96 | # fld f3, (8*34)(sp) 97 | # fld f4, (8*35)(sp) 98 | # fld f5, (8*36)(sp) 99 | # fld f6, (8*37)(sp) 100 | # fld f7, (8*38)(sp) 101 | # fld f8, (8*39)(sp) 102 | # fld f9, (8*40)(sp) 103 | # fld f10, (8*41)(sp) 104 | # fld f11, (8*42)(sp) 105 | # fld f12, (8*43)(sp) 106 | # fld f13, (8*44)(sp) 107 | # fld f14, (8*45)(sp) 108 | # fld f15, (8*46)(sp) 109 | # fld f16, (8*47)(sp) 110 | # fld f17, (8*48)(sp) 111 | # fld f18, (8*49)(sp) 112 | # fld f19, (8*50)(sp) 113 | # fld f20, (8*51)(sp) 114 | # fld f21, (8*52)(sp) 115 | # fld f22, (8*53)(sp) 116 | # fld f23, (8*54)(sp) 117 | # fld f24, (8*55)(sp) 118 | # fld f25, (8*56)(sp) 119 | # fld f26, (8*57)(sp) 120 | # fld f27, (8*58)(sp) 121 | # fld f28, (8*59)(sp) 122 | # fld f29, (8*60)(sp) 123 | # fld f30, (8*61)(sp) 124 | # fld f31, (8*62)(sp) 125 | # ld t0, (8*63)(sp) 126 | # fscsr t0 127 | 128 | // Restore context 129 | ld x1, (8*0)(sp) 130 | //ld x2, (8*1)(sp) 131 | //ld x3, (8*2)(sp) 132 | ld x4, (8*3)(sp) 133 | ld x5, (8*4)(sp) 134 | ld x6, (8*5)(sp) 135 | ld x7, (8*6)(sp) 136 | ld x8, (8*7)(sp) 137 | ld x9, (8*8)(sp) 138 | ld x10, (8*9)(sp) 139 | ld x11, (8*10)(sp) 140 | ld x12, (8*11)(sp) 141 | ld x13, (8*12)(sp) 142 | ld x14, (8*13)(sp) 143 | ld x15, (8*14)(sp) 144 | ld x16, (8*15)(sp) 145 | ld x17, (8*16)(sp) 146 | ld x18, (8*17)(sp) 147 | ld x19, (8*18)(sp) 148 | ld x20, (8*19)(sp) 149 | ld x21, (8*20)(sp) 150 | ld x22, (8*21)(sp) 151 | ld x23, (8*22)(sp) 152 | ld x24, (8*23)(sp) 153 | ld x25, (8*24)(sp) 154 | ld x26, (8*25)(sp) 155 | ld x27, (8*26)(sp) 156 | ld x28, (8*27)(sp) 157 | ld x29, (8*28)(sp) 158 | ld x30, (8*29)(sp) 159 | ld x31, (8*30)(sp) 160 | 161 | addi sp, sp, (31*8) 162 | 163 | ret 164 | 165 | .align 16 166 | task_start: 167 | mv sp, a2 168 | j task_entry 169 | -------------------------------------------------------------------------------- /src/arch/riscv64/kernel/systemtime.rs: -------------------------------------------------------------------------------- 1 | /// Returns the current time in microseconds since UNIX epoch. 2 | pub fn now_micros() -> u64 { 3 | debug!("time is currently stubbed"); 4 | super::processor::get_timer_ticks() 5 | } 6 | -------------------------------------------------------------------------------- /src/arch/riscv64/mm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod paging; 2 | 3 | pub use self::paging::init_page_tables; 4 | 5 | pub fn init() { 6 | crate::mm::physicalmem::init(); 7 | crate::mm::virtualmem::init(); 8 | } 9 | -------------------------------------------------------------------------------- /src/arch/riscv64/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod kernel; 2 | pub mod mm; 3 | 4 | #[allow(dead_code)] 5 | #[cfg(target_arch = "riscv64")] 6 | #[inline(always)] 7 | pub(crate) fn memory_barrier() { 8 | riscv::asm::sfence_vma_all(); 9 | } 10 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/core_local.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::vec::Vec; 3 | use core::arch::asm; 4 | use core::cell::{Cell, RefCell, RefMut}; 5 | #[cfg(feature = "smp")] 6 | use core::sync::atomic::AtomicBool; 7 | use core::sync::atomic::Ordering; 8 | use core::{mem, ptr}; 9 | 10 | #[cfg(feature = "smp")] 11 | use hermit_sync::InterruptTicketMutex; 12 | use x86_64::VirtAddr; 13 | use x86_64::registers::model_specific::GsBase; 14 | use x86_64::structures::tss::TaskStateSegment; 15 | 16 | use super::CPU_ONLINE; 17 | use super::interrupts::{IRQ_COUNTERS, IrqStatistics}; 18 | use crate::executor::task::AsyncTask; 19 | #[cfg(feature = "smp")] 20 | use crate::scheduler::SchedulerInput; 21 | use crate::scheduler::{CoreId, PerCoreScheduler}; 22 | 23 | pub(crate) struct CoreLocal { 24 | this: *const Self, 25 | /// Sequential ID of this CPU Core. 26 | core_id: CoreId, 27 | /// Scheduler for this CPU Core. 28 | scheduler: Cell<*mut PerCoreScheduler>, 29 | /// Task State Segment (TSS) allocated for this CPU Core. 30 | pub tss: Cell<*mut TaskStateSegment>, 31 | /// start address of the kernel stack 32 | pub kernel_stack: Cell<*mut u8>, 33 | /// Interface to the interrupt counters 34 | irq_statistics: &'static IrqStatistics, 35 | /// Queue of async tasks 36 | async_tasks: RefCell>, 37 | #[cfg(feature = "smp")] 38 | pub hlt: AtomicBool, 39 | /// Queues to handle incoming requests from the other cores 40 | #[cfg(feature = "smp")] 41 | pub scheduler_input: InterruptTicketMutex, 42 | } 43 | 44 | impl CoreLocal { 45 | pub fn install() { 46 | assert_eq!(VirtAddr::zero(), GsBase::read()); 47 | 48 | let core_id = CPU_ONLINE.load(Ordering::Relaxed); 49 | 50 | let irq_statistics = if core_id == 0 { 51 | static FIRST_IRQ_STATISTICS: IrqStatistics = IrqStatistics::new(); 52 | &FIRST_IRQ_STATISTICS 53 | } else { 54 | &*Box::leak(Box::new(IrqStatistics::new())) 55 | }; 56 | 57 | let this = Self { 58 | this: ptr::null_mut(), 59 | core_id, 60 | scheduler: Cell::new(ptr::null_mut()), 61 | tss: Cell::new(ptr::null_mut()), 62 | kernel_stack: Cell::new(ptr::null_mut()), 63 | irq_statistics, 64 | async_tasks: RefCell::new(Vec::new()), 65 | #[cfg(feature = "smp")] 66 | hlt: AtomicBool::new(false), 67 | #[cfg(feature = "smp")] 68 | scheduler_input: InterruptTicketMutex::new(SchedulerInput::new()), 69 | }; 70 | let this = if core_id == 0 { 71 | take_static::take_static! { 72 | static FIRST_CORE_LOCAL: Option = None; 73 | } 74 | FIRST_CORE_LOCAL.take().unwrap().insert(this) 75 | } else { 76 | this.add_irq_counter(); 77 | Box::leak(Box::new(this)) 78 | }; 79 | this.this = ptr::from_ref(this); 80 | 81 | GsBase::write(VirtAddr::from_ptr(this)); 82 | } 83 | 84 | #[inline] 85 | pub fn get() -> &'static Self { 86 | debug_assert_ne!(VirtAddr::zero(), GsBase::read()); 87 | unsafe { 88 | let raw: *const Self; 89 | asm!("mov {}, gs:{}", out(reg) raw, const mem::offset_of!(Self, this), options(nomem, nostack, preserves_flags)); 90 | &*raw 91 | } 92 | } 93 | 94 | pub fn add_irq_counter(&self) { 95 | IRQ_COUNTERS 96 | .lock() 97 | .insert(self.core_id, self.irq_statistics); 98 | } 99 | } 100 | 101 | pub(crate) fn core_id() -> CoreId { 102 | if cfg!(target_os = "none") { 103 | CoreLocal::get().core_id 104 | } else { 105 | 0 106 | } 107 | } 108 | 109 | pub(crate) fn core_scheduler() -> &'static mut PerCoreScheduler { 110 | unsafe { CoreLocal::get().scheduler.get().as_mut().unwrap() } 111 | } 112 | 113 | pub(crate) fn async_tasks() -> RefMut<'static, Vec> { 114 | CoreLocal::get().async_tasks.borrow_mut() 115 | } 116 | 117 | pub(crate) fn set_core_scheduler(scheduler: *mut PerCoreScheduler) { 118 | CoreLocal::get().scheduler.set(scheduler); 119 | } 120 | 121 | pub(crate) fn increment_irq_counter(irq_no: u8) { 122 | CoreLocal::get().irq_statistics.inc(irq_no); 123 | } 124 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/gdt.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::alloc; 2 | use alloc::boxed::Box; 3 | use core::alloc::Layout; 4 | use core::sync::atomic::Ordering; 5 | 6 | use x86_64::VirtAddr; 7 | use x86_64::instructions::tables; 8 | use x86_64::registers::segmentation::{CS, DS, ES, SS, Segment}; 9 | #[cfg(feature = "common-os")] 10 | use x86_64::structures::gdt::DescriptorFlags; 11 | use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable}; 12 | use x86_64::structures::tss::TaskStateSegment; 13 | 14 | use super::CURRENT_STACK_ADDRESS; 15 | use super::interrupts::{IST_ENTRIES, IST_SIZE}; 16 | use super::scheduler::TaskStacks; 17 | use crate::arch::x86_64::kernel::core_local::{CoreLocal, core_scheduler}; 18 | use crate::arch::x86_64::mm::paging::{BasePageSize, PageSize}; 19 | use crate::config::KERNEL_STACK_SIZE; 20 | 21 | pub fn add_current_core() { 22 | let gdt: &mut GlobalDescriptorTable = Box::leak(Box::new(GlobalDescriptorTable::new())); 23 | let kernel_code_selector = gdt.append(Descriptor::kernel_code_segment()); 24 | let kernel_data_selector = gdt.append(Descriptor::kernel_data_segment()); 25 | #[cfg(feature = "common-os")] 26 | { 27 | let _user_code32_selector = 28 | gdt.append(Descriptor::UserSegment(DescriptorFlags::USER_CODE32.bits())); 29 | let _user_data64_selector = gdt.append(Descriptor::user_data_segment()); 30 | let _user_code64_selector = gdt.append(Descriptor::user_code_segment()); 31 | } 32 | 33 | // Dynamically allocate memory for a Task-State Segment (TSS) for this core. 34 | let tss = Box::leak(Box::new(TaskStateSegment::new())); 35 | 36 | // Every task later gets its own stack, so this boot stack is only used by the Idle task on each core. 37 | // When switching to another task on this core, this entry is replaced. 38 | let rsp = CURRENT_STACK_ADDRESS.load(Ordering::Relaxed); 39 | let rsp = unsafe { rsp.add(KERNEL_STACK_SIZE - TaskStacks::MARKER_SIZE) }; 40 | tss.privilege_stack_table[0] = VirtAddr::from_ptr(rsp); 41 | CoreLocal::get().kernel_stack.set(rsp); 42 | 43 | // Allocate all ISTs for this core. 44 | // Every task later gets its own IST, so the IST allocated here is only used by the Idle task. 45 | for i in 0..IST_ENTRIES { 46 | let size = if i == 0 { 47 | IST_SIZE 48 | } else { 49 | BasePageSize::SIZE as usize 50 | }; 51 | 52 | let layout = Layout::from_size_align(size, BasePageSize::SIZE as usize).unwrap(); 53 | let ist = unsafe { alloc(layout) }; 54 | assert!(!ist.is_null()); 55 | let ist_start = unsafe { ist.add(size - TaskStacks::MARKER_SIZE) }; 56 | tss.interrupt_stack_table[i] = VirtAddr::from_ptr(ist_start); 57 | } 58 | 59 | CoreLocal::get().tss.set(tss); 60 | let tss_selector = gdt.append(Descriptor::tss_segment(tss)); 61 | 62 | // Load the GDT for the current core. 63 | gdt.load(); 64 | 65 | unsafe { 66 | // Reload the segment descriptors 67 | CS::set_reg(kernel_code_selector); 68 | DS::set_reg(kernel_data_selector); 69 | ES::set_reg(kernel_data_selector); 70 | SS::set_reg(kernel_data_selector); 71 | tables::load_tss(tss_selector); 72 | } 73 | } 74 | 75 | pub extern "C" fn set_current_kernel_stack() { 76 | #[cfg(feature = "common-os")] 77 | { 78 | use x86_64::PhysAddr; 79 | use x86_64::registers::control::Cr3; 80 | use x86_64::structures::paging::PhysFrame; 81 | 82 | let root = crate::scheduler::get_root_page_table(); 83 | let new_frame = 84 | PhysFrame::from_start_address(PhysAddr::new(root.try_into().unwrap())).unwrap(); 85 | 86 | let (current_frame, val) = Cr3::read_raw(); 87 | 88 | if current_frame != new_frame { 89 | unsafe { 90 | Cr3::write_raw(new_frame, val); 91 | } 92 | } 93 | } 94 | 95 | core_scheduler().set_current_kernel_stack(); 96 | } 97 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/pci.rs: -------------------------------------------------------------------------------- 1 | use pci_types::{ConfigRegionAccess, PciAddress, PciHeader}; 2 | use x86_64::instructions::port::Port; 3 | 4 | use crate::drivers::pci::{PCI_DEVICES, PciDevice}; 5 | 6 | const PCI_MAX_BUS_NUMBER: u8 = 32; 7 | const PCI_MAX_DEVICE_NUMBER: u8 = 32; 8 | 9 | const PCI_CONFIG_ADDRESS_ENABLE: u32 = 1 << 31; 10 | 11 | const CONFIG_ADDRESS: Port = Port::new(0xcf8); 12 | const CONFIG_DATA: Port = Port::new(0xcfc); 13 | 14 | #[derive(Debug, Copy, Clone)] 15 | pub(crate) struct PciConfigRegion; 16 | 17 | impl PciConfigRegion { 18 | pub const fn new() -> Self { 19 | Self {} 20 | } 21 | } 22 | 23 | impl ConfigRegionAccess for PciConfigRegion { 24 | #[inline] 25 | unsafe fn read(&self, pci_addr: PciAddress, register: u16) -> u32 { 26 | let mut config_address = CONFIG_ADDRESS; 27 | let mut config_data = CONFIG_DATA; 28 | 29 | let address = PCI_CONFIG_ADDRESS_ENABLE 30 | | (u32::from(pci_addr.bus()) << 16) 31 | | (u32::from(pci_addr.device()) << 11) 32 | | (u32::from(pci_addr.function()) << 8) 33 | | u32::from(register); 34 | 35 | unsafe { 36 | config_address.write(address); 37 | config_data.read() 38 | } 39 | } 40 | 41 | #[inline] 42 | unsafe fn write(&self, pci_addr: PciAddress, register: u16, value: u32) { 43 | let mut config_address = CONFIG_ADDRESS; 44 | let mut config_data = CONFIG_DATA; 45 | 46 | let address = PCI_CONFIG_ADDRESS_ENABLE 47 | | (u32::from(pci_addr.bus()) << 16) 48 | | (u32::from(pci_addr.device()) << 11) 49 | | (u32::from(pci_addr.function()) << 8) 50 | | u32::from(register); 51 | 52 | unsafe { 53 | config_address.write(address); 54 | config_data.write(value); 55 | } 56 | } 57 | } 58 | 59 | pub(crate) fn init() { 60 | debug!("Scanning PCI Busses 0 to {}", PCI_MAX_BUS_NUMBER - 1); 61 | 62 | // Hermit only uses PCI for network devices. 63 | // Therefore, multifunction devices as well as additional bridges are not scanned. 64 | // We also limit scanning to the first 32 buses. 65 | let pci_config = PciConfigRegion::new(); 66 | for bus in 0..PCI_MAX_BUS_NUMBER { 67 | for device in 0..PCI_MAX_DEVICE_NUMBER { 68 | let pci_address = PciAddress::new(0, bus, device, 0); 69 | let header = PciHeader::new(pci_address); 70 | 71 | let (device_id, vendor_id) = header.id(pci_config); 72 | if device_id != u16::MAX && vendor_id != u16::MAX { 73 | let device = PciDevice::new(pci_address, pci_config); 74 | PCI_DEVICES.with(|pci_devices| pci_devices.unwrap().push(device)); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/pic.rs: -------------------------------------------------------------------------------- 1 | use x86_64::instructions::port::Port; 2 | 3 | use super::interrupts::IDT; 4 | use crate::arch::x86_64::kernel::interrupts::ExceptionStackFrame; 5 | use crate::arch::x86_64::swapgs; 6 | use crate::scheduler; 7 | 8 | const PIC1_COMMAND: Port = Port::new(0x20); 9 | const PIC1_DATA: Port = Port::new(0x21); 10 | const PIC2_COMMAND: Port = Port::new(0xa0); 11 | const PIC2_DATA: Port = Port::new(0xa1); 12 | 13 | pub const PIC1_INTERRUPT_OFFSET: u8 = 32; 14 | const PIC2_INTERRUPT_OFFSET: u8 = 40; 15 | const SPURIOUS_IRQ_NUMBER: u8 = 7; 16 | 17 | /// End-Of-Interrupt Command for an Intel 8259 Programmable Interrupt Controller (PIC). 18 | const PIC_EOI_COMMAND: u8 = 0x20; 19 | 20 | pub fn eoi(int_no: u8) { 21 | let mut pic1_command = PIC1_COMMAND; 22 | let mut pic2_command = PIC2_COMMAND; 23 | 24 | unsafe { 25 | // For IRQ 8-15 (mapped to interrupt numbers >= 40), we need to send an EOI to the slave PIC. 26 | if int_no >= 40 { 27 | pic2_command.write(PIC_EOI_COMMAND); 28 | } 29 | 30 | // In all cases, we need to send an EOI to the master PIC. 31 | pic1_command.write(PIC_EOI_COMMAND); 32 | } 33 | } 34 | 35 | pub fn init() { 36 | let mut pic1_command = PIC1_COMMAND; 37 | let mut pic1_data = PIC1_DATA; 38 | let mut pic2_command = PIC2_COMMAND; 39 | let mut pic2_data = PIC2_DATA; 40 | 41 | // Even if we mask all interrupts, spurious interrupts may still occur. 42 | // This is especially true for real hardware. So provide a handler for them. 43 | unsafe { 44 | let mut idt = IDT.lock(); 45 | idt[PIC1_INTERRUPT_OFFSET + SPURIOUS_IRQ_NUMBER] 46 | .set_handler_fn(spurious_interrupt_on_master) 47 | .set_stack_index(0); 48 | idt[PIC2_INTERRUPT_OFFSET + SPURIOUS_IRQ_NUMBER] 49 | .set_handler_fn(spurious_interrupt_on_slave) 50 | .set_stack_index(0); 51 | 52 | // Remapping IRQs with a couple of IO output operations 53 | // 54 | // Normally, IRQs 0 to 7 are mapped to entries 8 to 15. This 55 | // is a problem in protected mode, because IDT entry 8 is a 56 | // Double Fault! Without remapping, every time IRQ0 fires, 57 | // you get a Double Fault Exception, which is NOT what's 58 | // actually happening. We send commands to the Programmable 59 | // Interrupt Controller (PICs - also called the 8259's) in 60 | // order to make IRQ0 to 15 be remapped to IDT entries 32 to 61 | // 47 62 | 63 | // Reinitialize PIC1 and PIC2. 64 | pic1_command.write(0x11); 65 | pic2_command.write(0x11); 66 | 67 | // Map PIC1 to interrupt numbers >= 32 and PIC2 to interrupt numbers >= 40. 68 | pic1_data.write(PIC1_INTERRUPT_OFFSET); 69 | pic2_data.write(PIC2_INTERRUPT_OFFSET); 70 | 71 | // Configure PIC1 as master and PIC2 as slave. 72 | pic1_data.write(0x04); 73 | pic2_data.write(0x02); 74 | 75 | // Start them in 8086 mode. 76 | pic1_data.write(0x01); 77 | pic2_data.write(0x01); 78 | 79 | // Mask all interrupts on both PICs. 80 | pic1_data.write(0xff); 81 | pic2_data.write(0xff); 82 | } 83 | } 84 | 85 | extern "x86-interrupt" fn spurious_interrupt_on_master(stack_frame: ExceptionStackFrame) { 86 | swapgs(&stack_frame); 87 | debug!("Spurious Interrupt on Master PIC (IRQ7)"); 88 | scheduler::abort(); 89 | } 90 | 91 | extern "x86-interrupt" fn spurious_interrupt_on_slave(stack_frame: ExceptionStackFrame) { 92 | swapgs(&stack_frame); 93 | debug!("Spurious Interrupt on Slave PIC (IRQ15)"); 94 | 95 | // As this is an interrupt forwarded by the master, we have to acknowledge it on the master 96 | // (but not on the slave as with all spurious interrupts). 97 | let mut pic1_command = PIC1_COMMAND; 98 | unsafe { 99 | pic1_command.write(PIC_EOI_COMMAND); 100 | } 101 | scheduler::abort(); 102 | } 103 | 104 | fn edit_mask(int_no: u8, insert: bool) { 105 | let mut port = if int_no >= 40 { PIC2_DATA } else { PIC1_DATA }; 106 | let offset = if int_no >= 40 { 40 } else { 32 }; 107 | 108 | unsafe { 109 | let mask = port.read(); 110 | 111 | if insert { 112 | port.write(mask | (1 << (int_no - offset))); 113 | } else { 114 | port.write(mask & !(1 << (int_no - offset))); 115 | } 116 | } 117 | } 118 | 119 | pub fn mask(int_no: u8) { 120 | edit_mask(int_no, true); 121 | } 122 | 123 | pub fn unmask(int_no: u8) { 124 | edit_mask(int_no, false); 125 | } 126 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/pit.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use x86_64::instructions::port::Port; 4 | 5 | use crate::arch::x86_64::kernel::pic; 6 | 7 | const PIT_CLOCK: u64 = 1_193_182; 8 | pub const PIT_INTERRUPT_NUMBER: u8 = pic::PIC1_INTERRUPT_OFFSET; 9 | 10 | const PIT_CHANNEL0_DATA: Port = Port::new(0x40); 11 | const PIT_CHANNEL1_DATA: Port = Port::new(0x41); 12 | const PIT_CHANNEL2_DATA: Port = Port::new(0x42); 13 | const PIT_COMMAND: Port = Port::new(0x43); 14 | 15 | const PIT_BINARY_OUTPUT: u8 = 0b0000_0000; 16 | const PIT_BCD_OUTPUT: u8 = 0b0000_0001; 17 | 18 | const PIT_COUNTDOWN_MODE: u8 = 0b0000_0000; 19 | const PIT_ONESHOT_MODE: u8 = 0b0000_0010; 20 | const PIT_RATE_GENERATOR_MODE: u8 = 0b0000_0100; 21 | const PIT_SQUARE_WAVE_GENERATOR_MODE: u8 = 0b0000_0110; 22 | const PIT_SW_TRIGGERED_STROBE_MODE: u8 = 0b0000_1000; 23 | const PIT_HW_TRIGGERED_STROBE_MODE: u8 = 0b0000_1010; 24 | 25 | const PIT_LOBYTE_ACCESS: u8 = 0b0001_0000; 26 | const PIT_HIBYTE_ACCESS: u8 = 0b0010_0000; 27 | 28 | const PIT_CHANNEL0: u8 = 0b0000_0000; 29 | const PIT_CHANNEL1: u8 = 0b0100_0000; 30 | const PIT_CHANNEL2: u8 = 0b1000_0000; 31 | 32 | pub fn init(frequency_in_hz: u64) { 33 | pic::unmask(PIT_INTERRUPT_NUMBER); 34 | 35 | let mut pit_command = PIT_COMMAND; 36 | let mut pit_channel0_data = PIT_CHANNEL0_DATA; 37 | 38 | unsafe { 39 | // Reset the Programmable Interval Timer (PIT). 40 | pit_command.write( 41 | PIT_BINARY_OUTPUT 42 | | PIT_RATE_GENERATOR_MODE 43 | | PIT_LOBYTE_ACCESS 44 | | PIT_HIBYTE_ACCESS 45 | | PIT_CHANNEL0, 46 | ); 47 | 48 | // Calculate the reload value to count down (round it to the closest integer). 49 | // Then transmit it as two individual bytes to the PIT. 50 | let count = (PIT_CLOCK + frequency_in_hz / 2) / frequency_in_hz; 51 | pit_channel0_data.write(count as u8); 52 | pit_channel0_data.write((count >> 8) as u8); 53 | } 54 | } 55 | 56 | pub fn deinit() { 57 | pic::mask(PIT_INTERRUPT_NUMBER); 58 | } 59 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/serial.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | use core::task::Waker; 3 | 4 | use crate::arch::x86_64::kernel::apic; 5 | use crate::arch::x86_64::kernel::core_local::increment_irq_counter; 6 | use crate::arch::x86_64::kernel::interrupts::{self, IDT}; 7 | use crate::executor::WakerRegistration; 8 | use crate::syscalls::interfaces::serial_buf_hypercall; 9 | 10 | const SERIAL_IRQ: u8 = 36; 11 | 12 | enum SerialInner { 13 | Uart(uart_16550::SerialPort), 14 | Uhyve, 15 | } 16 | 17 | pub struct SerialPort { 18 | inner: SerialInner, 19 | buffer: VecDeque, 20 | waker: WakerRegistration, 21 | } 22 | 23 | impl SerialPort { 24 | pub unsafe fn new(base: u16) -> Self { 25 | if crate::env::is_uhyve() { 26 | Self { 27 | inner: SerialInner::Uhyve, 28 | buffer: VecDeque::new(), 29 | waker: WakerRegistration::new(), 30 | } 31 | } else { 32 | let mut serial = unsafe { uart_16550::SerialPort::new(base) }; 33 | serial.init(); 34 | Self { 35 | inner: SerialInner::Uart(serial), 36 | buffer: VecDeque::new(), 37 | waker: WakerRegistration::new(), 38 | } 39 | } 40 | } 41 | 42 | pub fn buffer_input(&mut self) { 43 | if let SerialInner::Uart(s) = &mut self.inner { 44 | let c = s.receive(); 45 | if c == b'\r' { 46 | self.buffer.push_back(b'\n'); 47 | } else { 48 | self.buffer.push_back(c); 49 | } 50 | self.waker.wake(); 51 | } 52 | } 53 | 54 | pub fn register_waker(&mut self, waker: &Waker) { 55 | self.waker.register(waker); 56 | } 57 | 58 | pub fn read(&mut self) -> Option { 59 | self.buffer.pop_front() 60 | } 61 | 62 | pub fn is_empty(&self) -> bool { 63 | self.buffer.is_empty() 64 | } 65 | 66 | pub fn send(&mut self, buf: &[u8]) { 67 | match &mut self.inner { 68 | SerialInner::Uhyve => serial_buf_hypercall(buf), 69 | SerialInner::Uart(s) => { 70 | for &data in buf { 71 | s.send(data); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | extern "x86-interrupt" fn serial_interrupt(_stack_frame: crate::interrupts::ExceptionStackFrame) { 79 | crate::console::CONSOLE.lock().inner.buffer_input(); 80 | increment_irq_counter(SERIAL_IRQ); 81 | crate::executor::run(); 82 | 83 | apic::eoi(); 84 | } 85 | 86 | pub(crate) fn install_serial_interrupt() { 87 | unsafe { 88 | let mut idt = IDT.lock(); 89 | idt[SERIAL_IRQ] 90 | .set_handler_fn(serial_interrupt) 91 | .set_stack_index(0); 92 | } 93 | interrupts::add_irq_name(SERIAL_IRQ - 32, "COM1"); 94 | } 95 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/start.rs: -------------------------------------------------------------------------------- 1 | use core::arch::naked_asm; 2 | 3 | use hermit_entry::Entry; 4 | use hermit_entry::boot_info::RawBootInfo; 5 | 6 | use crate::KERNEL_STACK_SIZE; 7 | use crate::kernel::pre_init; 8 | use crate::kernel::scheduler::TaskStacks; 9 | 10 | #[unsafe(no_mangle)] 11 | #[unsafe(naked)] 12 | pub unsafe extern "C" fn _start(_boot_info: Option<&'static RawBootInfo>, cpu_id: u32) -> ! { 13 | // boot_info is in the `rdi` register 14 | 15 | // validate signatures 16 | // `_Start` is compatible to `Entry` 17 | { 18 | unsafe extern "C" fn _entry(_boot_info: &'static RawBootInfo, _cpu_id: u32) -> ! { 19 | unreachable!() 20 | } 21 | pub type _Start = 22 | unsafe extern "C" fn(boot_info: Option<&'static RawBootInfo>, cpu_id: u32) -> !; 23 | const _ENTRY: Entry = _entry; 24 | const _START: _Start = _start; 25 | const _PRE_INIT: _Start = pre_init; 26 | } 27 | 28 | naked_asm!( 29 | // use core::sync::atomic::{AtomicU32, Ordering}; 30 | // 31 | // pub static CPU_ONLINE: AtomicU32 = AtomicU32::new(0); 32 | // 33 | // while CPU_ONLINE.load(Ordering::Acquire) != this { 34 | // core::hint::spin_loop(); 35 | // } 36 | "mov rax, qword ptr [rip + {cpu_online}@GOTPCREL]", 37 | "2:", 38 | "mov ecx, dword ptr [rax]", 39 | "cmp ecx, esi", 40 | "je 3f", 41 | "pause", 42 | "jmp 2b", 43 | "3:", 44 | 45 | // Overwrite RSP if `CURRENT_STACK_ADDRESS != 0` 46 | "mov rax, qword ptr [rip + {current_stack_address}@GOTPCREL]", 47 | "mov rax, qword ptr [rax]", 48 | "test rax, rax", 49 | "cmovne rsp, rax", 50 | "mov rax, qword ptr [rip + {current_stack_address}@GOTPCREL]", 51 | "mov qword ptr [rax], rsp", 52 | 53 | // Add top stack offset 54 | "add rsp, {stack_top_offset}", 55 | 56 | // Jump into Rust code 57 | "jmp {pre_init}", 58 | 59 | cpu_online = sym super::CPU_ONLINE, 60 | current_stack_address = sym super::CURRENT_STACK_ADDRESS, 61 | stack_top_offset = const KERNEL_STACK_SIZE - TaskStacks::MARKER_SIZE, 62 | pre_init = sym pre_init, 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/syscall.rs: -------------------------------------------------------------------------------- 1 | use core::arch::naked_asm; 2 | use core::mem; 3 | 4 | use super::core_local::CoreLocal; 5 | use crate::syscalls::table::SYSHANDLER_TABLE; 6 | 7 | #[unsafe(no_mangle)] 8 | #[unsafe(naked)] 9 | pub(crate) unsafe extern "C" fn syscall_handler() -> ! { 10 | naked_asm!( 11 | // save context, see x86_64 ABI 12 | "push rcx", 13 | "push rdx", 14 | "push rsi", 15 | "push rdi", 16 | "push r8", 17 | "push r9", 18 | "push r10", 19 | "push r11", 20 | // switch to kernel stack 21 | "swapgs", 22 | "mov rcx, rsp", 23 | "mov rsp, gs:{core_local_kernel_stack}", 24 | // save user stack pointer 25 | "push rcx", 26 | // copy 4th argument to rcx to adhere x86_64 ABI 27 | "mov rcx, r10", 28 | "sti", 29 | "mov r10, qword ptr [rip + {table}@GOTPCREL]", 30 | "call [r10 + 8*rax]", 31 | "cli", 32 | // restore user stack pointer 33 | "pop rcx", 34 | "mov rsp, rcx", 35 | "swapgs", 36 | // restore context, see x86_64 ABI 37 | "pop r11", 38 | "pop r10", 39 | "pop r9", 40 | "pop r8", 41 | "pop rdi", 42 | "pop rsi", 43 | "pop rdx", 44 | "pop rcx", 45 | "sysretq", 46 | core_local_kernel_stack = const mem::offset_of!(CoreLocal, kernel_stack), 47 | table = sym SYSHANDLER_TABLE, 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/arch/x86_64/kernel/vga.rs: -------------------------------------------------------------------------------- 1 | use hermit_sync::SpinMutex; 2 | use memory_addresses::{PhysAddr, VirtAddr}; 3 | use x86_64::instructions::port::Port; 4 | 5 | use crate::arch::x86_64::mm::paging; 6 | use crate::arch::x86_64::mm::paging::{BasePageSize, PageTableEntryFlags, PageTableEntryFlagsExt}; 7 | 8 | const CRT_CONTROLLER_ADDRESS: Port = Port::new(0x3d4); 9 | const CRT_CONTROLLER_DATA: Port = Port::new(0x3d5); 10 | const CURSOR_START_REGISTER: u8 = 0x0a; 11 | const CURSOR_DISABLE: u8 = 0x20; 12 | 13 | const ATTRIBUTE_BLACK: u8 = 0x00; 14 | const ATTRIBUTE_LIGHTGREY: u8 = 0x07; 15 | const COLS: usize = 80; 16 | const ROWS: usize = 25; 17 | const VGA_BUFFER_ADDRESS: PhysAddr = PhysAddr::new(0xb8000); 18 | 19 | static VGA_SCREEN: SpinMutex = SpinMutex::new(VgaScreen::new()); 20 | 21 | #[derive(Clone, Copy)] 22 | #[repr(C, packed)] 23 | struct VgaCharacter { 24 | character: u8, 25 | attribute: u8, 26 | } 27 | 28 | impl VgaCharacter { 29 | const fn new(character: u8, attribute: u8) -> Self { 30 | Self { 31 | character, 32 | attribute, 33 | } 34 | } 35 | } 36 | 37 | struct VgaScreen { 38 | buffer: *mut [[VgaCharacter; COLS]; ROWS], 39 | current_col: usize, 40 | current_row: usize, 41 | is_initialized: bool, 42 | } 43 | 44 | // FIXME: make `buffer` implement `Send` instead 45 | unsafe impl Send for VgaScreen {} 46 | 47 | impl VgaScreen { 48 | const fn new() -> Self { 49 | Self { 50 | buffer: VGA_BUFFER_ADDRESS.as_u64() as *mut _, 51 | current_col: 0, 52 | current_row: 0, 53 | is_initialized: false, 54 | } 55 | } 56 | 57 | fn init(&mut self) { 58 | // Identity map the VGA buffer. We only need the first page. 59 | let mut flags = PageTableEntryFlags::empty(); 60 | flags.device().writable().execute_disable(); 61 | paging::map::( 62 | VirtAddr::new(VGA_BUFFER_ADDRESS.as_u64()), 63 | VGA_BUFFER_ADDRESS, 64 | 1, 65 | flags, 66 | ); 67 | 68 | // Disable the cursor. 69 | let mut crt_controller_address = CRT_CONTROLLER_ADDRESS; 70 | let mut crt_controller_data = CRT_CONTROLLER_DATA; 71 | unsafe { 72 | crt_controller_address.write(CURSOR_START_REGISTER); 73 | crt_controller_data.write(CURSOR_DISABLE); 74 | } 75 | 76 | // Clear the screen. 77 | for r in 0..ROWS { 78 | self.clear_row(r); 79 | } 80 | 81 | // Initialization done! 82 | self.is_initialized = true; 83 | } 84 | 85 | #[inline] 86 | fn clear_row(&mut self, row: usize) { 87 | // Overwrite this row by a bogus character in black. 88 | for c in 0..COLS { 89 | unsafe { 90 | (*self.buffer)[row][c] = VgaCharacter::new(0, ATTRIBUTE_BLACK); 91 | } 92 | } 93 | } 94 | 95 | fn write_byte(&mut self, byte: u8) { 96 | if !self.is_initialized { 97 | return; 98 | } 99 | 100 | // Move to the next row if we have a newline character or hit the end of a column. 101 | if byte == b'\n' || self.current_col == COLS { 102 | self.current_col = 0; 103 | self.current_row += 1; 104 | } 105 | 106 | // Check if we have hit the end of the screen rows. 107 | if self.current_row == ROWS { 108 | // Shift all rows up by one line, removing the oldest visible screen row. 109 | for r in 1..ROWS { 110 | for c in 0..COLS { 111 | unsafe { 112 | (*self.buffer)[r - 1][c] = (*self.buffer)[r][c]; 113 | } 114 | } 115 | } 116 | 117 | // Clear the last screen row and write to it next time. 118 | self.clear_row(ROWS - 1); 119 | self.current_row = ROWS - 1; 120 | } 121 | 122 | if byte != b'\n' { 123 | // Put our character into the VGA screen buffer and advance the column counter. 124 | unsafe { 125 | (*self.buffer)[self.current_row][self.current_col] = 126 | VgaCharacter::new(byte, ATTRIBUTE_LIGHTGREY); 127 | } 128 | self.current_col += 1; 129 | } 130 | } 131 | } 132 | 133 | pub fn init() { 134 | VGA_SCREEN.lock().init(); 135 | } 136 | 137 | pub fn write_byte(byte: u8) { 138 | VGA_SCREEN.lock().write_byte(byte); 139 | } 140 | -------------------------------------------------------------------------------- /src/arch/x86_64/mm/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod paging; 2 | 3 | use memory_addresses::arch::x86_64::{PhysAddr, VirtAddr}; 4 | #[cfg(feature = "common-os")] 5 | use x86_64::structures::paging::{PageSize, Size4KiB as BasePageSize}; 6 | 7 | pub use self::paging::init_page_tables; 8 | #[cfg(feature = "common-os")] 9 | use crate::arch::mm::paging::{PageTableEntryFlags, PageTableEntryFlagsExt}; 10 | 11 | #[cfg(feature = "common-os")] 12 | pub fn create_new_root_page_table() -> usize { 13 | use x86_64::registers::control::Cr3; 14 | 15 | let physaddr = crate::mm::physicalmem::allocate_aligned( 16 | BasePageSize::SIZE as usize, 17 | BasePageSize::SIZE as usize, 18 | ) 19 | .unwrap(); 20 | let virtaddr = crate::mm::virtualmem::allocate_aligned( 21 | 2 * BasePageSize::SIZE as usize, 22 | BasePageSize::SIZE as usize, 23 | ) 24 | .unwrap(); 25 | let mut flags = PageTableEntryFlags::empty(); 26 | flags.normal().writable(); 27 | 28 | let entry: u64 = unsafe { 29 | let (frame, _flags) = Cr3::read(); 30 | paging::map::(virtaddr, frame.start_address().into(), 1, flags); 31 | let entry: &u64 = &*virtaddr.as_ptr(); 32 | 33 | *entry 34 | }; 35 | 36 | let slice_addr = virtaddr + BasePageSize::SIZE; 37 | paging::map::(slice_addr, physaddr, 1, flags); 38 | 39 | unsafe { 40 | let pml4 = core::slice::from_raw_parts_mut(slice_addr.as_mut_ptr(), 512); 41 | 42 | // clear PML4 43 | for elem in pml4.iter_mut() { 44 | *elem = 0; 45 | } 46 | 47 | // copy first element and the self reference 48 | pml4[0] = entry; 49 | // create self reference 50 | pml4[511] = physaddr.as_u64() + 0x3; // PG_PRESENT | PG_RW 51 | }; 52 | 53 | paging::unmap::(virtaddr, 2); 54 | crate::mm::virtualmem::deallocate(virtaddr, 2 * BasePageSize::SIZE as usize); 55 | 56 | physaddr.as_usize() 57 | } 58 | 59 | pub fn init() { 60 | paging::init(); 61 | crate::mm::physicalmem::init(); 62 | crate::mm::virtualmem::init(); 63 | 64 | #[cfg(feature = "common-os")] 65 | { 66 | use x86_64::registers::control::Cr3; 67 | 68 | let (frame, _flags) = Cr3::read(); 69 | crate::scheduler::BOOT_ROOT_PAGE_TABLE 70 | .set(frame.start_address().as_u64().try_into().unwrap()) 71 | .unwrap(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/arch/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod kernel; 2 | pub mod mm; 3 | 4 | #[cfg(feature = "common-os")] 5 | use x86_64::registers::segmentation::SegmentSelector; 6 | 7 | use crate::arch::mm::paging::ExceptionStackFrame; 8 | 9 | /// Helper function to swap the GS register, if the user-space is 10 | /// is interrupted. 11 | #[cfg(feature = "common-os")] 12 | #[inline(always)] 13 | pub(crate) fn swapgs(stack_frame: &ExceptionStackFrame) { 14 | use core::arch::asm; 15 | if stack_frame.code_segment != SegmentSelector(8) { 16 | unsafe { 17 | asm!("swapgs", options(nomem, nostack, preserves_flags)); 18 | } 19 | } 20 | } 21 | 22 | #[cfg(not(feature = "common-os"))] 23 | #[inline(always)] 24 | pub(crate) fn swapgs(_stack_frame: &ExceptionStackFrame) {} 25 | 26 | /// Force strict CPU ordering, serializes load and store operations. 27 | #[allow(dead_code)] 28 | #[inline(always)] 29 | pub(crate) fn memory_barrier() { 30 | use core::arch::asm; 31 | unsafe { 32 | asm!("mfence", options(nostack, nomem, preserves_flags),); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | pub(crate) const KERNEL_STACK_SIZE: usize = 0x8000; 2 | 3 | pub const DEFAULT_STACK_SIZE: usize = 0x0001_0000; 4 | 5 | pub(crate) const USER_STACK_SIZE: usize = 0x0010_0000; 6 | 7 | #[cfg(any( 8 | all(any(feature = "tcp", feature = "udp"), not(feature = "rtl8139")), 9 | feature = "fuse", 10 | feature = "vsock" 11 | ))] 12 | pub(crate) const VIRTIO_MAX_QUEUE_SIZE: u16 = if cfg!(feature = "pci") { 2048 } else { 1024 }; 13 | 14 | /// Default keep alive interval in milliseconds 15 | #[cfg(feature = "tcp")] 16 | pub(crate) const DEFAULT_KEEP_ALIVE_INTERVAL: u64 = 75000; 17 | 18 | #[cfg(feature = "vsock")] 19 | pub(crate) const VSOCK_PACKET_SIZE: u32 = 8192; 20 | -------------------------------------------------------------------------------- /src/console.rs: -------------------------------------------------------------------------------- 1 | use core::task::Waker; 2 | use core::{fmt, mem}; 3 | 4 | use heapless::Vec; 5 | use hermit_sync::{InterruptTicketMutex, Lazy}; 6 | 7 | use crate::arch; 8 | 9 | const SERIAL_BUFFER_SIZE: usize = 256; 10 | 11 | pub(crate) struct Console { 12 | pub inner: arch::kernel::Console, 13 | buffer: Vec, 14 | } 15 | 16 | impl Console { 17 | fn new() -> Self { 18 | Self { 19 | inner: arch::kernel::Console::new(), 20 | buffer: Vec::new(), 21 | } 22 | } 23 | 24 | pub fn write(&mut self, buf: &[u8]) { 25 | if SERIAL_BUFFER_SIZE - self.buffer.len() >= buf.len() { 26 | // unwrap: we checked that buf fits in self.buffer 27 | self.buffer.extend_from_slice(buf).unwrap(); 28 | if buf.contains(&b'\n') { 29 | self.inner.write(&self.buffer); 30 | self.buffer.clear(); 31 | } 32 | } else { 33 | self.inner.write(&self.buffer); 34 | self.buffer.clear(); 35 | if buf.len() >= SERIAL_BUFFER_SIZE { 36 | self.inner.write(buf); 37 | } else { 38 | // unwrap: we checked that buf fits in self.buffer 39 | self.buffer.extend_from_slice(buf).unwrap(); 40 | if buf.contains(&b'\n') { 41 | self.inner.write(&self.buffer); 42 | self.buffer.clear(); 43 | } 44 | } 45 | } 46 | } 47 | 48 | pub fn read(&mut self) -> Option { 49 | self.inner.read() 50 | } 51 | 52 | pub fn is_empty(&self) -> bool { 53 | self.inner.is_empty() 54 | } 55 | 56 | pub fn register_waker(&mut self, waker: &Waker) { 57 | self.inner.register_waker(waker); 58 | } 59 | } 60 | 61 | /// A collection of methods that are required to format 62 | /// a message to Hermit's console. 63 | impl fmt::Write for Console { 64 | /// Print a string of characters. 65 | #[inline] 66 | fn write_str(&mut self, s: &str) -> fmt::Result { 67 | if !s.is_empty() { 68 | self.write(s.as_bytes()); 69 | } 70 | 71 | Ok(()) 72 | } 73 | } 74 | 75 | pub(crate) static CONSOLE: Lazy> = 76 | Lazy::new(|| InterruptTicketMutex::new(Console::new())); 77 | 78 | #[doc(hidden)] 79 | pub fn _print(args: fmt::Arguments<'_>) { 80 | use fmt::Write; 81 | CONSOLE.lock().write_fmt(args).unwrap(); 82 | } 83 | 84 | #[doc(hidden)] 85 | pub fn _panic_print(args: fmt::Arguments<'_>) { 86 | use fmt::Write; 87 | let mut console = unsafe { CONSOLE.make_guard_unchecked() }; 88 | console.write_fmt(args).ok(); 89 | mem::forget(console); 90 | } 91 | 92 | #[cfg(all(test, not(target_os = "none")))] 93 | mod tests { 94 | use super::*; 95 | 96 | #[test] 97 | fn test_console() { 98 | println!("HelloWorld"); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/drivers/fs/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "pci")] 2 | pub mod virtio_fs; 3 | #[cfg(feature = "pci")] 4 | pub mod virtio_pci; 5 | -------------------------------------------------------------------------------- /src/drivers/fs/virtio_pci.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | use volatile::VolatileRef; 4 | 5 | use crate::arch::pci::PciConfigRegion; 6 | use crate::drivers::fs::virtio_fs::{FsDevCfg, VirtioFsDriver}; 7 | use crate::drivers::pci::PciDevice; 8 | use crate::drivers::virtio::error::{self, VirtioError}; 9 | use crate::drivers::virtio::transport::pci; 10 | use crate::drivers::virtio::transport::pci::{PciCap, UniCapsColl}; 11 | 12 | impl VirtioFsDriver { 13 | fn map_cfg(cap: &PciCap) -> Option { 14 | let dev_cfg = pci::map_dev_cfg::(cap)?; 15 | 16 | let dev_cfg = VolatileRef::from_ref(dev_cfg); 17 | 18 | Some(FsDevCfg { 19 | raw: dev_cfg, 20 | dev_id: cap.dev_id(), 21 | features: virtio::fs::F::empty(), 22 | }) 23 | } 24 | 25 | /// Instantiates a new (VirtioFsDriver)[VirtioFsDriver] struct, by checking the available 26 | /// configuration structures and moving them into the struct. 27 | pub fn new( 28 | caps_coll: UniCapsColl, 29 | device: &PciDevice, 30 | ) -> Result { 31 | let device_id = device.device_id(); 32 | 33 | let UniCapsColl { 34 | com_cfg, 35 | notif_cfg, 36 | isr_cfg, 37 | dev_cfg_list, 38 | .. 39 | } = caps_coll; 40 | 41 | let Some(dev_cfg) = dev_cfg_list.iter().find_map(VirtioFsDriver::map_cfg) else { 42 | error!("No dev config. Aborting!"); 43 | return Err(error::VirtioFsError::NoDevCfg(device_id)); 44 | }; 45 | 46 | Ok(VirtioFsDriver { 47 | dev_cfg, 48 | com_cfg, 49 | isr_stat: isr_cfg, 50 | notif_cfg, 51 | vqueues: Vec::new(), 52 | irq: device.get_irq().unwrap(), 53 | }) 54 | } 55 | 56 | /// Initializes virtio filesystem device 57 | pub fn init(device: &PciDevice) -> Result { 58 | let mut drv = match pci::map_caps(device) { 59 | Ok(caps) => match VirtioFsDriver::new(caps, device) { 60 | Ok(driver) => driver, 61 | Err(fs_err) => { 62 | error!("Initializing new network driver failed. Aborting!"); 63 | return Err(VirtioError::FsDriver(fs_err)); 64 | } 65 | }, 66 | Err(err) => { 67 | error!("Mapping capabilities failed. Aborting!"); 68 | return Err(err); 69 | } 70 | }; 71 | 72 | match drv.init_dev() { 73 | Ok(()) => info!( 74 | "Filesystem device with id {:x}, has been initialized by driver!", 75 | drv.get_dev_id() 76 | ), 77 | Err(fs_err) => { 78 | drv.set_failed(); 79 | return Err(VirtioError::FsDriver(fs_err)); 80 | } 81 | } 82 | 83 | Ok(drv) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/drivers/mmio.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "tcp", feature = "udp"))] 2 | use alloc::collections::VecDeque; 3 | 4 | use ahash::RandomState; 5 | use hashbrown::HashMap; 6 | 7 | #[cfg(any(feature = "tcp", feature = "udp"))] 8 | pub(crate) use crate::arch::kernel::mmio::get_network_driver; 9 | #[cfg(any(feature = "tcp", feature = "udp"))] 10 | use crate::drivers::Driver; 11 | #[cfg(any(feature = "tcp", feature = "udp"))] 12 | use crate::drivers::net::NetworkDriver; 13 | use crate::drivers::{InterruptHandlerQueue, InterruptLine}; 14 | 15 | pub(crate) fn get_interrupt_handlers() -> HashMap 16 | { 17 | #[allow(unused_mut)] 18 | let mut handlers: HashMap = 19 | HashMap::with_hasher(RandomState::with_seeds(0, 0, 0, 0)); 20 | 21 | #[cfg(any(feature = "tcp", feature = "udp"))] 22 | if let Some(drv) = get_network_driver() { 23 | fn network_handler() { 24 | if let Some(driver) = get_network_driver() { 25 | driver.lock().handle_interrupt(); 26 | } 27 | } 28 | 29 | let irq_number = drv.lock().get_interrupt_number(); 30 | 31 | if let Some(map) = handlers.get_mut(&irq_number) { 32 | map.push_back(network_handler); 33 | } else { 34 | let mut map: InterruptHandlerQueue = VecDeque::new(); 35 | map.push_back(network_handler); 36 | handlers.insert(irq_number, map); 37 | } 38 | } 39 | 40 | handlers 41 | } 42 | -------------------------------------------------------------------------------- /src/drivers/mod.rs: -------------------------------------------------------------------------------- 1 | //! A module containing hermit-rs driver, hermit-rs driver trait and driver specific errors. 2 | 3 | #[cfg(feature = "fuse")] 4 | pub mod fs; 5 | #[cfg(not(feature = "pci"))] 6 | pub mod mmio; 7 | #[cfg(any(feature = "tcp", feature = "udp"))] 8 | pub mod net; 9 | #[cfg(feature = "pci")] 10 | pub mod pci; 11 | #[cfg(any( 12 | all(any(feature = "tcp", feature = "udp"), not(feature = "rtl8139")), 13 | feature = "fuse", 14 | feature = "vsock" 15 | ))] 16 | pub mod virtio; 17 | #[cfg(feature = "vsock")] 18 | pub mod vsock; 19 | 20 | use alloc::collections::VecDeque; 21 | 22 | #[cfg(feature = "pci")] 23 | pub(crate) use pci_types::InterruptLine; 24 | #[cfg(not(feature = "pci"))] 25 | pub(crate) type InterruptLine = u8; 26 | 27 | pub(crate) type InterruptHandlerQueue = VecDeque; 28 | 29 | /// A common error module for drivers. 30 | /// [DriverError](error::DriverError) values will be 31 | /// passed on to higher layers. 32 | pub mod error { 33 | use core::fmt; 34 | 35 | #[cfg(all(target_arch = "riscv64", feature = "gem-net"))] 36 | use crate::drivers::net::gem::GEMError; 37 | #[cfg(all(target_arch = "x86_64", feature = "rtl8139"))] 38 | use crate::drivers::net::rtl8139::RTL8139Error; 39 | #[cfg(any( 40 | all(any(feature = "tcp", feature = "udp"), not(feature = "rtl8139")), 41 | feature = "fuse", 42 | feature = "vsock" 43 | ))] 44 | use crate::drivers::virtio::error::VirtioError; 45 | 46 | #[derive(Debug)] 47 | pub enum DriverError { 48 | #[cfg(any( 49 | all(any(feature = "tcp", feature = "udp"), not(feature = "rtl8139")), 50 | feature = "fuse", 51 | feature = "vsock" 52 | ))] 53 | InitVirtioDevFail(VirtioError), 54 | #[cfg(all(target_arch = "x86_64", feature = "rtl8139"))] 55 | InitRTL8139DevFail(RTL8139Error), 56 | #[cfg(all(target_arch = "riscv64", feature = "gem-net"))] 57 | InitGEMDevFail(GEMError), 58 | } 59 | 60 | #[cfg(any( 61 | all(any(feature = "tcp", feature = "udp"), not(feature = "rtl8139")), 62 | feature = "fuse", 63 | feature = "vsock" 64 | ))] 65 | impl From for DriverError { 66 | fn from(err: VirtioError) -> Self { 67 | DriverError::InitVirtioDevFail(err) 68 | } 69 | } 70 | 71 | #[cfg(all(target_arch = "x86_64", feature = "rtl8139"))] 72 | impl From for DriverError { 73 | fn from(err: RTL8139Error) -> Self { 74 | DriverError::InitRTL8139DevFail(err) 75 | } 76 | } 77 | 78 | #[cfg(all(target_arch = "riscv64", feature = "gem-net"))] 79 | impl From for DriverError { 80 | fn from(err: GEMError) -> Self { 81 | DriverError::InitGEMDevFail(err) 82 | } 83 | } 84 | 85 | impl fmt::Display for DriverError { 86 | #[allow(unused_variables)] 87 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 | match *self { 89 | #[cfg(any( 90 | all(any(feature = "tcp", feature = "udp"), not(feature = "rtl8139")), 91 | feature = "fuse", 92 | feature = "vsock" 93 | ))] 94 | DriverError::InitVirtioDevFail(ref err) => { 95 | write!(f, "Virtio driver failed: {err:?}") 96 | } 97 | #[cfg(all(target_arch = "x86_64", feature = "rtl8139"))] 98 | DriverError::InitRTL8139DevFail(ref err) => { 99 | write!(f, "RTL8139 driver failed: {err:?}") 100 | } 101 | #[cfg(all(target_arch = "riscv64", feature = "gem-net"))] 102 | DriverError::InitGEMDevFail(ref err) => { 103 | write!(f, "GEM driver failed: {err:?}") 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | /// A trait to determine general driver information 111 | #[allow(dead_code)] 112 | pub(crate) trait Driver { 113 | /// Returns the interrupt number of the device 114 | fn get_interrupt_number(&self) -> InterruptLine; 115 | 116 | /// Returns the device driver name 117 | fn get_name(&self) -> &'static str; 118 | } 119 | 120 | pub(crate) fn init() { 121 | // Initialize PCI Drivers 122 | #[cfg(feature = "pci")] 123 | crate::drivers::pci::init(); 124 | #[cfg(all( 125 | not(feature = "pci"), 126 | target_arch = "x86_64", 127 | any(feature = "tcp", feature = "udp") 128 | ))] 129 | crate::arch::x86_64::kernel::mmio::init_drivers(); 130 | 131 | #[cfg(target_arch = "riscv64")] 132 | crate::arch::riscv64::kernel::init_drivers(); 133 | 134 | crate::arch::interrupts::install_handlers(); 135 | } 136 | -------------------------------------------------------------------------------- /src/drivers/net/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(target_arch = "riscv64", feature = "gem-net"))] 2 | pub mod gem; 3 | #[cfg(all(target_arch = "x86_64", feature = "rtl8139"))] 4 | pub mod rtl8139; 5 | #[cfg(not(all(target_arch = "x86_64", feature = "rtl8139")))] 6 | pub mod virtio; 7 | 8 | use smoltcp::phy::ChecksumCapabilities; 9 | 10 | #[allow(unused_imports)] 11 | use crate::arch::kernel::core_local::*; 12 | use crate::drivers::Driver; 13 | use crate::executor::device::{RxToken, TxToken}; 14 | 15 | /// A trait for accessing the network interface 16 | pub(crate) trait NetworkDriver: Driver { 17 | /// Returns smoltcp's checksum capabilities 18 | fn get_checksums(&self) -> ChecksumCapabilities { 19 | ChecksumCapabilities::default() 20 | } 21 | /// Returns the mac address of the device. 22 | fn get_mac_address(&self) -> [u8; 6]; 23 | /// Returns the current MTU of the device. 24 | fn get_mtu(&self) -> u16; 25 | /// Get buffer with the received packet 26 | fn receive_packet(&mut self) -> Option<(RxToken, TxToken)>; 27 | /// Send packet with the size `len` 28 | fn send_packet(&mut self, len: usize, f: F) -> R 29 | where 30 | F: FnOnce(&mut [u8]) -> R; 31 | /// Check if a packet is available 32 | #[allow(dead_code)] 33 | fn has_packet(&self) -> bool; 34 | /// Enable / disable the polling mode of the network interface 35 | fn set_polling_mode(&mut self, value: bool); 36 | /// Handle interrupt and check if a packet is available 37 | fn handle_interrupt(&mut self); 38 | } 39 | -------------------------------------------------------------------------------- /src/drivers/net/virtio/mmio.rs: -------------------------------------------------------------------------------- 1 | //! A module containing a virtio network driver. 2 | //! 3 | //! The module contains ... 4 | 5 | use core::str::FromStr; 6 | 7 | use smoltcp::phy::ChecksumCapabilities; 8 | use virtio::mmio::{DeviceRegisters, DeviceRegistersVolatileFieldAccess}; 9 | use volatile::VolatileRef; 10 | 11 | use crate::drivers::InterruptLine; 12 | use crate::drivers::net::virtio::{Init, NetDevCfg, Uninit, VirtioNetDriver}; 13 | use crate::drivers::virtio::error::{VirtioError, VirtioNetError}; 14 | use crate::drivers::virtio::transport::mmio::{ComCfg, IsrStatus, NotifCfg}; 15 | 16 | // Backend-dependent interface for Virtio network driver 17 | impl VirtioNetDriver { 18 | pub fn new( 19 | dev_id: u16, 20 | mut registers: VolatileRef<'static, DeviceRegisters>, 21 | irq: InterruptLine, 22 | ) -> Result { 23 | let dev_cfg_raw: &'static virtio::net::Config = unsafe { 24 | &*registers 25 | .borrow_mut() 26 | .as_mut_ptr() 27 | .config() 28 | .as_raw_ptr() 29 | .cast::() 30 | .as_ptr() 31 | }; 32 | let dev_cfg_raw = VolatileRef::from_ref(dev_cfg_raw); 33 | let dev_cfg = NetDevCfg { 34 | raw: dev_cfg_raw, 35 | dev_id, 36 | features: virtio::net::F::empty(), 37 | }; 38 | let isr_stat = IsrStatus::new(registers.borrow_mut()); 39 | let notif_cfg = NotifCfg::new(registers.borrow_mut()); 40 | 41 | let mtu = if let Some(my_mtu) = hermit_var!("HERMIT_MTU") { 42 | u16::from_str(&my_mtu).unwrap() 43 | } else { 44 | // fallback to the default MTU 45 | 1514 46 | }; 47 | 48 | Ok(VirtioNetDriver { 49 | dev_cfg, 50 | com_cfg: ComCfg::new(registers, 1), 51 | isr_stat, 52 | notif_cfg, 53 | inner: Uninit, 54 | num_vqs: 0, 55 | mtu, 56 | irq, 57 | checksums: ChecksumCapabilities::default(), 58 | }) 59 | } 60 | 61 | /// Initializes virtio network device by mapping configuration layout to 62 | /// respective structs (configuration structs are: 63 | /// 64 | /// Returns a driver instance of 65 | /// [VirtioNetDriver](structs.virtionetdriver.html) or an [VirtioError](enums.virtioerror.html). 66 | pub fn init( 67 | dev_id: u16, 68 | registers: VolatileRef<'static, DeviceRegisters>, 69 | irq: InterruptLine, 70 | ) -> Result, VirtioError> { 71 | if let Ok(drv) = VirtioNetDriver::new(dev_id, registers, irq) { 72 | match drv.init_dev() { 73 | Err(error_code) => Err(VirtioError::NetDriver(error_code)), 74 | Ok(mut initialized_drv) => { 75 | initialized_drv.print_information(); 76 | Ok(initialized_drv) 77 | } 78 | } 79 | } else { 80 | error!("Unable to create Driver. Aborting!"); 81 | Err(VirtioError::Unknown) 82 | } 83 | } 84 | } 85 | 86 | impl VirtioNetDriver { 87 | pub fn print_information(&mut self) { 88 | self.com_cfg.print_information(); 89 | if self.dev_status() == virtio::net::S::LINK_UP { 90 | info!("The link of the network device is up!"); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/drivers/net/virtio/pci.rs: -------------------------------------------------------------------------------- 1 | //! A module containing a virtio network driver. 2 | //! 3 | //! The module contains ... 4 | 5 | use core::str::FromStr; 6 | 7 | use pci_types::CommandRegister; 8 | use smoltcp::phy::ChecksumCapabilities; 9 | use volatile::VolatileRef; 10 | 11 | use super::{Init, Uninit}; 12 | use crate::arch::pci::PciConfigRegion; 13 | use crate::drivers::net::virtio::{NetDevCfg, VirtioNetDriver}; 14 | use crate::drivers::pci::PciDevice; 15 | use crate::drivers::virtio::error::{self, VirtioError}; 16 | use crate::drivers::virtio::transport::pci; 17 | use crate::drivers::virtio::transport::pci::{PciCap, UniCapsColl}; 18 | 19 | // Backend-dependent interface for Virtio network driver 20 | impl VirtioNetDriver { 21 | fn map_cfg(cap: &PciCap) -> Option { 22 | let dev_cfg = pci::map_dev_cfg::(cap)?; 23 | 24 | let dev_cfg = VolatileRef::from_ref(dev_cfg); 25 | 26 | Some(NetDevCfg { 27 | raw: dev_cfg, 28 | dev_id: cap.dev_id(), 29 | features: virtio::net::F::empty(), 30 | }) 31 | } 32 | 33 | /// Instantiates a new (VirtioNetDriver)[VirtioNetDriver] struct, by checking the available 34 | /// configuration structures and moving them into the struct. 35 | pub(crate) fn new( 36 | caps_coll: UniCapsColl, 37 | device: &PciDevice, 38 | ) -> Result { 39 | let device_id = device.device_id(); 40 | let UniCapsColl { 41 | com_cfg, 42 | notif_cfg, 43 | isr_cfg, 44 | dev_cfg_list, 45 | .. 46 | } = caps_coll; 47 | 48 | let Some(dev_cfg) = dev_cfg_list.iter().find_map(VirtioNetDriver::map_cfg) else { 49 | error!("No dev config. Aborting!"); 50 | return Err(error::VirtioNetError::NoDevCfg(device_id)); 51 | }; 52 | 53 | let mtu = if let Some(my_mtu) = hermit_var!("HERMIT_MTU") { 54 | u16::from_str(&my_mtu).unwrap() 55 | } else { 56 | // fallback to the default MTU 57 | 1514 58 | }; 59 | 60 | Ok(VirtioNetDriver { 61 | dev_cfg, 62 | com_cfg, 63 | isr_stat: isr_cfg, 64 | notif_cfg, 65 | inner: Uninit, 66 | num_vqs: 0, 67 | mtu, 68 | irq: device.get_irq().unwrap(), 69 | checksums: ChecksumCapabilities::default(), 70 | }) 71 | } 72 | 73 | /// Initializes virtio network device by mapping configuration layout to 74 | /// respective structs (configuration structs are: 75 | /// [ComCfg](structs.comcfg.html), [NotifCfg](structs.notifcfg.html) 76 | /// [IsrStatus](structs.isrstatus.html), [PciCfg](structs.pcicfg.html) 77 | /// [ShMemCfg](structs.ShMemCfg)). 78 | /// 79 | /// Returns a driver instance of 80 | /// [VirtioNetDriver](structs.virtionetdriver.html) or an [VirtioError](enums.virtioerror.html). 81 | pub(crate) fn init( 82 | device: &PciDevice, 83 | ) -> Result, VirtioError> { 84 | // enable bus master mode 85 | device.set_command(CommandRegister::BUS_MASTER_ENABLE); 86 | 87 | let drv = match pci::map_caps(device) { 88 | Ok(caps) => match VirtioNetDriver::new(caps, device) { 89 | Ok(driver) => driver, 90 | Err(vnet_err) => { 91 | error!("Initializing new network driver failed. Aborting!"); 92 | return Err(VirtioError::NetDriver(vnet_err)); 93 | } 94 | }, 95 | Err(err) => { 96 | error!("Mapping capabilities failed. Aborting!"); 97 | return Err(err); 98 | } 99 | }; 100 | 101 | let initialized_drv = match drv.init_dev() { 102 | Ok(initialized_drv) => { 103 | info!( 104 | "Network device with id {:x}, has been initialized by driver!", 105 | initialized_drv.get_dev_id() 106 | ); 107 | initialized_drv 108 | } 109 | Err(vnet_err) => { 110 | return Err(VirtioError::NetDriver(vnet_err)); 111 | } 112 | }; 113 | 114 | if initialized_drv.is_link_up() { 115 | info!("Virtio-net link is up after initialization."); 116 | } else { 117 | info!("Virtio-net link is down after initialization!"); 118 | } 119 | 120 | Ok(initialized_drv) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/drivers/virtio/transport/mod.rs: -------------------------------------------------------------------------------- 1 | //! A module containing virtios transport mechanisms. 2 | //! 3 | //! The module contains only PCI specific transport mechanism. 4 | //! Other mechanisms (MMIO and Channel I/O) are currently not 5 | //! supported. 6 | 7 | #[cfg(not(feature = "pci"))] 8 | pub mod mmio; 9 | #[cfg(feature = "pci")] 10 | pub mod pci; 11 | -------------------------------------------------------------------------------- /src/drivers/vsock/pci.rs: -------------------------------------------------------------------------------- 1 | use crate::arch::pci::PciConfigRegion; 2 | use crate::drivers::pci::PciDevice; 3 | use crate::drivers::virtio::error::{self, VirtioError}; 4 | use crate::drivers::virtio::transport::pci; 5 | use crate::drivers::virtio::transport::pci::{PciCap, UniCapsColl}; 6 | use crate::drivers::vsock::{EventQueue, RxQueue, TxQueue, VirtioVsockDriver, VsockDevCfg}; 7 | 8 | /// Virtio's socket device configuration structure. 9 | /// See specification v1.1. - 5.11.4 10 | /// 11 | #[derive(Debug, Copy, Clone)] 12 | #[repr(C)] 13 | pub(crate) struct VsockDevCfgRaw { 14 | /// The guest_cid field contains the guest’s context ID, which uniquely identifies the device 15 | /// for its lifetime. The upper 32 bits of the CID are reserved and zeroed. 16 | pub guest_cid: u64, 17 | } 18 | 19 | impl VirtioVsockDriver { 20 | fn map_cfg(cap: &PciCap) -> Option { 21 | let dev_cfg = pci::map_dev_cfg::(cap)?; 22 | 23 | Some(VsockDevCfg { 24 | raw: dev_cfg, 25 | dev_id: cap.dev_id(), 26 | features: virtio::vsock::F::empty(), 27 | }) 28 | } 29 | 30 | /// Instantiates a new VirtioVsockDriver struct, by checking the available 31 | /// configuration structures and moving them into the struct. 32 | pub fn new( 33 | caps_coll: UniCapsColl, 34 | device: &PciDevice, 35 | ) -> Result { 36 | let device_id = device.device_id(); 37 | 38 | let UniCapsColl { 39 | com_cfg, 40 | notif_cfg, 41 | isr_cfg, 42 | dev_cfg_list, 43 | .. 44 | } = caps_coll; 45 | 46 | let Some(dev_cfg) = dev_cfg_list.iter().find_map(VirtioVsockDriver::map_cfg) else { 47 | error!("No dev config. Aborting!"); 48 | return Err(error::VirtioVsockError::NoDevCfg(device_id)); 49 | }; 50 | 51 | Ok(VirtioVsockDriver { 52 | dev_cfg, 53 | com_cfg, 54 | isr_stat: isr_cfg, 55 | notif_cfg, 56 | irq: device.get_irq().unwrap(), 57 | event_vq: EventQueue::new(), 58 | recv_vq: RxQueue::new(), 59 | send_vq: TxQueue::new(), 60 | }) 61 | } 62 | 63 | /// Initializes virtio socket device 64 | /// 65 | /// Returns a driver instance of VirtioVsockDriver. 66 | pub(crate) fn init( 67 | device: &PciDevice, 68 | ) -> Result { 69 | let mut drv = match pci::map_caps(device) { 70 | Ok(caps) => match VirtioVsockDriver::new(caps, device) { 71 | Ok(driver) => driver, 72 | Err(vsock_err) => { 73 | error!("Initializing new virtio socket device driver failed. Aborting!"); 74 | return Err(VirtioError::VsockDriver(vsock_err)); 75 | } 76 | }, 77 | Err(err) => { 78 | error!("Mapping capabilities failed. Aborting!"); 79 | return Err(err); 80 | } 81 | }; 82 | 83 | match drv.init_dev() { 84 | Ok(()) => { 85 | info!( 86 | "Socket device with cid {:x}, has been initialized by driver!", 87 | drv.dev_cfg.raw.guest_cid 88 | ); 89 | 90 | Ok(drv) 91 | } 92 | Err(fs_err) => { 93 | drv.set_failed(); 94 | Err(VirtioError::VsockDriver(fs_err)) 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/entropy.rs: -------------------------------------------------------------------------------- 1 | //! Cryptographically secure random data generation. 2 | //! 3 | //! This currently uses a ChaCha-based generator (the same one Linux uses!) seeded 4 | //! with random data provided by the processor. 5 | 6 | use hermit_sync::InterruptTicketMutex; 7 | use rand_chacha::ChaCha20Rng; 8 | use rand_chacha::rand_core::{RngCore, SeedableRng}; 9 | 10 | use crate::arch::kernel::processor::{get_timer_ticks, seed_entropy}; 11 | use crate::errno::ENOSYS; 12 | 13 | // Reseed every second for increased security while maintaining the performance of 14 | // the PRNG. 15 | const RESEED_INTERVAL: u64 = 1_000_000; 16 | 17 | bitflags! { 18 | pub struct Flags: u32 {} 19 | } 20 | 21 | struct Pool { 22 | rng: ChaCha20Rng, 23 | last_reseed: u64, 24 | } 25 | 26 | static POOL: InterruptTicketMutex> = InterruptTicketMutex::new(None); 27 | 28 | /// Fills `buf` with random data, respecting the options in `flags`. 29 | /// 30 | /// Returns the number of bytes written or `-ENOSYS` if the system does not support 31 | /// random data generation. 32 | pub fn read(buf: &mut [u8], _flags: Flags) -> isize { 33 | let pool = &mut *POOL.lock(); 34 | let now = get_timer_ticks(); 35 | let pool = match pool { 36 | Some(pool) if now.saturating_sub(pool.last_reseed) <= RESEED_INTERVAL => pool, 37 | pool => { 38 | if let Some(seed) = seed_entropy() { 39 | pool.insert(Pool { 40 | rng: ChaCha20Rng::from_seed(seed), 41 | last_reseed: now, 42 | }) 43 | } else { 44 | return -ENOSYS as isize; 45 | } 46 | } 47 | }; 48 | 49 | pool.rng.fill_bytes(buf); 50 | // Slice lengths are always <= isize::MAX so this return value cannot conflict 51 | // with error numbers. 52 | buf.len() as isize 53 | } 54 | -------------------------------------------------------------------------------- /src/executor/task.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use core::fmt; 3 | use core::future::Future; 4 | use core::pin::Pin; 5 | use core::sync::atomic::{AtomicU32, Ordering}; 6 | use core::task::{Context, Poll}; 7 | 8 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 9 | pub(crate) struct AsyncTaskId(u32); 10 | 11 | impl AsyncTaskId { 12 | pub const fn into(self) -> u32 { 13 | self.0 14 | } 15 | 16 | pub const fn from(x: u32) -> Self { 17 | AsyncTaskId(x) 18 | } 19 | } 20 | 21 | impl fmt::Display for AsyncTaskId { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | write!(f, "{}", self.0) 24 | } 25 | } 26 | 27 | impl AsyncTaskId { 28 | fn new() -> Self { 29 | static NEXT_ID: AtomicU32 = AtomicU32::new(0); 30 | AsyncTaskId(NEXT_ID.fetch_add(1, Ordering::Relaxed)) 31 | } 32 | } 33 | 34 | pub(crate) struct AsyncTask { 35 | id: AsyncTaskId, 36 | future: Pin>>, 37 | } 38 | 39 | impl AsyncTask { 40 | pub fn new(future: impl Future + 'static) -> AsyncTask { 41 | AsyncTask { 42 | id: AsyncTaskId::new(), 43 | future: Box::pin(future), 44 | } 45 | } 46 | 47 | pub fn id(&self) -> AsyncTaskId { 48 | self.id 49 | } 50 | 51 | pub fn poll(&mut self, context: &mut Context<'_>) -> Poll<()> { 52 | self.future.as_mut().poll(context) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/fd/eventfd.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::collections::vec_deque::VecDeque; 3 | use core::future::{self, Future}; 4 | use core::mem::{self, MaybeUninit}; 5 | use core::task::{Poll, Waker, ready}; 6 | 7 | use async_lock::Mutex; 8 | use async_trait::async_trait; 9 | 10 | use crate::fd::{EventFlags, ObjectInterface, PollEvent}; 11 | use crate::io; 12 | 13 | #[derive(Debug)] 14 | struct EventState { 15 | pub counter: u64, 16 | pub read_queue: VecDeque, 17 | pub write_queue: VecDeque, 18 | } 19 | 20 | impl EventState { 21 | pub fn new(counter: u64) -> Self { 22 | Self { 23 | counter, 24 | read_queue: VecDeque::new(), 25 | write_queue: VecDeque::new(), 26 | } 27 | } 28 | } 29 | 30 | #[derive(Debug)] 31 | pub(crate) struct EventFd { 32 | state: Mutex, 33 | flags: EventFlags, 34 | } 35 | 36 | impl EventFd { 37 | pub fn new(initval: u64, flags: EventFlags) -> Self { 38 | debug!("Create EventFd {initval}, {flags:?}"); 39 | Self { 40 | state: Mutex::new(EventState::new(initval)), 41 | flags, 42 | } 43 | } 44 | } 45 | 46 | #[async_trait] 47 | impl ObjectInterface for EventFd { 48 | async fn read(&self, buf: &mut [MaybeUninit]) -> io::Result { 49 | let len = mem::size_of::(); 50 | 51 | if buf.len() < len { 52 | return Err(io::Error::EINVAL); 53 | } 54 | 55 | future::poll_fn(|cx| { 56 | if self.flags.contains(EventFlags::EFD_SEMAPHORE) { 57 | let mut pinned = core::pin::pin!(self.state.lock()); 58 | let mut guard = ready!(pinned.as_mut().poll(cx)); 59 | if guard.counter > 0 { 60 | guard.counter -= 1; 61 | buf[..len].write_copy_of_slice(&u64::to_ne_bytes(1)); 62 | if let Some(cx) = guard.write_queue.pop_front() { 63 | cx.wake_by_ref(); 64 | } 65 | Poll::Ready(Ok(len)) 66 | } else { 67 | guard.read_queue.push_back(cx.waker().clone()); 68 | Poll::Pending 69 | } 70 | } else { 71 | let mut pinned = core::pin::pin!(self.state.lock()); 72 | let mut guard = ready!(pinned.as_mut().poll(cx)); 73 | let tmp = guard.counter; 74 | if tmp > 0 { 75 | guard.counter = 0; 76 | buf[..len].write_copy_of_slice(&u64::to_ne_bytes(tmp)); 77 | if let Some(cx) = guard.read_queue.pop_front() { 78 | cx.wake_by_ref(); 79 | } 80 | Poll::Ready(Ok(len)) 81 | } else if self.flags.contains(EventFlags::EFD_NONBLOCK) { 82 | Poll::Ready(Err(io::Error::EAGAIN)) 83 | } else { 84 | guard.read_queue.push_back(cx.waker().clone()); 85 | Poll::Pending 86 | } 87 | } 88 | }) 89 | .await 90 | } 91 | 92 | async fn write(&self, buf: &[u8]) -> io::Result { 93 | let len = mem::size_of::(); 94 | 95 | if buf.len() < len { 96 | return Err(io::Error::EINVAL); 97 | } 98 | 99 | let c = u64::from_ne_bytes(buf[..len].try_into().unwrap()); 100 | 101 | future::poll_fn(|cx| { 102 | let mut pinned = core::pin::pin!(self.state.lock()); 103 | let mut guard = ready!(pinned.as_mut().poll(cx)); 104 | if u64::MAX - guard.counter > c { 105 | guard.counter += c; 106 | if self.flags.contains(EventFlags::EFD_SEMAPHORE) { 107 | for _i in 0..c { 108 | if let Some(cx) = guard.read_queue.pop_front() { 109 | cx.wake_by_ref(); 110 | } else { 111 | break; 112 | } 113 | } 114 | } else if let Some(cx) = guard.read_queue.pop_front() { 115 | cx.wake_by_ref(); 116 | } 117 | 118 | Poll::Ready(Ok(len)) 119 | } else if self.flags.contains(EventFlags::EFD_NONBLOCK) { 120 | Poll::Ready(Err(io::Error::EAGAIN)) 121 | } else { 122 | guard.write_queue.push_back(cx.waker().clone()); 123 | Poll::Pending 124 | } 125 | }) 126 | .await 127 | } 128 | 129 | async fn poll(&self, event: PollEvent) -> io::Result { 130 | let guard = self.state.lock().await; 131 | 132 | let mut available = PollEvent::empty(); 133 | 134 | if guard.counter < u64::MAX - 1 { 135 | available.insert(PollEvent::POLLOUT | PollEvent::POLLWRNORM | PollEvent::POLLWRBAND); 136 | } 137 | 138 | if guard.counter > 0 { 139 | available.insert(PollEvent::POLLIN | PollEvent::POLLRDNORM | PollEvent::POLLRDBAND); 140 | } 141 | 142 | drop(guard); 143 | 144 | let ret = event & available; 145 | 146 | future::poll_fn(|cx| { 147 | if ret.is_empty() { 148 | let mut pinned = core::pin::pin!(self.state.lock()); 149 | let mut guard = ready!(pinned.as_mut().poll(cx)); 150 | if event 151 | .intersects(PollEvent::POLLIN | PollEvent::POLLRDNORM | PollEvent::POLLRDNORM) 152 | { 153 | guard.read_queue.push_back(cx.waker().clone()); 154 | Poll::Pending 155 | } else if event 156 | .intersects(PollEvent::POLLOUT | PollEvent::POLLWRNORM | PollEvent::POLLWRBAND) 157 | { 158 | guard.write_queue.push_back(cx.waker().clone()); 159 | Poll::Pending 160 | } else { 161 | Poll::Ready(Ok(ret)) 162 | } 163 | } else { 164 | Poll::Ready(Ok(ret)) 165 | } 166 | }) 167 | .await 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/fd/socket/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "tcp")] 2 | pub(crate) mod tcp; 3 | #[cfg(feature = "udp")] 4 | pub(crate) mod udp; 5 | #[cfg(feature = "vsock")] 6 | pub(crate) mod vsock; 7 | -------------------------------------------------------------------------------- /src/fd/stdio.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use core::future; 3 | use core::mem::MaybeUninit; 4 | use core::task::Poll; 5 | 6 | use async_trait::async_trait; 7 | use uhyve_interface::parameters::WriteParams; 8 | use uhyve_interface::{GuestVirtAddr, Hypercall}; 9 | use zerocopy::IntoBytes; 10 | 11 | use crate::console::CONSOLE; 12 | use crate::fd::{ObjectInterface, PollEvent, STDERR_FILENO, STDOUT_FILENO}; 13 | use crate::io; 14 | use crate::syscalls::interfaces::uhyve_hypercall; 15 | 16 | #[derive(Debug)] 17 | pub struct GenericStdin; 18 | 19 | #[async_trait] 20 | impl ObjectInterface for GenericStdin { 21 | async fn poll(&self, event: PollEvent) -> io::Result { 22 | let available = if CONSOLE.lock().is_empty() { 23 | PollEvent::empty() 24 | } else { 25 | PollEvent::POLLIN | PollEvent::POLLRDNORM | PollEvent::POLLRDBAND 26 | }; 27 | 28 | Ok(event & available) 29 | } 30 | 31 | async fn read(&self, buf: &mut [MaybeUninit]) -> io::Result { 32 | future::poll_fn(|cx| { 33 | let mut read_bytes = 0; 34 | let mut guard = CONSOLE.lock(); 35 | 36 | while let Some(byte) = guard.read() { 37 | let c = unsafe { char::from_u32_unchecked(byte.into()) }; 38 | guard.write(c.as_bytes()); 39 | 40 | buf[read_bytes].write(byte); 41 | read_bytes += 1; 42 | 43 | if read_bytes >= buf.len() { 44 | return Poll::Ready(Ok(read_bytes)); 45 | } 46 | } 47 | 48 | if read_bytes > 0 { 49 | Poll::Ready(Ok(read_bytes)) 50 | } else { 51 | guard.register_waker(cx.waker()); 52 | Poll::Pending 53 | } 54 | }) 55 | .await 56 | } 57 | 58 | async fn isatty(&self) -> io::Result { 59 | Ok(true) 60 | } 61 | } 62 | 63 | impl GenericStdin { 64 | pub const fn new() -> Self { 65 | Self {} 66 | } 67 | } 68 | 69 | #[derive(Debug)] 70 | pub struct GenericStdout; 71 | 72 | #[async_trait] 73 | impl ObjectInterface for GenericStdout { 74 | async fn poll(&self, event: PollEvent) -> io::Result { 75 | let available = PollEvent::POLLOUT | PollEvent::POLLWRNORM | PollEvent::POLLWRBAND; 76 | Ok(event & available) 77 | } 78 | 79 | async fn write(&self, buf: &[u8]) -> io::Result { 80 | CONSOLE.lock().write(buf); 81 | Ok(buf.len()) 82 | } 83 | 84 | async fn isatty(&self) -> io::Result { 85 | Ok(true) 86 | } 87 | } 88 | 89 | impl GenericStdout { 90 | pub const fn new() -> Self { 91 | Self {} 92 | } 93 | } 94 | 95 | #[derive(Debug)] 96 | pub struct GenericStderr; 97 | 98 | #[async_trait] 99 | impl ObjectInterface for GenericStderr { 100 | async fn poll(&self, event: PollEvent) -> io::Result { 101 | let available = PollEvent::POLLOUT | PollEvent::POLLWRNORM | PollEvent::POLLWRBAND; 102 | Ok(event & available) 103 | } 104 | 105 | async fn write(&self, buf: &[u8]) -> io::Result { 106 | CONSOLE.lock().write(buf); 107 | Ok(buf.len()) 108 | } 109 | 110 | async fn isatty(&self) -> io::Result { 111 | Ok(true) 112 | } 113 | } 114 | 115 | impl GenericStderr { 116 | pub const fn new() -> Self { 117 | Self {} 118 | } 119 | } 120 | 121 | #[derive(Debug)] 122 | pub struct UhyveStdin; 123 | 124 | #[async_trait] 125 | impl ObjectInterface for UhyveStdin { 126 | async fn isatty(&self) -> io::Result { 127 | Ok(true) 128 | } 129 | } 130 | 131 | impl UhyveStdin { 132 | pub const fn new() -> Self { 133 | Self {} 134 | } 135 | } 136 | 137 | #[derive(Debug)] 138 | pub struct UhyveStdout; 139 | 140 | #[async_trait] 141 | impl ObjectInterface for UhyveStdout { 142 | async fn poll(&self, event: PollEvent) -> io::Result { 143 | let available = PollEvent::POLLOUT | PollEvent::POLLWRNORM | PollEvent::POLLWRBAND; 144 | Ok(event & available) 145 | } 146 | 147 | async fn write(&self, buf: &[u8]) -> io::Result { 148 | let write_params = WriteParams { 149 | fd: STDOUT_FILENO, 150 | buf: GuestVirtAddr::new(buf.as_ptr() as u64), 151 | len: buf.len(), 152 | }; 153 | uhyve_hypercall(Hypercall::FileWrite(&write_params)); 154 | 155 | Ok(write_params.len) 156 | } 157 | 158 | async fn isatty(&self) -> io::Result { 159 | Ok(true) 160 | } 161 | } 162 | 163 | impl UhyveStdout { 164 | pub const fn new() -> Self { 165 | Self {} 166 | } 167 | } 168 | 169 | #[derive(Debug)] 170 | pub struct UhyveStderr; 171 | 172 | #[async_trait] 173 | impl ObjectInterface for UhyveStderr { 174 | async fn poll(&self, event: PollEvent) -> io::Result { 175 | let available = PollEvent::POLLOUT | PollEvent::POLLWRNORM | PollEvent::POLLWRBAND; 176 | Ok(event & available) 177 | } 178 | 179 | async fn write(&self, buf: &[u8]) -> io::Result { 180 | let write_params = WriteParams { 181 | fd: STDERR_FILENO, 182 | buf: GuestVirtAddr::new(buf.as_ptr() as u64), 183 | len: buf.len(), 184 | }; 185 | uhyve_hypercall(Hypercall::FileWrite(&write_params)); 186 | 187 | Ok(write_params.len) 188 | } 189 | 190 | async fn isatty(&self) -> io::Result { 191 | Ok(true) 192 | } 193 | } 194 | 195 | impl UhyveStderr { 196 | pub const fn new() -> Self { 197 | Self {} 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/init_cell.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all( 3 | not(feature = "pci"), 4 | not(all(target_arch = "x86_64", feature = "tcp")), 5 | not(all(target_arch = "riscv64", feature = "tcp")), 6 | ), 7 | expect(dead_code) 8 | )] 9 | 10 | use hermit_sync::{OnceCell, SpinMutex}; 11 | 12 | /// A cell for iteratively initializing a `OnceCell`. 13 | /// 14 | /// This should be used as a stop-gap measure only. 15 | pub struct InitCell { 16 | init: SpinMutex>, 17 | once: OnceCell, 18 | } 19 | 20 | impl InitCell { 21 | pub const fn new(val: T) -> Self { 22 | Self { 23 | init: SpinMutex::new(Some(val)), 24 | once: OnceCell::new(), 25 | } 26 | } 27 | 28 | pub fn with(&self, f: impl FnOnce(Option<&mut T>)) { 29 | let mut guard = self.init.lock(); 30 | f((*guard).as_mut()); 31 | } 32 | 33 | #[cfg_attr(all(feature = "pci", not(feature = "tcp")), expect(dead_code))] 34 | pub fn get(&self) -> Option<&T> { 35 | self.once.get() 36 | } 37 | 38 | pub fn finalize(&self) -> &T { 39 | self.once.get_or_init(|| self.init.lock().take().unwrap()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/io.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use alloc::vec::Vec; 3 | use core::{fmt, result}; 4 | 5 | // TODO: Integrate with src/errno.rs ? 6 | #[allow(clippy::upper_case_acronyms)] 7 | #[derive(Debug, PartialEq, FromPrimitive, ToPrimitive)] 8 | pub enum Error { 9 | ENOENT = crate::errno::ENOENT as isize, 10 | ENOSYS = crate::errno::ENOSYS as isize, 11 | EIO = crate::errno::EIO as isize, 12 | EBADF = crate::errno::EBADF as isize, 13 | EISDIR = crate::errno::EISDIR as isize, 14 | EINVAL = crate::errno::EINVAL as isize, 15 | ETIME = crate::errno::ETIME as isize, 16 | EAGAIN = crate::errno::EAGAIN as isize, 17 | EFAULT = crate::errno::EFAULT as isize, 18 | ENOBUFS = crate::errno::ENOBUFS as isize, 19 | ENOTCONN = crate::errno::ENOTCONN as isize, 20 | ENOTDIR = crate::errno::ENOTDIR as isize, 21 | EMFILE = crate::errno::EMFILE as isize, 22 | EEXIST = crate::errno::EEXIST as isize, 23 | EADDRINUSE = crate::errno::EADDRINUSE as isize, 24 | EOVERFLOW = crate::errno::EOVERFLOW as isize, 25 | ENOTSOCK = crate::errno::ENOTSOCK as isize, 26 | } 27 | 28 | pub type Result = result::Result; 29 | 30 | /// The Read trait allows for reading bytes from a source. 31 | /// 32 | /// The Read trait is derived from Rust's std library. 33 | pub trait Read { 34 | fn read(&mut self, buf: &mut [u8]) -> Result; 35 | 36 | /// Read all bytes until EOF in this source, placing them into buf. 37 | fn read_to_end(&mut self, buf: &mut Vec) -> Result { 38 | let start_len = buf.len(); 39 | 40 | loop { 41 | let mut probe = [0u8; 512]; 42 | 43 | match self.read(&mut probe) { 44 | Ok(0) => return Ok(buf.len() - start_len), 45 | Ok(n) => { 46 | buf.extend_from_slice(&probe[..n]); 47 | } 48 | Err(e) => return Err(e), 49 | } 50 | } 51 | } 52 | 53 | /// Read all bytes until EOF in this source, appending them to `buf`. 54 | /// 55 | /// If successful, this function returns the number of bytes which were read 56 | /// and appended to `buf`. 57 | fn read_to_string(&mut self, buf: &mut String) -> Result { 58 | unsafe { self.read_to_end(buf.as_mut_vec()) } 59 | } 60 | } 61 | 62 | /// The Write trait allows for reading bytes from a source. 63 | /// 64 | /// The Write trait is derived from Rust's std library. 65 | pub trait Write { 66 | fn write(&mut self, buf: &[u8]) -> Result; 67 | 68 | /// Attempts to write an entire buffer into this writer. 69 | fn write_all(&mut self, mut buf: &[u8]) -> Result<()> { 70 | while !buf.is_empty() { 71 | match self.write(buf) { 72 | Ok(0) => { 73 | return Err(Error::EIO); 74 | } 75 | Ok(n) => buf = &buf[n..], 76 | Err(e) => return Err(e), 77 | } 78 | } 79 | 80 | Ok(()) 81 | } 82 | 83 | /// Writes a formatted string into this writer, returning any error encountered. 84 | fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> { 85 | // Create a shim which translates a Write to a fmt::Write and saves 86 | // off I/O errors. instead of discarding them 87 | struct Adapter<'a, T: ?Sized> { 88 | inner: &'a mut T, 89 | error: Result<()>, 90 | } 91 | 92 | impl fmt::Write for Adapter<'_, T> { 93 | fn write_str(&mut self, s: &str) -> fmt::Result { 94 | match self.inner.write_all(s.as_bytes()) { 95 | Ok(()) => Ok(()), 96 | Err(e) => { 97 | self.error = Err(e); 98 | Err(fmt::Error) 99 | } 100 | } 101 | } 102 | } 103 | 104 | let mut output = Adapter { 105 | inner: self, 106 | error: Ok(()), 107 | }; 108 | match fmt::write(&mut output, fmt) { 109 | Ok(()) => Ok(()), 110 | Err(..) => { 111 | // check if the error came from the underlying `Write` or not 112 | if output.error.is_err() { 113 | output.error 114 | } else { 115 | Err(Error::EINVAL) 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/logging.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use anstyle::AnsiColor; 4 | use log::{Level, LevelFilter, Metadata, Record}; 5 | 6 | /// Data structure to filter kernel messages 7 | struct KernelLogger; 8 | 9 | impl log::Log for KernelLogger { 10 | fn enabled(&self, _: &Metadata<'_>) -> bool { 11 | true 12 | } 13 | 14 | fn flush(&self) { 15 | // nothing to do 16 | } 17 | 18 | fn log(&self, record: &Record<'_>) { 19 | if self.enabled(record.metadata()) { 20 | println!( 21 | "[{}][{}] {}", 22 | crate::arch::core_local::core_id(), 23 | ColorLevel(record.level()), 24 | record.args() 25 | ); 26 | } 27 | } 28 | } 29 | 30 | struct ColorLevel(Level); 31 | 32 | impl fmt::Display for ColorLevel { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | let level = self.0; 35 | 36 | if no_color() { 37 | write!(f, "{level}") 38 | } else { 39 | let color = match level { 40 | Level::Trace => AnsiColor::Magenta, 41 | Level::Debug => AnsiColor::Blue, 42 | Level::Info => AnsiColor::Green, 43 | Level::Warn => AnsiColor::Yellow, 44 | Level::Error => AnsiColor::Red, 45 | }; 46 | 47 | let style = anstyle::Style::new().fg_color(Some(color.into())); 48 | write!(f, "{style}{level}{style:#}") 49 | } 50 | } 51 | } 52 | 53 | fn no_color() -> bool { 54 | option_env!("NO_COLOR").is_some_and(|val| !val.is_empty()) 55 | } 56 | 57 | pub unsafe fn init() { 58 | log::set_logger(&KernelLogger).expect("Can't initialize logger"); 59 | // Determines LevelFilter at compile time 60 | let log_level: Option<&'static str> = option_env!("HERMIT_LOG_LEVEL_FILTER"); 61 | let mut max_level = LevelFilter::Info; 62 | 63 | if let Some(log_level) = log_level { 64 | max_level = if log_level.eq_ignore_ascii_case("off") { 65 | LevelFilter::Off 66 | } else if log_level.eq_ignore_ascii_case("error") { 67 | LevelFilter::Error 68 | } else if log_level.eq_ignore_ascii_case("warn") { 69 | LevelFilter::Warn 70 | } else if log_level.eq_ignore_ascii_case("info") { 71 | LevelFilter::Info 72 | } else if log_level.eq_ignore_ascii_case("debug") { 73 | LevelFilter::Debug 74 | } else if log_level.eq_ignore_ascii_case("trace") { 75 | LevelFilter::Trace 76 | } else { 77 | error!("Could not parse HERMIT_LOG_LEVEL_FILTER, falling back to `info`."); 78 | LevelFilter::Info 79 | }; 80 | } 81 | 82 | log::set_max_level(max_level); 83 | } 84 | 85 | #[cfg(any(not(target_arch = "riscv64"), feature = "pci", feature = "tcp"))] 86 | macro_rules! infoheader { 87 | // This should work on paper, but it's currently not supported :( 88 | // Refer to https://github.com/rust-lang/rust/issues/46569 89 | /*($($arg:tt)+) => ({ 90 | info!(""); 91 | info!("{:=^70}", format_args!($($arg)+)); 92 | });*/ 93 | ($str:expr) => {{ 94 | ::log::info!(""); 95 | ::log::info!("{:=^70}", $str); 96 | }}; 97 | } 98 | 99 | #[cfg_attr(target_arch = "riscv64", allow(unused))] 100 | macro_rules! infoentry { 101 | ($str:expr, $rhs:expr) => (infoentry!($str, "{}", $rhs)); 102 | ($str:expr, $($arg:tt)+) => (::log::info!("{:25}{}", concat!($str, ":"), format_args!($($arg)+))); 103 | } 104 | 105 | #[cfg(any(not(target_arch = "riscv64"), feature = "pci", feature = "tcp"))] 106 | macro_rules! infofooter { 107 | () => {{ 108 | ::log::info!("{:=^70}", '='); 109 | ::log::info!(""); 110 | }}; 111 | } 112 | -------------------------------------------------------------------------------- /src/mm/allocator.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the Hermit Allocator for dynamically allocating heap memory 2 | //! in the kernel. 3 | 4 | use core::alloc::{GlobalAlloc, Layout}; 5 | 6 | use hermit_sync::RawInterruptTicketMutex; 7 | use talc::{ErrOnOom, Span, Talc, Talck}; 8 | 9 | pub struct LockedAllocator(Talck); 10 | 11 | impl LockedAllocator { 12 | pub const fn new() -> Self { 13 | Self(Talc::new(ErrOnOom).lock()) 14 | } 15 | 16 | #[inline] 17 | fn align_layout(layout: Layout) -> Layout { 18 | let align = layout 19 | .align() 20 | .max(core::mem::align_of::>()); 21 | Layout::from_size_align(layout.size(), align).unwrap() 22 | } 23 | 24 | pub unsafe fn init(&self, heap_bottom: *mut u8, heap_size: usize) { 25 | let arena = Span::from_base_size(heap_bottom, heap_size); 26 | unsafe { 27 | self.0.lock().claim(arena).unwrap(); 28 | } 29 | } 30 | } 31 | 32 | /// To avoid false sharing, the global memory allocator align 33 | /// all requests to a cache line. 34 | unsafe impl GlobalAlloc for LockedAllocator { 35 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 36 | let layout = Self::align_layout(layout); 37 | unsafe { self.0.alloc(layout) } 38 | } 39 | 40 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 41 | let layout = Self::align_layout(layout); 42 | unsafe { self.0.dealloc(ptr, layout) } 43 | } 44 | 45 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 46 | let layout = Self::align_layout(layout); 47 | unsafe { self.0.alloc_zeroed(layout) } 48 | } 49 | 50 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 51 | let layout = Self::align_layout(layout); 52 | unsafe { self.0.realloc(ptr, layout, new_size) } 53 | } 54 | } 55 | 56 | #[cfg(all(test, not(target_os = "none")))] 57 | mod tests { 58 | use core::mem; 59 | 60 | use super::*; 61 | 62 | #[test] 63 | fn empty() { 64 | const ARENA_SIZE: usize = 0x1000; 65 | let mut arena: [u8; ARENA_SIZE] = [0; ARENA_SIZE]; 66 | let allocator: LockedAllocator = LockedAllocator::new(); 67 | unsafe { 68 | allocator.init(arena.as_mut_ptr(), ARENA_SIZE); 69 | } 70 | 71 | let layout = Layout::from_size_align(1, 1).unwrap(); 72 | // we have 4 kbyte memory 73 | assert!(unsafe { !allocator.alloc(layout.clone()).is_null() }); 74 | 75 | let layout = Layout::from_size_align(0x1000, mem::align_of::()).unwrap(); 76 | let addr = unsafe { allocator.alloc(layout) }; 77 | assert!(addr.is_null()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/mm/device_alloc.rs: -------------------------------------------------------------------------------- 1 | use core::alloc::{AllocError, Allocator, Layout}; 2 | use core::ptr::{self, NonNull}; 3 | 4 | use align_address::Align; 5 | use memory_addresses::PhysAddr; 6 | 7 | use crate::arch::mm::paging::{BasePageSize, PageSize}; 8 | 9 | /// An [`Allocator`] for memory that is used to communicate with devices. 10 | /// 11 | /// Allocations from this allocator always correspond to contiguous physical memory. 12 | pub struct DeviceAlloc; 13 | 14 | unsafe impl Allocator for DeviceAlloc { 15 | fn allocate(&self, layout: Layout) -> Result, AllocError> { 16 | assert!(layout.align() <= BasePageSize::SIZE as usize); 17 | let size = layout.size().align_up(BasePageSize::SIZE as usize); 18 | 19 | let phys_addr = super::physicalmem::allocate(size).unwrap(); 20 | 21 | let ptr = ptr::with_exposed_provenance_mut(phys_addr.as_usize()); 22 | let slice = ptr::slice_from_raw_parts_mut(ptr, size); 23 | Ok(NonNull::new(slice).unwrap()) 24 | } 25 | 26 | unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { 27 | assert!(layout.align() <= BasePageSize::SIZE as usize); 28 | let size = layout.size().align_up(BasePageSize::SIZE as usize); 29 | 30 | let virt_addr = ptr.as_ptr().expose_provenance(); 31 | let phys_addr = PhysAddr::from(virt_addr); 32 | 33 | super::physicalmem::deallocate(phys_addr, size); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/mm/virtualmem.rs: -------------------------------------------------------------------------------- 1 | use align_address::Align; 2 | use free_list::{AllocError, FreeList, PageLayout, PageRange}; 3 | use hermit_sync::InterruptTicketMutex; 4 | use memory_addresses::VirtAddr; 5 | 6 | use crate::arch::{BasePageSize, PageSize}; 7 | 8 | pub static KERNEL_FREE_LIST: InterruptTicketMutex> = 9 | InterruptTicketMutex::new(FreeList::new()); 10 | 11 | pub fn init() { 12 | let range = PageRange::new( 13 | kernel_heap_end().as_usize().div_ceil(2), 14 | kernel_heap_end().as_usize() + 1, 15 | ) 16 | .unwrap(); 17 | 18 | unsafe { 19 | KERNEL_FREE_LIST.lock().deallocate(range).unwrap(); 20 | } 21 | } 22 | 23 | /// End of the virtual memory address space reserved for kernel memory (inclusive). 24 | /// The virtual memory address space reserved for the task heap starts after this. 25 | #[inline] 26 | pub fn kernel_heap_end() -> VirtAddr { 27 | cfg_if::cfg_if! { 28 | if #[cfg(target_arch = "aarch64")] { 29 | // maximum address, which can be supported by TTBR0 30 | VirtAddr::new(0xFFFF_FFFF_FFFF) 31 | } else if #[cfg(target_arch = "riscv64")] { 32 | // 256 GiB 33 | VirtAddr::new(0x0040_0000_0000 - 1) 34 | } else if #[cfg(target_arch = "x86_64")] { 35 | use x86_64::structures::paging::PageTableIndex; 36 | 37 | let p4_index = if cfg!(feature = "common-os") { 38 | PageTableIndex::new(1) 39 | } else { 40 | PageTableIndex::new(256) 41 | }; 42 | 43 | let addr = u64::from(p4_index) << 39; 44 | assert_eq!(VirtAddr::new_truncate(addr).p4_index(), p4_index); 45 | 46 | VirtAddr::new_truncate(addr - 1) 47 | } 48 | } 49 | } 50 | 51 | pub fn allocate(size: usize) -> Result { 52 | assert!(size > 0); 53 | assert_eq!( 54 | size % BasePageSize::SIZE as usize, 55 | 0, 56 | "Size {:#X} is not a multiple of {:#X}", 57 | size, 58 | BasePageSize::SIZE 59 | ); 60 | 61 | let layout = PageLayout::from_size(size).unwrap(); 62 | 63 | Ok(VirtAddr::new( 64 | KERNEL_FREE_LIST 65 | .lock() 66 | .allocate(layout)? 67 | .start() 68 | .try_into() 69 | .unwrap(), 70 | )) 71 | } 72 | 73 | pub fn allocate_aligned(size: usize, align: usize) -> Result { 74 | assert!(size > 0); 75 | assert!(align > 0); 76 | assert_eq!( 77 | size % align, 78 | 0, 79 | "Size {size:#X} is not a multiple of the given alignment {align:#X}" 80 | ); 81 | assert_eq!( 82 | align % BasePageSize::SIZE as usize, 83 | 0, 84 | "Alignment {:#X} is not a multiple of {:#X}", 85 | align, 86 | BasePageSize::SIZE 87 | ); 88 | 89 | let layout = PageLayout::from_size_align(size, align).unwrap(); 90 | 91 | Ok(VirtAddr::new( 92 | KERNEL_FREE_LIST 93 | .lock() 94 | .allocate(layout)? 95 | .start() 96 | .try_into() 97 | .unwrap(), 98 | )) 99 | } 100 | 101 | pub fn deallocate(virtual_address: VirtAddr, size: usize) { 102 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 103 | assert!( 104 | virtual_address >= super::kernel_end_address() 105 | || virtual_address < super::kernel_start_address(), 106 | "Virtual address {virtual_address:p} belongs to the kernel" 107 | ); 108 | assert!( 109 | virtual_address <= kernel_heap_end(), 110 | "Virtual address {virtual_address:p} is not <= kernel_heap_end()" 111 | ); 112 | assert!( 113 | virtual_address.is_aligned_to(BasePageSize::SIZE), 114 | "Virtual address {:p} is not a multiple of {:#X}", 115 | virtual_address, 116 | BasePageSize::SIZE 117 | ); 118 | assert!(size > 0); 119 | assert_eq!( 120 | size % BasePageSize::SIZE as usize, 121 | 0, 122 | "Size {:#X} is not a multiple of {:#X}", 123 | size, 124 | BasePageSize::SIZE 125 | ); 126 | 127 | let range = PageRange::from_start_len(virtual_address.as_u64() as usize, size).unwrap(); 128 | 129 | unsafe { 130 | KERNEL_FREE_LIST.lock().deallocate(range).unwrap(); 131 | } 132 | } 133 | 134 | /*pub fn reserve(virtual_address: VirtAddr, size: usize) { 135 | assert!( 136 | virtual_address >= VirtAddr(mm::kernel_end_address().as_u64()), 137 | "Virtual address {:#X} is not >= KERNEL_END_ADDRESS", 138 | virtual_address 139 | ); 140 | assert!( 141 | virtual_address <= kernel_heap_end(), 142 | "Virtual address {:#X} is not <= kernel_heap_end()", 143 | virtual_address 144 | ); 145 | assert_eq!( 146 | virtual_address % BasePageSize::SIZE, 147 | 0, 148 | "Virtual address {:#X} is not a multiple of {:#X}", 149 | virtual_address, 150 | BasePageSize::SIZE 151 | ); 152 | assert!(size > 0); 153 | assert_eq!( 154 | size % BasePageSize::SIZE as usize, 155 | 0, 156 | "Size {:#X} is not a multiple of {:#X}", 157 | size, 158 | BasePageSize::SIZE 159 | ); 160 | 161 | let result = KERNEL_FREE_LIST 162 | .lock() 163 | .reserve(virtual_address.as_usize(), size); 164 | assert!( 165 | result.is_ok(), 166 | "Could not reserve {:#X} bytes of virtual memory at {:#X}", 167 | size, 168 | virtual_address 169 | ); 170 | }*/ 171 | 172 | pub fn print_information() { 173 | let free_list = KERNEL_FREE_LIST.lock(); 174 | info!("Virtual memory free list:\n{free_list}"); 175 | } 176 | -------------------------------------------------------------------------------- /src/shell.rs: -------------------------------------------------------------------------------- 1 | use simple_shell::*; 2 | 3 | use crate::interrupts::print_statistics; 4 | 5 | fn read() -> Option { 6 | crate::console::CONSOLE.lock().read() 7 | } 8 | 9 | pub(crate) fn init() { 10 | let (print, read) = (|s: &str| print!("{}", s), read); 11 | let mut shell = Shell::new(print, read); 12 | 13 | shell.commands.insert( 14 | "help", 15 | ShellCommand { 16 | help: "Print this help message", 17 | func: |_, shell| { 18 | shell.print_help_screen(); 19 | Ok(()) 20 | }, 21 | aliases: &["?", "h"], 22 | }, 23 | ); 24 | shell.commands.insert( 25 | "interrupts", 26 | ShellCommand { 27 | help: "Shows the number of received interrupts", 28 | func: |_, _| { 29 | print_statistics(); 30 | Ok(()) 31 | }, 32 | aliases: &["i"], 33 | }, 34 | ); 35 | shell.commands.insert( 36 | "shutdown", 37 | ShellCommand { 38 | help: "Shutdown HermitOS", 39 | func: |_, _| crate::scheduler::shutdown(0), 40 | aliases: &["s"], 41 | }, 42 | ); 43 | 44 | // Also supports async 45 | crate::executor::spawn(async move { shell.run_async().await }); 46 | } 47 | -------------------------------------------------------------------------------- /src/synch/mod.rs: -------------------------------------------------------------------------------- 1 | //! Synchronization primitives 2 | 3 | pub mod futex; 4 | #[cfg(feature = "newlib")] 5 | pub mod recmutex; 6 | pub mod semaphore; 7 | -------------------------------------------------------------------------------- /src/synch/recmutex.rs: -------------------------------------------------------------------------------- 1 | use hermit_sync::TicketMutex; 2 | 3 | use crate::arch::core_local::*; 4 | use crate::scheduler::PerCoreSchedulerExt; 5 | use crate::scheduler::task::{TaskHandlePriorityQueue, TaskId}; 6 | 7 | struct RecursiveMutexState { 8 | current_tid: Option, 9 | count: usize, 10 | queue: TaskHandlePriorityQueue, 11 | } 12 | 13 | pub struct RecursiveMutex { 14 | state: TicketMutex, 15 | } 16 | 17 | impl RecursiveMutex { 18 | pub const fn new() -> Self { 19 | Self { 20 | state: TicketMutex::new(RecursiveMutexState { 21 | current_tid: None, 22 | count: 0, 23 | queue: TaskHandlePriorityQueue::new(), 24 | }), 25 | } 26 | } 27 | 28 | pub fn acquire(&self) { 29 | // Get information about the current task. 30 | let core_scheduler = core_scheduler(); 31 | let tid = core_scheduler.get_current_task_id(); 32 | 33 | loop { 34 | { 35 | let mut locked_state = self.state.lock(); 36 | 37 | // Is the mutex currently acquired? 38 | if let Some(current_tid) = locked_state.current_tid { 39 | // Has it been acquired by the same task? 40 | if current_tid == tid { 41 | // Yes, so just increment the counter (recursive mutex behavior). 42 | locked_state.count += 1; 43 | return; 44 | } 45 | } else { 46 | // The mutex is currently not acquired, so we become its new owner. 47 | locked_state.current_tid = Some(tid); 48 | locked_state.count = 1; 49 | return; 50 | } 51 | 52 | // The mutex is currently acquired by another task. 53 | // Block the current task and add it to the wakeup queue. 54 | core_scheduler.block_current_task(None); 55 | locked_state 56 | .queue 57 | .push(core_scheduler.get_current_task_handle()); 58 | } 59 | 60 | // Switch to the next task. 61 | core_scheduler.reschedule(); 62 | } 63 | } 64 | 65 | pub fn release(&self) { 66 | if let Some(task) = { 67 | let mut locked_state = self.state.lock(); 68 | 69 | // We could do a sanity check here whether the RecursiveMutex is actually held by the current task. 70 | // But let's just trust our code using this function for the sake of simplicity and performance. 71 | 72 | // Decrement the counter (recursive mutex behavior). 73 | if locked_state.count > 0 { 74 | locked_state.count -= 1; 75 | } 76 | 77 | if locked_state.count == 0 { 78 | // Release the entire recursive mutex. 79 | locked_state.current_tid = None; 80 | 81 | locked_state.queue.pop() 82 | } else { 83 | None 84 | } 85 | } { 86 | // Wake up any task that has been waiting for this mutex. 87 | core_scheduler().custom_wakeup(task); 88 | } 89 | } 90 | } 91 | 92 | impl Default for RecursiveMutex { 93 | fn default() -> Self { 94 | Self::new() 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/synch/semaphore.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "smp")] 2 | use crossbeam_utils::Backoff; 3 | use hermit_sync::InterruptTicketMutex; 4 | 5 | use crate::arch::core_local::*; 6 | use crate::scheduler::PerCoreSchedulerExt; 7 | use crate::scheduler::task::TaskHandlePriorityQueue; 8 | 9 | struct SemaphoreState { 10 | /// Resource available count 11 | count: isize, 12 | /// Priority queue of waiting tasks 13 | queue: TaskHandlePriorityQueue, 14 | } 15 | 16 | /// A counting, blocking, semaphore. 17 | /// 18 | /// Semaphores are a form of atomic counter where access is only granted if the 19 | /// counter is a positive value. Each acquisition will block the calling thread 20 | /// until the counter is positive, and each release will increment the counter 21 | /// and unblock any threads if necessary. 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// 27 | /// // Create a semaphore that represents 5 resources 28 | /// let sem = Semaphore::new(5); 29 | /// 30 | /// // Acquire one of the resources 31 | /// sem.acquire(); 32 | /// 33 | /// // Acquire one of the resources for a limited period of time 34 | /// { 35 | /// let _guard = sem.access(); 36 | /// // ... 37 | /// } // resources is released here 38 | /// 39 | /// // Release our initially acquired resource 40 | /// sem.release(); 41 | /// 42 | /// Interface is derived from https://doc.rust-lang.org/1.7.0/src/std/sync/semaphore.rs.html 43 | /// ``` 44 | pub struct Semaphore { 45 | state: InterruptTicketMutex, 46 | } 47 | 48 | impl Semaphore { 49 | /// Creates a new semaphore with the initial count specified. 50 | /// 51 | /// The count specified can be thought of as a number of resources, and a 52 | /// call to `acquire` or `access` will block until at least one resource is 53 | /// available. It is valid to initialize a semaphore with a negative count. 54 | pub const fn new(count: isize) -> Self { 55 | Self { 56 | state: InterruptTicketMutex::new(SemaphoreState { 57 | count, 58 | queue: TaskHandlePriorityQueue::new(), 59 | }), 60 | } 61 | } 62 | 63 | /// Acquires a resource of this semaphore, blocking the current thread until 64 | /// it can do so or until the wakeup time (in ms) has elapsed. 65 | /// 66 | /// This method will block until the internal count of the semaphore is at 67 | /// least 1. 68 | pub fn acquire(&self, time: Option) -> bool { 69 | #[cfg(feature = "smp")] 70 | let backoff = Backoff::new(); 71 | let core_scheduler = core_scheduler(); 72 | 73 | let wakeup_time = time.map(|ms| crate::arch::processor::get_timer_ticks() + ms * 1000); 74 | 75 | // Loop until we have acquired the semaphore. 76 | loop { 77 | let mut locked_state = self.state.lock(); 78 | 79 | if locked_state.count > 0 { 80 | // Successfully acquired the semaphore. 81 | locked_state.count -= 1; 82 | return true; 83 | } else if let Some(t) = wakeup_time { 84 | if t < crate::arch::processor::get_timer_ticks() { 85 | // We could not acquire the semaphore and we were woken up because the wakeup time has elapsed. 86 | // Don't try again and return the failure status. 87 | locked_state 88 | .queue 89 | .remove(core_scheduler.get_current_task_handle()); 90 | return false; 91 | } 92 | } 93 | 94 | #[cfg(feature = "smp")] 95 | if backoff.is_completed() { 96 | // We couldn't acquire the semaphore. 97 | // Block the current task and add it to the wakeup queue. 98 | core_scheduler.block_current_task(wakeup_time); 99 | locked_state 100 | .queue 101 | .push(core_scheduler.get_current_task_handle()); 102 | drop(locked_state); 103 | // Switch to the next task. 104 | core_scheduler.reschedule(); 105 | } else { 106 | drop(locked_state); 107 | backoff.snooze(); 108 | } 109 | 110 | #[cfg(not(feature = "smp"))] 111 | { 112 | // We couldn't acquire the semaphore. 113 | // Block the current task and add it to the wakeup queue. 114 | core_scheduler.block_current_task(wakeup_time); 115 | locked_state 116 | .queue 117 | .push(core_scheduler.get_current_task_handle()); 118 | drop(locked_state); 119 | // Switch to the next task. 120 | core_scheduler.reschedule(); 121 | } 122 | } 123 | } 124 | 125 | pub fn try_acquire(&self) -> bool { 126 | let mut locked_state = self.state.lock(); 127 | 128 | if locked_state.count > 0 { 129 | locked_state.count -= 1; 130 | true 131 | } else { 132 | false 133 | } 134 | } 135 | 136 | /// Release a resource from this semaphore. 137 | /// 138 | /// This will increment the number of resources in this semaphore by 1 and 139 | /// will notify any pending waiters in `acquire` or `access` if necessary. 140 | pub fn release(&self) { 141 | if let Some(task) = { 142 | let mut locked_state = self.state.lock(); 143 | locked_state.count += 1; 144 | locked_state.queue.pop() 145 | } { 146 | // Wake up any task that has been waiting for this semaphore. 147 | core_scheduler().custom_wakeup(task); 148 | }; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/syscalls/condvar.rs: -------------------------------------------------------------------------------- 1 | // The implementation is inspired by Andrew D. Birrell's paper 2 | // "Implementing Condition Variables with Semaphores" 3 | 4 | use alloc::boxed::Box; 5 | use core::sync::atomic::{AtomicIsize, Ordering}; 6 | use core::{mem, ptr}; 7 | 8 | use crate::synch::semaphore::Semaphore; 9 | 10 | struct CondQueue { 11 | counter: AtomicIsize, 12 | sem1: Semaphore, 13 | sem2: Semaphore, 14 | } 15 | 16 | impl CondQueue { 17 | pub fn new() -> Self { 18 | CondQueue { 19 | counter: AtomicIsize::new(0), 20 | sem1: Semaphore::new(0), 21 | sem2: Semaphore::new(0), 22 | } 23 | } 24 | } 25 | 26 | #[hermit_macro::system] 27 | #[unsafe(no_mangle)] 28 | pub unsafe extern "C" fn sys_destroy_queue(ptr: usize) -> i32 { 29 | unsafe { 30 | let id = ptr::with_exposed_provenance_mut::(ptr); 31 | if id.is_null() { 32 | debug!("sys_wait: invalid address to condition variable"); 33 | return -1; 34 | } 35 | 36 | if *id != 0 { 37 | let cond = Box::from_raw(ptr::with_exposed_provenance_mut::(*id)); 38 | mem::drop(cond); 39 | } 40 | 41 | 0 42 | } 43 | } 44 | 45 | #[hermit_macro::system] 46 | #[unsafe(no_mangle)] 47 | pub unsafe extern "C" fn sys_notify(ptr: usize, count: i32) -> i32 { 48 | unsafe { 49 | let id = ptr::with_exposed_provenance::(ptr); 50 | 51 | if id.is_null() { 52 | // invalid argument 53 | debug!("sys_notify: invalid address to condition variable"); 54 | return -1; 55 | } 56 | 57 | if *id == 0 { 58 | debug!("sys_notify: invalid reference to condition variable"); 59 | return -1; 60 | } 61 | 62 | let cond = &mut *(ptr::with_exposed_provenance_mut::(*id)); 63 | 64 | if count < 0 { 65 | // Wake up all task that has been waiting for this condition variable 66 | while cond.counter.load(Ordering::SeqCst) > 0 { 67 | cond.counter.fetch_sub(1, Ordering::SeqCst); 68 | cond.sem1.release(); 69 | cond.sem2.acquire(None); 70 | } 71 | } else { 72 | for _ in 0..count { 73 | cond.counter.fetch_sub(1, Ordering::SeqCst); 74 | cond.sem1.release(); 75 | cond.sem2.acquire(None); 76 | } 77 | } 78 | 79 | 0 80 | } 81 | } 82 | 83 | #[hermit_macro::system] 84 | #[unsafe(no_mangle)] 85 | pub unsafe extern "C" fn sys_init_queue(ptr: usize) -> i32 { 86 | unsafe { 87 | let id = ptr::with_exposed_provenance_mut::(ptr); 88 | if id.is_null() { 89 | debug!("sys_init_queue: invalid address to condition variable"); 90 | return -1; 91 | } 92 | 93 | if *id == 0 { 94 | debug!("Create condition variable queue"); 95 | let queue = Box::new(CondQueue::new()); 96 | *id = Box::into_raw(queue) as usize; 97 | } 98 | 99 | 0 100 | } 101 | } 102 | 103 | #[hermit_macro::system] 104 | #[unsafe(no_mangle)] 105 | pub unsafe extern "C" fn sys_add_queue(ptr: usize, timeout_ns: i64) -> i32 { 106 | unsafe { 107 | let id = ptr::with_exposed_provenance_mut::(ptr); 108 | if id.is_null() { 109 | debug!("sys_add_queue: invalid address to condition variable"); 110 | return -1; 111 | } 112 | 113 | if *id == 0 { 114 | debug!("Create condition variable queue"); 115 | let queue = Box::new(CondQueue::new()); 116 | *id = Box::into_raw(queue) as usize; 117 | } 118 | 119 | if timeout_ns <= 0 { 120 | let cond = &mut *(ptr::with_exposed_provenance_mut::(*id)); 121 | cond.counter.fetch_add(1, Ordering::SeqCst); 122 | 123 | 0 124 | } else { 125 | error!("Conditional variables with timeout is currently not supported"); 126 | 127 | -1 128 | } 129 | } 130 | } 131 | 132 | #[hermit_macro::system] 133 | #[unsafe(no_mangle)] 134 | pub unsafe extern "C" fn sys_wait(ptr: usize) -> i32 { 135 | unsafe { 136 | let id = ptr::with_exposed_provenance_mut::(ptr); 137 | if id.is_null() { 138 | debug!("sys_wait: invalid address to condition variable"); 139 | return -1; 140 | } 141 | 142 | if *id == 0 { 143 | error!("sys_wait: Unable to determine condition variable"); 144 | return -1; 145 | } 146 | 147 | let cond = &mut *(ptr::with_exposed_provenance_mut::(*id)); 148 | cond.sem1.acquire(None); 149 | cond.sem2.release(); 150 | 151 | 0 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/syscalls/entropy.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "newlib"))] 2 | use core::mem::size_of; 3 | use core::slice; 4 | 5 | use hermit_sync::TicketMutex; 6 | 7 | use crate::arch; 8 | use crate::entropy::{self, Flags}; 9 | use crate::errno::EINVAL; 10 | 11 | static PARK_MILLER_LEHMER_SEED: TicketMutex = TicketMutex::new(0); 12 | const RAND_MAX: u64 = 0x7fff_ffff; 13 | 14 | fn generate_park_miller_lehmer_random_number() -> u32 { 15 | let mut seed = PARK_MILLER_LEHMER_SEED.lock(); 16 | let random = ((u64::from(*seed) * 48271) % RAND_MAX) as u32; 17 | *seed = random; 18 | random 19 | } 20 | 21 | unsafe fn read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize { 22 | let Some(flags) = Flags::from_bits(flags) else { 23 | return -EINVAL as isize; 24 | }; 25 | 26 | let buf = unsafe { 27 | // Cap the number of bytes to be read at a time to isize::MAX to uphold 28 | // the safety guarantees of `from_raw_parts`. 29 | let len = usize::min(len, isize::MAX as usize); 30 | buf.write_bytes(0, len); 31 | slice::from_raw_parts_mut(buf, len) 32 | }; 33 | 34 | let ret = entropy::read(buf, flags); 35 | if ret < 0 { 36 | warn!("Unable to read entropy! Fallback to a naive implementation!"); 37 | for i in &mut *buf { 38 | *i = (generate_park_miller_lehmer_random_number() & 0xff) 39 | .try_into() 40 | .unwrap(); 41 | } 42 | buf.len().try_into().unwrap() 43 | } else { 44 | ret 45 | } 46 | } 47 | 48 | unsafe extern "C" fn __sys_read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize { 49 | unsafe { read_entropy(buf, len, flags) } 50 | } 51 | 52 | /// Fill `len` bytes in `buf` with cryptographically secure random data. 53 | /// 54 | /// Returns either the number of bytes written to buf (a positive value) or 55 | /// * `-EINVAL` if `flags` contains unknown flags. 56 | /// * `-ENOSYS` if the system does not support random data generation. 57 | #[unsafe(no_mangle)] 58 | pub unsafe extern "C" fn sys_read_entropy(buf: *mut u8, len: usize, flags: u32) -> isize { 59 | unsafe { kernel_function!(__sys_read_entropy(buf, len, flags)) } 60 | } 61 | 62 | /// Create a cryptographicly secure 32bit random number with the support of 63 | /// the underlying hardware. If the required hardware isn't available, 64 | /// the function returns `-1`. 65 | #[cfg(not(feature = "newlib"))] 66 | #[hermit_macro::system] 67 | #[unsafe(no_mangle)] 68 | pub unsafe extern "C" fn sys_secure_rand32(value: *mut u32) -> i32 { 69 | let mut buf = value.cast(); 70 | let mut len = size_of::(); 71 | while len != 0 { 72 | let res = unsafe { read_entropy(buf, len, 0) }; 73 | if res < 0 { 74 | return -1; 75 | } 76 | 77 | buf = unsafe { buf.add(res as usize) }; 78 | len -= res as usize; 79 | } 80 | 81 | 0 82 | } 83 | 84 | /// Create a cryptographicly secure 64bit random number with the support of 85 | /// the underlying hardware. If the required hardware isn't available, 86 | /// the function returns -1. 87 | #[cfg(not(feature = "newlib"))] 88 | #[hermit_macro::system] 89 | #[unsafe(no_mangle)] 90 | pub unsafe extern "C" fn sys_secure_rand64(value: *mut u64) -> i32 { 91 | let mut buf = value.cast(); 92 | let mut len = size_of::(); 93 | while len != 0 { 94 | let res = unsafe { read_entropy(buf, len, 0) }; 95 | if res < 0 { 96 | return -1; 97 | } 98 | 99 | buf = unsafe { buf.add(res as usize) }; 100 | len -= res as usize; 101 | } 102 | 103 | 0 104 | } 105 | 106 | /// The function computes a sequence of pseudo-random integers 107 | /// in the range of 0 to RAND_MAX 108 | #[hermit_macro::system] 109 | #[unsafe(no_mangle)] 110 | pub extern "C" fn sys_rand() -> u32 { 111 | generate_park_miller_lehmer_random_number() 112 | } 113 | 114 | /// The function sets its argument as the seed for a new sequence 115 | /// of pseudo-random numbers to be returned by rand() 116 | #[hermit_macro::system] 117 | #[unsafe(no_mangle)] 118 | pub extern "C" fn sys_srand(seed: u32) { 119 | *(PARK_MILLER_LEHMER_SEED.lock()) = seed; 120 | } 121 | 122 | pub(crate) fn init_entropy() { 123 | let seed: u32 = arch::processor::get_timestamp() as u32; 124 | 125 | *PARK_MILLER_LEHMER_SEED.lock() = seed; 126 | } 127 | -------------------------------------------------------------------------------- /src/syscalls/futex.rs: -------------------------------------------------------------------------------- 1 | use core::sync::atomic::AtomicU32; 2 | 3 | use crate::errno::EINVAL; 4 | use crate::synch::futex::{self as synch, Flags}; 5 | use crate::time::timespec; 6 | 7 | /// Like `synch::futex_wait`, but does extra sanity checks and takes a `timespec`. 8 | /// 9 | /// Returns -EINVAL if 10 | /// * `address` is null 11 | /// * `timeout` is negative 12 | /// * `flags` contains unknown flags 13 | #[hermit_macro::system] 14 | #[unsafe(no_mangle)] 15 | pub unsafe extern "C" fn sys_futex_wait( 16 | address: *mut u32, 17 | expected: u32, 18 | timeout: *const timespec, 19 | flags: u32, 20 | ) -> i32 { 21 | if address.is_null() { 22 | return -EINVAL; 23 | } 24 | 25 | let address = unsafe { &*(address as *const AtomicU32) }; 26 | let timeout = if timeout.is_null() { 27 | None 28 | } else { 29 | match unsafe { timeout.read().into_usec() } { 30 | Some(usec) if usec >= 0 => Some(usec as u64), 31 | _ => return -EINVAL, 32 | } 33 | }; 34 | let Some(flags) = Flags::from_bits(flags) else { 35 | return -EINVAL; 36 | }; 37 | 38 | synch::futex_wait(address, expected, timeout, flags) 39 | } 40 | 41 | /// Like `synch::futex_wake`, but does extra sanity checks. 42 | /// 43 | /// Returns -EINVAL if `address` is null. 44 | /// `address` is used only for its address. 45 | /// It is safe to pass a dangling pointer. 46 | #[hermit_macro::system] 47 | #[unsafe(no_mangle)] 48 | pub unsafe extern "C" fn sys_futex_wake(address: *mut u32, count: i32) -> i32 { 49 | if address.is_null() { 50 | return -EINVAL; 51 | } 52 | 53 | synch::futex_wake(address as *const AtomicU32, count) 54 | } 55 | -------------------------------------------------------------------------------- /src/syscalls/interfaces/generic.rs: -------------------------------------------------------------------------------- 1 | use crate::syscalls::interfaces::SyscallInterface; 2 | 3 | // The generic interface simply uses all default implementations of the 4 | // SyscallInterface trait. 5 | pub struct Generic; 6 | impl SyscallInterface for Generic {} 7 | -------------------------------------------------------------------------------- /src/syscalls/interfaces/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::vec::Vec; 3 | 4 | pub use self::generic::*; 5 | pub use self::uhyve::*; 6 | use crate::{arch, env}; 7 | 8 | mod generic; 9 | pub(crate) mod uhyve; 10 | 11 | pub trait SyscallInterface: Send + Sync { 12 | fn init(&self) { 13 | // Interface-specific initialization steps. 14 | } 15 | 16 | fn get_application_parameters(&self) -> (i32, *const *const u8, *const *const u8) { 17 | let mut argv = Vec::new(); 18 | 19 | let name = Box::leak(Box::new("bin\0")).as_ptr(); 20 | argv.push(name); 21 | 22 | let args = env::args(); 23 | debug!("Setting argv as: {args:?}"); 24 | for arg in args { 25 | let ptr = Box::leak(format!("{arg}\0").into_boxed_str()).as_ptr(); 26 | argv.push(ptr); 27 | } 28 | 29 | let mut envv = Vec::new(); 30 | 31 | let envs = env::vars(); 32 | debug!("Setting envv as: {envs:?}"); 33 | for (key, value) in envs { 34 | let ptr = Box::leak(format!("{key}={value}\0").into_boxed_str()).as_ptr(); 35 | envv.push(ptr); 36 | } 37 | envv.push(core::ptr::null::()); 38 | 39 | let argc = argv.len() as i32; 40 | let argv = argv.leak().as_ptr(); 41 | // do we have more than a end marker? If not, return as null pointer 42 | let envv = if envv.len() == 1 { 43 | core::ptr::null::<*const u8>() 44 | } else { 45 | envv.leak().as_ptr() 46 | }; 47 | 48 | (argc, argv, envv) 49 | } 50 | 51 | fn shutdown(&self, error_code: i32) -> ! { 52 | // This is a stable message used for detecting exit codes for different hypervisors. 53 | panic_println!("exit status {error_code}"); 54 | 55 | arch::processor::shutdown(error_code) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/syscalls/interfaces/uhyve.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | 3 | use memory_addresses::VirtAddr; 4 | use uhyve_interface::parameters::{ExitParams, SerialWriteBufferParams}; 5 | use uhyve_interface::{Hypercall, HypercallAddress}; 6 | 7 | use crate::arch; 8 | use crate::arch::mm::paging::{self, virtual_to_physical}; 9 | use crate::syscalls::interfaces::SyscallInterface; 10 | 11 | /// perform a SerialWriteBuffer hypercall with `buf` as payload. 12 | #[inline] 13 | #[cfg_attr(target_arch = "riscv64", expect(dead_code))] 14 | pub(crate) fn serial_buf_hypercall(buf: &[u8]) { 15 | let p = SerialWriteBufferParams { 16 | buf: virtual_to_physical(VirtAddr::from_ptr(core::ptr::from_ref::<[u8]>(buf))).unwrap(), 17 | len: buf.len(), 18 | }; 19 | uhyve_hypercall(Hypercall::SerialWriteBuffer(&p)); 20 | } 21 | 22 | /// calculates the physical address of the struct passed as reference. 23 | #[inline] 24 | fn data_addr(data: &T) -> u64 { 25 | paging::virtual_to_physical(VirtAddr::from_ptr(ptr::from_ref(data))) 26 | .unwrap() 27 | .as_u64() 28 | } 29 | 30 | /// calculates the hypercall data argument 31 | #[inline] 32 | fn hypercall_data(hypercall: &Hypercall<'_>) -> u64 { 33 | match hypercall { 34 | Hypercall::Cmdsize(data) => data_addr(*data), 35 | Hypercall::Cmdval(data) => data_addr(*data), 36 | Hypercall::Exit(data) => data_addr(*data), 37 | Hypercall::FileClose(data) => data_addr(*data), 38 | Hypercall::FileLseek(data) => data_addr(*data), 39 | Hypercall::FileOpen(data) => data_addr(*data), 40 | Hypercall::FileRead(data) => data_addr(*data), 41 | Hypercall::FileUnlink(data) => data_addr(*data), 42 | Hypercall::FileWrite(data) => data_addr(*data), 43 | Hypercall::SerialWriteBuffer(data) => data_addr(*data), 44 | Hypercall::SerialWriteByte(byte) => u64::from(*byte), 45 | h => todo!("unimplemented hypercall {h:?}"), 46 | } 47 | } 48 | 49 | /// Perform a hypercall to the uhyve hypervisor 50 | #[inline] 51 | #[allow(unused_variables)] // until riscv64 is implemented 52 | pub(crate) fn uhyve_hypercall(hypercall: Hypercall<'_>) { 53 | let ptr = HypercallAddress::from(&hypercall) as u16; 54 | let data = hypercall_data(&hypercall); 55 | 56 | #[cfg(target_arch = "x86_64")] 57 | unsafe { 58 | use x86_64::instructions::port::Port; 59 | 60 | let data = 61 | u32::try_from(data).expect("Hypercall data must lie in the first 4GiB of memory"); 62 | Port::new(ptr).write(data); 63 | } 64 | 65 | #[cfg(target_arch = "aarch64")] 66 | unsafe { 67 | use core::arch::asm; 68 | asm!( 69 | "str x8, [{ptr}]", 70 | ptr = in(reg) u64::from(ptr), 71 | in("x8") data, 72 | options(nostack), 73 | ); 74 | } 75 | 76 | #[cfg(target_arch = "riscv64")] 77 | todo!() 78 | } 79 | 80 | pub struct Uhyve; 81 | 82 | impl SyscallInterface for Uhyve { 83 | fn shutdown(&self, error_code: i32) -> ! { 84 | let sysexit = ExitParams { arg: error_code }; 85 | uhyve_hypercall(Hypercall::Exit(&sysexit)); 86 | 87 | loop { 88 | arch::processor::halt(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/syscalls/mmap.rs: -------------------------------------------------------------------------------- 1 | use align_address::Align; 2 | use memory_addresses::VirtAddr; 3 | 4 | use crate::arch; 5 | #[cfg(target_arch = "x86_64")] 6 | use crate::arch::mm::paging::PageTableEntryFlagsExt; 7 | use crate::arch::mm::paging::{BasePageSize, PageSize, PageTableEntryFlags}; 8 | 9 | bitflags! { 10 | #[repr(transparent)] 11 | #[derive(Debug, Copy, Clone, Default)] 12 | pub struct MemoryProtection: u32 { 13 | /// Pages may not be accessed. 14 | const None = 0; 15 | /// Indicates that the memory region should be readable. 16 | const Read = 1 << 0; 17 | /// Indicates that the memory region should be writable. 18 | const Write = 1 << 1; 19 | /// Indicates that the memory region should be executable. 20 | const Exec = 1 << 2; 21 | } 22 | } 23 | 24 | /// Creates a new virtual memory mapping of the `size` specified with 25 | /// protection bits specified in `prot_flags`. 26 | #[hermit_macro::system] 27 | #[unsafe(no_mangle)] 28 | pub extern "C" fn sys_mmap(size: usize, prot_flags: MemoryProtection, ret: &mut *mut u8) -> i32 { 29 | let size = size.align_up(BasePageSize::SIZE as usize); 30 | let virtual_address = crate::mm::virtualmem::allocate(size).unwrap(); 31 | if prot_flags.is_empty() { 32 | *ret = virtual_address.as_mut_ptr(); 33 | return 0; 34 | } 35 | let physical_address = crate::mm::physicalmem::allocate(size).unwrap(); 36 | 37 | let count = size / BasePageSize::SIZE as usize; 38 | let mut flags = PageTableEntryFlags::empty(); 39 | flags.normal().writable(); 40 | if prot_flags.contains(MemoryProtection::Write) { 41 | flags.writable(); 42 | } 43 | if !prot_flags.contains(MemoryProtection::Exec) { 44 | flags.execute_disable(); 45 | } 46 | 47 | arch::mm::paging::map::(virtual_address, physical_address, count, flags); 48 | 49 | *ret = virtual_address.as_mut_ptr(); 50 | 51 | 0 52 | } 53 | 54 | /// Unmaps memory at the specified `ptr` for `size` bytes. 55 | #[hermit_macro::system] 56 | #[unsafe(no_mangle)] 57 | pub extern "C" fn sys_munmap(ptr: *mut u8, size: usize) -> i32 { 58 | let virtual_address = VirtAddr::from_ptr(ptr); 59 | let size = size.align_up(BasePageSize::SIZE as usize); 60 | 61 | if let Some(phys_addr) = arch::mm::paging::virtual_to_physical(virtual_address) { 62 | arch::mm::paging::unmap::( 63 | virtual_address, 64 | size / BasePageSize::SIZE as usize, 65 | ); 66 | crate::mm::physicalmem::deallocate(phys_addr, size); 67 | } 68 | 69 | crate::mm::virtualmem::deallocate(virtual_address, size); 70 | 71 | 0 72 | } 73 | 74 | /// Configures the protections associated with a region of virtual memory 75 | /// starting at `ptr` and going to `size`. 76 | /// 77 | /// Returns 0 on success and an error code on failure. 78 | #[hermit_macro::system] 79 | #[unsafe(no_mangle)] 80 | pub extern "C" fn sys_mprotect(ptr: *mut u8, size: usize, prot_flags: MemoryProtection) -> i32 { 81 | let count = size / BasePageSize::SIZE as usize; 82 | let mut flags = PageTableEntryFlags::empty(); 83 | flags.normal().writable(); 84 | if prot_flags.contains(MemoryProtection::Write) { 85 | flags.writable(); 86 | } 87 | if !prot_flags.contains(MemoryProtection::Exec) { 88 | flags.execute_disable(); 89 | } 90 | 91 | let virtual_address = VirtAddr::from_ptr(ptr); 92 | 93 | if let Some(physical_address) = arch::mm::paging::virtual_to_physical(virtual_address) { 94 | arch::mm::paging::map::(virtual_address, physical_address, count, flags); 95 | 0 96 | } else { 97 | let physical_address = crate::mm::physicalmem::allocate(size).unwrap(); 98 | arch::mm::paging::map::(virtual_address, physical_address, count, flags); 99 | 0 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/syscalls/processor.rs: -------------------------------------------------------------------------------- 1 | use crate::arch::get_processor_count; 2 | 3 | /// Returns the number of processors currently online. 4 | #[hermit_macro::system] 5 | #[unsafe(no_mangle)] 6 | pub extern "C" fn sys_get_processor_count() -> usize { 7 | get_processor_count().try_into().unwrap() 8 | } 9 | 10 | #[hermit_macro::system] 11 | #[unsafe(no_mangle)] 12 | pub extern "C" fn sys_available_parallelism() -> usize { 13 | get_processor_count().try_into().unwrap() 14 | } 15 | 16 | /// Returns the processor frequency in MHz. 17 | #[hermit_macro::system] 18 | #[unsafe(no_mangle)] 19 | pub extern "C" fn sys_get_processor_frequency() -> u16 { 20 | crate::arch::processor::get_frequency() 21 | } 22 | -------------------------------------------------------------------------------- /src/syscalls/recmutex.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | use crate::errno::*; 4 | use crate::synch::recmutex::RecursiveMutex; 5 | 6 | #[hermit_macro::system] 7 | #[unsafe(no_mangle)] 8 | pub unsafe extern "C" fn sys_recmutex_init(recmutex: *mut *mut RecursiveMutex) -> i32 { 9 | if recmutex.is_null() { 10 | return -EINVAL; 11 | } 12 | 13 | // Create a new boxed recursive mutex and return a pointer to the raw memory. 14 | let boxed_mutex = Box::new(RecursiveMutex::new()); 15 | unsafe { 16 | *recmutex = Box::into_raw(boxed_mutex); 17 | } 18 | 19 | 0 20 | } 21 | 22 | #[hermit_macro::system] 23 | #[unsafe(no_mangle)] 24 | pub unsafe extern "C" fn sys_recmutex_destroy(recmutex: *mut RecursiveMutex) -> i32 { 25 | if recmutex.is_null() { 26 | return -EINVAL; 27 | } 28 | 29 | // Consume the pointer to the raw memory into a Box again 30 | // and drop the Box to free the associated memory. 31 | unsafe { 32 | drop(Box::from_raw(recmutex)); 33 | } 34 | 35 | 0 36 | } 37 | 38 | #[hermit_macro::system] 39 | #[unsafe(no_mangle)] 40 | pub unsafe extern "C" fn sys_recmutex_lock(recmutex: *mut RecursiveMutex) -> i32 { 41 | if recmutex.is_null() { 42 | return -EINVAL; 43 | } 44 | 45 | let mutex = unsafe { &*recmutex }; 46 | mutex.acquire(); 47 | 48 | 0 49 | } 50 | 51 | #[hermit_macro::system] 52 | #[unsafe(no_mangle)] 53 | pub unsafe extern "C" fn sys_recmutex_unlock(recmutex: *mut RecursiveMutex) -> i32 { 54 | if recmutex.is_null() { 55 | return -EINVAL; 56 | } 57 | 58 | let mutex = unsafe { &*recmutex }; 59 | mutex.release(); 60 | 61 | 0 62 | } 63 | -------------------------------------------------------------------------------- /src/syscalls/semaphore.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | use crate::errno::*; 4 | use crate::synch::semaphore::Semaphore; 5 | use crate::syscalls::{CLOCK_REALTIME, sys_clock_gettime}; 6 | use crate::time::timespec; 7 | 8 | #[allow(non_camel_case_types)] 9 | pub type sem_t = *const Semaphore; 10 | 11 | /// Create a new, unnamed semaphore. 12 | /// 13 | /// This function can be used to get the raw memory location of a semaphore. 14 | /// 15 | /// Stores the raw memory location of the new semaphore in parameter `sem`. 16 | /// Returns `0` on success, `-EINVAL` if `sem` is null. 17 | #[hermit_macro::system] 18 | #[unsafe(no_mangle)] 19 | pub unsafe extern "C" fn sys_sem_init(sem: *mut sem_t, pshared: i32, value: u32) -> i32 { 20 | if sem.is_null() || pshared != 0 { 21 | return -EINVAL; 22 | } 23 | 24 | // Create a new boxed semaphore and return a pointer to the raw memory. 25 | let boxed_semaphore = Box::new(Semaphore::new(value as isize)); 26 | unsafe { 27 | *sem = Box::into_raw(boxed_semaphore); 28 | } 29 | 0 30 | } 31 | 32 | /// Destroy and deallocate a semaphore. 33 | /// 34 | /// This function can be used to manually deallocate a semaphore via a reference. 35 | /// 36 | /// Returns `0` on success, `-EINVAL` if `sem` is null. 37 | #[hermit_macro::system] 38 | #[unsafe(no_mangle)] 39 | pub unsafe extern "C" fn sys_sem_destroy(sem: *mut sem_t) -> i32 { 40 | if sem.is_null() { 41 | return -EINVAL; 42 | } 43 | 44 | // Consume the pointer to the raw memory into a Box again 45 | // and drop the Box to free the associated memory. 46 | unsafe { 47 | drop(Box::from_raw((*sem).cast_mut())); 48 | } 49 | 0 50 | } 51 | 52 | /// Release a semaphore. 53 | /// 54 | /// This function can be used to allow the next blocked waiter to access this semaphore. 55 | /// It will notify the next waiter that `sem` is available. 56 | /// The semaphore is not deallocated after being released. 57 | /// 58 | /// Returns `0` on success, or `-EINVAL` if `sem` is null. 59 | #[hermit_macro::system] 60 | #[unsafe(no_mangle)] 61 | pub unsafe extern "C" fn sys_sem_post(sem: *mut sem_t) -> i32 { 62 | if sem.is_null() { 63 | return -EINVAL; 64 | } 65 | 66 | // Get a reference to the given semaphore and release it. 67 | let semaphore = unsafe { &**sem }; 68 | semaphore.release(); 69 | 0 70 | } 71 | 72 | /// Try to acquire a lock on a semaphore. 73 | /// 74 | /// This function does not block if the acquire fails. 75 | /// If the acquire fails (i.e. the semaphore's count is already 0), the function returns immediately. 76 | /// 77 | /// Returns `0` on lock acquire, `-EINVAL` if `sem` is null, or `-ECANCELED` if the decrement fails. 78 | #[hermit_macro::system] 79 | #[unsafe(no_mangle)] 80 | pub unsafe extern "C" fn sys_sem_trywait(sem: *mut sem_t) -> i32 { 81 | if sem.is_null() { 82 | return -EINVAL; 83 | } 84 | 85 | // Get a reference to the given semaphore and acquire it in a non-blocking fashion. 86 | let semaphore = unsafe { &**sem }; 87 | if semaphore.try_acquire() { 88 | 0 89 | } else { 90 | -ECANCELED 91 | } 92 | } 93 | 94 | unsafe fn sem_timedwait(sem: *mut sem_t, ms: u32) -> i32 { 95 | if sem.is_null() { 96 | return -EINVAL; 97 | } 98 | 99 | let delay = if ms > 0 { Some(u64::from(ms)) } else { None }; 100 | 101 | // Get a reference to the given semaphore and wait until we have acquired it or the wakeup time has elapsed. 102 | let semaphore = unsafe { &**sem }; 103 | if semaphore.acquire(delay) { 0 } else { -ETIME } 104 | } 105 | 106 | /// Try to acquire a lock on a semaphore. 107 | /// 108 | /// Blocks until semaphore is acquired or until specified time passed 109 | /// 110 | /// Returns `0` on lock acquire, `-EINVAL` if sem is null, or `-ETIME` on timeout. 111 | #[hermit_macro::system] 112 | #[unsafe(no_mangle)] 113 | pub unsafe extern "C" fn sys_sem_timedwait(sem: *mut sem_t, ts: *const timespec) -> i32 { 114 | if ts.is_null() { 115 | unsafe { sem_timedwait(sem, 0) } 116 | } else { 117 | let mut current_ts = timespec::default(); 118 | 119 | unsafe { 120 | sys_clock_gettime(CLOCK_REALTIME, &raw mut current_ts); 121 | 122 | let ts = &*ts; 123 | let ms: i64 = (ts.tv_sec - current_ts.tv_sec) * 1000 124 | + (i64::from(ts.tv_nsec) - i64::from(current_ts.tv_nsec)) / 1_000_000; 125 | 126 | if ms > 0 { 127 | sem_timedwait(sem, ms.try_into().unwrap()) 128 | } else { 129 | 0 130 | } 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/syscalls/spinlock.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | use hermit_sync::{InterruptTicketMutex, InterruptTicketMutexGuard, TicketMutex, TicketMutexGuard}; 4 | 5 | use crate::errno::*; 6 | 7 | pub struct SpinlockContainer<'a> { 8 | lock: TicketMutex<()>, 9 | guard: Option>, 10 | } 11 | 12 | pub struct SpinlockIrqSaveContainer<'a> { 13 | lock: InterruptTicketMutex<()>, 14 | guard: Option>, 15 | } 16 | 17 | #[hermit_macro::system] 18 | #[unsafe(no_mangle)] 19 | pub unsafe extern "C" fn sys_spinlock_init(lock: *mut *mut SpinlockContainer<'_>) -> i32 { 20 | if lock.is_null() { 21 | return -EINVAL; 22 | } 23 | 24 | let boxed_container = Box::new(SpinlockContainer { 25 | lock: TicketMutex::new(()), 26 | guard: None, 27 | }); 28 | unsafe { 29 | *lock = Box::into_raw(boxed_container); 30 | } 31 | 0 32 | } 33 | 34 | #[hermit_macro::system] 35 | #[unsafe(no_mangle)] 36 | pub unsafe extern "C" fn sys_spinlock_destroy(lock: *mut SpinlockContainer<'_>) -> i32 { 37 | if lock.is_null() { 38 | return -EINVAL; 39 | } 40 | 41 | // Consume the lock into a box, which is then dropped. 42 | unsafe { 43 | drop(Box::from_raw(lock)); 44 | } 45 | 0 46 | } 47 | 48 | #[hermit_macro::system] 49 | #[unsafe(no_mangle)] 50 | pub unsafe extern "C" fn sys_spinlock_lock(lock: *mut SpinlockContainer<'_>) -> i32 { 51 | if lock.is_null() { 52 | return -EINVAL; 53 | } 54 | 55 | let container = unsafe { &mut *lock }; 56 | assert!( 57 | container.guard.is_none(), 58 | "Called sys_spinlock_lock when a lock is already held!" 59 | ); 60 | container.guard = Some(container.lock.lock()); 61 | 0 62 | } 63 | 64 | #[hermit_macro::system] 65 | #[unsafe(no_mangle)] 66 | pub unsafe extern "C" fn sys_spinlock_unlock(lock: *mut SpinlockContainer<'_>) -> i32 { 67 | if lock.is_null() { 68 | return -EINVAL; 69 | } 70 | 71 | let container = unsafe { &mut *lock }; 72 | assert!( 73 | container.guard.is_some(), 74 | "Called sys_spinlock_unlock when no lock is currently held!" 75 | ); 76 | container.guard = None; 77 | 0 78 | } 79 | 80 | #[hermit_macro::system] 81 | #[unsafe(no_mangle)] 82 | pub unsafe extern "C" fn sys_spinlock_irqsave_init( 83 | lock: *mut *mut SpinlockIrqSaveContainer<'_>, 84 | ) -> i32 { 85 | if lock.is_null() { 86 | return -EINVAL; 87 | } 88 | 89 | let boxed_container = Box::new(SpinlockIrqSaveContainer { 90 | lock: InterruptTicketMutex::new(()), 91 | guard: None, 92 | }); 93 | unsafe { 94 | *lock = Box::into_raw(boxed_container); 95 | } 96 | 0 97 | } 98 | 99 | #[hermit_macro::system] 100 | #[unsafe(no_mangle)] 101 | pub unsafe extern "C" fn sys_spinlock_irqsave_destroy( 102 | lock: *mut SpinlockIrqSaveContainer<'_>, 103 | ) -> i32 { 104 | if lock.is_null() { 105 | return -EINVAL; 106 | } 107 | 108 | // Consume the lock into a box, which is then dropped. 109 | unsafe { 110 | drop(Box::from_raw(lock)); 111 | } 112 | 0 113 | } 114 | 115 | #[hermit_macro::system] 116 | #[unsafe(no_mangle)] 117 | pub unsafe extern "C" fn sys_spinlock_irqsave_lock(lock: *mut SpinlockIrqSaveContainer<'_>) -> i32 { 118 | if lock.is_null() { 119 | return -EINVAL; 120 | } 121 | 122 | let container = unsafe { &mut *lock }; 123 | assert!( 124 | container.guard.is_none(), 125 | "Called sys_spinlock_irqsave_lock when a lock is already held!" 126 | ); 127 | container.guard = Some(container.lock.lock()); 128 | 0 129 | } 130 | 131 | #[hermit_macro::system] 132 | #[unsafe(no_mangle)] 133 | pub unsafe extern "C" fn sys_spinlock_irqsave_unlock( 134 | lock: *mut SpinlockIrqSaveContainer<'_>, 135 | ) -> i32 { 136 | if lock.is_null() { 137 | return -EINVAL; 138 | } 139 | 140 | let container = unsafe { &mut *lock }; 141 | assert!( 142 | container.guard.is_some(), 143 | "Called sys_spinlock_irqsave_unlock when no lock is currently held!" 144 | ); 145 | container.guard = None; 146 | 0 147 | } 148 | -------------------------------------------------------------------------------- /src/syscalls/system.rs: -------------------------------------------------------------------------------- 1 | use crate::arch::mm::paging::{BasePageSize, PageSize}; 2 | 3 | /// Returns the base page size, in bytes, of the current system. 4 | #[hermit_macro::system] 5 | #[unsafe(no_mangle)] 6 | pub extern "C" fn sys_getpagesize() -> i32 { 7 | BasePageSize::SIZE.try_into().unwrap() 8 | } 9 | -------------------------------------------------------------------------------- /src/syscalls/table.rs: -------------------------------------------------------------------------------- 1 | use core::arch::naked_asm; 2 | 3 | use crate::syscalls::*; 4 | 5 | /// number of the system call `exit` 6 | const SYSNO_EXIT: usize = 0; 7 | /// number of the system call `write` 8 | const SYSNO_WRITE: usize = 1; 9 | /// number of the system call `read` 10 | const SYSNO_READ: usize = 2; 11 | /// number of the system call `usleep` 12 | const SYSNO_USLEEP: usize = 3; 13 | /// number of the system call `getpid` 14 | const SYSNO_GETPID: usize = 4; 15 | /// number of the system call `yield` 16 | const SYSNO_YIELD: usize = 5; 17 | /// number of the system call `read_entropy` 18 | const SYSNO_READ_ENTROPY: usize = 6; 19 | /// number of the system call `get_processor_count` 20 | const SYSNO_GET_PROCESSOR_COUNT: usize = 7; 21 | /// number of the system call `close` 22 | const SYSNO_CLOSE: usize = 8; 23 | /// number of the system call `futex_wait` 24 | const SYSNO_FUTEX_WAIT: usize = 9; 25 | /// number of the system call `futex_wake` 26 | const SYSNO_FUTEX_WAKE: usize = 10; 27 | /// number of the system call `open` 28 | const SYSNO_OPEN: usize = 11; 29 | /// number of the system call `writev` 30 | const SYSNO_WRITEV: usize = 12; 31 | /// number of the system call `readv` 32 | const SYSNO_READV: usize = 13; 33 | 34 | /// total number of system calls 35 | const NO_SYSCALLS: usize = 32; 36 | 37 | extern "C" fn invalid_syscall(sys_no: u64) -> ! { 38 | error!("Invalid syscall {sys_no}"); 39 | sys_exit(1); 40 | } 41 | 42 | #[allow(unused_assignments)] 43 | #[unsafe(no_mangle)] 44 | #[unsafe(naked)] 45 | pub(crate) unsafe extern "C" fn sys_invalid() { 46 | naked_asm!( 47 | "mov rdi, rax", 48 | "call {}", 49 | sym invalid_syscall, 50 | ); 51 | } 52 | 53 | #[repr(align(64))] 54 | #[repr(C)] 55 | pub(crate) struct SyscallTable { 56 | handle: [*const usize; NO_SYSCALLS], 57 | } 58 | 59 | impl SyscallTable { 60 | pub const fn new() -> Self { 61 | let mut table = SyscallTable { 62 | handle: [sys_invalid as *const _; NO_SYSCALLS], 63 | }; 64 | 65 | table.handle[SYSNO_EXIT] = sys_exit as *const _; 66 | table.handle[SYSNO_WRITE] = sys_write as *const _; 67 | table.handle[SYSNO_READ] = sys_read as *const _; 68 | table.handle[SYSNO_USLEEP] = sys_usleep as *const _; 69 | table.handle[SYSNO_GETPID] = sys_getpid as *const _; 70 | table.handle[SYSNO_YIELD] = sys_yield as *const _; 71 | table.handle[SYSNO_READ_ENTROPY] = sys_read_entropy as *const _; 72 | table.handle[SYSNO_GET_PROCESSOR_COUNT] = sys_get_processor_count as *const _; 73 | table.handle[SYSNO_CLOSE] = sys_close as *const _; 74 | table.handle[SYSNO_FUTEX_WAIT] = sys_futex_wait as *const _; 75 | table.handle[SYSNO_FUTEX_WAKE] = sys_futex_wake as *const _; 76 | table.handle[SYSNO_OPEN] = sys_open as *const _; 77 | table.handle[SYSNO_READV] = sys_readv as *const _; 78 | table.handle[SYSNO_WRITEV] = sys_writev as *const _; 79 | 80 | table 81 | } 82 | } 83 | 84 | unsafe impl Send for SyscallTable {} 85 | unsafe impl Sync for SyscallTable {} 86 | 87 | #[unsafe(no_mangle)] 88 | pub(crate) static SYSHANDLER_TABLE: SyscallTable = SyscallTable::new(); 89 | -------------------------------------------------------------------------------- /src/time.rs: -------------------------------------------------------------------------------- 1 | use core::time::Duration; 2 | 3 | use crate::arch; 4 | 5 | #[allow(non_camel_case_types)] 6 | pub type time_t = i64; 7 | #[allow(non_camel_case_types)] 8 | pub type useconds_t = u32; 9 | #[allow(non_camel_case_types)] 10 | pub type suseconds_t = i32; 11 | 12 | /// Represent the number of seconds and microseconds since 13 | /// the Epoch (1970-01-01 00:00:00 +0000 (UTC)) 14 | #[derive(Copy, Clone, Debug)] 15 | #[repr(C)] 16 | pub struct timeval { 17 | /// seconds 18 | pub tv_sec: time_t, 19 | /// microseconds 20 | pub tv_usec: suseconds_t, 21 | } 22 | 23 | impl timeval { 24 | pub fn from_usec(microseconds: i64) -> Self { 25 | Self { 26 | tv_sec: (microseconds / 1_000_000), 27 | tv_usec: (microseconds % 1_000_000) as i32, 28 | } 29 | } 30 | 31 | pub fn into_usec(&self) -> Option { 32 | self.tv_sec 33 | .checked_mul(1_000_000) 34 | .and_then(|usec| usec.checked_add(self.tv_usec.into())) 35 | } 36 | } 37 | 38 | /// Represent the timer interval in seconds and microseconds 39 | #[derive(Copy, Clone, Debug)] 40 | #[repr(C)] 41 | pub struct itimerval { 42 | pub it_interval: timeval, 43 | pub it_value: timeval, 44 | } 45 | 46 | /// Represent the number of seconds and nanoseconds since 47 | /// the Epoch (1970-01-01 00:00:00 +0000 (UTC)) 48 | #[derive(Copy, Clone, Debug, Default)] 49 | #[repr(C)] 50 | pub struct timespec { 51 | /// seconds 52 | pub tv_sec: time_t, 53 | /// nanoseconds 54 | pub tv_nsec: i32, 55 | } 56 | 57 | impl timespec { 58 | pub fn from_usec(microseconds: i64) -> Self { 59 | Self { 60 | tv_sec: (microseconds / 1_000_000), 61 | tv_nsec: ((microseconds % 1_000_000) * 1000) as i32, 62 | } 63 | } 64 | 65 | pub fn into_usec(&self) -> Option { 66 | self.tv_sec 67 | .checked_mul(1_000_000) 68 | .and_then(|usec| usec.checked_add((self.tv_nsec / 1000).into())) 69 | } 70 | } 71 | 72 | #[derive(Copy, Clone, Debug, Default)] 73 | pub struct SystemTime(timespec); 74 | 75 | impl SystemTime { 76 | pub const UNIX_EPOCH: SystemTime = Self(timespec { 77 | tv_sec: 0, 78 | tv_nsec: 0, 79 | }); 80 | 81 | /// Returns the system time corresponding to "now". 82 | pub fn now() -> Self { 83 | Self(timespec::from_usec( 84 | arch::kernel::systemtime::now_micros() as i64 85 | )) 86 | } 87 | 88 | /// Returns the amount of time elapsed from an earlier point in time. 89 | pub fn duration_since(&self, earlier: SystemTime) -> Duration { 90 | Duration::from_micros( 91 | self.0 92 | .into_usec() 93 | .unwrap() 94 | .checked_sub(earlier.0.into_usec().unwrap()) 95 | .unwrap() 96 | .try_into() 97 | .unwrap(), 98 | ) 99 | } 100 | } 101 | 102 | impl From for SystemTime { 103 | fn from(t: timespec) -> Self { 104 | Self(t) 105 | } 106 | } 107 | 108 | impl From for timespec { 109 | fn from(value: SystemTime) -> Self { 110 | value.0 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /tests/basic_mem.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | #![no_std] 3 | #![no_main] 4 | #![test_runner(common::test_case_runner)] 5 | #![feature(custom_test_frameworks)] 6 | #![reexport_test_harness_main = "test_main"] 7 | 8 | extern crate alloc; 9 | 10 | mod common; 11 | 12 | use alloc::vec::Vec; 13 | use core::mem::size_of; 14 | 15 | const PATTERN: u8 = 0xab; 16 | 17 | /// Mainly test if memcpy works as expected. Also somewhat tests memcmp 18 | /// Works with u8, u16, u32, u64, u128, i16, i32, i64 and i128 19 | /// Probably not a super good test 20 | fn mem() 21 | where 22 | T: core::fmt::Debug, 23 | T: num_traits::int::PrimInt, 24 | { 25 | unsafe extern "C" { 26 | fn memcpy(dest: *mut u8, src: *const u8, n: usize) -> *mut u8; 27 | fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32; 28 | } 29 | let vec_size: u32 = 10000; 30 | let pre_dest_vec_size: u32 = 1; 31 | let post_dest_vec_size: u32 = 1; 32 | let t_base_pattern: T = T::from(PATTERN).unwrap(); 33 | let mut pattern: T = t_base_pattern; 34 | // Fill pattern of type T with size_of times the byte pattern 35 | // The "pre" and "post part of the destination vector are later filled with this pattern 36 | for _i in 1..size_of::() { 37 | pattern = pattern.shl(8) + t_base_pattern; 38 | } 39 | let pattern = pattern; // remove mut 40 | 41 | let a: Vec = { 42 | //Vec containing 0..min(vec_size, T::max_value()) as pattern for vec_size elements 43 | let mut a: Vec = Vec::with_capacity(vec_size as usize); 44 | let max = { 45 | // the max value in a is the minimum of (vec_size -1) and T::max 46 | let tmax = T::max_value(); 47 | if T::from(vec_size).is_none() { 48 | tmax.to_u64().unwrap() // If vec_size can't be represented in T, then tmax must fit in u64 49 | } else { 50 | u64::from(vec_size - 1) 51 | } 52 | }; 53 | // ToDo - This loop should be rewritten in a nicer way 54 | while a.len() < vec_size as usize { 55 | for i in 0..=max { 56 | a.push(T::from(i).unwrap()); 57 | if a.len() == vec_size as usize { 58 | break; 59 | }; 60 | } 61 | } 62 | a 63 | }; 64 | assert_eq!(a.len(), vec_size as usize); 65 | 66 | let mut b: Vec = 67 | Vec::with_capacity((vec_size + pre_dest_vec_size + post_dest_vec_size) as usize); 68 | // Fill pre and post section with `pattern` 69 | for i in 0..pre_dest_vec_size { 70 | b.spare_capacity_mut()[i as usize].write(pattern); 71 | } 72 | for i in 0..post_dest_vec_size { 73 | b.spare_capacity_mut()[(pre_dest_vec_size + vec_size + i) as usize].write(pattern); 74 | } 75 | // Manually set length, since we manually filled the vector 76 | unsafe { 77 | b.set_len((vec_size + pre_dest_vec_size + post_dest_vec_size) as usize); 78 | } 79 | // Copy the actual vector 80 | unsafe { 81 | memcpy( 82 | b.as_mut_ptr().add(pre_dest_vec_size as usize).cast::(), 83 | a.as_ptr().cast::(), 84 | ((size_of::() as u32) * vec_size) as usize, 85 | ); 86 | } 87 | // Assert that `pattern` in pre section was not changed by memcpy 88 | for i in 0..pre_dest_vec_size { 89 | assert_eq!(b[i as usize], pattern); 90 | } 91 | // Assert that `a` was correctly copied to `b` 92 | { 93 | let mut i = 0; // a[i] should match b[pre_dest_vec_size + i] 94 | let mut j: T = T::from(0).unwrap(); 95 | while i < vec_size { 96 | assert_eq!(b[(pre_dest_vec_size + i) as usize], j); 97 | i += 1; 98 | j = if j == T::max_value() { 99 | T::from(0).unwrap() 100 | } else { 101 | j + T::from(1).unwrap() 102 | } 103 | } 104 | } 105 | // Assert that `pattern` in post section was not changed 106 | for i in 0..post_dest_vec_size { 107 | assert_eq!(b[(pre_dest_vec_size + vec_size + i) as usize], pattern); 108 | } 109 | // Do the assertions again, but this time using `memcmp` 110 | unsafe { 111 | assert_eq!( 112 | memcmp( 113 | b.as_ptr().add(pre_dest_vec_size as usize).cast::(), 114 | a.as_ptr().cast::(), 115 | size_of::() * vec_size as usize, 116 | ), 117 | 0 118 | ); 119 | // pattern is larger, a[0] is 0 120 | assert!(memcmp(b.as_ptr().cast::(), a.as_ptr().cast::(), 1) > 0); 121 | assert!(memcmp(a.as_ptr().cast::(), b.as_ptr().cast::(), 1) < 0); 122 | assert!( 123 | memcmp( 124 | b.as_ptr() 125 | .add((vec_size + pre_dest_vec_size) as usize) 126 | .cast::(), 127 | a.as_ptr().cast::(), 128 | 1, 129 | ) > 0 130 | ); 131 | } 132 | } 133 | 134 | #[test_case] 135 | fn test_mem() { 136 | mem::(); 137 | mem::(); 138 | mem::(); 139 | mem::(); 140 | mem::(); 141 | mem::(); 142 | } 143 | 144 | #[unsafe(no_mangle)] 145 | extern "C" fn runtime_entry(_argc: i32, _argv: *const *const u8, _env: *const *const u8) -> ! { 146 | test_main(); 147 | common::exit(false) 148 | } 149 | -------------------------------------------------------------------------------- /tests/basic_print.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | 6 | #[macro_use] 7 | extern crate hermit; 8 | 9 | mod common; 10 | 11 | use alloc::string::String; 12 | use alloc::vec::Vec; 13 | 14 | /// Print all Strings the application got passed as arguments 15 | #[unsafe(no_mangle)] 16 | pub fn main(args: Vec) -> Result<(), String> { 17 | for s in args { 18 | println!("{}", &s); 19 | } 20 | Ok(()) // real assertion is done by the runner 21 | } 22 | 23 | runtime_entry_with_args!(); 24 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common code for integration tests. 2 | //! 3 | //! See https://github.com/rust-lang/rust/blob/1.83.0/library/test/src/types.rs for reference. 4 | //! See https://github.com/rust-lang/rust/issues/50297#issuecomment-524180479 for details. 5 | 6 | use hermit::{print, println}; 7 | 8 | pub trait Testable { 9 | fn run(&self); 10 | } 11 | 12 | impl Testable for T 13 | where 14 | T: Fn(), 15 | { 16 | fn run(&self) { 17 | print!("{}...\t", core::any::type_name::()); 18 | self(); 19 | println!("[ok]"); 20 | } 21 | } 22 | 23 | #[allow(dead_code)] 24 | pub fn test_case_runner(tests: &[&dyn Testable]) { 25 | println!("Running {} tests", tests.len()); 26 | for test in tests { 27 | test.run(); 28 | } 29 | exit(false); 30 | } 31 | 32 | pub fn exit(failure: bool) -> ! { 33 | match failure { 34 | true => hermit::syscalls::sys_exit(1), 35 | false => hermit::syscalls::sys_exit(0), 36 | } 37 | } 38 | 39 | /// defines runtime_entry and passes arguments as Rust String to main method with signature: 40 | /// `fn main(args: Vec) -> Result<(), ()>;` 41 | #[macro_export] 42 | macro_rules! runtime_entry_with_args { 43 | () => { 44 | // ToDo: Maybe we could add a hard limit on the length of `s` to make this slightly safer? 45 | unsafe fn parse_str(s: *const u8) -> Result { 46 | use alloc::string::String; 47 | use alloc::vec::Vec; 48 | 49 | let mut vec: Vec = Vec::new(); 50 | let mut off = s; 51 | while unsafe { *off } != 0 { 52 | vec.push(unsafe { *off }); 53 | off = unsafe { off.offset(1) }; 54 | } 55 | let str = String::from_utf8(vec); 56 | match str { 57 | Ok(s) => Ok(s), 58 | Err(_) => Err(()), //Convert error here since we might want to add another error type later 59 | } 60 | } 61 | 62 | #[unsafe(no_mangle)] 63 | extern "C" fn runtime_entry( 64 | argc: i32, 65 | argv: *const *const u8, 66 | _env: *const *const u8, 67 | ) -> ! { 68 | use alloc::string::String; 69 | use alloc::vec::Vec; 70 | 71 | let mut str_vec: Vec = Vec::new(); 72 | let mut off = argv; 73 | for i in 0..argc { 74 | let s = unsafe { parse_str(*off) }; 75 | unsafe { 76 | off = off.offset(1); 77 | } 78 | match s { 79 | Ok(s) => str_vec.push(s), 80 | Err(_) => println!( 81 | "Warning: Application argument {} is not valid utf-8 - Dropping it", 82 | i 83 | ), 84 | } 85 | } 86 | 87 | let res = main(str_vec); 88 | match res { 89 | Ok(_) => common::exit(false), 90 | Err(_) => common::exit(true), 91 | } 92 | } 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /tests/measure_startup_time.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | 6 | #[macro_use] 7 | extern crate hermit; 8 | 9 | mod common; 10 | 11 | use alloc::string::String; 12 | use alloc::vec::Vec; 13 | 14 | /// This Test lets the runner measure the basic overhead of the tests including 15 | /// - hypervisor startup time 16 | /// - kernel boot-time 17 | /// - overhead of runtime_entry (test entry) 18 | #[unsafe(no_mangle)] 19 | pub fn main(_args: Vec) -> Result<(), String> { 20 | Ok(()) 21 | } 22 | 23 | runtime_entry_with_args!(); 24 | -------------------------------------------------------------------------------- /tests/thread.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | #![feature(thread_local)] 3 | #![no_std] 4 | #![no_main] 5 | #![test_runner(common::test_case_runner)] 6 | #![feature(custom_test_frameworks)] 7 | #![reexport_test_harness_main = "test_main"] 8 | 9 | extern crate alloc; 10 | 11 | #[macro_use] 12 | extern crate hermit; 13 | 14 | use core::ptr; 15 | use core::sync::atomic::AtomicU32; 16 | use core::sync::atomic::Ordering::Relaxed; 17 | 18 | mod common; 19 | 20 | use alloc::vec; 21 | 22 | use hermit::errno::{EAGAIN, ETIMEDOUT}; 23 | use hermit::syscalls::{sys_futex_wait, sys_futex_wake, sys_join, sys_spawn2, sys_usleep}; 24 | use hermit::time::timespec; 25 | 26 | const USER_STACK_SIZE: usize = 0x0010_0000; 27 | const NORMAL_PRIO: u8 = 2; 28 | 29 | extern "C" fn thread_func(i: usize) { 30 | println!("this is thread number {}", i); 31 | sys_usleep(2_000_000); 32 | println!("---------------THREAD DONE!---------- {}", i); 33 | } 34 | 35 | #[test_case] 36 | pub fn thread_test() { 37 | let mut children = vec![]; 38 | 39 | let threadnum = 5; 40 | for i in 0..threadnum { 41 | println!("SPAWNING THREAD {}", i); 42 | let id = unsafe { sys_spawn2(thread_func, i, NORMAL_PRIO, USER_STACK_SIZE, -1) }; 43 | children.push(id); 44 | } 45 | println!("SPAWNED THREADS"); 46 | 47 | for child in children { 48 | sys_join(child); 49 | } 50 | } 51 | 52 | unsafe extern "C" fn waker_func(futex: usize) { 53 | let futex = unsafe { &*(futex as *const AtomicU32) }; 54 | 55 | sys_usleep(100_000); 56 | 57 | futex.store(1, Relaxed); 58 | let ret = unsafe { sys_futex_wake(futex.as_ptr(), i32::MAX) }; 59 | assert_eq!(ret, 1); 60 | } 61 | 62 | #[test_case] 63 | pub fn test_futex() { 64 | let futex = AtomicU32::new(0); 65 | let futex_ptr = futex.as_ptr(); 66 | 67 | let ret = unsafe { sys_futex_wait(futex_ptr, 1, ptr::null(), 0) }; 68 | assert_eq!(ret, -EAGAIN); 69 | 70 | let timeout = timespec { 71 | tv_sec: 0, 72 | tv_nsec: 100_000_000, 73 | }; 74 | let ret = unsafe { sys_futex_wait(futex_ptr, 0, &raw const timeout, 1) }; 75 | assert_eq!(ret, -ETIMEDOUT); 76 | 77 | let waker = unsafe { 78 | sys_spawn2( 79 | waker_func, 80 | futex_ptr as usize, 81 | NORMAL_PRIO, 82 | USER_STACK_SIZE, 83 | -1, 84 | ) 85 | }; 86 | assert!(waker >= 0); 87 | 88 | let ret = unsafe { sys_futex_wait(futex_ptr, 0, ptr::null(), 0) }; 89 | assert_eq!(ret, 0); 90 | assert_eq!(futex.load(Relaxed), 1); 91 | 92 | let ret = sys_join(waker); 93 | assert_eq!(ret, 0); 94 | } 95 | 96 | #[test_case] 97 | pub fn test_thread_local() { 98 | #[repr(C, align(0x10))] 99 | struct AlignedByte(u8); 100 | 101 | #[thread_local] 102 | static mut BYTE: u8 = 0x42; 103 | 104 | #[thread_local] 105 | static mut CAFECAFE: u64 = 0xcafe_cafe; 106 | 107 | #[thread_local] 108 | static mut DEADBEEF: u64 = 0xdead_beef; 109 | 110 | #[thread_local] 111 | static mut ALIGNED_BYTE: AlignedByte = AlignedByte(0x53); 112 | 113 | // If the thread local statics are not mut, they get optimized away in release. 114 | unsafe { 115 | assert_eq!(0x42, { BYTE }); 116 | assert_eq!(0xcafe_cafe, { CAFECAFE }); 117 | assert_eq!(0xdead_beef, { DEADBEEF }); 118 | assert_eq!(0x53, { ALIGNED_BYTE.0 }); 119 | } 120 | } 121 | 122 | #[unsafe(no_mangle)] 123 | extern "C" fn runtime_entry(_argc: i32, _argv: *const *const u8, _env: *const *const u8) -> ! { 124 | test_main(); 125 | common::exit(false) 126 | } 127 | -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | locale = "en-us" 3 | extend-ignore-identifiers-re = [ 4 | "IST", 5 | "ist", 6 | "sie", 7 | ] 8 | 9 | [default.extend-identifiers] 10 | BARs = "BARs" 11 | BMCR_ANE = "BMCR_ANE" 12 | DT_WHT = "DT_WHT" 13 | RCR_AER = "RCR_AER" 14 | -------------------------------------------------------------------------------- /xtask/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | anyhow = "1.0" 8 | clap = { version = "4", features = ["derive"] } 9 | goblin = { version = "0.10", default-features = false, features = ["archive", "elf32", "elf64", "std"] } 10 | home = "0.5" 11 | ovmf-prebuilt = "0.2" 12 | sysinfo = "0.35" 13 | ureq = "3" 14 | wait-timeout = "0.2" 15 | xshell = "0.2" 16 | -------------------------------------------------------------------------------- /xtask/src/arch.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use anyhow::Result; 4 | use clap::ValueEnum; 5 | 6 | /// Target architecture. 7 | #[derive(ValueEnum, Clone, Copy, PartialEq, Eq, Debug)] 8 | #[value(rename_all = "snake_case")] 9 | pub enum Arch { 10 | /// x86-64 11 | X86_64, 12 | /// AArch64 13 | Aarch64, 14 | /// 64-bit RISC-V 15 | Riscv64, 16 | } 17 | 18 | impl Arch { 19 | pub fn all() -> &'static [Self] { 20 | &[Self::X86_64, Self::Aarch64, Self::Riscv64] 21 | } 22 | 23 | pub fn install(&self) -> Result<()> { 24 | let mut rustup = crate::rustup(); 25 | rustup.args(["target", "add", self.triple()]); 26 | 27 | eprintln!("$ {rustup:?}"); 28 | let status = rustup.status()?; 29 | assert!(status.success()); 30 | 31 | Ok(()) 32 | } 33 | 34 | pub fn install_for_build(&self) -> Result<()> { 35 | if self == &Self::X86_64 { 36 | self.install()?; 37 | } 38 | Ok(()) 39 | } 40 | 41 | pub fn name(&self) -> &'static str { 42 | match self { 43 | Self::X86_64 => "x86_64", 44 | Self::Aarch64 => "aarch64", 45 | Self::Riscv64 => "riscv64", 46 | } 47 | } 48 | 49 | pub fn triple(&self) -> &'static str { 50 | match self { 51 | Self::X86_64 => "x86_64-unknown-none", 52 | Self::Aarch64 => "aarch64-unknown-none-softfloat", 53 | Self::Riscv64 => "riscv64gc-unknown-none-elf", 54 | } 55 | } 56 | 57 | pub fn hermit_triple(&self) -> &'static str { 58 | match self { 59 | Self::X86_64 => "x86_64-unknown-hermit", 60 | Self::Aarch64 => "aarch64-unknown-hermit", 61 | Self::Riscv64 => "riscv64gc-unknown-hermit", 62 | } 63 | } 64 | 65 | pub fn builtins_cargo_args(&self) -> &'static [&'static str] { 66 | match self { 67 | Self::X86_64 => &[ 68 | "--target=x86_64-unknown-hermit", 69 | "-Zbuild-std=core", 70 | "-Zbuild-std-features=compiler-builtins-mem", 71 | ], 72 | Self::Aarch64 => &[ 73 | "--target=aarch64-unknown-hermit", 74 | "-Zbuild-std=core", 75 | "-Zbuild-std-features=compiler-builtins-mem", 76 | ], 77 | Arch::Riscv64 => &[ 78 | "--target=riscv64gc-unknown-hermit", 79 | "-Zbuild-std=core", 80 | "-Zbuild-std-features=compiler-builtins-mem", 81 | ], 82 | } 83 | } 84 | 85 | pub fn cargo_args(&self) -> &'static [&'static str] { 86 | match self { 87 | Self::X86_64 => &["--target=x86_64-unknown-none"], 88 | Self::Aarch64 => &[ 89 | "--target=aarch64-unknown-none-softfloat", 90 | // We can't use prebuilt std here because it is built with 91 | // relocation-model=static and we need relocation-model=pic 92 | "-Zbuild-std=core,alloc", 93 | "-Zbuild-std-features=compiler-builtins-mem", 94 | ], 95 | Self::Riscv64 => &[ 96 | "--target=riscv64gc-unknown-none-elf", 97 | // We can't use prebuilt std here because it is built with 98 | // relocation-model=static and we need relocation-model=pic 99 | "-Zbuild-std=core,alloc", 100 | "-Zbuild-std-features=compiler-builtins-mem", 101 | ], 102 | } 103 | } 104 | 105 | pub fn ci_cargo_args(&self) -> &'static [&'static str] { 106 | match self { 107 | Self::X86_64 => &[ 108 | "--target=x86_64-unknown-hermit", 109 | "-Zbuild-std=std,panic_abort", 110 | ], 111 | Self::Aarch64 => &[ 112 | "--target=aarch64-unknown-hermit", 113 | "-Zbuild-std=std,panic_abort", 114 | ], 115 | Arch::Riscv64 => &[ 116 | "--target=riscv64gc-unknown-hermit", 117 | "-Zbuild-std=std,panic_abort", 118 | ], 119 | } 120 | } 121 | 122 | pub fn rustflags(&self) -> &'static [&'static str] { 123 | match self { 124 | Self::X86_64 => &[], 125 | Self::Aarch64 => &["-Crelocation-model=pic"], 126 | Self::Riscv64 => &["-Cno-redzone", "-Crelocation-model=pic"], 127 | } 128 | } 129 | } 130 | 131 | impl fmt::Display for Arch { 132 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 133 | f.write_str(self.name()) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /xtask/src/archive.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fmt::Write; 3 | use std::path::{Path, PathBuf}; 4 | 5 | use anyhow::Result; 6 | use goblin::archive::Archive as GoblinArchive; 7 | use goblin::elf64::header; 8 | use xshell::cmd; 9 | 10 | pub struct Archive(PathBuf); 11 | 12 | impl From for Archive { 13 | fn from(archive: PathBuf) -> Self { 14 | Self(archive) 15 | } 16 | } 17 | 18 | impl AsRef for Archive { 19 | fn as_ref(&self) -> &Path { 20 | &self.0 21 | } 22 | } 23 | 24 | impl Archive { 25 | pub fn syscall_symbols(&self) -> Result> { 26 | let sh = crate::sh()?; 27 | let archive = self.as_ref(); 28 | 29 | let archive_bytes = sh.read_binary_file(archive)?; 30 | let archive = GoblinArchive::parse(&archive_bytes)?; 31 | let symbols = archive 32 | .summarize() 33 | .into_iter() 34 | .filter(|(member_name, _, _)| member_name.starts_with("hermit-")) 35 | .flat_map(|(_, _, symbols)| symbols) 36 | .filter(|symbol| symbol.starts_with("sys_")) 37 | .map(String::from) 38 | .collect(); 39 | 40 | Ok(symbols) 41 | } 42 | 43 | pub fn retain_symbols(&self, mut exported_symbols: HashSet<&str>) -> Result<()> { 44 | let sh = crate::sh()?; 45 | let archive = self.as_ref(); 46 | let prefix = { 47 | let file_stem = archive.file_stem().unwrap().to_str().unwrap(); 48 | file_stem.strip_prefix("lib").unwrap_or(file_stem) 49 | }; 50 | 51 | let all_symbols = { 52 | let nm = crate::binutil("nm").unwrap(); 53 | let stdout = cmd!(sh, "{nm} --export-symbols {archive}").output()?.stdout; 54 | String::from_utf8(stdout)? 55 | }; 56 | 57 | let symbol_renames = all_symbols 58 | .lines() 59 | .fold(String::new(), |mut output, symbol| { 60 | if exported_symbols.remove(symbol) { 61 | return output; 62 | } 63 | 64 | if let Some(symbol) = symbol.strip_prefix("_ZN") { 65 | let prefix_len = prefix.len(); 66 | let _ = writeln!(output, "_ZN{symbol} _ZN{prefix_len}{prefix}{symbol}",); 67 | } else { 68 | let _ = writeln!(output, "{symbol} {prefix}_{symbol}"); 69 | } 70 | output 71 | }); 72 | 73 | let rename_path = archive.with_extension("redefine-syms"); 74 | sh.write_file(&rename_path, symbol_renames)?; 75 | 76 | let objcopy = crate::binutil("objcopy").unwrap(); 77 | cmd!(sh, "{objcopy} --redefine-syms={rename_path} {archive}").run()?; 78 | 79 | sh.remove_path(&rename_path)?; 80 | 81 | Ok(()) 82 | } 83 | 84 | pub fn append(&self, file: &Self) -> Result<()> { 85 | let sh = crate::sh()?; 86 | let archive = self.as_ref(); 87 | let file = file.as_ref(); 88 | 89 | let ar = crate::binutil("ar").unwrap(); 90 | cmd!(sh, "{ar} qL {archive} {file}").run()?; 91 | 92 | Ok(()) 93 | } 94 | 95 | pub fn set_osabi(&self) -> Result<()> { 96 | let sh = crate::sh()?; 97 | let archive_path = self.as_ref(); 98 | 99 | let mut archive_bytes = sh.read_binary_file(archive_path)?; 100 | let archive = GoblinArchive::parse(&archive_bytes)?; 101 | 102 | let file_offsets = (0..archive.len()) 103 | .map(|i| archive.get_at(i).unwrap().offset) 104 | .collect::>(); 105 | 106 | for file_offset in file_offsets { 107 | let file_offset = usize::try_from(file_offset).unwrap(); 108 | archive_bytes[file_offset + header::EI_OSABI] = header::ELFOSABI_STANDALONE; 109 | } 110 | 111 | sh.write_file(archive_path, archive_bytes)?; 112 | 113 | Ok(()) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /xtask/src/artifact.rs: -------------------------------------------------------------------------------- 1 | use std::path::{self, PathBuf}; 2 | 3 | use clap::Args; 4 | 5 | use crate::arch::Arch; 6 | use crate::archive::Archive; 7 | use crate::ci; 8 | 9 | #[derive(Args)] 10 | pub struct Artifact { 11 | /// Target architecture. 12 | #[arg(value_enum, long)] 13 | pub arch: Arch, 14 | 15 | /// Directory for all generated artifacts. 16 | #[arg(long, id = "DIRECTORY")] 17 | pub target_dir: Option, 18 | 19 | /// Copy final artifacts to this directory 20 | #[arg(long, id = "PATH")] 21 | pub artifact_dir: Option, 22 | 23 | /// Build artifacts in release mode, with optimizations. 24 | #[arg(short, long)] 25 | pub release: bool, 26 | 27 | /// Build artifacts with the specified profile. 28 | #[arg(long, id = "PROFILE-NAME")] 29 | pub profile: Option, 30 | } 31 | 32 | impl Artifact { 33 | pub fn profile(&self) -> &str { 34 | self.profile 35 | .as_deref() 36 | .unwrap_or(if self.release { "release" } else { "dev" }) 37 | } 38 | 39 | pub fn profile_path_component(&self) -> &str { 40 | match self.profile() { 41 | "dev" => "debug", 42 | profile => profile, 43 | } 44 | } 45 | 46 | pub fn target_dir(&self) -> PathBuf { 47 | if let Some(target_dir) = &self.target_dir { 48 | return path::absolute(target_dir).unwrap(); 49 | } 50 | 51 | crate::project_root().join("target") 52 | } 53 | 54 | pub fn builtins_target_dir(&self) -> PathBuf { 55 | self.target_dir().join("hermit-builtins") 56 | } 57 | 58 | pub fn builtins_archive(&self) -> Archive { 59 | [ 60 | self.builtins_target_dir().as_path(), 61 | self.arch.hermit_triple().as_ref(), 62 | "release".as_ref(), 63 | "libhermit_builtins.a".as_ref(), 64 | ] 65 | .iter() 66 | .collect::() 67 | .into() 68 | } 69 | 70 | pub fn build_archive(&self) -> Archive { 71 | [ 72 | self.target_dir().as_path(), 73 | self.arch.triple().as_ref(), 74 | self.profile_path_component().as_ref(), 75 | "libhermit.a".as_ref(), 76 | ] 77 | .iter() 78 | .collect::() 79 | .into() 80 | } 81 | 82 | fn artifact_dir(&self) -> PathBuf { 83 | if let Some(artifact_dir) = &self.artifact_dir { 84 | return path::absolute(artifact_dir).unwrap(); 85 | } 86 | 87 | [ 88 | self.target_dir().as_path(), 89 | self.arch.name().as_ref(), 90 | self.profile_path_component().as_ref(), 91 | ] 92 | .iter() 93 | .collect() 94 | } 95 | 96 | pub fn dist_archive(&self) -> Archive { 97 | self.artifact_dir().join("libhermit.a").into() 98 | } 99 | 100 | pub fn ci_image(&self, package: &str) -> PathBuf { 101 | [ 102 | ci::parent_root(), 103 | "target".as_ref(), 104 | self.arch.hermit_triple().as_ref(), 105 | self.profile_path_component().as_ref(), 106 | package.as_ref(), 107 | ] 108 | .iter() 109 | .collect() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /xtask/src/binutil.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::path::PathBuf; 3 | use std::sync::LazyLock; 4 | 5 | pub fn binutil(name: &str) -> Option { 6 | static LLVM_TOOLS: LazyLock = LazyLock::new(|| LlvmTools::new().unwrap()); 7 | 8 | LLVM_TOOLS.tool(name) 9 | } 10 | 11 | struct LlvmTools { 12 | bin: PathBuf, 13 | } 14 | 15 | impl LlvmTools { 16 | pub fn new() -> io::Result { 17 | let mut rustc = crate::rustc(); 18 | rustc.args(["--print", "sysroot"]); 19 | 20 | eprintln!("$ {rustc:?}"); 21 | let output = rustc.output()?; 22 | assert!(output.status.success()); 23 | 24 | let sysroot = String::from_utf8(output.stdout).unwrap(); 25 | let rustlib = [sysroot.trim_end(), "lib", "rustlib"] 26 | .iter() 27 | .collect::(); 28 | 29 | let example_exe = exe("objdump"); 30 | for entry in rustlib.read_dir()? { 31 | let bin = entry?.path().join("bin"); 32 | if bin.join(&example_exe).exists() { 33 | return Ok(Self { bin }); 34 | } 35 | } 36 | Err(io::Error::new( 37 | io::ErrorKind::NotFound, 38 | "Could not find llvm-tools component\n\ 39 | \n\ 40 | Maybe the rustup component `llvm-tools` is missing? Install it through: `rustup component add llvm-tools`", 41 | )) 42 | } 43 | 44 | pub fn tool(&self, name: &str) -> Option { 45 | let path = self.bin.join(exe(name)); 46 | path.exists().then_some(path) 47 | } 48 | } 49 | 50 | fn exe(name: &str) -> String { 51 | let exe_suffix = std::env::consts::EXE_SUFFIX; 52 | format!("llvm-{name}{exe_suffix}") 53 | } 54 | -------------------------------------------------------------------------------- /xtask/src/build.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::env::{self, VarError}; 3 | 4 | use anyhow::Result; 5 | use clap::Args; 6 | 7 | use crate::cargo_build::CargoBuild; 8 | 9 | /// Build the kernel. 10 | #[derive(Args)] 11 | pub struct Build { 12 | #[command(flatten)] 13 | cargo_build: CargoBuild, 14 | 15 | /// Enable the `-Z instrument-mcount` flag. 16 | #[arg(long)] 17 | pub instrument_mcount: bool, 18 | 19 | /// Enable the `-Z randomize-layout` flag. 20 | #[arg(long)] 21 | pub randomize_layout: bool, 22 | } 23 | 24 | impl Build { 25 | pub fn run(self) -> Result<()> { 26 | let sh = crate::sh()?; 27 | 28 | self.cargo_build.artifact.arch.install_for_build()?; 29 | 30 | let careful = match env::var_os("HERMIT_CAREFUL") { 31 | Some(val) if val == "1" => &["careful"][..], 32 | _ => &[], 33 | }; 34 | 35 | eprintln!("Building kernel"); 36 | let mut cargo = crate::cargo(); 37 | cargo 38 | .args(careful) 39 | .arg("build") 40 | .env("CARGO_ENCODED_RUSTFLAGS", self.cargo_encoded_rustflags()?) 41 | .args(self.cargo_build.artifact.arch.cargo_args()) 42 | .args(self.cargo_build.cargo_build_args()); 43 | 44 | eprintln!("$ {cargo:?}"); 45 | let status = cargo.status()?; 46 | assert!(status.success()); 47 | 48 | let build_archive = self.cargo_build.artifact.build_archive(); 49 | let dist_archive = self.cargo_build.artifact.dist_archive(); 50 | eprintln!( 51 | "Copying {} to {}", 52 | build_archive.as_ref().display(), 53 | dist_archive.as_ref().display() 54 | ); 55 | sh.create_dir(dist_archive.as_ref().parent().unwrap())?; 56 | sh.copy_file(&build_archive, &dist_archive)?; 57 | 58 | eprintln!("Exporting symbols"); 59 | self.export_syms()?; 60 | 61 | eprintln!("Building hermit-builtins"); 62 | let mut cargo = crate::cargo(); 63 | cargo 64 | .args(["build", "--release"]) 65 | .arg("--manifest-path=hermit-builtins/Cargo.toml") 66 | .args(self.cargo_build.artifact.arch.builtins_cargo_args()) 67 | .args(self.cargo_build.builtins_target_dir_arg()); 68 | 69 | eprintln!("$ {cargo:?}"); 70 | let status = cargo.status()?; 71 | assert!(status.success()); 72 | 73 | eprintln!("Exporting hermit-builtins symbols"); 74 | let builtins = self.cargo_build.artifact.builtins_archive(); 75 | let builtin_symbols = sh.read_file("hermit-builtins/exports")?; 76 | builtins.retain_symbols(builtin_symbols.lines().collect::>())?; 77 | 78 | dist_archive.append(&builtins)?; 79 | 80 | eprintln!("Setting OSABI"); 81 | dist_archive.set_osabi()?; 82 | 83 | eprintln!("Kernel available at {}", dist_archive.as_ref().display()); 84 | Ok(()) 85 | } 86 | 87 | fn cargo_encoded_rustflags(&self) -> Result { 88 | let outer_rustflags = match env::var("CARGO_ENCODED_RUSTFLAGS") { 89 | Ok(s) => Some(s), 90 | Err(VarError::NotPresent) => None, 91 | Err(err) => return Err(err.into()), 92 | }; 93 | let mut rustflags = outer_rustflags 94 | .as_deref() 95 | .map(|s| vec![s]) 96 | .unwrap_or_default(); 97 | 98 | if self.instrument_mcount { 99 | rustflags.push("-Zinstrument-mcount"); 100 | rustflags.push("-Cpasses=ee-instrument"); 101 | } 102 | 103 | if self.randomize_layout { 104 | rustflags.push("-Zrandomize-layout") 105 | } 106 | 107 | rustflags.extend(self.cargo_build.artifact.arch.rustflags()); 108 | 109 | Ok(rustflags.join("\x1f")) 110 | } 111 | 112 | fn export_syms(&self) -> Result<()> { 113 | let archive = self.cargo_build.artifact.dist_archive(); 114 | 115 | let syscall_symbols = archive.syscall_symbols()?; 116 | let explicit_exports = ["_start", "__bss_start", "mcount", "runtime_entry"].into_iter(); 117 | 118 | let symbols = explicit_exports.chain(syscall_symbols.iter().map(String::as_str)); 119 | 120 | archive.retain_symbols(symbols.collect::>())?; 121 | 122 | Ok(()) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /xtask/src/cargo_build.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | 3 | use clap::Args; 4 | 5 | use crate::artifact::Artifact; 6 | 7 | #[derive(Args)] 8 | pub struct CargoBuild { 9 | #[command(flatten)] 10 | pub artifact: Artifact, 11 | 12 | /// Do not activate the `default` feature. 13 | #[arg(long)] 14 | no_default_features: bool, 15 | 16 | /// Space or comma separated list of features to activate. 17 | #[arg(long)] 18 | pub features: Vec, 19 | } 20 | 21 | impl CargoBuild { 22 | pub fn cargo_build_args(&self) -> Vec { 23 | let mut args = vec![]; 24 | args.extend(self.target_dir_args()); 25 | args.extend( 26 | self.no_default_features_args() 27 | .iter() 28 | .map(|s| s.to_string()), 29 | ); 30 | args.extend(self.features_args().map(|s| s.to_string())); 31 | args.extend(self.release_args().iter().map(|s| s.to_string())); 32 | if let Some(profile) = &self.artifact.profile { 33 | args.push("--profile".to_string()); 34 | args.push(profile.to_string()); 35 | } 36 | args 37 | } 38 | 39 | pub fn target_dir_args(&self) -> Vec { 40 | if self.artifact.target_dir.is_some() { 41 | vec![ 42 | "--target-dir".to_string(), 43 | self.artifact 44 | .target_dir() 45 | .into_os_string() 46 | .into_string() 47 | .unwrap(), 48 | ] 49 | } else { 50 | vec![] 51 | } 52 | } 53 | 54 | pub fn builtins_target_dir_arg(&self) -> [OsString; 2] { 55 | [ 56 | OsString::from("--target-dir"), 57 | self.artifact.builtins_target_dir().into_os_string(), 58 | ] 59 | } 60 | 61 | fn release_args(&self) -> &'static [&'static str] { 62 | if self.artifact.release { 63 | &["--release"] 64 | } else { 65 | &[] 66 | } 67 | } 68 | 69 | fn no_default_features_args(&self) -> &'static [&'static str] { 70 | if self.no_default_features { 71 | &["--no-default-features"] 72 | } else { 73 | &[] 74 | } 75 | } 76 | 77 | fn features_args(&self) -> impl Iterator { 78 | self.features 79 | .iter() 80 | .flat_map(|feature| ["--features", feature.as_str()]) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /xtask/src/ci/c.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Result; 4 | use clap::{Args, Subcommand}; 5 | use xshell::cmd; 6 | 7 | use crate::arch::Arch; 8 | 9 | /// Work with hermit-c images 10 | #[derive(Args)] 11 | pub struct C { 12 | /// Target architecture. 13 | #[arg(value_enum, long)] 14 | pub arch: Arch, 15 | 16 | /// Build type to use. 17 | #[arg(long, default_value = "debug")] 18 | pub buildtype: String, 19 | 20 | /// Target to build. 21 | #[arg(long, id = "SPEC")] 22 | pub target: String, 23 | 24 | /// Create multiple vCPUs. 25 | #[arg(long, default_value_t = 1)] 26 | pub smp: usize, 27 | 28 | #[command(subcommand)] 29 | action: Action, 30 | } 31 | 32 | #[derive(Subcommand)] 33 | pub enum Action { 34 | /// Build image. 35 | Build, 36 | Firecracker(super::firecracker::Firecracker), 37 | Qemu(super::qemu::Qemu), 38 | Uhyve(super::uhyve::Uhyve), 39 | } 40 | 41 | impl C { 42 | pub fn run(mut self) -> Result<()> { 43 | let image = self.build()?; 44 | 45 | match self.action { 46 | Action::Build => Ok(()), 47 | Action::Firecracker(firecracker) => firecracker.run(&image, self.smp), 48 | Action::Qemu(qemu) => qemu.run(&image, self.smp, self.arch, false), 49 | Action::Uhyve(uhyve) => uhyve.run(&image, self.smp), 50 | } 51 | } 52 | 53 | pub fn build(&mut self) -> Result { 54 | if super::in_ci() { 55 | eprintln!("::group::meson compile"); 56 | } 57 | 58 | let arch = self.arch.name(); 59 | let buildtype = self.buildtype.as_str(); 60 | let target = self.target.as_str(); 61 | let build_dir = format!("build-{arch}-hermit-{buildtype}"); 62 | 63 | let sh = crate::sh()?; 64 | sh.change_dir(super::parent_root()); 65 | 66 | cmd!( 67 | sh, 68 | "meson setup --buildtype {buildtype} --cross-file cross/{arch}-hermit.ini {build_dir}" 69 | ) 70 | .run()?; 71 | 72 | cmd!(sh, "meson setup --reconfigure {build_dir}").run()?; 73 | 74 | cmd!(sh, "meson compile -C {build_dir} -v {target}").run()?; 75 | 76 | let image = { 77 | let mut image = super::parent_root().to_path_buf(); 78 | image.push(build_dir); 79 | image.push(target); 80 | image 81 | }; 82 | 83 | if super::in_ci() { 84 | eprintln!("::endgroup::"); 85 | } 86 | 87 | Ok(image) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /xtask/src/ci/firecracker.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | use anyhow::Result; 5 | use clap::Args; 6 | use xshell::cmd; 7 | 8 | /// Run image on Firecracker. 9 | #[derive(Args)] 10 | pub struct Firecracker { 11 | /// Run Firecracker using `sudo`. 12 | #[arg(long)] 13 | sudo: bool, 14 | } 15 | 16 | impl Firecracker { 17 | pub fn run(self, image: &Path, smp: usize) -> Result<()> { 18 | let sh = crate::sh()?; 19 | 20 | let config = format!( 21 | include_str!("firecracker_vm_config.json"), 22 | kernel_image_path = "hermit-loader-x86_64-fc", 23 | initrd_path = image.display(), 24 | vcpu_count = smp, 25 | ); 26 | eprintln!("firecracker config"); 27 | eprintln!("{config}"); 28 | let config_path = Path::new("firecracker_vm_config.json"); 29 | sh.write_file(config_path, config)?; 30 | 31 | let firecracker = env::var("FIRECRACKER").unwrap_or_else(|_| "firecracker".to_string()); 32 | let program = if self.sudo { 33 | "sudo" 34 | } else { 35 | firecracker.as_str() 36 | }; 37 | let arg = self.sudo.then_some(firecracker.as_str()); 38 | 39 | let log_path = Path::new("firecracker.log"); 40 | sh.write_file(log_path, "")?; 41 | cmd!(sh, "{program} {arg...} --no-api --config-file {config_path} --log-path {log_path} --level Info --show-level --show-log-origin").run()?; 42 | let log = sh.read_file(log_path)?; 43 | 44 | eprintln!("firecracker log"); 45 | eprintln!("{log}"); 46 | 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /xtask/src/ci/firecracker_vm_config.json: -------------------------------------------------------------------------------- 1 | {{ 2 | "boot-source": {{ 3 | "kernel_image_path": "{kernel_image_path}", 4 | "initrd_path": "{initrd_path}", 5 | "boot_args": "" 6 | }}, 7 | "drives": [], 8 | "machine-config": {{ 9 | "vcpu_count": {vcpu_count}, 10 | "mem_size_mib": 256, 11 | "smt": false 12 | }} 13 | }} 14 | -------------------------------------------------------------------------------- /xtask/src/ci/mod.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::Result; 4 | use clap::Subcommand; 5 | 6 | mod c; 7 | mod firecracker; 8 | mod qemu; 9 | mod rs; 10 | mod uhyve; 11 | 12 | /// Run CI tasks. 13 | #[derive(Subcommand)] 14 | pub enum Ci { 15 | C(c::C), 16 | Rs(rs::Rs), 17 | } 18 | 19 | impl Ci { 20 | pub fn run(self) -> Result<()> { 21 | match self { 22 | Self::C(c) => c.run(), 23 | Self::Rs(rs) => rs.run(), 24 | } 25 | } 26 | } 27 | 28 | fn in_ci() -> bool { 29 | std::env::var_os("CI") == Some("true".into()) 30 | } 31 | 32 | pub fn parent_root() -> &'static Path { 33 | crate::project_root().parent().unwrap() 34 | } 35 | -------------------------------------------------------------------------------- /xtask/src/ci/rftrace.snap: -------------------------------------------------------------------------------- 1 | # TID FUNCTION 2 | [ 1] | rftrace_example::f1() { 3 | [ 1] | rftrace_example::f2() { 4 | [ 1] | rftrace_example::f3() -------------------------------------------------------------------------------- /xtask/src/ci/rs.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anyhow::Result; 4 | use clap::{Args, Subcommand}; 5 | 6 | use crate::cargo_build::CargoBuild; 7 | 8 | /// Work with hermit-rs images 9 | #[derive(Args)] 10 | pub struct Rs { 11 | #[command(flatten)] 12 | pub cargo_build: CargoBuild, 13 | 14 | /// Package to build (see `cargo help pkgid`) 15 | #[arg(short, long, id = "SPEC")] 16 | pub package: String, 17 | 18 | /// Create multiple vCPUs. 19 | #[arg(long, default_value_t = 1)] 20 | pub smp: usize, 21 | 22 | #[command(subcommand)] 23 | action: Action, 24 | } 25 | 26 | #[derive(Subcommand)] 27 | pub enum Action { 28 | /// Build image. 29 | Build, 30 | Firecracker(super::firecracker::Firecracker), 31 | Qemu(super::qemu::Qemu), 32 | Uhyve(super::uhyve::Uhyve), 33 | } 34 | 35 | impl Rs { 36 | pub fn run(mut self) -> Result<()> { 37 | let image = self.build()?; 38 | 39 | let arch = self.cargo_build.artifact.arch; 40 | let small = self.cargo_build.artifact.profile() == "release"; 41 | match self.action { 42 | Action::Build => Ok(()), 43 | Action::Firecracker(firecracker) => firecracker.run(&image, self.smp), 44 | Action::Qemu(qemu) => qemu.run(&image, self.smp, arch, small), 45 | Action::Uhyve(uhyve) => uhyve.run(&image, self.smp), 46 | } 47 | } 48 | 49 | pub fn build(&mut self) -> Result { 50 | if super::in_ci() { 51 | eprintln!("::group::cargo build"); 52 | } 53 | 54 | if self.smp > 1 { 55 | self.cargo_build.features.push("hermit/smp".to_string()); 56 | } 57 | 58 | let mut cargo = crate::cargo(); 59 | 60 | if self.package.contains("rftrace") { 61 | cargo.env( 62 | "RUSTFLAGS", 63 | "-Zinstrument-mcount -Cpasses=ee-instrument", 64 | ); 65 | }; 66 | 67 | cargo 68 | .current_dir(super::parent_root()) 69 | .arg("build") 70 | .args(self.cargo_build.artifact.arch.ci_cargo_args()) 71 | .args(self.cargo_build.cargo_build_args()) 72 | .args(["--package", self.package.as_str()]); 73 | 74 | eprintln!("$ {cargo:?}"); 75 | let status = cargo.status()?; 76 | assert!(status.success()); 77 | 78 | if super::in_ci() { 79 | eprintln!("::endgroup::"); 80 | } 81 | 82 | Ok(self.cargo_build.artifact.ci_image(&self.package)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /xtask/src/ci/uhyve.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | 4 | use anyhow::Result; 5 | use clap::Args; 6 | use xshell::cmd; 7 | 8 | /// Run image on Uhyve. 9 | #[derive(Args)] 10 | pub struct Uhyve { 11 | /// Run Uhyve using `sudo`. 12 | #[arg(long)] 13 | sudo: bool, 14 | } 15 | 16 | impl Uhyve { 17 | pub fn run(self, image: &Path, smp: usize) -> Result<()> { 18 | let sh = crate::sh()?; 19 | 20 | let uhyve = env::var("UHYVE").unwrap_or_else(|_| "uhyve".to_string()); 21 | let program = if self.sudo { "sudo" } else { uhyve.as_str() }; 22 | let arg = self.sudo.then_some(uhyve.as_str()); 23 | let smp_arg = format!("--cpu-count={smp}"); 24 | 25 | cmd!(sh, "{program} {arg...} {smp_arg} {image}") 26 | .env("RUST_LOG", "debug") 27 | .run()?; 28 | 29 | Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /xtask/src/clippy.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Args; 3 | use xshell::cmd; 4 | 5 | use crate::arch::Arch; 6 | 7 | /// Run Clippy for all targets. 8 | #[derive(Args)] 9 | pub struct Clippy; 10 | 11 | impl Clippy { 12 | pub fn run(self) -> Result<()> { 13 | let sh = crate::sh()?; 14 | 15 | for arch in Arch::all() { 16 | arch.install()?; 17 | 18 | let triple = arch.triple(); 19 | let clippy = || cmd!(sh, "cargo clippy --target={triple} --all-targets"); 20 | 21 | clippy().run()?; 22 | clippy().arg("--features=common-os").run()?; 23 | clippy() 24 | .arg("--features=acpi,dns,fsgsbase,pci,smp,vga") 25 | .run()?; 26 | clippy().arg("--no-default-features").run()?; 27 | clippy().arg("--all-features").run()?; 28 | clippy() 29 | .arg("--no-default-features") 30 | .arg("--features=tcp") 31 | .run()?; 32 | clippy() 33 | .arg("--no-default-features") 34 | .arg("--features=acpi,fsgsbase,pci,smp,vga") 35 | .run()?; 36 | 37 | match *arch { 38 | Arch::X86_64 => { 39 | clippy().arg("--features=shell").run()?; 40 | } 41 | Arch::Aarch64 => {} 42 | Arch::Riscv64 => { 43 | clippy() 44 | .arg("--no-default-features") 45 | .arg("--features=gem-net,tcp") 46 | .run()?; 47 | } 48 | } 49 | 50 | clippy() 51 | .arg("--no-default-features") 52 | .arg("--features=acpi,fsgsbase,newlib,smp,vga") 53 | .run()?; 54 | } 55 | 56 | cmd!(sh, "cargo clippy") 57 | .arg("--manifest-path=hermit-builtins/Cargo.toml") 58 | .arg("--target=x86_64-unknown-none") 59 | .run()?; 60 | 61 | cmd!(sh, "cargo clippy --package xtask").run()?; 62 | 63 | Ok(()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /xtask/src/doc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Args; 3 | use xshell::cmd; 4 | 5 | use crate::arch::Arch; 6 | 7 | /// Run rustdoc for all targets. 8 | #[derive(Args)] 9 | pub struct Doc; 10 | 11 | impl Doc { 12 | pub fn run(self) -> Result<()> { 13 | let sh = crate::sh()?; 14 | 15 | for arch in Arch::all() { 16 | arch.install()?; 17 | let triple = arch.triple(); 18 | 19 | cmd!(sh, "cargo doc --target={triple}") 20 | .arg("--no-deps") 21 | .arg("--document-private-items") 22 | .arg("--package=hermit-kernel") 23 | .run()?; 24 | 25 | cmd!(sh, "cargo doc --target={triple}") 26 | .arg("--no-deps") 27 | .arg("--document-private-items") 28 | .arg("--manifest-path=hermit-builtins/Cargo.toml") 29 | .run()?; 30 | } 31 | 32 | cmd!(sh, "cargo doc") 33 | .arg("--no-deps") 34 | .arg("--document-private-items") 35 | .arg("--package=xtask") 36 | .run()?; 37 | 38 | Ok(()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | //! See . 2 | 3 | mod arch; 4 | mod archive; 5 | mod artifact; 6 | mod binutil; 7 | mod build; 8 | mod cargo_build; 9 | mod ci; 10 | mod clippy; 11 | mod doc; 12 | 13 | use std::env; 14 | use std::path::{Path, PathBuf}; 15 | use std::process::Command; 16 | 17 | use anyhow::Result; 18 | pub(crate) use binutil::binutil; 19 | use clap::Parser; 20 | use xshell::Shell; 21 | 22 | #[derive(Parser)] 23 | enum Cli { 24 | Build(build::Build), 25 | #[command(subcommand)] 26 | Ci(ci::Ci), 27 | Clippy(clippy::Clippy), 28 | Doc(doc::Doc), 29 | } 30 | 31 | impl Cli { 32 | fn run(self) -> Result<()> { 33 | match self { 34 | Self::Build(build) => build.run(), 35 | Self::Ci(ci) => ci.run(), 36 | Self::Clippy(clippy) => clippy.run(), 37 | Self::Doc(doc) => doc.run(), 38 | } 39 | } 40 | } 41 | 42 | fn main() -> Result<()> { 43 | let cli = Cli::parse(); 44 | cli.run() 45 | } 46 | 47 | pub fn sh() -> Result { 48 | let sh = Shell::new()?; 49 | sh.change_dir(project_root()); 50 | Ok(sh) 51 | } 52 | 53 | pub fn project_root() -> &'static Path { 54 | Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap() 55 | } 56 | 57 | pub fn rustup() -> Command { 58 | sanitize("rustup") 59 | } 60 | 61 | pub fn rustc() -> Command { 62 | sanitize("rustc") 63 | } 64 | 65 | pub fn cargo() -> Command { 66 | sanitize("cargo") 67 | } 68 | 69 | fn sanitize(cmd: &str) -> Command { 70 | let cmd = { 71 | let exe = format!("{cmd}{}", env::consts::EXE_SUFFIX); 72 | // On windows, the userspace toolchain ends up in front of the rustup proxy in $PATH. 73 | // To reach the rustup proxy nonetheless, we explicitly query $CARGO_HOME. 74 | let mut cargo_home = home::cargo_home().unwrap(); 75 | cargo_home.push("bin"); 76 | cargo_home.push(&exe); 77 | if cargo_home.exists() { 78 | cargo_home 79 | } else { 80 | // Custom `$CARGO_HOME` values do not necessarily reflect in the environment. 81 | // For these cases, our best bet is using `$PATH` for resolution. 82 | PathBuf::from(exe) 83 | } 84 | }; 85 | 86 | let mut cmd = Command::new(cmd); 87 | 88 | cmd.current_dir(project_root()); 89 | 90 | // Remove rust-toolchain-specific environment variables from kernel cargo 91 | cmd.env_remove("LD_LIBRARY_PATH"); 92 | env::vars() 93 | .filter(|(key, _value)| { 94 | key.starts_with("CARGO") && !key.starts_with("CARGO_HOME") 95 | || key.starts_with("RUST") && !key.starts_with("RUSTUP_HOME") 96 | }) 97 | .for_each(|(key, _value)| { 98 | cmd.env_remove(&key); 99 | }); 100 | 101 | cmd 102 | } 103 | --------------------------------------------------------------------------------