├── .github ├── dependabot.yml └── workflows │ ├── build.yaml │ ├── dco.yaml │ ├── docker-image.yaml │ ├── integration-windows.yaml │ ├── release.yaml │ └── tests.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── MAINTAINERS.md ├── README.md ├── aarch64-unknown-none.json ├── aarch64-unknown-none.ld ├── build.rs ├── resources ├── Dockerfile ├── cloud-init │ ├── clear │ │ └── openstack │ │ │ └── latest │ │ │ ├── meta_data.json │ │ │ └── user_data │ └── ubuntu │ │ ├── meta-data │ │ ├── network-config │ │ └── user-data └── coreboot │ └── qemu-q35-config.in ├── riscv64gcv-unknown-none-elf.json ├── riscv64gcv-unknown-none-elf.ld ├── rust-toolchain.toml ├── rustfmt.toml ├── scripts ├── dev_cli.sh ├── fetch_images.sh ├── make-test-disks.sh ├── run_cargo_tests.sh ├── run_coreboot_integration_tests.sh ├── run_integration_tests.sh ├── run_integration_tests_windows.sh └── run_unit_tests.sh ├── src ├── arch │ ├── aarch64 │ │ ├── asm.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ ├── paging.rs │ │ ├── ram64.s │ │ ├── simd.rs │ │ └── translation.rs │ ├── mod.rs │ ├── riscv64 │ │ ├── asm.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ └── ram64.s │ └── x86_64 │ │ ├── asm.rs │ │ ├── gdt.rs │ │ ├── layout.rs │ │ ├── mod.rs │ │ ├── paging.rs │ │ ├── ram32.s │ │ └── sse.rs ├── block.rs ├── boot.rs ├── bootinfo.rs ├── bzimage.rs ├── cmos.rs ├── common.rs ├── coreboot.rs ├── delay.rs ├── efi │ ├── alloc.rs │ ├── block.rs │ ├── boot_services.rs │ ├── console.rs │ ├── device_path.rs │ ├── file.rs │ ├── mem_file.rs │ ├── mod.rs │ ├── runtime_services.rs │ └── var.rs ├── fat.rs ├── fdt.rs ├── integration.rs ├── layout.rs ├── loader.rs ├── logger.rs ├── main.rs ├── mem.rs ├── part.rs ├── pci.rs ├── pe.rs ├── pvh.rs ├── rtc.rs ├── rtc_goldfish.rs ├── rtc_pl031.rs ├── serial.rs ├── uart_mmio.rs ├── uart_pl011.rs └── virtio.rs ├── x86_64-unknown-none.json └── x86_64-unknown-none.ld /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 3 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Rust Hypervisor Firmware Build 2 | on: [pull_request, merge_group] 3 | 4 | env: 5 | RUSTFLAGS: "-D warnings" 6 | 7 | jobs: 8 | build: 9 | name: Build 10 | runs-on: ubuntu-22.04 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | include: 15 | - target: x86_64-unknown-none.json 16 | tests: true 17 | - target: aarch64-unknown-none.json 18 | tests: false 19 | - target: riscv64gcv-unknown-none-elf.json 20 | tests: false 21 | steps: 22 | - name: Code checkout 23 | uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | - name: Install Rust toolchain 27 | uses: dtolnay/rust-toolchain@stable 28 | - name: Install Rust components 29 | run: rustup component add rust-src clippy rustfmt 30 | - name: Build (debug) 31 | run: cargo build --target ${{ matrix.target }} -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 32 | - name: Build (release) 33 | run: cargo build --release --target ${{ matrix.target }} -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 34 | - name: Clippy (default) 35 | run: cargo clippy --target ${{ matrix.target }} -Zbuild-std=core 36 | - name: Clippy (all targets, all features) 37 | run: cargo clippy --all-targets --all-features 38 | - name: Formatting 39 | run: cargo fmt --all -- --check 40 | - if: ${{ matrix.tests }} 41 | name: Unit tests 42 | run: | 43 | sudo apt-get install -y mtools 44 | bash scripts/run_unit_tests.sh 45 | -------------------------------------------------------------------------------- /.github/workflows/dco.yaml: -------------------------------------------------------------------------------- 1 | name: DCO 2 | on: [pull_request, merge_group] 3 | 4 | jobs: 5 | check: 6 | name: DCO Check ("Signed-Off-By") 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Set up Python 3.x 11 | uses: actions/setup-python@v1 12 | with: 13 | python-version: '3.x' 14 | - name: Check DCO 15 | if: ${{ github.event_name == 'pull_request' }} 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | run: | 19 | pip3 install -U dco-check 20 | dco-check -e "49699333+dependabot[bot]@users.noreply.github.com" 21 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yaml: -------------------------------------------------------------------------------- 1 | name: Rust-Hypervisor-Firmware's Docker image update 2 | 3 | on: 4 | push: 5 | branches: main 6 | paths: 7 | - resources/Dockerfile 8 | - rust-toolchain.toml 9 | pull_request: 10 | paths: 11 | - resources/Dockerfile 12 | - rust-toolchain.toml 13 | env: 14 | REGISTRY: ghcr.io 15 | IMAGE_NAME: ${{ github.repository }} 16 | 17 | jobs: 18 | main: 19 | runs-on: ubuntu-22.04 20 | steps: 21 | - name: Code checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up QEMU 25 | uses: docker/setup-qemu-action@v3 26 | 27 | - name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: Get active Rust toolchain 31 | id: get-toolchain 32 | run: echo "toolchain=`rustup show active-toolchain | cut -d ' ' -f1`" >> $GITHUB_ENV 33 | 34 | - name: Login to ghcr 35 | uses: docker/login-action@v3 36 | with: 37 | registry: ${{ env.REGISTRY }} 38 | username: ${{ github.actor }} 39 | password: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Extract metadata (tags, labels) for Docker 42 | id: meta 43 | uses: docker/metadata-action@v5 44 | with: 45 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 46 | flavor: | 47 | latest=true 48 | 49 | - name: Build 50 | uses: docker/build-push-action@v5 51 | with: 52 | file: ./resources/Dockerfile 53 | build-args: | 54 | RUST_TOOLCHAIN=${{ env.toolchain }} 55 | platforms: | 56 | linux/arm64 57 | linux/amd64 58 | push: ${{ github.event_name == 'push' }} 59 | tags: ${{ steps.meta.outputs.tags }} 60 | 61 | - name: Image digest 62 | run: echo ${{ steps.docker_build.outputs.digest }} 63 | -------------------------------------------------------------------------------- /.github/workflows/integration-windows.yaml: -------------------------------------------------------------------------------- 1 | name: Rust Hypervisor Firmware Tests (Windows Guest) 2 | on: [pull_request, merge_group] 3 | 4 | jobs: 5 | build: 6 | name: Tests (Windows Guest) 7 | runs-on: ${{ github.event_name == 'pull_request' && 'ubuntu-latest' || 'garm-jammy-16' }} 8 | steps: 9 | - name: Code checkout 10 | if: ${{ github.event_name != 'pull_request' }} 11 | uses: actions/checkout@v4 12 | with: 13 | fetch-depth: 0 14 | - name: Install Docker 15 | if: ${{ github.event_name != 'pull_request' }} 16 | run: | 17 | sudo apt-get update 18 | sudo apt-get -y install ca-certificates curl gnupg 19 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg 20 | sudo chmod a+r /usr/share/keyrings/docker-archive-keyring.gpg 21 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 22 | sudo apt-get update 23 | sudo apt install -y docker-ce docker-ce-cli 24 | - name: Install Azure CLI 25 | if: ${{ github.event_name != 'pull_request' }} 26 | run: | 27 | sudo apt install -y ca-certificates curl apt-transport-https lsb-release gnupg 28 | curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/microsoft.gpg > /dev/null 29 | echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ jammy main" | sudo tee /etc/apt/sources.list.d/azure-cli.list 30 | sudo apt update 31 | sudo apt install -y azure-cli 32 | - name: Download Windows image 33 | if: ${{ github.event_name != 'pull_request' }} 34 | run: | 35 | mkdir $HOME/workloads 36 | az storage blob download --container-name private-images --file "$HOME/workloads/windows-server-2022-amd64-2.raw" --name windows-server-2022-amd64-2.raw --connection-string "${{ secrets.CH_PRIVATE_IMAGES }}" 37 | - name: Run Windows guest integration tests 38 | if: ${{ github.event_name != 'pull_request' }} 39 | timeout-minutes: 60 40 | run: scripts/dev_cli.sh tests --integration-windows 41 | - name: Skipping build for PR 42 | if: ${{ github.event_name == 'pull_request' }} 43 | run: echo "Skipping build for PR" 44 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Rust Hypervisor Firmware Release 2 | on: [create] 3 | 4 | env: 5 | RUSTFLAGS: "-D warnings" 6 | 7 | jobs: 8 | release: 9 | if: github.event_name == 'create' && github.event.ref_type == 'tag' 10 | name: Release 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - name: Code checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Install Rust toolchain 18 | uses: dtolnay/rust-toolchain@stable 19 | - name: Install rust-src 20 | run: rustup component add rust-src 21 | - name: Build (release) for x86_64 22 | run: cargo build --release --target x86_64-unknown-none.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 23 | - name: Build (release) for aarch64 24 | run: cargo build --release --target aarch64-unknown-none.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 25 | - name: Create release 26 | id: create_release 27 | uses: actions/create-release@v1 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | with: 31 | tag_name: ${{ github.ref }} 32 | release_name: ${{ github.ref }} 33 | draft: true 34 | - name: Upload hypervisor-fw for x86_64 35 | id: upload-release-hypervisor-fw 36 | uses: actions/upload-release-asset@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | upload_url: ${{ steps.create_release.outputs.upload_url }} 41 | asset_path: target/x86_64-unknown-none/release/hypervisor-fw 42 | asset_name: hypervisor-fw 43 | asset_content_type: application/octet-stream 44 | - name: Upload hypervisor-fw for aarch64 45 | id: upload-release-hypervisor-fw-aarch64 46 | uses: actions/upload-release-asset@v1 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | with: 50 | upload_url: ${{ steps.create_release.outputs.upload_url }} 51 | asset_path: target/aarch64-unknown-none/release/hypervisor-fw 52 | asset_name: hypervisor-fw-aarch64 53 | asset_content_type: application/octet-stream 54 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Rust Hypervisor Firmware Tests 2 | on: [pull_request, merge_group] 3 | 4 | jobs: 5 | build: 6 | name: Tests 7 | runs-on: ${{ matrix.runner }} 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | include: 12 | - runner: ubuntu-22.04 13 | coreboot-tests: true 14 | - runner: bookworm-arm64 15 | coreboot-tests: false 16 | steps: 17 | - name: Code checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Run unit tests 22 | run: scripts/dev_cli.sh tests --unit 23 | - name: Run integration tests 24 | run: scripts/dev_cli.sh tests --integration 25 | - if: ${{ matrix.coreboot-tests }} 26 | name: Run coreboot integration tests 27 | run: scripts/dev_cli.sh tests --integration-coreboot 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | clear-*-kvm.img* 3 | fat*.img 4 | target 5 | test_data 6 | resources/cloud-hypervisor 7 | resources/images 8 | resources/coreboot/coreboot 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, as 6 | contributors and maintainers we pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include but is not limited to: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address without explicit consent 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at otc.community@intel.com. All complaints 59 | will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at: https://www.contributor-covenant.org/version/1/4/code-of-conduct/ 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Rust Hypervisor Firmware 2 | 3 | Rust Hypervisor Firmware is an open source project licensed under the [Apache v2 License](https://opensource.org/licenses/Apache-2.0). 4 | 5 | ## Coding Style 6 | 7 | We follow the [Rust Style](https://github.com/rust-dev-tools/fmt-rfcs/blob/master/guide/guide.md) 8 | convention and enforce it through the Continuous Integration (CI) process calling into `rustfmt` 9 | for each submitted Pull Request (PR). 10 | 11 | ## Certificate of Origin 12 | 13 | In order to get a clear contribution chain of trust we use the [signed-off-by language] (https://01.org/community/signed-process) 14 | used by the Linux kernel project. 15 | 16 | ## Patch format 17 | 18 | Beside the signed-off-by footer, we expect each patch to comply with the following format: 19 | 20 | ``` 21 | : Change summary 22 | 23 | More detailed explanation of your changes: Why and how. 24 | Wrap it to 72 characters. 25 | See http://chris.beams.io/posts/git-commit/ 26 | for some more good pieces of advice. 27 | 28 | Signed-off-by: 29 | ``` 30 | 31 | For example: 32 | 33 | ``` 34 | commit 6e9477ba25ac09cc6d918e6512b9eb4e0fb5a2a5 35 | Author: Rob Bradford 36 | Date: Wed May 1 17:30:51 2019 +0100 37 | 38 | block: Partially split VirtioMMIOBlockDevice 39 | 40 | Create a new trait called VirtioTransport and create a 41 | VirtioMMIOTransport that implements that trait moving all the virtio 42 | MMIO register updates into that. This means the block code is somewhat 43 | independent of MMIO. 44 | 45 | Signed-off-by: Rob Bradford 46 | 47 | ``` 48 | 49 | ## Pull requests 50 | 51 | Rust Hypervisor Firmware uses the “fork-and-pull” development model. Follow these steps if 52 | you want to merge your changes to the project`: 53 | 54 | 1. Fork the [rust-hypervisor-firmware](https://github.com/intel/rust-hypervisor-firmware) project 55 | into your github organization. 56 | 2. Within your fork, create a branch for your contribution. 57 | 3. [Create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) 58 | against the master branch of the repository. 59 | 4. Once the pull request is approved, one of the maintainers will merge it. 60 | 61 | ## Issue tracking 62 | 63 | If you have a problem, please let us know. We recommend using 64 | [github issues](https://github.com/intel/rust-hypervisor-firmware/issues/new) for formally 65 | reporting and documenting them. 66 | 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hypervisor-fw" 3 | version = "0.5.0" 4 | authors = ["The Rust Hypervisor Firmware Authors"] 5 | edition = "2021" 6 | 7 | # Despite "panic-strategy": "abort" being set in x86_64-unknown-none.json, panic = "abort" is 8 | # needed here to make "cargo check" and "cargo clippy" run without errors. 9 | [profile.dev] 10 | panic = "abort" 11 | 12 | [profile.release] 13 | panic = "abort" 14 | lto = "thin" 15 | 16 | [features] 17 | default = ["log-serial", "log-panic"] 18 | # Have the log! macro write to serial output. Disabling this significantly 19 | # reduces code size, but makes debugging essentially impossible 20 | log-serial = [] 21 | # Log panics to serial output. Disabling this (without disabling log-serial) 22 | # gets you most of the code size reduction, without losing _all_ debugging. 23 | log-panic = ["log-serial"] 24 | integration_tests = [] 25 | coreboot = [] 26 | efi-var = [] 27 | 28 | [dependencies] 29 | bitflags = "2.9.1" 30 | atomic_refcell = "0.1.13" 31 | r-efi = { version = "5.2.0", features = ["efiapi"] } 32 | heapless = "0.8.0" 33 | log = "0.4.27" 34 | 35 | [target.'cfg(target_arch = "aarch64")'.dependencies] 36 | tock-registers = "0.9.0" 37 | aarch64-cpu = "10.0.0" 38 | fdt = "0.1.5" 39 | chrono = { version = "0.4", default-features = false } 40 | 41 | [target.'cfg(target_arch = "x86_64")'.dependencies] 42 | uart_16550 = "0.3.2" 43 | x86_64 = { version = "0.15.2", default-features = false, features = [ 44 | "instructions", 45 | ] } 46 | 47 | [target.'cfg(target_arch = "riscv64")'.dependencies] 48 | chrono = { version = "0.4", default-features = false } 49 | fdt = "0.1.5" 50 | 51 | [dev-dependencies] 52 | dirs = "6.0.0" 53 | rand = "0.9.1" 54 | ssh2 = { version = "0.9.5", features = ["vendored-openssl"] } 55 | tempfile = "3.20.0" 56 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | - Rob Bradford - @rbradford 4 | - Akira Moroo - @retrage 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Hypervisor Firmware 2 | 3 | This repository contains a simple firmware that is designed to be launched from 4 | anything that supports loading ELF binaries and running them with the 5 | PVH booting standard 6 | 7 | The purpose is to be able to use this firmware to be able to load a 8 | bootloader from within a disk image without requiring the use of a complex 9 | firmware such as TianoCore/edk2 and without requiring the VMM to reuse 10 | functionality used for booting the Linux kernel. 11 | 12 | Currently it will directly load a kernel from a disk image that follows the 13 | [Boot Loader Specification](https://uapi-group.org/specifications/specs/boot_loader_specification) 14 | 15 | There is also minimal EFI compatibility support allowing the boot of some 16 | images that use EFI (shim + GRUB2 as used by Ubuntu). 17 | 18 | The firmware is primarily developed against [Cloud 19 | Hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor) but there is 20 | also support for using QEMU's PVH loader. 21 | 22 | This project was originally developed using 23 | [Firecracker](https://github.com/firecracker-microvm) however as it does not 24 | currently support resetting the virtio block device it is not possible to boot 25 | all the way into the OS. 26 | 27 | ## Features 28 | 29 | * virtio (PCI) block support 30 | * GPT parsing (to find EFI system partition) 31 | * FAT12/16/32 directory traversal and file reading 32 | * bzImage loader 33 | * "Boot Loader Specification" parser 34 | * PE32+ loader 35 | * Minimal EFI environment (sufficient to boot shim + GRUB2 as used by Ubuntu) 36 | 37 | ## x86-64 Support 38 | 39 | ### Building 40 | 41 | To compile: 42 | 43 | ``` 44 | cargo build --release --target x86_64-unknown-none.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 45 | ``` 46 | 47 | The result will be in: 48 | 49 | ``` 50 | target/x86_64-unknown-none/release/hypervisor-fw 51 | ``` 52 | 53 | ### Running 54 | 55 | Works with Cloud Hypervisor and QEMU via their PVH loaders as an alternative to 56 | the Linux kernel. 57 | 58 | Cloud Hypervisor and QEMU are currently the primary development targets for the 59 | firmware although support for other VMMs will be considered. 60 | 61 | #### Cloud Hypervisor 62 | 63 | As per [getting 64 | started](https://github.com/cloud-hypervisor/cloud-hypervisor/blob/master/README.md#2-getting-started) 65 | 66 | However instead of using the binary firmware for the parameter to `--kernel` 67 | instead use the binary you build above. 68 | 69 | ``` 70 | $ pushd $CLOUDH 71 | $ sudo setcap cap_net_admin+ep ./cloud-hypervisor/target/release/cloud-hypervisor 72 | $ ./cloud-hypervisor/target/release/cloud-hypervisor \ 73 | --kernel ./target/x86_64-unknown-none/release/hypervisor-fw \ 74 | --disk path=focal-server-cloudimg-amd64.raw \ 75 | --cpus boot=4 \ 76 | --memory size=512M \ 77 | --net "tap=,mac=,ip=,mask=" \ 78 | --rng 79 | $ popd 80 | ``` 81 | 82 | #### QEMU 83 | 84 | Use the QEMU `-kernel` parameter to specify the path to the firmware. 85 | 86 | e.g. 87 | 88 | ``` 89 | $ qemu-system-x86_64 -machine q35,accel=kvm -cpu host,-vmx -m 1G\ 90 | -kernel ./target/x86_64-unknown-none/release/hypervisor-fw \ 91 | -display none -nodefaults \ 92 | -serial stdio \ 93 | -drive id=os,file=focal-server-cloudimg-amd64.raw,if=none \ 94 | -device virtio-blk-pci,drive=os,disable-legacy=on 95 | ``` 96 | 97 | ## AArch64 Support 98 | 99 | ### Building 100 | 101 | To compile: 102 | 103 | ``` 104 | cargo build --release --target aarch64-unknown-none.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 105 | ``` 106 | 107 | The result will be in: 108 | 109 | ``` 110 | target/aarch64-unknown-none/release/hypervisor-fw 111 | ``` 112 | 113 | ## RISC-V Support 114 | 115 | Experimental RISC-V support is available. This is currently designed to run as a 116 | payload from OpenSBI under QEMU virt. It is expected wider platform support 117 | will become available in the future. 118 | 119 | ### Building 120 | 121 | To compile: 122 | 123 | ``` 124 | cargo build --release --target riscv64gcv-unknown-none-elf.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 125 | ``` 126 | 127 | The result will be in: 128 | 129 | ``` 130 | target/riscv64gcv-unknown-none-elf/release/hypervisor-fw 131 | ``` 132 | 133 | ### Running 134 | 135 | Currently only QEMU has been tested. 136 | 137 | #### QEMU 138 | 139 | ``` 140 | $ qemu-system-riscv64 -M virt -cpu rv64 -smp 1 -m 1024 \ 141 | -nographic -kernel target/riscv64gcv-unknown-none-elf/release/hypervisor-fw \ 142 | -drive id=mydrive,file=root.img,format=raw \ 143 | -device virtio-blk-pci,drive=mydrive,disable-legacy=on 144 | ``` 145 | 146 | ## Testing 147 | 148 | "cargo test" needs disk images from make-test-disks.sh 149 | 150 | And clear-28660-kvm.img: 151 | 152 | https://download.clearlinux.org/releases/28660/clear/clear-28660-kvm.img.xz 153 | 154 | sha1sum: 5fc086643dea4b20c59a795a262e0d2400fab15f 155 | 156 | ## Security issues 157 | 158 | Please contact the maintainers listed in the MAINTAINERS.md file with security issues. 159 | -------------------------------------------------------------------------------- /aarch64-unknown-none.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "aarch64-unknown-none", 3 | "abi": "softfloat", 4 | "arch": "aarch64", 5 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32", 6 | "disable-redzone": true, 7 | "features": "+strict-align,-neon,-fp-armv8", 8 | "linker": "rust-lld", 9 | "linker-flavor": "gnu-lld", 10 | "os": "none", 11 | "executables": true, 12 | "max-atomic-width": 128, 13 | "panic-strategy": "abort", 14 | "code-model": "small", 15 | "relocation-model": "pic", 16 | "target-pointer-width": "64", 17 | "pre-link-args": { 18 | "gnu-lld": [ 19 | "--script=aarch64-unknown-none.ld", 20 | "--oformat=binary" 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /aarch64-unknown-none.ld: -------------------------------------------------------------------------------- 1 | ENTRY(ram64_start) 2 | 3 | /* Cloud Hypervisor Memory layout: 4 | DRAM: [0x4000_0000-0xfc00_0000] 5 | FDT: [0x4000_0000-0x401f_ffff) 6 | ACPI: [0x4020_0000-0x403f_ffff) 7 | payload:[0x4040_0000-0x405f_ffff) 8 | RHF: [0x40600000-] 9 | Assuming 2MB is enough to load payload. 10 | The stack start is at the end of the RHF region. */ 11 | ram_min = 0x40600000; 12 | 13 | /* This value must be identical with arch::aarch64::layout::map::dram::KERNEL_START. */ 14 | PAYLOAD_START = 0x40400000; 15 | 16 | efi_image_size = rhf_end - ram_min; 17 | efi_image_offset = ram_min - PAYLOAD_START; 18 | 19 | SECTIONS 20 | { 21 | /* Mapping the program headers and note into RAM makes the file smaller. */ 22 | . = ram_min; 23 | 24 | /* These sections are mapped into RAM from the file. Omitting :ram from 25 | later sections avoids emitting empty sections in the final binary. */ 26 | code_start = .; 27 | .text.boot : { *(.text.boot) } 28 | .text : { *(.text .text.*) } 29 | . = ALIGN(64K); 30 | code_end = .; 31 | 32 | data_start = .; 33 | .data : { *(.data .data.*) } 34 | .rodata : { *(.rodata .rodata.*) } 35 | .got : { *(.got .got.*) } 36 | 37 | /* The BSS section isn't mapped from file data. It is just zeroed in RAM. */ 38 | .bss : { 39 | *(.bss .bss.*) 40 | } 41 | . = ALIGN(4K); 42 | data_end = .; 43 | 44 | stack_start = .; 45 | .stack (NOLOAD) : ALIGN(4K) { . += 128K; } 46 | stack_end = .; 47 | 48 | /* Strip symbols from the output binary (comment out to get symbols) */ 49 | /DISCARD/ : { 50 | *(.symtab) 51 | *(.strtab) 52 | } 53 | 54 | . = ALIGN(4K); 55 | rhf_end = .; 56 | } 57 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-changed=aarch64-unknown-none.json"); 3 | println!("cargo:rerun-if-changed=aarch64-unknown-none.ld"); 4 | println!("cargo:rerun-if-changed=riscv64gcv-unknown-none-elf.json"); 5 | println!("cargo:rerun-if-changed=riscv64gcv-unknown-none-elf.ld"); 6 | println!("cargo:rerun-if-changed=x86_64-unknown-none.json"); 7 | println!("cargo:rerun-if-changed=x86_64-unknown-none.ld"); 8 | } 9 | -------------------------------------------------------------------------------- /resources/Dockerfile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | 3 | FROM ubuntu:22.04 as dev 4 | 5 | ARG TARGETARCH 6 | ARG RUST_TOOLCHAIN 7 | ARG RHF_SRC_DIR="/rust-hypervisor-firmware" 8 | ARG RHF_BUILD_DIR="$RHF_SRC_DIR/build" 9 | ARG CARGO_REGISTRY_DIR="$RHF_BUILD_DIR/cargo_registry" 10 | ARG CARGO_GIT_REGISTRY_DIR="$RHF_BUILD_DIR/cargo_git_registry" 11 | ARG COREBOOT_VERSION="4.19" 12 | 13 | ENV CARGO_HOME=/usr/local/rust 14 | ENV RUSTUP_HOME=$CARGO_HOME 15 | ENV PATH="$PATH:$CARGO_HOME/bin" 16 | ENV COREBOOT_DIR=/opt/coreboot/src 17 | 18 | # Install all CI dependencies 19 | RUN if [ "$TARGETARCH" = "amd64" ]; then \ 20 | apt-get update \ 21 | && apt-get -yq upgrade \ 22 | && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ 23 | build-essential \ 24 | bc \ 25 | docker.io \ 26 | curl \ 27 | wget \ 28 | sudo \ 29 | mtools \ 30 | musl-tools \ 31 | libssl-dev \ 32 | pkg-config \ 33 | flex \ 34 | bison \ 35 | libelf-dev \ 36 | qemu-utils \ 37 | qemu-system \ 38 | libglib2.0-dev \ 39 | libpixman-1-dev \ 40 | libseccomp-dev \ 41 | libcap-ng-dev \ 42 | socat \ 43 | dosfstools \ 44 | cpio \ 45 | python3 \ 46 | python3-setuptools \ 47 | ntfs-3g \ 48 | python3-distutils \ 49 | uuid-dev \ 50 | m4 \ 51 | zlib1g-dev \ 52 | gnat \ 53 | && apt-get clean \ 54 | && rm -rf /var/lib/apt/lists/* \ 55 | ; fi 56 | 57 | RUN if [ "$TARGETARCH" = "arm64" ]; then \ 58 | apt-get update \ 59 | && apt-get -yq upgrade \ 60 | && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ 61 | build-essential \ 62 | docker.io \ 63 | curl \ 64 | wget \ 65 | sudo \ 66 | mtools \ 67 | libssl-dev \ 68 | pkg-config \ 69 | qemu-utils \ 70 | libseccomp-dev \ 71 | libcap-ng-dev \ 72 | libcap2-bin \ 73 | dosfstools \ 74 | && apt-get clean \ 75 | && rm -rf /var/lib/apt/lists/* \ 76 | ; fi 77 | 78 | # Fix the libssl-dev install 79 | RUN export ARCH="$(uname -m)" \ 80 | && cp /usr/include/$ARCH-linux-gnu/openssl/opensslconf.h /usr/include/openssl/ 81 | ENV X86_64_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu/ 82 | ENV X86_64_UNKNOWN_LINUX_MUSL_OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-gnu/ 83 | ENV AARCH64_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR=/usr/lib/aarch64-linux-gnu/ 84 | ENV AARCH64_UNKNOWN_LINUX_MUSL_OPENSSL_LIB_DIR=/usr/lib/aarch64-linux-gnu/ 85 | ENV OPENSSL_INCLUDE_DIR=/usr/include/ 86 | 87 | # Checkout coreboot repository and setup cross toolchains 88 | RUN if [ "$TARGETARCH" = "amd64" ]; then \ 89 | git clone --quiet --branch "$COREBOOT_VERSION" --depth 1 https://github.com/coreboot/coreboot.git "$COREBOOT_DIR" \ 90 | && cd "$COREBOOT_DIR" \ 91 | && git submodule update --init --checkout \ 92 | && make crossgcc-i386 CPUS=`nproc`; \ 93 | fi 94 | 95 | # Install the rust toolchain 96 | RUN export ARCH="$(uname -m)" \ 97 | && nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \ 98 | && rustup component add rustfmt \ 99 | && rustup component add clippy \ 100 | && rustup component add rust-src \ 101 | && rustup target add aarch64-unknown-linux-gnu \ 102 | && rustup target add riscv64gc-unknown-linux-gnu \ 103 | && rustup target add x86_64-unknown-linux-gnu \ 104 | && rm -rf "$CARGO_HOME/registry" \ 105 | && ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \ 106 | && rm -rf "$CARGO_HOME/git" \ 107 | && ln -s "$CARGO_GIT_REGISTRY_DIR" "$CARGO_HOME/git" 108 | 109 | # Set the rust environment 110 | RUN echo 'source $CARGO_HOME/env' >> $HOME/.bashrc \ 111 | && mkdir $HOME/.cargo \ 112 | && ln -s $CARGO_HOME/env $HOME/.cargo/env 113 | -------------------------------------------------------------------------------- /resources/cloud-init/clear/openstack/latest/meta_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostname" : "cloud" 3 | } 4 | -------------------------------------------------------------------------------- /resources/cloud-init/clear/openstack/latest/user_data: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | users: 3 | - name: cloud 4 | passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K. 5 | sudo: 6 | - ALL=(ALL) NOPASSWD:ALL 7 | write_files: 8 | - 9 | path: /etc/systemd/network/00-static-l1.network 10 | permissions: 0644 11 | content: | 12 | [Match] 13 | MACAddress=12:34:56:78:90:ab 14 | 15 | [Network] 16 | Address=192.168.2.2/24 17 | Gateway=192.168.2.1 18 | -------------------------------------------------------------------------------- /resources/cloud-init/ubuntu/meta-data: -------------------------------------------------------------------------------- 1 | instance-id: cloud 2 | local-hostname: cloud 3 | -------------------------------------------------------------------------------- /resources/cloud-init/ubuntu/network-config: -------------------------------------------------------------------------------- 1 | network: 2 | version: 1 3 | config: 4 | - type: physical 5 | name: eth0 6 | mac_address: 12:34:56:78:90:ab 7 | subnets: 8 | - type: static 9 | address: 192.168.2.2/24 10 | gateway: 192.168.2.1 11 | dns_nameservers: 12 | - 192.168.2.1 13 | -------------------------------------------------------------------------------- /resources/cloud-init/ubuntu/user-data: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | users: 3 | - name: cloud 4 | passwd: $6$7125787751a8d18a$sHwGySomUA1PawiNFWVCKYQN.Ec.Wzz0JtPPL1MvzFrkwmop2dq7.4CYf03A5oemPQ4pOFCCrtCelvFBEle/K. 5 | sudo: ALL=(ALL) NOPASSWD:ALL 6 | lock_passwd: False 7 | inactive: False 8 | shell: /bin/bash 9 | 10 | ssh_pwauth: True 11 | -------------------------------------------------------------------------------- /riscv64gcv-unknown-none-elf.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "riscv64", 3 | "code-model": "medium", 4 | "cpu": "generic-rv64", 5 | "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", 6 | "eh-frame-header": false, 7 | "emit-debug-gdb-scripts": false, 8 | "features": "+m,+a,-f,+d,+c,-v", 9 | "linker": "rust-lld", 10 | "linker-flavor": "gnu-lld", 11 | "llvm-abiname": "lp64d", 12 | "llvm-target": "riscv64", 13 | "max-atomic-width": 64, 14 | "panic-strategy": "abort", 15 | "relocation-model": "static", 16 | "target-pointer-width": "64", 17 | "pre-link-args": { 18 | "gnu-lld": [ 19 | "--script=riscv64gcv-unknown-none-elf.ld" 20 | ] 21 | } 22 | } -------------------------------------------------------------------------------- /riscv64gcv-unknown-none-elf.ld: -------------------------------------------------------------------------------- 1 | ENTRY(ram64_start) 2 | 3 | /* OpenSBI loads here */ 4 | ram_min = 0x80200000; 5 | 6 | SECTIONS 7 | { 8 | /* Mapping the program headers and note into RAM makes the file smaller. */ 9 | . = ram_min; 10 | 11 | /* These sections are mapped into RAM from the file. Omitting :ram from 12 | later sections avoids emitting empty sections in the final binary. */ 13 | code_start = .; 14 | .text.boot : { *(.text.boot) } 15 | .text : { *(.text .text.*) } 16 | . = ALIGN(4K); 17 | code_end = .; 18 | 19 | data_start = .; 20 | 21 | .data : { 22 | . = ALIGN(4096); 23 | *(.data .data.*) 24 | . = ALIGN(8); 25 | PROVIDE(__global_pointer$ = . + 0x800); 26 | } 27 | 28 | .rodata : { *(.rodata .rodata.*) } 29 | .got : { *(.got .got.*) } 30 | 31 | /* The BSS section isn't mapped from file data. It is just zeroed in RAM. */ 32 | .bss : { 33 | *(.bss .bss.*) 34 | } 35 | . = ALIGN(4K); 36 | data_end = .; 37 | 38 | stack_start = .; 39 | .stack (NOLOAD) : ALIGN(4K) { . += 128K; } 40 | stack_end = .; 41 | 42 | /* Strip symbols from the output binary (comment out to get symbols) */ 43 | /DISCARD/ : { 44 | *(.symtab) 45 | *(.strtab) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2025-06-05" 3 | components = ["rust-src", "clippy", "rustfmt"] 4 | targets = [ 5 | "aarch64-unknown-linux-gnu", 6 | "riscv64gc-unknown-linux-gnu", 7 | "x86_64-unknown-linux-gnu", 8 | ] 9 | profile = "default" 10 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloud-hypervisor/rust-hypervisor-firmware/9930cf24069b305f5a0bc487a40e34a84112eb72/rustfmt.toml -------------------------------------------------------------------------------- /scripts/fetch_images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | fetch_ch() { 5 | CH_PATH="$1" 6 | CH_ARCH="$2" 7 | CH_VERSION="v36.0" 8 | CH_URL_BASE="https://github.com/cloud-hypervisor/cloud-hypervisor/releases/download/$CH_VERSION" 9 | 10 | [ "$CH_ARCH" = "aarch64" ] && CH_NAME="cloud-hypervisor-static-aarch64" 11 | [ "$CH_ARCH" = "x86_64" ] && CH_NAME="cloud-hypervisor" 12 | CH_URL="$CH_URL_BASE/$CH_NAME" 13 | 14 | WGET_RETRY_MAX=10 15 | WGET_RETRY=0 16 | 17 | until [ "$WGET_RETRY" -ge "$WGET_RETRY_MAX" ]; do 18 | wget --quiet $CH_URL -O $CH_PATH && break 19 | WGET_RETRY=$[$WGET_RETRY+1] 20 | done 21 | 22 | if [ "$WGET_RETRY" -ge "$WGET_RETRY_MAX" ]; then 23 | echo "Failed to download $CH_URL" 24 | exit 1 25 | fi 26 | 27 | wget --quiet $CH_URL -O $CH_PATH 28 | chmod +x $CH_PATH 29 | sudo setcap cap_net_admin+ep $CH_PATH 30 | } 31 | 32 | fetch_image() { 33 | OS_IMAGE="$1" 34 | OS_IMAGE_URL="$2" 35 | if [ ! -f "$OS_IMAGE" ]; then 36 | pushd $WORKLOADS_DIR 37 | time wget --quiet $OS_IMAGE_URL 38 | popd 39 | fi 40 | } 41 | 42 | convert_image() { 43 | OS_IMAGE="$1" 44 | OS_RAW_IMAGE="$2" 45 | if [ ! -f "$OS_RAW_IMAGE" ]; then 46 | time qemu-img convert -p -f qcow2 -O raw $OS_IMAGE $OS_RAW_IMAGE 47 | fi 48 | } 49 | 50 | fetch_raw_ubuntu_image() { 51 | OS_NAME="$1" 52 | OS_ARCH="$2" 53 | OS_DATE="$3" 54 | OS_IMAGE_NAME="$OS_NAME-server-cloudimg-$OS_ARCH.img" 55 | OS_RAW_IMAGE_NAME="$OS_NAME-server-cloudimg-$OS_ARCH-raw.img" 56 | OS_IMAGE_BASE="https://cloud-images.ubuntu.com" 57 | OS_IMAGE_URL="$OS_IMAGE_BASE/$OS_NAME/$OS_DATE/$OS_IMAGE_NAME" 58 | fetch_image "$OS_IMAGE_NAME" "$OS_IMAGE_URL" 59 | convert_image "$OS_IMAGE_NAME" "$OS_RAW_IMAGE_NAME" 60 | } 61 | 62 | fetch_clear_image() { 63 | OS_VERSION="$1" 64 | OS_IMAGE_NAME="clear-$OS_VERSION-cloudguest.img" 65 | OS_IMAGE_BASE="https://cdn.download.clearlinux.org/releases/$OS_VERSION/clear" 66 | OS_IMAGE_URL="$OS_IMAGE_BASE/$OS_IMAGE_NAME.xz" 67 | fetch_image "$OS_IMAGE_NAME" "$OS_IMAGE_URL" 68 | xz -d "$OS_IMAGE_NAME.xz" 69 | } 70 | 71 | aarch64_fetch_disk_images() { 72 | fetch_raw_ubuntu_image "focal" "arm64" "current" 73 | fetch_raw_ubuntu_image "jammy" "arm64" "current" 74 | } 75 | 76 | x86_64_fetch_disk_images() { 77 | fetch_clear_image "31310" 78 | 79 | fetch_raw_ubuntu_image "focal" "amd64" "current" 80 | fetch_raw_ubuntu_image "jammy" "amd64" "current" 81 | } 82 | 83 | fetch_disk_images() { 84 | WORKLOADS_DIR="$1" 85 | ARCH="$2" 86 | 87 | pushd "$WORKLOADS_DIR" 88 | 89 | [ "$ARCH" = "aarch64" ] && aarch64_fetch_disk_images 90 | [ "$ARCH" = "x86_64" ] && x86_64_fetch_disk_images 91 | 92 | popd 93 | } 94 | -------------------------------------------------------------------------------- /scripts/make-test-disks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | make_test_disks() { 4 | WORKLOADS_DIR="$1" 5 | pushd "$WORKLOADS_DIR" 6 | 7 | rm -f fat12.img fat16.img fat32.img 8 | 9 | mkdosfs -F 12 -C fat12.img 8192 10 | file fat12.img 11 | mkdosfs -F 16 -C fat16.img 32768 12 | file fat16.img 13 | mkdosfs -F 32 -C fat32.img 1048576 14 | file fat32.img 15 | 16 | rm -rf test_data 17 | mkdir -p test_data/a/b/c 18 | for x in `seq 0 32767`; do 19 | echo -n "a" >> test_data/a/b/c/d 20 | done 21 | 22 | dd if=test_data/a/b/c/d of=test_data/a/b/c/0 count=0 bs=1 23 | for x in `seq 9 15`; do 24 | n=$[2**x] 25 | dd if=test_data/a/b/c/d of=test_data/a/b/c/$[$n - 1] count=$[$n - 1] bs=1 26 | dd if=test_data/a/b/c/d of=test_data/a/b/c/$n count=$n bs=1 27 | done 28 | 29 | mkdir -p test_data/largedir 30 | for x in `seq 0 100`; do 31 | touch test_data/largedir/$x 32 | done 33 | 34 | touch test_data/longfilenametest 35 | 36 | export MTOOLS_SKIP_CHECK=1 37 | mcopy -oi fat12.img -s test_data/* :: 38 | mcopy -oi fat16.img -s test_data/* :: 39 | mcopy -oi fat32.img -s test_data/* :: 40 | 41 | rm -rf test_data 42 | 43 | popd 44 | } 45 | -------------------------------------------------------------------------------- /scripts/run_cargo_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | source "${CARGO_HOME:-$HOME/.cargo}/env" 6 | 7 | export RUSTFLAGS="-D warnings" 8 | 9 | arch="$(uname -m)" 10 | 11 | do_cargo_tests() { 12 | local cargo_args=("-Zbuild-std=core" "-Zbuild-std-features=compiler-builtins-mem") 13 | local cmd="$1" 14 | local target="$2" 15 | local features="$3" 16 | [ -n "$features" ] && cargo_args+=("--features" "$features") 17 | time cargo "$cmd" --target "$target" "${cargo_args[@]}" 18 | time cargo "$cmd" --target "$target" --release "${cargo_args[@]}" 19 | } 20 | 21 | cargo_tests() { 22 | local features="$1" 23 | 24 | [ "$arch" = "aarch64" ] && target="aarch64-unknown-none.json" 25 | [ "$arch" = "x86_64" ] && target="x86_64-unknown-none.json" 26 | 27 | do_cargo_tests "build" "$target" "$features" 28 | do_cargo_tests "clippy" "$target" "$features" 29 | } 30 | 31 | # Install cargo components 32 | time rustup component add clippy 33 | time rustup component add rustfmt 34 | time rustup component add rust-src 35 | 36 | # Run cargo builds and checks 37 | cargo_tests "" 38 | if [ "$arch" = "x86_64" ] ; then 39 | cargo_tests "coreboot" 40 | fi 41 | time cargo clippy --all-targets --all-features 42 | time cargo fmt --all -- --check 43 | -------------------------------------------------------------------------------- /scripts/run_coreboot_integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | RHF_ROOT_DIR=$(cd "$(dirname "$0")/../" && pwd) 5 | 6 | source "${CARGO_HOME:-$HOME/.cargo}/env" 7 | source "$(dirnam "$0")/fetch_images.sh" 8 | 9 | arch="$(uname -m)" 10 | 11 | WORKLOADS_DIR="$HOME/workloads" 12 | mkdir -p "$WORKLOADS_DIR" 13 | 14 | fetch_disk_images "$WORKLOADS_DIR" "$arch" 15 | 16 | [ "$arch" = "x86_64" ] && target="x86_64-unknown-none" 17 | 18 | rustup component add rust-src 19 | cargo build --release --target "$target.json" -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem --features "coreboot" 20 | 21 | RHF_BIN="$RHF_ROOT_DIR/target/$target/release/hypervisor-fw" 22 | COREBOOT_CONFIG_IN="$RHF_ROOT_DIR/resources/coreboot/qemu-q35-config.in" 23 | 24 | cat $COREBOOT_CONFIG_IN | sed -e "s#@CONFIG_PAYLOAD_FILE@#$RHF_BIN#g" > "$COREBOOT_DIR/.config" 25 | make -C $COREBOOT_DIR olddefconfig 26 | make -C $COREBOOT_DIR -j"$(nproc)" 27 | 28 | export RUST_BACKTRACE=1 29 | cargo test --features "coreboot integration_tests" "integration::tests::linux::$arch" -- --test-threads=1 30 | -------------------------------------------------------------------------------- /scripts/run_integration_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | source "${CARGO_HOME:-$HOME/.cargo}/env" 5 | source "$(dirname "$0")/fetch_images.sh" 6 | 7 | arch="$(uname -m)" 8 | 9 | WORKLOADS_DIR="$HOME/workloads" 10 | mkdir -p "$WORKLOADS_DIR" 11 | 12 | CH_PATH="$WORKLOADS_DIR/cloud-hypervisor" 13 | fetch_ch "$CH_PATH" "$arch" 14 | 15 | fetch_disk_images "$WORKLOADS_DIR" "$arch" 16 | 17 | [ "$arch" = "aarch64" ] && target="aarch64-unknown-none" 18 | [ "$arch" = "x86_64" ] && target="x86_64-unknown-none" 19 | 20 | rustup component add rust-src 21 | cargo build --release --target "$target.json" -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 22 | 23 | export RUST_BACKTRACE=1 24 | time cargo test --features "integration_tests" "integration::tests::linux::$arch" -- --test-threads=1 25 | -------------------------------------------------------------------------------- /scripts/run_integration_tests_windows.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | source "${CARGO_HOME:-$HOME/.cargo}/env" 5 | source "$(dirname "$0")/fetch_images.sh" 6 | 7 | arch="$(uname -m)" 8 | 9 | WORKLOADS_DIR="$HOME/workloads" 10 | mkdir -p "$WORKLOADS_DIR" 11 | 12 | WIN_IMAGE_FILE="$WORKLOADS_DIR/windows-server-2022-amd64-2.raw" 13 | 14 | # Check if the image is present 15 | if [ ! -f "$WIN_IMAGE_FILE" ]; then 16 | echo "Windows image not present in the host" 17 | exit 1 18 | fi 19 | 20 | CH_PATH="$WORKLOADS_DIR/cloud-hypervisor" 21 | fetch_ch "$CH_PATH" "$arch" 22 | 23 | # Use device mapper to create a snapshot of the Windows image 24 | img_blk_size=$(du -b -B 512 ${WIN_IMAGE_FILE} | awk '{print $1;}') 25 | loop_device=$(losetup --find --show --read-only ${WIN_IMAGE_FILE}) 26 | dmsetup create windows-base --table "0 $img_blk_size linear $loop_device 0" 27 | dmsetup mknodes 28 | dmsetup create windows-snapshot-base --table "0 $img_blk_size snapshot-origin /dev/mapper/windows-base" 29 | dmsetup mknodes 30 | 31 | [ "$arch" = "x86_64" ] && target="x86_64-unknown-none" 32 | 33 | rustup component add rust-src 34 | cargo build --release --target "$target.json" -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem 35 | 36 | export RUST_BACKTRACE=1 37 | time cargo test --features "integration_tests" "integration::tests::windows::$arch" 38 | RES=$? 39 | 40 | dmsetup remove_all -f 41 | losetup -D 42 | 43 | exit $RES 44 | -------------------------------------------------------------------------------- /scripts/run_unit_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "${CARGO_HOME:-$HOME/.cargo}/env" 4 | source $(dirname "$0")/make-test-disks.sh 5 | 6 | WORKLOADS_DIR="$HOME/workloads" 7 | mkdir -p "$WORKLOADS_DIR" 8 | 9 | make_test_disks "$WORKLOADS_DIR" 10 | 11 | CLEAR_OS_VERSION="28660" 12 | CLEAR_OS_IMAGE_XZ_NAME="clear-$CLEAR_OS_VERSION-kvm.img.xz" 13 | CLEAR_OS_IMAGE_XZ_URL="https://download.clearlinux.org/releases/$CLEAR_OS_VERSION/clear/$CLEAR_OS_IMAGE_XZ_NAME" 14 | CLEAR_OS_IMAGE_XZ="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_XZ_NAME" 15 | if [ ! -f "$CLEAR_OS_IMAGE_XZ" ]; then 16 | pushd $WORKLOADS_DIR 17 | time wget --quiet $CLEAR_OS_IMAGE_XZ_URL || exit 1 18 | popd 19 | fi 20 | 21 | CLEAR_OS_IMAGE_NAME="clear-$CLEAR_OS_VERSION-kvm.img" 22 | CLEAR_OS_IMAGE="$WORKLOADS_DIR/$CLEAR_OS_IMAGE_NAME" 23 | if [ ! -f "$CLEAR_OS_IMAGE" ]; then 24 | pushd $WORKLOADS_DIR 25 | time unxz $CLEAR_OS_IMAGE_XZ 26 | popd 27 | fi 28 | 29 | export RUST_BACKTRACE=1 30 | cargo test || exit 1; 31 | -------------------------------------------------------------------------------- /src/arch/aarch64/asm.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use super::layout::map; 5 | use core::arch::global_asm; 6 | 7 | global_asm!(include_str!("ram64.s"), 8 | FDT_START = const map::dram::FDT_START); 9 | -------------------------------------------------------------------------------- /src/arch/aarch64/layout.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | // Copyright (c) 2021-2022 Andre Richter 4 | 5 | use core::{ 6 | cell::UnsafeCell, 7 | ops::{Range, RangeInclusive}, 8 | }; 9 | 10 | use crate::layout::{MemoryAttribute, MemoryDescriptor, MemoryLayout}; 11 | 12 | use super::paging::*; 13 | 14 | extern "Rust" { 15 | static code_start: UnsafeCell<()>; 16 | static code_end: UnsafeCell<()>; 17 | static data_start: UnsafeCell<()>; 18 | static data_end: UnsafeCell<()>; 19 | static stack_start: UnsafeCell<()>; 20 | static stack_end: UnsafeCell<()>; 21 | } 22 | 23 | pub mod map { 24 | // Create page table for 2T 25 | pub const END: usize = 0x20_000_000_000; 26 | 27 | // Firmware region won't be used by this firmware, so merge it into mmio region 28 | // is harmless and better for management. 29 | pub mod mmio { 30 | pub const START: usize = 0x0000_0000; 31 | pub const PL011_START: usize = 0x0900_0000; 32 | pub const PL031_START: usize = 0x0901_0000; 33 | pub const END: usize = 0x4000_0000; 34 | } 35 | 36 | pub mod dram { 37 | pub const FDT_SIZE: usize = 0x0020_0000; 38 | pub const ACPI_SIZE: usize = 0x0020_0000; 39 | 40 | pub const START: usize = super::mmio::END; 41 | pub const FDT_START: usize = START; 42 | pub const ACPI_START: usize = FDT_START + FDT_SIZE; 43 | pub const KERNEL_START: usize = ACPI_START + ACPI_SIZE; 44 | pub const END: usize = super::END; 45 | } 46 | } 47 | 48 | pub type KernelAddrSpace = AddressSpace<{ map::END }>; 49 | 50 | const NUM_MEM_RANGES: usize = 2; 51 | 52 | pub static LAYOUT: KernelVirtualLayout = KernelVirtualLayout::new( 53 | map::END - 1, 54 | [ 55 | TranslationDescriptor { 56 | name: "Device MMIO", 57 | virtual_range: RangeInclusive::new(map::mmio::START, map::mmio::END - 1), 58 | physical_range_translation: Translation::Identity, 59 | attribute_fields: AttributeFields { 60 | mem_attributes: MemAttributes::Device, 61 | acc_perms: AccessPermissions::ReadWrite, 62 | execute_never: true, 63 | }, 64 | }, 65 | TranslationDescriptor { 66 | name: "System Memory", 67 | virtual_range: RangeInclusive::new(map::dram::START, map::dram::END - 1), 68 | physical_range_translation: Translation::Identity, 69 | attribute_fields: AttributeFields { 70 | mem_attributes: MemAttributes::CacheableDRAM, 71 | acc_perms: AccessPermissions::ReadWrite, // FIXME 72 | execute_never: false, 73 | }, 74 | }, 75 | ], 76 | ); 77 | 78 | pub fn virt_mem_layout() -> &'static KernelVirtualLayout { 79 | &LAYOUT 80 | } 81 | 82 | pub fn mmio_range() -> Range { 83 | map::mmio::START..map::mmio::END 84 | } 85 | 86 | pub fn reserved_range() -> Range { 87 | map::dram::START..map::dram::KERNEL_START 88 | } 89 | 90 | pub fn code_range() -> Range { 91 | unsafe { (code_start.get() as _)..(code_end.get() as _) } 92 | } 93 | 94 | pub fn data_range() -> Range { 95 | unsafe { (data_start.get() as _)..(data_end.get() as _) } 96 | } 97 | 98 | pub fn stack_range() -> Range { 99 | unsafe { (stack_start.get() as _)..(stack_end.get() as _) } 100 | } 101 | 102 | const NUM_MEM_DESCS: usize = 5; 103 | 104 | pub static MEM_LAYOUT: MemoryLayout = [ 105 | MemoryDescriptor { 106 | name: "MMIO", 107 | range: mmio_range, 108 | attribute: MemoryAttribute::Mmio, 109 | }, 110 | MemoryDescriptor { 111 | name: "Reserved", 112 | range: reserved_range, 113 | attribute: MemoryAttribute::Unusable, 114 | }, 115 | MemoryDescriptor { 116 | name: "Code", 117 | range: code_range, 118 | attribute: MemoryAttribute::Code, 119 | }, 120 | MemoryDescriptor { 121 | name: "Data", 122 | range: data_range, 123 | attribute: MemoryAttribute::Data, 124 | }, 125 | MemoryDescriptor { 126 | name: "Stack", 127 | range: stack_range, 128 | attribute: MemoryAttribute::Data, 129 | }, 130 | ]; 131 | -------------------------------------------------------------------------------- /src/arch/aarch64/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | #[cfg(not(test))] 5 | pub mod asm; 6 | pub mod layout; 7 | pub mod paging; 8 | pub mod simd; 9 | mod translation; 10 | -------------------------------------------------------------------------------- /src/arch/aarch64/paging.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | // Copyright (c) 2021-2022 Andre Richter 4 | 5 | use core::{cell::SyncUnsafeCell, ops::RangeInclusive}; 6 | 7 | use aarch64_cpu::{ 8 | asm::barrier, 9 | registers::{Readable, Writeable, *}, 10 | }; 11 | 12 | use self::interface::Mmu; 13 | 14 | use super::layout::map::dram::{ACPI_SIZE, FDT_SIZE, FDT_START}; 15 | use super::{layout::KernelAddrSpace, translation::TranslationTable}; 16 | use crate::arch::aarch64::layout::code_range; 17 | 18 | /// MMU enable errors variants. 19 | #[derive(Debug)] 20 | pub enum MmuEnableError { 21 | AlreadyEnabled, 22 | #[allow(dead_code)] 23 | Other(&'static str), 24 | } 25 | 26 | /// Memory Management interfaces. 27 | pub mod interface { 28 | use super::*; 29 | 30 | /// MMU functions. 31 | pub trait Mmu { 32 | unsafe fn enable_mmu_and_caching(&self) -> Result<(), MmuEnableError>; 33 | 34 | fn is_enabled(&self) -> bool; 35 | } 36 | } 37 | 38 | /// Describes the characteristics of a translation granule. 39 | pub struct TranslationGranule; 40 | 41 | /// Describes properties of an address space. 42 | pub struct AddressSpace; 43 | 44 | /// Architecture agnostic translation types. 45 | #[allow(dead_code)] 46 | #[derive(Copy, Clone)] 47 | pub enum Translation { 48 | Identity, 49 | Offset(usize), 50 | } 51 | 52 | /// Architecture agnostic memory attributes. 53 | #[derive(Copy, Clone)] 54 | pub enum MemAttributes { 55 | CacheableDRAM, 56 | Device, 57 | } 58 | 59 | /// Architecture agnostic access permissions. 60 | #[derive(Copy, Clone)] 61 | pub enum AccessPermissions { 62 | ReadOnly, 63 | ReadWrite, 64 | } 65 | 66 | /// Collection of memory attributes. 67 | #[derive(Copy, Clone)] 68 | pub struct AttributeFields { 69 | pub mem_attributes: MemAttributes, 70 | pub acc_perms: AccessPermissions, 71 | pub execute_never: bool, 72 | } 73 | 74 | impl Default for AttributeFields { 75 | fn default() -> AttributeFields { 76 | AttributeFields { 77 | mem_attributes: MemAttributes::CacheableDRAM, 78 | acc_perms: AccessPermissions::ReadWrite, 79 | execute_never: true, 80 | } 81 | } 82 | } 83 | 84 | /// Architecture agnostic descriptor for a memory range. 85 | pub struct TranslationDescriptor { 86 | #[allow(dead_code)] 87 | pub name: &'static str, 88 | pub virtual_range: RangeInclusive, 89 | pub physical_range_translation: Translation, 90 | pub attribute_fields: AttributeFields, 91 | } 92 | 93 | /// Type for expressing the kernel's virtual memory layout. 94 | pub struct KernelVirtualLayout { 95 | /// The last (inclusive) address of the address space. 96 | max_virt_addr_inclusive: usize, 97 | 98 | /// Array of descriptors for non-standard (normal cacheable DRAM) memory regions. 99 | inner: [TranslationDescriptor; NUM_SPECIAL_RANGES], 100 | } 101 | 102 | impl TranslationGranule { 103 | /// The granule's size. 104 | pub const SIZE: usize = Self::size_checked(); 105 | 106 | /// The granule's shift, aka log2(size). 107 | pub const SHIFT: usize = Self::SIZE.trailing_zeros() as usize; 108 | 109 | /// The page descriptor's output address mask (48bits) 110 | pub const ADDR_MASK: usize = 0xffffffff << Self::SHIFT; 111 | 112 | const fn size_checked() -> usize { 113 | assert!(GRANULE_SIZE.is_power_of_two()); 114 | 115 | GRANULE_SIZE 116 | } 117 | } 118 | 119 | impl AddressSpace { 120 | /// The address space size. 121 | pub const SIZE: usize = Self::size_checked(); 122 | 123 | /// The address space shift, aka log2(size). 124 | pub const SIZE_SHIFT: usize = Self::SIZE.trailing_zeros() as usize; 125 | 126 | const fn size_checked() -> usize { 127 | assert!(AS_SIZE.is_power_of_two()); 128 | 129 | // Check for architectural restrictions as well. 130 | Self::arch_address_space_size_sanity_checks(); 131 | 132 | AS_SIZE 133 | } 134 | } 135 | 136 | impl KernelVirtualLayout<{ NUM_SPECIAL_RANGES }> { 137 | /// Create a new instance. 138 | pub const fn new(max: usize, layout: [TranslationDescriptor; NUM_SPECIAL_RANGES]) -> Self { 139 | Self { 140 | max_virt_addr_inclusive: max, 141 | inner: layout, 142 | } 143 | } 144 | 145 | /// For a virtual address, find and return the physical output address and corresponding 146 | /// attributes. 147 | /// 148 | /// If the address is not found in `inner`, return an identity mapped default with normal 149 | /// cacheable DRAM attributes. 150 | pub fn virt_addr_properties( 151 | &self, 152 | virt_addr: usize, 153 | ) -> Result<(usize, AttributeFields), &'static str> { 154 | if virt_addr > self.max_virt_addr_inclusive { 155 | return Err("Address out of range"); 156 | } 157 | 158 | // Enhance security for fdt, acpi and code memory range 159 | let code = code_range(); 160 | let fdt_acpi = FDT_START..(FDT_START + FDT_SIZE + ACPI_SIZE); 161 | if code.contains(&virt_addr) { 162 | let attr = AttributeFields { 163 | mem_attributes: MemAttributes::CacheableDRAM, 164 | acc_perms: AccessPermissions::ReadOnly, 165 | execute_never: false, 166 | }; 167 | return Ok((virt_addr, attr)); 168 | } else if fdt_acpi.contains(&virt_addr) { 169 | let attr = AttributeFields { 170 | mem_attributes: MemAttributes::CacheableDRAM, 171 | acc_perms: AccessPermissions::ReadOnly, 172 | execute_never: true, 173 | }; 174 | return Ok((virt_addr, attr)); 175 | } 176 | 177 | for i in self.inner.iter() { 178 | if i.virtual_range.contains(&virt_addr) { 179 | let output_addr = match i.physical_range_translation { 180 | Translation::Identity => virt_addr, 181 | Translation::Offset(a) => a + (virt_addr - (i.virtual_range).start()), 182 | }; 183 | 184 | return Ok((output_addr, i.attribute_fields)); 185 | } 186 | } 187 | 188 | Ok((virt_addr, AttributeFields::default())) 189 | } 190 | } 191 | 192 | /// Memory Management Unit type. 193 | struct MemoryManagementUnit; 194 | 195 | pub type Granule512MiB = TranslationGranule<{ 512 * 1024 * 1024 }>; 196 | pub type Granule64KiB = TranslationGranule<{ 64 * 1024 }>; 197 | 198 | /// Constants for indexing the MAIR_EL1. 199 | pub mod mair { 200 | pub const DEVICE: u64 = 0; 201 | pub const NORMAL: u64 = 1; 202 | } 203 | 204 | /// The kernel translation tables. 205 | /// 206 | /// # Safety 207 | /// 208 | /// - Supposed to land in `.bss`. Therefore, ensure that all initial member values boil down to "0". 209 | static mut KERNEL_TABLES: SyncUnsafeCell = 210 | SyncUnsafeCell::new(TranslationTable::new()); 211 | 212 | static MMU: MemoryManagementUnit = MemoryManagementUnit; 213 | 214 | impl AddressSpace { 215 | /// Checks for architectural restrictions. 216 | pub const fn arch_address_space_size_sanity_checks() { 217 | // Size must be at least one full 512 MiB table. 218 | assert!((AS_SIZE % Granule512MiB::SIZE) == 0); 219 | 220 | // Check for 48 bit virtual address size as maximum, which is supported by any ARMv8 221 | // version. 222 | assert!(AS_SIZE <= (1 << 48)); 223 | } 224 | } 225 | 226 | impl MemoryManagementUnit { 227 | /// Setup function for the MAIR_EL1 register. 228 | fn setup_mair(&self) { 229 | // Define the memory types being mapped. 230 | MAIR_EL1.write( 231 | // Attribute 1 - Cacheable normal DRAM. 232 | MAIR_EL1::Attr1_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc 233 | + MAIR_EL1::Attr1_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc 234 | // Attribute 0 - Device. 235 | + MAIR_EL1::Attr0_Device::nonGathering_nonReordering_EarlyWriteAck, 236 | ); 237 | } 238 | 239 | /// Configure various settings of stage 1 of the EL1 translation regime. 240 | fn configure_translation_control(&self) { 241 | let t0sz = (64 - KernelAddrSpace::SIZE_SHIFT) as u64; 242 | 243 | TCR_EL1.write( 244 | TCR_EL1::TBI0::Used 245 | + TCR_EL1::IPS::Bits_40 246 | + TCR_EL1::TG0::KiB_64 247 | + TCR_EL1::SH0::Inner 248 | + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable 249 | + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable 250 | + TCR_EL1::EPD0::EnableTTBR0Walks 251 | + TCR_EL1::A1::TTBR0 252 | + TCR_EL1::T0SZ.val(t0sz) 253 | + TCR_EL1::EPD1::DisableTTBR1Walks, 254 | ); 255 | } 256 | } 257 | 258 | /// Return a reference to the MMU instance. 259 | fn mmu() -> &'static impl interface::Mmu { 260 | &MMU 261 | } 262 | 263 | impl interface::Mmu for MemoryManagementUnit { 264 | unsafe fn enable_mmu_and_caching(&self) -> Result<(), MmuEnableError> { 265 | if self.is_enabled() { 266 | return Err(MmuEnableError::AlreadyEnabled); 267 | } 268 | 269 | // Fail early if translation granule is not supported. 270 | if !ID_AA64MMFR0_EL1.matches_all(ID_AA64MMFR0_EL1::TGran64::Supported) { 271 | return Err(MmuEnableError::Other( 272 | "Translation granule not supported in HW", 273 | )); 274 | } 275 | 276 | // Prepare the memory attribute indirection register. 277 | self.setup_mair(); 278 | 279 | // Populate translation tables. 280 | #[allow(static_mut_refs)] 281 | KERNEL_TABLES 282 | .get_mut() 283 | .populate_tt_entries() 284 | .map_err(MmuEnableError::Other)?; 285 | 286 | // Set the "Translation Table Base Register". 287 | TTBR0_EL1.set_baddr( 288 | #[allow(static_mut_refs)] 289 | KERNEL_TABLES.get_mut().phys_base_address(), 290 | ); 291 | 292 | self.configure_translation_control(); 293 | 294 | // Switch the MMU on. 295 | // 296 | // First, force all previous changes to be seen before the MMU is enabled. 297 | barrier::isb(barrier::SY); 298 | 299 | // Enable the MMU and turn on data and instruction caching. 300 | SCTLR_EL1.modify_no_read( 301 | SCTLR_EL1.extract(), 302 | SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable, 303 | ); 304 | 305 | // Force MMU init to complete before next instruction. 306 | barrier::isb(barrier::SY); 307 | 308 | Ok(()) 309 | } 310 | 311 | #[inline(always)] 312 | fn is_enabled(&self) -> bool { 313 | SCTLR_EL1.matches_all(SCTLR_EL1::M::Enable) 314 | } 315 | } 316 | 317 | pub fn setup() { 318 | unsafe { 319 | if let Err(e) = mmu().enable_mmu_and_caching() { 320 | panic!("Failed to setup paging: {:?}", e); 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/arch/aarch64/ram64.s: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: Apache-2.0 */ 2 | /* Copyright (C) 2022 Akira Moroo */ 3 | 4 | .section .text.boot, "ax" 5 | .global ram64_start 6 | .global efi_image_size 7 | .global efi_image_offset 8 | 9 | ram64_start: 10 | /* 11 | * This header follows the AArch64 Linux kernel image header [1] to load 12 | * as a PE binary by the hypervisor. 13 | * 14 | * [1] https://docs.kernel.org/arm64/booting.html#call-the-kernel-image 15 | */ 16 | add x13, x18, #0x16 /* code0: UEFI "MZ" signature magic instruction */ 17 | b jump_to_rust /* code1 */ 18 | 19 | .quad efi_image_offset /* text_offset */ 20 | .quad efi_image_size /* image_size */ 21 | .quad 0 /* flags */ 22 | .quad 0 /* res2 */ 23 | .quad 0 /* res3 */ 24 | .quad 0 /* res4 */ 25 | 26 | .long 0x644d5241 /* "ARM\x64" magic number */ 27 | .long 0 /* res5 */ 28 | .align 3 29 | 30 | jump_to_rust: 31 | /* x0 typically points to device tree at entry */ 32 | ldr x0, ={FDT_START} 33 | 34 | /* setup stack */ 35 | ldr x30, =stack_end 36 | mov sp, x30 37 | 38 | /* x0: pointer to device tree */ 39 | b rust64_start 40 | -------------------------------------------------------------------------------- /src/arch/aarch64/simd.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Akira Moroo 3 | 4 | use aarch64_cpu::registers::*; 5 | 6 | pub fn setup_simd() { 7 | CPACR_EL1.modify_no_read(CPACR_EL1.extract(), CPACR_EL1::FPEN::TrapNothing); 8 | } 9 | -------------------------------------------------------------------------------- /src/arch/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | #[cfg(target_arch = "aarch64")] 5 | pub mod aarch64; 6 | 7 | #[cfg(target_arch = "x86_64")] 8 | pub mod x86_64; 9 | 10 | #[cfg(target_arch = "riscv64")] 11 | pub mod riscv64; 12 | -------------------------------------------------------------------------------- /src/arch/riscv64/asm.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Rivos Inc. 3 | 4 | use core::arch::global_asm; 5 | 6 | global_asm!(include_str!("ram64.s")); 7 | -------------------------------------------------------------------------------- /src/arch/riscv64/layout.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | // Copyright (c) 2021-2022 Andre Richter 4 | // Copyright (C) 2023 Rivos Inc. 5 | 6 | use core::{cell::UnsafeCell, ops::Range}; 7 | 8 | use crate::layout::{MemoryAttribute, MemoryDescriptor, MemoryLayout}; 9 | 10 | extern "Rust" { 11 | static code_start: UnsafeCell<()>; 12 | static code_end: UnsafeCell<()>; 13 | static data_start: UnsafeCell<()>; 14 | static data_end: UnsafeCell<()>; 15 | static stack_start: UnsafeCell<()>; 16 | static stack_end: UnsafeCell<()>; 17 | } 18 | 19 | pub fn code_range() -> Range { 20 | unsafe { (code_start.get() as _)..(code_end.get() as _) } 21 | } 22 | 23 | pub fn data_range() -> Range { 24 | unsafe { (data_start.get() as _)..(data_end.get() as _) } 25 | } 26 | 27 | pub fn stack_range() -> Range { 28 | unsafe { (stack_start.get() as _)..(stack_end.get() as _) } 29 | } 30 | 31 | pub fn reserved_range() -> Range { 32 | 0x8000_0000..0x8020_0000 33 | } 34 | 35 | const NUM_MEM_DESCS: usize = 4; 36 | 37 | pub static MEM_LAYOUT: MemoryLayout = [ 38 | MemoryDescriptor { 39 | name: "Code", 40 | range: code_range, 41 | attribute: MemoryAttribute::Code, 42 | }, 43 | MemoryDescriptor { 44 | name: "Data", 45 | range: data_range, 46 | attribute: MemoryAttribute::Data, 47 | }, 48 | MemoryDescriptor { 49 | name: "Stack", 50 | range: stack_range, 51 | attribute: MemoryAttribute::Data, 52 | }, 53 | MemoryDescriptor { 54 | name: "SBI", 55 | range: reserved_range, 56 | attribute: MemoryAttribute::Unusable, 57 | }, 58 | ]; 59 | -------------------------------------------------------------------------------- /src/arch/riscv64/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Rivos Inc. 3 | 4 | pub mod asm; 5 | pub mod layout; 6 | -------------------------------------------------------------------------------- /src/arch/riscv64/ram64.s: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 by Rivos Inc. 2 | // Licensed under the Apache License, Version 2.0, see LICENSE for details. 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | .option norvc 6 | 7 | .section .text.boot 8 | 9 | // The entry point for the boot CPU. 10 | .global ram64_start 11 | ram64_start: 12 | 13 | .option push 14 | .option norelax 15 | la gp, __global_pointer$ 16 | .option pop 17 | csrw sstatus, zero 18 | csrw sie, zero 19 | 20 | la sp, stack_end 21 | call rust64_start 22 | wfi_loop: 23 | wfi 24 | j wfi_loop 25 | -------------------------------------------------------------------------------- /src/arch/x86_64/asm.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2020 Google LLC 3 | 4 | use core::arch::global_asm; 5 | 6 | global_asm!(include_str!("ram32.s"), options(att_syntax, raw)); 7 | -------------------------------------------------------------------------------- /src/arch/x86_64/gdt.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2020 Google LLC 3 | 4 | bitflags::bitflags! { 5 | // An extension of x86_64::structures::gdt::DescriptorFlags 6 | struct Descriptor: u64 { 7 | const LIMIT_0_15 = 0xFFFF; 8 | const BASE_0_23 = 0xFF_FFFF << 16; 9 | const ACCESSED = 1 << 40; 10 | const WRITABLE = 1 << 41; // Only for Data-Segments 11 | const READABLE = 1 << 41; // Only for Code-Segments 12 | const EXPANSION = 1 << 42; // Only for Data-Segments 13 | const CONFORMING = 1 << 42; // Only for Code-Segments 14 | const EXECUTABLE = 1 << 43; 15 | const USER_SEGMENT = 1 << 44; 16 | const DPL_RING_3 = 3 << 45; 17 | const PRESENT = 1 << 47; 18 | const LIMIT_16_19 = 0xF << 48; 19 | const SOFTWARE = 1 << 52; 20 | const BIT64 = 1 << 53; 21 | const BIT32 = 1 << 54; 22 | const GRANULARITY = 1 << 55; 23 | const BASE_24_31 = 0xFF << 56; 24 | 25 | // All segments are nonconforming, non-system, ring-0 only, and present. 26 | // We set ACCESSED in advance to avoid writing to the descriptor. 27 | const COMMON = Self::ACCESSED.bits() | Self::USER_SEGMENT.bits() | Self::PRESENT.bits(); 28 | // BIT32 must be 0, all other bits (not yet mentioned) are ignored. 29 | const CODE64 = Self::COMMON.bits() | Self::READABLE.bits() | Self::EXECUTABLE.bits() | Self::BIT64.bits(); 30 | const DATA64 = Self::COMMON.bits() | Self::WRITABLE.bits() | Self::BIT64.bits(); 31 | } 32 | } 33 | 34 | // An alternative to x86_64::structures::DescriptorTablePointer that avoids 35 | // "pointer-to-integer cast" (which rust does not support in statics). 36 | #[repr(C, packed)] 37 | struct Pointer { 38 | limit: u16, 39 | base: &'static Descriptor, 40 | } 41 | 42 | impl Pointer { 43 | const fn new(gdt: &'static [Descriptor]) -> Self { 44 | let size = core::mem::size_of_val(gdt); 45 | Self { 46 | limit: size as u16 - 1, 47 | base: &gdt[0], 48 | } 49 | } 50 | } 51 | 52 | // Our 64-bit GDT lives in RAM, so it can be accessed like any other global. 53 | #[no_mangle] 54 | static GDT64_PTR: Pointer = Pointer::new(&GDT64); 55 | static GDT64: [Descriptor; 3] = [Descriptor::empty(), Descriptor::CODE64, Descriptor::DATA64]; 56 | -------------------------------------------------------------------------------- /src/arch/x86_64/layout.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use core::{cell::UnsafeCell, ops::Range}; 5 | 6 | use crate::layout::{MemoryAttribute, MemoryDescriptor, MemoryLayout}; 7 | 8 | extern "Rust" { 9 | static ram_min: UnsafeCell<()>; 10 | static code_start: UnsafeCell<()>; 11 | static code_end: UnsafeCell<()>; 12 | static data_start: UnsafeCell<()>; 13 | static data_end: UnsafeCell<()>; 14 | static stack_start: UnsafeCell<()>; 15 | static stack_end: UnsafeCell<()>; 16 | } 17 | 18 | pub fn header_range() -> Range { 19 | unsafe { (ram_min.get() as _)..(code_start.get() as _) } 20 | } 21 | 22 | pub fn code_range() -> Range { 23 | unsafe { (code_start.get() as _)..(code_end.get() as _) } 24 | } 25 | 26 | pub fn data_range() -> Range { 27 | unsafe { (data_start.get() as _)..(data_end.get() as _) } 28 | } 29 | 30 | pub fn stack_range() -> Range { 31 | unsafe { (stack_start.get() as _)..(stack_end.get() as _) } 32 | } 33 | 34 | const NUM_MEM_DESCS: usize = 4; 35 | 36 | pub const KERNEL_START: u64 = 0x20_0000; 37 | 38 | pub static MEM_LAYOUT: MemoryLayout = [ 39 | MemoryDescriptor { 40 | name: "PVH Header", 41 | range: header_range, 42 | attribute: MemoryAttribute::Data, 43 | }, 44 | MemoryDescriptor { 45 | name: "Code", 46 | range: code_range, 47 | attribute: MemoryAttribute::Code, 48 | }, 49 | MemoryDescriptor { 50 | name: "Data", 51 | range: data_range, 52 | attribute: MemoryAttribute::Data, 53 | }, 54 | MemoryDescriptor { 55 | name: "Stack", 56 | range: stack_range, 57 | attribute: MemoryAttribute::Data, 58 | }, 59 | ]; 60 | -------------------------------------------------------------------------------- /src/arch/x86_64/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | #[cfg(not(test))] 5 | pub mod asm; 6 | pub mod gdt; 7 | pub mod layout; 8 | pub mod paging; 9 | pub mod sse; 10 | -------------------------------------------------------------------------------- /src/arch/x86_64/paging.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2020 Google LLC 3 | 4 | use core::cell::SyncUnsafeCell; 5 | use log::info; 6 | use x86_64::{ 7 | registers::control::Cr3, 8 | structures::paging::{PageSize, PageTable, PageTableFlags, PhysFrame, Size2MiB}, 9 | PhysAddr, 10 | }; 11 | 12 | // Amount of memory we identity map in setup(), max 512 GiB. 13 | const ADDRESS_SPACE_GIB: usize = 4; 14 | const TABLE: PageTable = PageTable::new(); 15 | 16 | // Put the Page Tables in static muts to make linking easier 17 | #[no_mangle] 18 | static mut L4_TABLE: SyncUnsafeCell = SyncUnsafeCell::new(PageTable::new()); 19 | #[no_mangle] 20 | static mut L3_TABLE: SyncUnsafeCell = SyncUnsafeCell::new(PageTable::new()); 21 | #[no_mangle] 22 | static mut L2_TABLES: SyncUnsafeCell<[PageTable; ADDRESS_SPACE_GIB]> = 23 | SyncUnsafeCell::new([TABLE; ADDRESS_SPACE_GIB]); 24 | 25 | pub fn setup() { 26 | // SAFETY: This function is idempontent and only writes to static memory and 27 | // CR3. Thus, it is safe to run multiple times or on multiple threads. 28 | #[allow(static_mut_refs)] 29 | let (l4, l3, l2s) = unsafe { (L4_TABLE.get_mut(), L3_TABLE.get_mut(), L2_TABLES.get_mut()) }; 30 | info!("Setting up {ADDRESS_SPACE_GIB} GiB identity mapping"); 31 | let pt_flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; 32 | 33 | // Setup Identity map using L2 huge pages 34 | let mut next_addr = PhysAddr::new(0); 35 | for l2 in l2s.iter_mut() { 36 | for l2e in l2.iter_mut() { 37 | l2e.set_addr(next_addr, pt_flags | PageTableFlags::HUGE_PAGE); 38 | next_addr += Size2MiB::SIZE; 39 | } 40 | } 41 | 42 | // Point L3 at L2s 43 | for (i, l2) in l2s.iter().enumerate() { 44 | l3[i].set_addr(phys_addr(l2), pt_flags); 45 | } 46 | 47 | // Point L4 at L3 48 | l4[0].set_addr(phys_addr(l3), pt_flags); 49 | 50 | // Point Cr3 at L4 51 | let (cr3_frame, cr3_flags) = Cr3::read(); 52 | let l4_frame = PhysFrame::from_start_address(phys_addr(l4)).unwrap(); 53 | if cr3_frame != l4_frame { 54 | unsafe { Cr3::write(l4_frame, cr3_flags) }; 55 | } 56 | info!("Page tables setup"); 57 | } 58 | 59 | // Map a virtual address to a PhysAddr (assumes identity mapping) 60 | fn phys_addr(virt_addr: *const T) -> PhysAddr { 61 | PhysAddr::new(virt_addr as u64) 62 | } 63 | -------------------------------------------------------------------------------- /src/arch/x86_64/ram32.s: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: Apache-2.0 2 | # Copyright 2020 Google LLC 3 | 4 | .section .text32, "ax" 5 | .global ram32_start 6 | .code32 7 | 8 | ram32_start: 9 | # Stash the PVH start_info struct in %rdi. 10 | movl %ebx, %edi 11 | 12 | setup_page_tables: 13 | # First L2 entry identity maps [0, 2 MiB) 14 | movl $0b10000011, (L2_TABLES) # huge (bit 7), writable (bit 1), present (bit 0) 15 | # First L3 entry points to L2 table 16 | movl $L2_TABLES, %eax 17 | orb $0b00000011, %al # writable (bit 1), present (bit 0) 18 | movl %eax, (L3_TABLE) 19 | # First L4 entry points to L3 table 20 | movl $L3_TABLE, %eax 21 | orb $0b00000011, %al # writable (bit 1), present (bit 0) 22 | movl %eax, (L4_TABLE) 23 | 24 | enable_paging: 25 | # Load page table root into CR3 26 | movl $L4_TABLE, %eax 27 | movl %eax, %cr3 28 | 29 | # Set CR4.PAE (Physical Address Extension) 30 | movl %cr4, %eax 31 | orb $0b00100000, %al # Set bit 5 32 | movl %eax, %cr4 33 | # Set EFER.LME (Long Mode Enable) 34 | movl $0xC0000080, %ecx 35 | rdmsr 36 | orb $0b00000001, %ah # Set bit 8 37 | wrmsr 38 | # Set CRO.PG (Paging) 39 | movl %cr0, %eax 40 | orl $(1 << 31), %eax 41 | movl %eax, %cr0 42 | 43 | jump_to_64bit: 44 | # We are now in 32-bit compatibility mode. To enter 64-bit mode, we need to 45 | # load a 64-bit code segment into our GDT. 46 | lgdtl GDT64_PTR 47 | # Initialize the stack pointer (Rust code always uses the stack) 48 | movl $stack_end, %esp 49 | # Set segment registers to a 64-bit segment. 50 | movw $0x10, %ax 51 | movw %ax, %ds 52 | movw %ax, %es 53 | movw %ax, %gs 54 | movw %ax, %fs 55 | movw %ax, %ss 56 | # Set CS to a 64-bit segment and jump to 64-bit Rust code. 57 | # PVH start_info is in %rdi, the first paramter of the System V ABI. 58 | ljmpl $0x08, $rust64_start 59 | -------------------------------------------------------------------------------- /src/arch/x86_64/sse.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2020 Google LLC 3 | 4 | use x86_64::registers::control::{Cr0, Cr0Flags, Cr4, Cr4Flags}; 5 | 6 | // Enable SSE2 for XMM registers (needed for EFI calling) 7 | pub fn enable_sse() { 8 | let mut cr0 = Cr0::read(); 9 | cr0.remove(Cr0Flags::EMULATE_COPROCESSOR); 10 | cr0.insert(Cr0Flags::MONITOR_COPROCESSOR); 11 | unsafe { Cr0::write(cr0) }; 12 | let mut cr4 = Cr4::read(); 13 | cr4.insert(Cr4Flags::OSFXSR); 14 | cr4.insert(Cr4Flags::OSXMMEXCPT_ENABLE); 15 | unsafe { Cr4::write(cr4) }; 16 | } 17 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use core::cell::RefCell; 5 | 6 | use crate::virtio::{Error as VirtioError, VirtioTransport}; 7 | 8 | const QUEUE_SIZE: usize = 16; 9 | 10 | #[repr(C)] 11 | #[repr(align(16))] 12 | #[derive(Default)] 13 | /// A virtio qeueue entry descriptor 14 | struct Desc { 15 | addr: u64, 16 | length: u32, 17 | flags: u16, 18 | next: u16, 19 | } 20 | 21 | #[repr(C)] 22 | #[repr(align(2))] 23 | #[derive(Default)] 24 | /// The virtio available ring 25 | struct AvailRing { 26 | flags: u16, 27 | idx: u16, 28 | ring: [u16; QUEUE_SIZE], 29 | } 30 | 31 | #[repr(C)] 32 | #[repr(align(4))] 33 | #[derive(Default)] 34 | /// The virtio used ring 35 | struct UsedRing { 36 | flags: u16, 37 | idx: u16, 38 | ring: [UsedElem; QUEUE_SIZE], 39 | } 40 | 41 | #[repr(C)] 42 | #[derive(Default)] 43 | /// A single element in the used ring 44 | struct UsedElem { 45 | id: u32, 46 | len: u32, 47 | } 48 | 49 | #[repr(C)] 50 | #[repr(align(64))] 51 | /// Device driver for virtio block over any transport 52 | pub struct VirtioBlockDevice<'a> { 53 | transport: &'a mut dyn VirtioTransport, 54 | state: RefCell, 55 | read_only: bool, 56 | } 57 | 58 | #[repr(C)] 59 | #[repr(align(64))] 60 | #[derive(Default)] 61 | struct DriverState { 62 | descriptors: [Desc; QUEUE_SIZE], 63 | avail: AvailRing, 64 | used: UsedRing, 65 | next_head: usize, 66 | } 67 | 68 | #[derive(Debug, PartialEq, Eq)] 69 | pub enum Error { 70 | BlockIO, 71 | NoDataBuf, 72 | InvalidDataBufSize, 73 | 74 | BlockNotSupported, 75 | } 76 | 77 | #[repr(C)] 78 | /// Header used for virtio block requests 79 | struct BlockRequestHeader { 80 | request: u32, 81 | reserved: u32, 82 | sector: u64, 83 | } 84 | 85 | #[repr(C)] 86 | /// Footer used for virtio block requests 87 | struct BlockRequestFooter { 88 | status: u8, 89 | } 90 | 91 | const SECTOR_SIZE: usize = 512; 92 | 93 | #[repr(C)] 94 | pub struct SectorBuf([u8; SECTOR_SIZE]); 95 | 96 | impl SectorBuf { 97 | pub const fn new() -> Self { 98 | Self([0_u8; SECTOR_SIZE]) 99 | } 100 | 101 | #[inline] 102 | pub const fn len() -> usize { 103 | SECTOR_SIZE 104 | } 105 | 106 | pub fn as_bytes(&self) -> &[u8] { 107 | &self.0 108 | } 109 | 110 | pub fn as_mut_bytes(&mut self) -> &mut [u8] { 111 | &mut self.0 112 | } 113 | } 114 | 115 | pub trait SectorRead { 116 | /// Read a single sector (512 bytes) from the block device. `data` must be 117 | /// exactly 512 bytes long. 118 | fn read(&self, sector: u64, data: &mut [u8]) -> Result<(), Error>; 119 | } 120 | 121 | pub trait SectorWrite { 122 | /// Write a single sector (512 bytes) from the block device. `data` must be 123 | /// exactly 512 bytes long. 124 | fn write(&self, sector: u64, data: &mut [u8]) -> Result<(), Error>; 125 | fn flush(&self) -> Result<(), Error>; 126 | } 127 | 128 | #[derive(PartialEq, Eq, Copy, Clone)] 129 | enum RequestType { 130 | Read = 0, 131 | Write = 1, 132 | Flush = 4, 133 | } 134 | 135 | impl<'a> VirtioBlockDevice<'a> { 136 | pub fn new(transport: &'a mut dyn VirtioTransport) -> VirtioBlockDevice<'a> { 137 | VirtioBlockDevice { 138 | transport, 139 | state: RefCell::new(DriverState::default()), 140 | read_only: false, 141 | } 142 | } 143 | 144 | pub fn init(&mut self) -> Result<(), VirtioError> { 145 | const VIRTIO_SUBSYSTEM_BLOCK: u32 = 0x2; 146 | const VIRTIO_F_VERSION_1: u64 = 1 << 32; 147 | const VIRTIO_BLK_F_RO: u64 = 1 << 5; 148 | 149 | const VIRTIO_STATUS_RESET: u32 = 0; 150 | const VIRTIO_STATUS_ACKNOWLEDGE: u32 = 1; 151 | const VIRTIO_STATUS_DRIVER: u32 = 2; 152 | const VIRTIO_STATUS_FEATURES_OK: u32 = 8; 153 | const VIRTIO_STATUS_DRIVER_OK: u32 = 4; 154 | const VIRTIO_STATUS_FAILED: u32 = 128; 155 | 156 | // Initialise the transport 157 | self.transport.init(VIRTIO_SUBSYSTEM_BLOCK)?; 158 | 159 | // Reset device 160 | self.transport.set_status(VIRTIO_STATUS_RESET); 161 | 162 | // Acknowledge 163 | self.transport.add_status(VIRTIO_STATUS_ACKNOWLEDGE); 164 | 165 | // And advertise driver 166 | self.transport.add_status(VIRTIO_STATUS_DRIVER); 167 | 168 | // Request device features 169 | let device_features = self.transport.get_features(); 170 | 171 | if device_features & VIRTIO_F_VERSION_1 != VIRTIO_F_VERSION_1 { 172 | self.transport.add_status(VIRTIO_STATUS_FAILED); 173 | return Err(VirtioError::LegacyOnly); 174 | } 175 | 176 | // Detect if device is read-only 177 | self.read_only = (device_features & VIRTIO_BLK_F_RO) == VIRTIO_BLK_F_RO; 178 | 179 | // Don't support any advanced features for now 180 | let supported_features = VIRTIO_F_VERSION_1 | VIRTIO_BLK_F_RO; 181 | 182 | // Report driver features 183 | self.transport 184 | .set_features(device_features & supported_features); 185 | 186 | self.transport.add_status(VIRTIO_STATUS_FEATURES_OK); 187 | if self.transport.get_status() & VIRTIO_STATUS_FEATURES_OK != VIRTIO_STATUS_FEATURES_OK { 188 | self.transport.add_status(VIRTIO_STATUS_FAILED); 189 | return Err(VirtioError::FeatureNegotiationFailed); 190 | } 191 | 192 | // Program queues 193 | self.transport.set_queue(0); 194 | 195 | let max_queue = self.transport.get_queue_max_size(); 196 | 197 | // Hardcoded queue size to QUEUE_SIZE at the moment 198 | if max_queue < QUEUE_SIZE as u16 { 199 | self.transport.add_status(VIRTIO_STATUS_FAILED); 200 | return Err(VirtioError::QueueTooSmall); 201 | } 202 | self.transport.set_queue_size(QUEUE_SIZE as u16); 203 | 204 | // Update all queue parts 205 | let state = self.state.borrow_mut(); 206 | let addr = state.descriptors.as_ptr() as u64; 207 | self.transport.set_descriptors_address(addr); 208 | 209 | let addr = (&state.avail as *const _) as u64; 210 | self.transport.set_avail_ring(addr); 211 | 212 | let addr = (&state.used as *const _) as u64; 213 | self.transport.set_used_ring(addr); 214 | 215 | // Confirm queue 216 | self.transport.set_queue_enable(); 217 | 218 | // Report driver ready 219 | self.transport.add_status(VIRTIO_STATUS_DRIVER_OK); 220 | 221 | Ok(()) 222 | } 223 | 224 | // Number of sectors that this device holds 225 | pub fn get_capacity(&self) -> u64 { 226 | u64::from(self.transport.read_device_config(0)) 227 | | u64::from(self.transport.read_device_config(4)) << 32 228 | } 229 | 230 | fn request( 231 | &self, 232 | sector: u64, 233 | data: Option<&mut [u8]>, 234 | request: RequestType, 235 | ) -> Result<(), Error> { 236 | const VIRTQ_DESC_F_NEXT: u16 = 1; 237 | const VIRTQ_DESC_F_WRITE: u16 = 2; 238 | 239 | const VIRTIO_BLK_S_OK: u8 = 0; 240 | const VIRTIO_BLK_S_IOERR: u8 = 1; 241 | const VIRTIO_BLK_S_UNSUPP: u8 = 2; 242 | 243 | let header = BlockRequestHeader { 244 | request: request as u32, 245 | reserved: 0, 246 | sector, 247 | }; 248 | 249 | let footer = BlockRequestFooter { status: 0 }; 250 | 251 | let mut state = self.state.borrow_mut(); 252 | 253 | let next_head = state.next_head; 254 | let d = &mut state.descriptors[next_head]; 255 | let next_desc = (next_head + 1) % QUEUE_SIZE; 256 | d.addr = (&header as *const _) as u64; 257 | d.length = core::mem::size_of::() as u32; 258 | d.flags = VIRTQ_DESC_F_NEXT; 259 | d.next = next_desc as u16; 260 | 261 | let d = &mut state.descriptors[next_desc]; 262 | let next_desc = (next_desc + 1) % QUEUE_SIZE; 263 | if request != RequestType::Flush { 264 | match data { 265 | None => { 266 | return Err(Error::NoDataBuf); 267 | } 268 | Some(data) => { 269 | if data.len() != SectorBuf::len() { 270 | return Err(Error::InvalidDataBufSize); 271 | } 272 | d.addr = data.as_ptr() as u64; 273 | d.length = SectorBuf::len() as u32; 274 | } 275 | } 276 | } 277 | 278 | d.flags = VIRTQ_DESC_F_NEXT 279 | | if request == RequestType::Read { 280 | VIRTQ_DESC_F_WRITE 281 | } else { 282 | 0 283 | }; 284 | d.next = next_desc as u16; 285 | 286 | let d = &mut state.descriptors[next_desc]; 287 | d.addr = (&footer as *const _) as u64; 288 | d.length = core::mem::size_of::() as u32; 289 | d.flags = VIRTQ_DESC_F_WRITE; 290 | d.next = 0; 291 | 292 | // Update ring to point to head of chain. Fence. Then update idx 293 | let avail_index = state.avail.idx; 294 | state.avail.ring[(avail_index % QUEUE_SIZE as u16) as usize] = state.next_head as u16; 295 | core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); 296 | 297 | state.avail.idx = state.avail.idx.wrapping_add(1); 298 | 299 | // Next free descriptor to use 300 | state.next_head = (next_desc + 1) % QUEUE_SIZE; 301 | 302 | // Notify queue has been updated 303 | self.transport.notify_queue(0); 304 | 305 | // Check for the completion of the request 306 | while unsafe { core::ptr::read_volatile(&state.used.idx) } != state.avail.idx { 307 | core::sync::atomic::fence(core::sync::atomic::Ordering::Acquire); 308 | } 309 | 310 | match footer.status { 311 | VIRTIO_BLK_S_OK => Ok(()), 312 | VIRTIO_BLK_S_IOERR => Err(Error::BlockIO), 313 | VIRTIO_BLK_S_UNSUPP => Err(Error::BlockNotSupported), 314 | _ => Err(Error::BlockNotSupported), 315 | } 316 | } 317 | } 318 | 319 | impl<'a> SectorRead for VirtioBlockDevice<'a> { 320 | fn read(&self, sector: u64, data: &mut [u8]) -> Result<(), Error> { 321 | self.request(sector, Some(data), RequestType::Read) 322 | } 323 | } 324 | 325 | impl<'a> SectorWrite for VirtioBlockDevice<'a> { 326 | fn write(&self, sector: u64, data: &mut [u8]) -> Result<(), Error> { 327 | if self.read_only { 328 | return Err(Error::BlockNotSupported); 329 | } 330 | self.request(sector, Some(data), RequestType::Write) 331 | } 332 | 333 | fn flush(&self) -> Result<(), Error> { 334 | if self.read_only { 335 | return Err(Error::BlockNotSupported); 336 | } 337 | self.request(0, None, RequestType::Flush) 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /src/boot.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use core::mem; 5 | 6 | use crate::{ 7 | bootinfo::{EntryType, Info, MemoryEntry}, 8 | fat::{Error, Read}, 9 | mem::MemoryRegion, 10 | }; 11 | 12 | #[derive(Clone, Copy, Debug)] 13 | #[repr(C, packed)] 14 | pub struct E820Entry { 15 | pub addr: u64, 16 | pub size: u64, 17 | pub entry_type: u32, 18 | } 19 | 20 | impl E820Entry { 21 | pub const RAM_TYPE: u32 = 1; 22 | pub const RESERVED_TYPE: u32 = 2; 23 | pub const ACPI_RECLAIMABLE_TYPE: u32 = 3; 24 | pub const ACPI_NVS_TYPE: u32 = 4; 25 | pub const BAD_TYPE: u32 = 5; 26 | pub const VENDOR_RESERVED_TYPE: u32 = 6; // coreboot only 27 | pub const COREBOOT_TABLE_TYPE: u32 = 16; // coreboot only 28 | } 29 | 30 | impl From for EntryType { 31 | fn from(value: u32) -> Self { 32 | match value { 33 | E820Entry::RAM_TYPE => Self::Ram, 34 | E820Entry::RESERVED_TYPE => Self::Reserved, 35 | E820Entry::ACPI_RECLAIMABLE_TYPE => Self::AcpiReclaimable, 36 | E820Entry::ACPI_NVS_TYPE => Self::AcpiNvs, 37 | E820Entry::BAD_TYPE => Self::Bad, 38 | E820Entry::VENDOR_RESERVED_TYPE => Self::VendorReserved, 39 | E820Entry::COREBOOT_TABLE_TYPE => Self::CorebootTable, 40 | _ => panic!("Unsupported e820 type"), 41 | } 42 | } 43 | } 44 | 45 | impl From for u32 { 46 | fn from(value: EntryType) -> Self { 47 | match value { 48 | EntryType::Ram => E820Entry::RAM_TYPE, 49 | EntryType::Reserved => E820Entry::RESERVED_TYPE, 50 | EntryType::AcpiReclaimable => E820Entry::ACPI_RECLAIMABLE_TYPE, 51 | EntryType::AcpiNvs => E820Entry::ACPI_NVS_TYPE, 52 | EntryType::Bad => E820Entry::BAD_TYPE, 53 | EntryType::VendorReserved => E820Entry::VENDOR_RESERVED_TYPE, 54 | EntryType::CorebootTable => E820Entry::COREBOOT_TABLE_TYPE, 55 | } 56 | } 57 | } 58 | 59 | impl From for E820Entry { 60 | fn from(value: MemoryEntry) -> Self { 61 | Self { 62 | addr: value.addr, 63 | size: value.size, 64 | entry_type: u32::from(value.entry_type), 65 | } 66 | } 67 | } 68 | 69 | impl From for MemoryEntry { 70 | fn from(value: E820Entry) -> Self { 71 | Self { 72 | addr: value.addr, 73 | size: value.size, 74 | entry_type: EntryType::from(value.entry_type), 75 | } 76 | } 77 | } 78 | 79 | // The so-called "zeropage" 80 | #[derive(Clone, Copy)] 81 | #[repr(C, packed)] 82 | pub struct Params { 83 | screen_info: ScreenInfo, // 0x000 84 | apm_bios_info: ApmBiosInfo, // 0x040 85 | _pad2: [u8; 4], // 0x054 86 | tboot_addr: u64, // 0x058 87 | ist_info: IstInfo, // 0x060 88 | pub acpi_rsdp_addr: u64, // 0x070 89 | _pad3: [u8; 8], // 0x078 90 | hd0_info: HdInfo, // 0x080 - obsolete 91 | hd1_info: HdInfo, // 0x090 - obsolete 92 | sys_desc_table: SysDescTable, // 0x0a0 - obsolete 93 | olpc_ofw_header: OlpcOfwHeader, // 0x0b0 94 | ext_ramdisk_image: u32, // 0x0c0 95 | ext_ramdisk_size: u32, // 0x0c4 96 | ext_cmd_line_ptr: u32, // 0x0c8 97 | _pad4: [u8; 0x74], // 0x0cc 98 | edd_info: EdidInfo, // 0x140 99 | efi_info: EfiInfo, // 0x1c0 100 | alt_mem_k: u32, // 0x1e0 101 | scratch: u32, // 0x1e4 102 | e820_entries: u8, // 0x1e8 103 | eddbuf_entries: u8, // 0x1e9 104 | edd_mbr_sig_buf_entries: u8, // 0x1ea 105 | kbd_status: u8, // 0x1eb 106 | secure_boot: u8, // 0x1ec 107 | _pad5: [u8; 2], // 0x1ed 108 | sentinel: u8, // 0x1ef 109 | _pad6: [u8; 1], // 0x1f0 110 | pub hdr: Header, // 0x1f1 111 | _pad7: [u8; 0x290 - HEADER_END], 112 | edd_mbr_sig_buffer: [u32; 16], // 0x290 113 | e820_table: [E820Entry; 128], // 0x2d0 114 | _pad8: [u8; 0x30], // 0xcd0 115 | eddbuf: [EddInfo; 6], // 0xd00 116 | _pad9: [u8; 0x114], // 0xeec 117 | } 118 | 119 | impl Default for Params { 120 | fn default() -> Self { 121 | // SAFETY: Struct consists entirely of primitive integral types. 122 | unsafe { mem::zeroed() } 123 | } 124 | } 125 | 126 | impl Params { 127 | pub fn set_entries(&mut self, info: &dyn Info) { 128 | self.e820_entries = info.num_entries() as u8; 129 | for i in 0..self.e820_entries { 130 | self.e820_table[i as usize] = info.entry(i as usize).into(); 131 | } 132 | } 133 | 134 | pub fn num_entries(&self) -> usize { 135 | self.e820_entries as usize 136 | } 137 | 138 | pub fn entry(&self, idx: usize) -> MemoryEntry { 139 | assert!(idx < self.num_entries()); 140 | let entry = self.e820_table[idx]; 141 | MemoryEntry::from(entry) 142 | } 143 | } 144 | 145 | const HEADER_START: usize = 0x1f1; 146 | const HEADER_END: usize = HEADER_START + mem::size_of::
(); 147 | 148 | #[derive(Clone, Copy, Debug)] 149 | #[repr(C, packed)] 150 | pub struct Header { 151 | pub setup_sects: u8, 152 | pub root_flags: u16, 153 | pub syssize: u32, 154 | pub ram_size: u16, 155 | pub vid_mode: u16, 156 | pub root_dev: u16, 157 | pub boot_flag: u16, 158 | pub jump: u16, 159 | pub header: [u8; 4], 160 | pub version: u16, 161 | pub realmode_swtch: u32, 162 | pub start_sys_seg: u16, 163 | pub kernel_version: u16, 164 | pub type_of_loader: u8, 165 | pub loadflags: u8, 166 | pub setup_move_size: u16, 167 | pub code32_start: u32, 168 | pub ramdisk_image: u32, 169 | pub ramdisk_size: u32, 170 | pub bootsect_kludge: u32, 171 | pub heap_end_ptr: u16, 172 | pub ext_loader_ver: u8, 173 | pub ext_loader_type: u8, 174 | pub cmd_line_ptr: u32, 175 | pub initrd_addr_max: u32, 176 | pub kernel_alignment: u32, 177 | pub relocatable_kernel: u8, 178 | pub min_alignment: u8, 179 | pub xloadflags: u16, 180 | pub cmdline_size: u32, 181 | pub hardware_subarch: u32, 182 | pub hardware_subarch_data: u64, 183 | pub payload_offset: u32, 184 | pub payload_length: u32, 185 | pub setup_data: u64, 186 | pub pref_address: u64, 187 | pub init_size: u32, 188 | pub handover_offset: u32, 189 | } 190 | 191 | impl Header { 192 | // Read a kernel header from the first two sectors of a file 193 | pub fn from_file(f: &mut dyn Read) -> Result { 194 | let data: [u8; 1024] = [0; 1024]; 195 | let mut region = MemoryRegion::from_bytes(&data); 196 | 197 | f.seek(0)?; 198 | f.load_file(&mut region)?; 199 | 200 | #[repr(C)] 201 | struct HeaderData { 202 | before: [u8; HEADER_START], 203 | hdr: Header, 204 | after: [u8; 1024 - HEADER_END], 205 | } 206 | // SAFETY: Struct consists entirely of primitive integral types. 207 | Ok(unsafe { mem::transmute::<[u8; 1024], HeaderData>(data) }.hdr) 208 | } 209 | } 210 | 211 | // Right now the stucts below are unused, so we only need them to be the correct 212 | // size. Update test_size_and_offset if a struct's real definition is added. 213 | #[derive(Clone, Copy)] 214 | #[repr(C, packed)] 215 | struct ScreenInfo([u8; 0x40]); 216 | #[derive(Clone, Copy)] 217 | #[repr(C, packed)] 218 | struct ApmBiosInfo([u8; 0x14]); 219 | #[derive(Clone, Copy)] 220 | #[repr(C, packed)] 221 | struct IstInfo([u8; 0x10]); 222 | #[derive(Clone, Copy)] 223 | #[repr(C, packed)] 224 | struct HdInfo([u8; 0x10]); 225 | #[derive(Clone, Copy)] 226 | #[repr(C, packed)] 227 | struct SysDescTable([u8; 0x10]); 228 | #[derive(Clone, Copy)] 229 | #[repr(C, packed)] 230 | struct OlpcOfwHeader([u8; 0x10]); 231 | #[derive(Clone, Copy)] 232 | #[repr(C, packed)] 233 | struct EdidInfo([u8; 0x80]); 234 | #[derive(Clone, Copy)] 235 | #[repr(C, packed)] 236 | struct EfiInfo([u8; 0x20]); 237 | #[derive(Clone, Copy)] 238 | #[repr(C, packed)] 239 | struct EddInfo([u8; 0x52]); 240 | 241 | #[cfg(test)] 242 | mod tests { 243 | use super::*; 244 | #[test] 245 | fn test_size_and_offset() { 246 | assert_eq!(mem::size_of::
(), 119); 247 | assert_eq!(mem::size_of::(), 20); 248 | assert_eq!(mem::size_of::(), 4096); 249 | 250 | assert_eq!(core::mem::offset_of!(Params, hdr), HEADER_START); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/bootinfo.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use crate::layout::MemoryDescriptor; 5 | 6 | // Common data needed for all boot paths 7 | pub trait Info { 8 | // Name of for this boot protocol 9 | fn name(&self) -> &str; 10 | // Starting address of the Root System Descriptor Pointer 11 | fn rsdp_addr(&self) -> Option { 12 | None 13 | } 14 | // Address/size of FDT used for booting 15 | fn fdt_reservation(&self) -> Option { 16 | None 17 | } 18 | // The kernel command line (not including null terminator) 19 | fn cmdline(&self) -> &[u8]; 20 | // Methods to access the Memory map 21 | fn num_entries(&self) -> usize; 22 | fn entry(&self, idx: usize) -> MemoryEntry; 23 | // Where to load kernel 24 | fn kernel_load_addr(&self) -> u64; 25 | // Reference to memory layout 26 | fn memory_layout(&self) -> &'static [MemoryDescriptor]; 27 | // MMIO address space that can be used for PCI BARs if needed 28 | fn pci_bar_memory(&self) -> Option { 29 | None 30 | } 31 | } 32 | 33 | #[derive(Clone, Copy)] 34 | pub struct MemoryEntry { 35 | pub addr: u64, 36 | pub size: u64, 37 | pub entry_type: EntryType, 38 | } 39 | 40 | #[derive(Clone, Copy, PartialEq)] 41 | pub enum EntryType { 42 | Ram, 43 | Reserved, 44 | AcpiReclaimable, 45 | AcpiNvs, 46 | Bad, 47 | VendorReserved, 48 | CorebootTable, 49 | } 50 | -------------------------------------------------------------------------------- /src/bzimage.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use atomic_refcell::AtomicRefCell; 5 | 6 | use crate::{ 7 | block::SectorBuf, 8 | boot::{Header, Params}, 9 | bootinfo::{EntryType, Info}, 10 | fat::{self, Read}, 11 | mem::MemoryRegion, 12 | }; 13 | 14 | #[derive(Debug)] 15 | pub enum Error { 16 | #[allow(dead_code)] 17 | File(fat::Error), 18 | NoInitrdMemory, 19 | MagicMissing, 20 | NotRelocatable, 21 | } 22 | 23 | impl From for Error { 24 | fn from(e: fat::Error) -> Error { 25 | Error::File(e) 26 | } 27 | } 28 | 29 | #[repr(transparent)] 30 | pub struct Kernel(Params); 31 | 32 | impl Kernel { 33 | pub fn new(info: &dyn Info) -> Self { 34 | let mut kernel = Self(Params::default()); 35 | kernel.0.acpi_rsdp_addr = info.rsdp_addr().unwrap_or_default(); 36 | kernel.0.set_entries(info); 37 | kernel 38 | } 39 | 40 | pub fn load_kernel(&mut self, info: &dyn Info, f: &mut dyn Read) -> Result<(), Error> { 41 | self.0.hdr = Header::from_file(f)?; 42 | 43 | if self.0.hdr.boot_flag != 0xAA55 || self.0.hdr.header != *b"HdrS" { 44 | return Err(Error::MagicMissing); 45 | } 46 | // Check relocatable 47 | if self.0.hdr.version < 0x205 || self.0.hdr.relocatable_kernel == 0 { 48 | return Err(Error::NotRelocatable); 49 | } 50 | 51 | // Skip over the setup sectors 52 | let setup_sects = match self.0.hdr.setup_sects { 53 | 0 => 4, 54 | n => n as u32, 55 | }; 56 | let setup_bytes = (setup_sects + 1) * SectorBuf::len() as u32; 57 | let remaining_bytes = f.get_size() - setup_bytes; 58 | 59 | let mut region = MemoryRegion::new(info.kernel_load_addr(), remaining_bytes as u64); 60 | f.seek(setup_bytes)?; 61 | f.load_file(&mut region)?; 62 | 63 | // Fill out "write/modify" fields 64 | self.0.hdr.type_of_loader = 0xff; // Unknown Loader 65 | self.0.hdr.code32_start = info.kernel_load_addr() as u32; // Where we load the kernel 66 | self.0.hdr.cmd_line_ptr = CMDLINE_START as u32; // Where we load the cmdline 67 | Ok(()) 68 | } 69 | 70 | // Compute the load address for the initial ramdisk 71 | fn initrd_addr(&self, size: u64) -> Option { 72 | let initrd_addr_max = match self.0.hdr.initrd_addr_max { 73 | 0 => 0x37FF_FFFF, 74 | a => a as u64, 75 | }; 76 | 77 | // Limit to 4GiB identity mapped area 78 | let initrd_addr_max = u64::min(initrd_addr_max, (4 << 30) - 1); 79 | 80 | let max_start = (initrd_addr_max + 1) - size; 81 | 82 | // Align address to 2MiB boundary as we use 2 MiB pages 83 | let max_start = max_start & !((2 << 20) - 1); 84 | 85 | let mut current_addr = None; 86 | for i in 0..self.0.num_entries() { 87 | let entry = self.0.entry(i); 88 | if entry.entry_type != EntryType::Ram { 89 | continue; 90 | } 91 | 92 | // Disregard regions beyond the max 93 | if entry.addr > max_start { 94 | continue; 95 | } 96 | 97 | // Disregard regions that are too small 98 | if size > entry.size { 99 | continue; 100 | } 101 | 102 | // Place at the top of the region 103 | let potential_addr = entry.addr + entry.size - size; 104 | 105 | // Align address to 2MiB boundary as we use 2 MiB pages 106 | let potential_addr = potential_addr & !((2 << 20) - 1); 107 | 108 | // But clamp to the maximum start 109 | let potential_addr = u64::min(potential_addr, max_start); 110 | 111 | // Use the higest address we can find 112 | if let Some(current_addr) = current_addr { 113 | if current_addr >= potential_addr { 114 | continue; 115 | } 116 | } 117 | current_addr = Some(potential_addr) 118 | } 119 | current_addr 120 | } 121 | 122 | pub fn load_initrd(&mut self, f: &mut dyn Read) -> Result<(), Error> { 123 | let size = f.get_size() as u64; 124 | let addr = match self.initrd_addr(size) { 125 | Some(addr) => addr, 126 | None => return Err(Error::NoInitrdMemory), 127 | }; 128 | 129 | let mut region = MemoryRegion::new(addr, size); 130 | f.seek(0)?; 131 | f.load_file(&mut region)?; 132 | 133 | // initrd pointer/size 134 | self.0.hdr.ramdisk_image = addr as u32; 135 | self.0.hdr.ramdisk_size = size as u32; 136 | Ok(()) 137 | } 138 | 139 | pub fn append_cmdline(&mut self, addition: &[u8]) { 140 | if !addition.is_empty() { 141 | CMDLINE.borrow_mut().append(addition); 142 | assert!(CMDLINE.borrow().len() < self.0.hdr.cmdline_size); 143 | } 144 | } 145 | 146 | pub fn boot(&mut self) { 147 | // 0x200 is the startup_64 offset 148 | let jump_address = self.0.hdr.code32_start as u64 + 0x200; 149 | // Rely on x86 C calling convention where second argument is put into %rsi register 150 | let ptr = jump_address as *const (); 151 | let code: extern "C" fn(usize, usize) = unsafe { core::mem::transmute(ptr) }; 152 | (code)(0 /* dummy value */, &mut self.0 as *mut _ as usize); 153 | } 154 | } 155 | 156 | // This is the highest region at which we can load the kernel command line. 157 | const CMDLINE_START: u64 = 0x4b000; 158 | const CMDLINE_MAX_LEN: u64 = 0x10000; 159 | 160 | static CMDLINE: AtomicRefCell = AtomicRefCell::new(CmdLine::new()); 161 | 162 | struct CmdLine { 163 | region: MemoryRegion, 164 | length: usize, // Does not include null pointer 165 | } 166 | 167 | impl CmdLine { 168 | const fn new() -> Self { 169 | Self { 170 | region: MemoryRegion::new(CMDLINE_START, CMDLINE_MAX_LEN), 171 | length: 0, 172 | } 173 | } 174 | 175 | const fn len(&self) -> u32 { 176 | self.length as u32 177 | } 178 | 179 | fn append(&mut self, args: &[u8]) { 180 | let bytes = self.region.as_bytes(); 181 | bytes[self.length] = b' '; 182 | self.length += 1; 183 | 184 | bytes[self.length..self.length + args.len()].copy_from_slice(args); 185 | self.length += args.len(); 186 | bytes[self.length] = 0; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/cmos.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2021 Akira Moroo 3 | 4 | use atomic_refcell::AtomicRefCell; 5 | use x86_64::instructions::port::{Port, PortWriteOnly}; 6 | 7 | static CMOS: AtomicRefCell = AtomicRefCell::new(Cmos::new()); 8 | 9 | struct Cmos { 10 | address_port: PortWriteOnly, 11 | data_port: Port, 12 | reg_b: Option, 13 | } 14 | 15 | impl Cmos { 16 | const fn new() -> Self { 17 | Self { 18 | address_port: PortWriteOnly::new(0x70), 19 | data_port: Port::new(0x71), 20 | reg_b: None, 21 | } 22 | } 23 | 24 | fn read_cmos(&mut self, addr: u8) -> u8 { 25 | assert!(addr < 128); 26 | unsafe { 27 | self.address_port.write(addr); 28 | self.data_port.read() 29 | } 30 | } 31 | 32 | fn get_update_status(&mut self) -> bool { 33 | self.read_cmos(0x0a) & 0x80 != 0 34 | } 35 | 36 | fn read(&mut self, offset: u8) -> Result { 37 | if crate::delay::wait_while(1, || self.get_update_status()) { 38 | return Err(()); 39 | } 40 | Ok(self.read_cmos(offset)) 41 | } 42 | 43 | fn get_reg_b(&mut self) -> u8 { 44 | if self.reg_b.is_none() { 45 | self.reg_b = Some(self.read_cmos(0x0b)); 46 | } 47 | self.reg_b.unwrap() 48 | } 49 | 50 | fn read_date(&mut self) -> Result<(u8, u8, u8), ()> { 51 | let mut year = self.read(0x09)?; 52 | let mut month = self.read(0x08)?; 53 | let mut day = self.read(0x07)?; 54 | 55 | if (self.get_reg_b() & 0x04) == 0 { 56 | year = bcd2dec(year); 57 | month = bcd2dec(month); 58 | day = bcd2dec(day); 59 | } 60 | 61 | Ok((year, month, day)) 62 | } 63 | 64 | fn read_time(&mut self) -> Result<(u8, u8, u8), ()> { 65 | let mut hour = self.read(0x04)?; 66 | let mut minute = self.read(0x02)?; 67 | let mut second = self.read(0x00)?; 68 | 69 | if (self.get_reg_b() & 0x04) == 0 { 70 | hour = bcd2dec(hour); 71 | minute = bcd2dec(minute); 72 | second = bcd2dec(second); 73 | } 74 | 75 | if ((self.get_reg_b() & 0x02) == 0) && ((hour & 0x80) != 0) { 76 | hour = ((hour & 0x7f) + 12) % 24; 77 | } 78 | 79 | Ok((hour, minute, second)) 80 | } 81 | } 82 | 83 | fn bcd2dec(b: u8) -> u8 { 84 | ((b >> 4) & 0x0f) * 10 + (b & 0x0f) 85 | } 86 | 87 | pub fn read_date() -> Result<(u8, u8, u8), ()> { 88 | CMOS.borrow_mut().read_date() 89 | } 90 | 91 | pub fn read_time() -> Result<(u8, u8, u8), ()> { 92 | CMOS.borrow_mut().read_time() 93 | } 94 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | #[macro_export] 5 | macro_rules! container_of { 6 | ($ptr:ident, $container:ty, $field:ident) => {{ 7 | (($ptr as usize) - core::mem::offset_of!($container, $field)) as *const $container 8 | }}; 9 | } 10 | 11 | #[macro_export] 12 | macro_rules! container_of_mut { 13 | ($ptr:ident, $container:ty, $field:ident) => {{ 14 | (($ptr as usize) - core::mem::offset_of!($container, $field)) as *mut $container 15 | }}; 16 | } 17 | 18 | // SAFETY: Requires that addr point to a static, null-terminated C-string. 19 | // The returned slice does not include the null-terminator. 20 | #[cfg(all(target_arch = "x86_64", not(feature = "coreboot")))] 21 | pub unsafe fn from_cstring(addr: u64) -> &'static [u8] { 22 | if addr == 0 { 23 | return &[]; 24 | } 25 | let start = addr as *const u8; 26 | let mut size: usize = 0; 27 | while start.add(size).read() != 0 { 28 | size += 1; 29 | } 30 | core::slice::from_raw_parts(start, size) 31 | } 32 | 33 | pub fn ascii_strip(s: &[u8]) -> &str { 34 | core::str::from_utf8(s).unwrap().trim_matches(char::from(0)) 35 | } 36 | 37 | pub fn ucs2_as_ascii_length(input: *const u16) -> usize { 38 | let mut len = 0; 39 | loop { 40 | let v = (unsafe { *(((input as u64) + (2 * len as u64)) as *const u16) } & 0xffu16) as u8; 41 | 42 | if v == 0 { 43 | break; 44 | } 45 | len += 1; 46 | } 47 | len 48 | } 49 | 50 | pub fn ascii_length(input: &str) -> usize { 51 | let mut len = 0; 52 | for c in input.chars() { 53 | if c == '\0' { 54 | break; 55 | } 56 | len += 1; 57 | } 58 | len 59 | } 60 | 61 | pub fn ucs2_to_ascii(input: *const u16, output: &mut [u8]) { 62 | let mut i: usize = 0; 63 | assert!(output.len() >= ucs2_as_ascii_length(input)); 64 | while i < output.len() { 65 | unsafe { 66 | output[i] = (*(((input as u64) + (2 * i as u64)) as *const u16) & 0xffu16) as u8; 67 | } 68 | if output[i] == 0 { 69 | break; 70 | } 71 | i += 1; 72 | } 73 | } 74 | 75 | pub fn ascii_to_ucs2(input: &str, output: &mut [u16]) { 76 | assert!(output.len() >= input.len() * 2); 77 | 78 | for (i, c) in input.bytes().enumerate() { 79 | output[i] = u16::from(c); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/coreboot.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (C) 2020 Akira Moroo 3 | // Copyright (C) 2009 coresystems GmbH 4 | // Copyright (C) 2008 Advanced Micro Devices, Inc. 5 | 6 | use core::mem::size_of; 7 | 8 | use crate::bootinfo::{EntryType, Info, MemoryEntry}; 9 | 10 | #[derive(Debug)] 11 | #[repr(C)] 12 | struct Header { 13 | signature: [u8; 4], 14 | header_bytes: u32, 15 | header_checksum: u32, 16 | table_bytes: u32, 17 | table_checksum: u32, 18 | table_entries: u32, 19 | } 20 | 21 | #[derive(Debug)] 22 | #[repr(C)] 23 | struct Record { 24 | tag: u32, 25 | size: u32, 26 | } 27 | 28 | impl Record { 29 | pub const TAG_FORWARD: u32 = 0x11; 30 | pub const TAG_MEMORY: u32 = 0x01; 31 | } 32 | 33 | #[derive(Debug)] 34 | #[repr(C)] 35 | struct Forward { 36 | tag: u32, 37 | size: u32, 38 | forward: u64, 39 | } 40 | 41 | #[derive(Clone, Copy)] 42 | #[repr(packed, C)] 43 | struct MemMapEntry { 44 | addr: u64, 45 | size: u64, 46 | entry_type: u32, 47 | } 48 | 49 | impl From for MemoryEntry { 50 | fn from(value: MemMapEntry) -> Self { 51 | Self { 52 | addr: value.addr, 53 | size: value.size, 54 | entry_type: EntryType::from(value.entry_type), 55 | } 56 | } 57 | } 58 | 59 | #[derive(Debug)] 60 | #[repr(C)] 61 | pub struct StartInfo { 62 | rsdp_addr: u64, 63 | memmap_addr: u64, 64 | memmap_entries: usize, 65 | } 66 | 67 | impl Default for StartInfo { 68 | fn default() -> Self { 69 | let (memmap_addr, memmap_entries) = match parse_info(0x0, 0x1000) { 70 | Some((addr, n_entries)) => (addr, n_entries), 71 | None => match parse_info(0xf0000, 0x1000) { 72 | Some((addr, n_entries)) => (addr, n_entries), 73 | None => panic!("coreboot table not found"), 74 | }, 75 | }; 76 | let ebda_addr = unsafe { *(0x40e as *const u16) }; 77 | let rsdp_addr = match find_rsdp(ebda_addr as u64, 0x400) { 78 | Some(addr) => addr, 79 | None => match find_rsdp(0xe0000, 0x20000) { 80 | Some(addr) => addr, 81 | None => panic!("RSDP table not found"), 82 | }, 83 | }; 84 | Self { 85 | rsdp_addr, 86 | memmap_addr, 87 | memmap_entries, 88 | } 89 | } 90 | } 91 | 92 | impl Info for StartInfo { 93 | fn name(&self) -> &str { 94 | "coreboot" 95 | } 96 | fn rsdp_addr(&self) -> Option { 97 | Some(self.rsdp_addr) 98 | } 99 | fn cmdline(&self) -> &[u8] { 100 | b"" 101 | } 102 | fn num_entries(&self) -> usize { 103 | if self.memmap_addr == 0 { 104 | return 0; 105 | } 106 | self.memmap_entries 107 | } 108 | fn entry(&self, idx: usize) -> MemoryEntry { 109 | assert!(idx < self.num_entries()); 110 | let ptr = self.memmap_addr as *const MemMapEntry; 111 | let entry = unsafe { &*ptr.add(idx) }; 112 | MemoryEntry::from(*entry) 113 | } 114 | fn kernel_load_addr(&self) -> u64 { 115 | crate::arch::x86_64::layout::KERNEL_START 116 | } 117 | fn memory_layout(&self) -> &'static [crate::layout::MemoryDescriptor] { 118 | &crate::arch::x86_64::layout::MEM_LAYOUT[..] 119 | } 120 | } 121 | 122 | fn find_header(start: u64, len: usize) -> Option { 123 | const CB_SIGNATURE: u32 = 0x4f49424c; 124 | for addr in (start..(start + len as u64)).step_by(16) { 125 | let val = unsafe { *(addr as *const u32) }; 126 | if val == CB_SIGNATURE { 127 | return Some(addr); 128 | } 129 | } 130 | None 131 | } 132 | 133 | fn find_rsdp(start: u64, len: usize) -> Option { 134 | const RSDP_SIGNATURE: u64 = 0x2052_5450_2044_5352; 135 | for addr in (start..(start + len as u64)).step_by(16) { 136 | let val = unsafe { *(addr as *const u64) }; 137 | if val == RSDP_SIGNATURE { 138 | return Some(addr); 139 | } 140 | } 141 | None 142 | } 143 | 144 | fn parse_info(start: u64, len: usize) -> Option<(u64, usize)> { 145 | let header_addr = match find_header(start, len) { 146 | Some(addr) => addr, 147 | None => { 148 | return None; 149 | } 150 | }; 151 | let header = unsafe { &*(header_addr as *const Header) }; 152 | let ptr = unsafe { (header_addr as *const Header).offset(1) }; 153 | let mut offset = 0; 154 | for _ in 0..header.table_entries { 155 | let rec_ptr = unsafe { (ptr as *const u8).offset(offset as isize) }; 156 | let record = unsafe { &(*(rec_ptr as *const Record)) }; 157 | match record.tag { 158 | Record::TAG_FORWARD => { 159 | let forward = unsafe { &*(rec_ptr as *const Forward) }; 160 | return parse_info(forward.forward, len); 161 | } 162 | Record::TAG_MEMORY => { 163 | return Some(parse_memmap(record)); 164 | } 165 | _ => {} 166 | } 167 | offset += record.size; 168 | } 169 | None 170 | } 171 | 172 | fn parse_memmap(record: &Record) -> (u64, usize) { 173 | assert_eq!(record.tag, Record::TAG_MEMORY); 174 | let n_entries = record.size as usize / size_of::(); 175 | let rec_size = size_of::() as isize; 176 | let rec_ptr = (record as *const Record) as *const u8; 177 | let mem_ptr = unsafe { rec_ptr.offset(rec_size) as *const MemMapEntry }; 178 | (mem_ptr as u64, n_entries) 179 | } 180 | -------------------------------------------------------------------------------- /src/delay.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSD-3-Clause 2 | // Copyright (C) 2021 Akira Moroo 3 | // Copyright (C) 2018 Google LLC 4 | 5 | use core::arch::asm; 6 | #[cfg(target_arch = "riscv64")] 7 | use core::arch::riscv64::pause; 8 | #[cfg(target_arch = "x86_64")] 9 | use core::arch::x86_64::_rdtsc; 10 | 11 | #[cfg(target_arch = "aarch64")] 12 | #[inline] 13 | unsafe fn rdtsc() -> u64 { 14 | let value: u64; 15 | asm!("mrs {}, cntvct_el0", out(reg) value); 16 | value 17 | } 18 | 19 | #[cfg(target_arch = "riscv64")] 20 | unsafe fn rdtsc() -> u64 { 21 | let r: u64; 22 | unsafe { asm!("csrr {rd}, time", rd = out(reg) r) }; 23 | r 24 | } 25 | 26 | #[cfg(target_arch = "x86_64")] 27 | #[inline] 28 | unsafe fn rdtsc() -> u64 { 29 | _rdtsc() 30 | } 31 | 32 | #[cfg(target_arch = "aarch64")] 33 | #[inline] 34 | unsafe fn pause() { 35 | asm!("yield"); 36 | } 37 | 38 | #[cfg(target_arch = "x86_64")] 39 | #[inline] 40 | unsafe fn pause() { 41 | asm!("pause"); 42 | } 43 | 44 | pub fn ndelay(ns: u64) { 45 | #[cfg(not(target_arch = "riscv64"))] 46 | const CPU_KHZ_DEFAULT: u64 = 200; 47 | #[cfg(target_arch = "riscv64")] 48 | const CPU_KHZ_DEFAULT: u64 = 1_000_000; /* QEMU currently defines as 1GHz */ 49 | const NSECS_PER_SEC: u64 = 1_000_000_000; 50 | const PAUSE_THRESHOLD_TICKS: u64 = 150; 51 | 52 | let delta = ns * CPU_KHZ_DEFAULT / NSECS_PER_SEC; 53 | let mut pause_delta = 0; 54 | unsafe { 55 | let start = rdtsc(); 56 | if delta > PAUSE_THRESHOLD_TICKS { 57 | pause_delta = delta - PAUSE_THRESHOLD_TICKS; 58 | } 59 | while rdtsc() - start < pause_delta { 60 | pause(); 61 | } 62 | while rdtsc() - start < delta {} 63 | } 64 | } 65 | 66 | pub fn udelay(us: u64) { 67 | for _i in 0..us as usize { 68 | ndelay(1000) 69 | } 70 | } 71 | 72 | #[allow(dead_code)] 73 | pub fn mdelay(ms: u64) { 74 | for _i in 0..ms as usize { 75 | udelay(1000) 76 | } 77 | } 78 | 79 | #[allow(dead_code)] 80 | pub fn wait_while(ms: u64, mut cond: F) -> bool 81 | where 82 | F: FnMut() -> bool, 83 | { 84 | let mut us = ms * 1000; 85 | while cond() && us > 0 { 86 | udelay(1); 87 | us -= 1; 88 | } 89 | cond() 90 | } 91 | 92 | #[allow(dead_code)] 93 | pub fn wait_until(ms: u64, mut cond: F) -> bool 94 | where 95 | F: FnMut() -> bool, 96 | { 97 | let mut us = ms * 1000; 98 | while !cond() && us > 0 { 99 | udelay(1); 100 | us -= 1; 101 | } 102 | cond() 103 | } 104 | -------------------------------------------------------------------------------- /src/efi/block.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use core::ffi::c_void; 5 | 6 | use r_efi::{ 7 | efi::{self, Status}, 8 | protocols::{ 9 | block_io::{Media, Protocol as BlockIoProtocol}, 10 | device_path::{HardDriveMedia, Protocol as DevicePathProtocol}, 11 | }, 12 | }; 13 | 14 | use crate::{ 15 | block::{SectorBuf, VirtioBlockDevice}, 16 | part::{get_partitions, PartitionEntry}, 17 | }; 18 | 19 | #[allow(dead_code)] 20 | #[repr(C, packed)] 21 | pub struct ControllerDevicePathProtocol { 22 | pub device_path: DevicePathProtocol, 23 | pub controller: u32, 24 | } 25 | 26 | #[repr(C)] 27 | pub struct BlockWrapper<'a> { 28 | hw: super::HandleWrapper, 29 | block: &'a VirtioBlockDevice<'a>, 30 | media: Media, 31 | pub proto: BlockIoProtocol, 32 | // The ordering of these paths are very important, along with the C 33 | // representation as the device path "flows" from the first. 34 | pub controller_path: ControllerDevicePathProtocol, 35 | pub disk_paths: [HardDriveMedia; 2], 36 | start_lba: u64, 37 | } 38 | 39 | pub struct BlockWrappers<'a> { 40 | pub wrappers: [*mut BlockWrapper<'a>; 16], 41 | pub count: usize, 42 | } 43 | 44 | pub extern "efiapi" fn reset(_: *mut BlockIoProtocol, _: efi::Boolean) -> Status { 45 | Status::UNSUPPORTED 46 | } 47 | 48 | pub extern "efiapi" fn read_blocks( 49 | proto: *mut BlockIoProtocol, 50 | _: u32, 51 | start: u64, 52 | size: usize, 53 | buffer: *mut c_void, 54 | ) -> Status { 55 | let wrapper = container_of!(proto, BlockWrapper, proto); 56 | let wrapper = unsafe { &*wrapper }; 57 | 58 | let block_size = wrapper.media.block_size as usize; 59 | let blocks = size / block_size; 60 | let mut region = crate::mem::MemoryRegion::new(buffer as u64, size as u64); 61 | 62 | for i in 0..blocks { 63 | use crate::block::SectorRead; 64 | let data = region.as_mut_slice((i * block_size) as u64, block_size as u64); 65 | let block = wrapper.block; 66 | match block.read(wrapper.start_lba + start + i as u64, data) { 67 | Ok(()) => continue, 68 | Err(_) => { 69 | return Status::DEVICE_ERROR; 70 | } 71 | }; 72 | } 73 | 74 | Status::SUCCESS 75 | } 76 | 77 | pub extern "efiapi" fn write_blocks( 78 | proto: *mut BlockIoProtocol, 79 | _: u32, 80 | start: u64, 81 | size: usize, 82 | buffer: *mut c_void, 83 | ) -> Status { 84 | let wrapper = container_of!(proto, BlockWrapper, proto); 85 | let wrapper = unsafe { &*wrapper }; 86 | 87 | let block_size = wrapper.media.block_size as usize; 88 | let blocks = size / block_size; 89 | let mut region = crate::mem::MemoryRegion::new(buffer as u64, size as u64); 90 | 91 | for i in 0..blocks { 92 | use crate::block::SectorWrite; 93 | let data = region.as_mut_slice((i * block_size) as u64, block_size as u64); 94 | let block = wrapper.block; 95 | match block.write(wrapper.start_lba + start + i as u64, data) { 96 | Ok(()) => continue, 97 | Err(_) => { 98 | return Status::DEVICE_ERROR; 99 | } 100 | }; 101 | } 102 | 103 | Status::SUCCESS 104 | } 105 | 106 | pub extern "efiapi" fn flush_blocks(proto: *mut BlockIoProtocol) -> Status { 107 | let wrapper = container_of!(proto, BlockWrapper, proto); 108 | let wrapper = unsafe { &*wrapper }; 109 | use crate::block::SectorWrite; 110 | let block = wrapper.block; 111 | match block.flush() { 112 | Ok(()) => Status::SUCCESS, 113 | Err(_) => Status::DEVICE_ERROR, 114 | } 115 | } 116 | 117 | impl<'a> BlockWrapper<'a> { 118 | pub fn new( 119 | block: &'a VirtioBlockDevice<'a>, 120 | partition_number: u32, 121 | start_lba: u64, 122 | last_lba: u64, 123 | uuid: [u8; 16], 124 | ) -> *mut BlockWrapper<'a> { 125 | let last_block = (*block).get_capacity() - 1; 126 | 127 | let (status, new_address) = super::ALLOCATOR 128 | .borrow_mut() 129 | .allocate_pool(efi::LOADER_DATA, core::mem::size_of::()); 130 | assert!(status == Status::SUCCESS); 131 | 132 | let bw = new_address as *mut BlockWrapper; 133 | 134 | unsafe { 135 | *bw = BlockWrapper { 136 | hw: super::HandleWrapper { 137 | handle_type: super::HandleType::Block, 138 | }, 139 | block, 140 | media: Media { 141 | media_id: 0, 142 | removable_media: false, 143 | media_present: true, 144 | logical_partition: false, 145 | read_only: true, 146 | write_caching: false, 147 | block_size: SectorBuf::len() as u32, 148 | io_align: 0, 149 | last_block, 150 | lowest_aligned_lba: 0, 151 | logical_blocks_per_physical_block: 1, 152 | optimal_transfer_length_granularity: 1, 153 | }, 154 | proto: BlockIoProtocol { 155 | revision: 0x0001_0000, // EFI_BLOCK_IO_PROTOCOL_REVISION 156 | media: core::ptr::null(), 157 | reset, 158 | read_blocks, 159 | write_blocks, 160 | flush_blocks, 161 | }, 162 | start_lba, 163 | controller_path: ControllerDevicePathProtocol { 164 | device_path: DevicePathProtocol { 165 | r#type: 1, 166 | sub_type: 5, 167 | length: [8, 0], 168 | }, 169 | controller: 0, 170 | }, 171 | // full disk vs partition 172 | disk_paths: if partition_number == 0 { 173 | [ 174 | HardDriveMedia { 175 | header: DevicePathProtocol { 176 | r#type: r_efi::protocols::device_path::TYPE_END, 177 | sub_type: 0xff, // End of full path 178 | length: [4, 0], 179 | }, 180 | partition_number: 0, 181 | partition_format: 0x0, 182 | partition_start: 0, 183 | partition_size: 0, 184 | partition_signature: [0; 16], 185 | signature_type: 0, 186 | }, 187 | HardDriveMedia { 188 | header: DevicePathProtocol { 189 | r#type: r_efi::protocols::device_path::TYPE_END, 190 | sub_type: 0xff, // End of full path 191 | length: [4, 0], 192 | }, 193 | partition_number: 0, 194 | partition_format: 0x0, 195 | partition_start: 0, 196 | partition_size: 0, 197 | partition_signature: [0; 16], 198 | signature_type: 0, 199 | }, 200 | ] 201 | } else { 202 | [ 203 | HardDriveMedia { 204 | header: DevicePathProtocol { 205 | r#type: r_efi::protocols::device_path::TYPE_MEDIA, 206 | sub_type: 1, 207 | length: [42, 0], 208 | }, 209 | partition_number, 210 | partition_format: 0x02, // GPT 211 | partition_start: start_lba, 212 | partition_size: last_lba - start_lba + 1, 213 | partition_signature: uuid, 214 | signature_type: 0x02, 215 | }, 216 | HardDriveMedia { 217 | header: DevicePathProtocol { 218 | r#type: r_efi::protocols::device_path::TYPE_END, 219 | sub_type: 0xff, // End of full path 220 | length: [4, 0], 221 | }, 222 | partition_number: 0, 223 | partition_format: 0x0, 224 | partition_start: 0, 225 | partition_size: 0, 226 | partition_signature: [0; 16], 227 | signature_type: 0, 228 | }, 229 | ] 230 | }, 231 | }; 232 | 233 | (*bw).proto.media = &(*bw).media; 234 | } 235 | bw 236 | } 237 | } 238 | 239 | pub fn populate_block_wrappers( 240 | wrappers: &mut BlockWrappers, 241 | block: *const VirtioBlockDevice, 242 | ) -> Option { 243 | let mut parts = [PartitionEntry::default(); 16]; 244 | 245 | wrappers.wrappers[0] = BlockWrapper::new( 246 | unsafe { &*block.cast::>() }, 247 | 0, 248 | 0, 249 | 0, 250 | [0; 16], 251 | ); 252 | 253 | let mut efi_part_id = None; 254 | let part_count = get_partitions(unsafe { &*block }, &mut parts).unwrap(); 255 | for i in 0..part_count { 256 | let p = parts[i as usize]; 257 | wrappers.wrappers[i as usize + 1] = BlockWrapper::new( 258 | unsafe { &*block.cast::>() }, 259 | i + 1, 260 | p.first_lba, 261 | p.last_lba, 262 | p.guid, 263 | ); 264 | if p.is_efi_partition() { 265 | efi_part_id = Some(i + 1); 266 | } 267 | } 268 | wrappers.count = part_count as usize + 1; 269 | efi_part_id 270 | } 271 | -------------------------------------------------------------------------------- /src/efi/console.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use r_efi::{ 5 | efi::{Boolean, Char16, Event, Handle, Status}, 6 | protocols::{ 7 | simple_text_input::{InputKey, Protocol as SimpleTextInputProtocol}, 8 | simple_text_output::{Mode as SimpleTextOutputMode, Protocol as SimpleTextOutputProtocol}, 9 | }, 10 | }; 11 | 12 | use super::{HandleType, HandleWrapper}; 13 | 14 | pub const STDIN_HANDLE: Handle = &HandleWrapper { 15 | handle_type: HandleType::None, 16 | } as *const _ as Handle; 17 | pub const STDOUT_HANDLE: Handle = &HandleWrapper { 18 | handle_type: HandleType::None, 19 | } as *const _ as Handle; 20 | pub const STDERR_HANDLE: Handle = &HandleWrapper { 21 | handle_type: HandleType::None, 22 | } as *const _ as Handle; 23 | 24 | pub extern "efiapi" fn stdin_reset(_: *mut SimpleTextInputProtocol, _: Boolean) -> Status { 25 | Status::UNSUPPORTED 26 | } 27 | 28 | pub extern "efiapi" fn stdin_read_key_stroke( 29 | _: *mut SimpleTextInputProtocol, 30 | _: *mut InputKey, 31 | ) -> Status { 32 | Status::NOT_READY 33 | } 34 | 35 | pub extern "efiapi" fn stdout_reset(_: *mut SimpleTextOutputProtocol, _: Boolean) -> Status { 36 | Status::SUCCESS 37 | } 38 | 39 | pub extern "efiapi" fn stdout_output_string( 40 | _: *mut SimpleTextOutputProtocol, 41 | message: *mut Char16, 42 | ) -> Status { 43 | use core::fmt::Write; 44 | let mut serial = crate::serial::Serial; 45 | let mut string_end = false; 46 | 47 | loop { 48 | let mut output: [u8; 128] = [0; 128]; 49 | let mut i: usize = 0; 50 | while i < output.len() { 51 | output[i] = (unsafe { *message.add(i) } & 0xffu16) as u8; 52 | if output[i] == 0 { 53 | string_end = true; 54 | break; 55 | } 56 | i += 1; 57 | } 58 | let s = unsafe { core::str::from_utf8_unchecked(&output) }; 59 | serial.write_str(s).unwrap(); 60 | if string_end { 61 | break; 62 | } 63 | } 64 | Status::SUCCESS 65 | } 66 | 67 | pub extern "efiapi" fn stdout_test_string( 68 | _: *mut SimpleTextOutputProtocol, 69 | _: *mut Char16, 70 | ) -> Status { 71 | Status::SUCCESS 72 | } 73 | 74 | pub extern "efiapi" fn stdout_query_mode( 75 | _: *mut SimpleTextOutputProtocol, 76 | mode: usize, 77 | columns: *mut usize, 78 | rows: *mut usize, 79 | ) -> Status { 80 | if mode == 0 { 81 | unsafe { 82 | *columns = 80; 83 | *rows = 25; 84 | } 85 | Status::SUCCESS 86 | } else { 87 | Status::UNSUPPORTED 88 | } 89 | } 90 | 91 | pub extern "efiapi" fn stdout_set_mode(_: *mut SimpleTextOutputProtocol, mode: usize) -> Status { 92 | if mode == 0 { 93 | Status::SUCCESS 94 | } else { 95 | Status::UNSUPPORTED 96 | } 97 | } 98 | 99 | pub extern "efiapi" fn stdout_set_attribute(_: *mut SimpleTextOutputProtocol, _: usize) -> Status { 100 | // Accept all attribute changes but ignore them 101 | Status::SUCCESS 102 | } 103 | 104 | pub extern "efiapi" fn stdout_clear_screen(_: *mut SimpleTextOutputProtocol) -> Status { 105 | Status::UNSUPPORTED 106 | } 107 | 108 | pub extern "efiapi" fn stdout_set_cursor_position( 109 | _: *mut SimpleTextOutputProtocol, 110 | _: usize, 111 | _: usize, 112 | ) -> Status { 113 | Status::UNSUPPORTED 114 | } 115 | 116 | pub extern "efiapi" fn stdout_enable_cursor( 117 | _: *mut SimpleTextOutputProtocol, 118 | _: Boolean, 119 | ) -> Status { 120 | Status::UNSUPPORTED 121 | } 122 | 123 | pub const STDIN: SimpleTextInputProtocol = SimpleTextInputProtocol { 124 | reset: stdin_reset, 125 | read_key_stroke: stdin_read_key_stroke, 126 | wait_for_key: 0 as Event, 127 | }; 128 | 129 | pub const STDOUT_OUTPUT_MODE: SimpleTextOutputMode = SimpleTextOutputMode { 130 | max_mode: 1, 131 | mode: 0, 132 | attribute: 0, 133 | cursor_column: 0, 134 | cursor_row: 0, 135 | cursor_visible: Boolean::FALSE, 136 | }; 137 | 138 | pub const STDOUT: SimpleTextOutputProtocol = SimpleTextOutputProtocol { 139 | reset: stdout_reset, 140 | output_string: stdout_output_string, 141 | test_string: stdout_test_string, 142 | query_mode: stdout_query_mode, 143 | set_mode: stdout_set_mode, 144 | set_attribute: stdout_set_attribute, 145 | clear_screen: stdout_clear_screen, 146 | set_cursor_position: stdout_set_cursor_position, 147 | enable_cursor: stdout_enable_cursor, 148 | mode: &STDOUT_OUTPUT_MODE as *const SimpleTextOutputMode as *mut SimpleTextOutputMode, 149 | }; 150 | -------------------------------------------------------------------------------- /src/efi/device_path.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Rivos Inc. 3 | 4 | use core::{ 5 | ffi::c_void, 6 | mem::{offset_of, size_of}, 7 | ptr::null_mut, 8 | }; 9 | 10 | use log::error; 11 | use r_efi::{ 12 | efi::{self, MemoryType, Status}, 13 | protocols::device_path::Protocol as DevicePathProtocol, 14 | }; 15 | 16 | #[allow(clippy::large_enum_variant)] 17 | pub enum DevicePath { 18 | File([u8; 256]), 19 | Memory(MemoryType, u64, u64), 20 | Unsupported, 21 | } 22 | 23 | impl DevicePath { 24 | pub fn parse(dpp: &DevicePathProtocol) -> DevicePath { 25 | let mut dpp = dpp; 26 | loop { 27 | if dpp.r#type == r_efi::protocols::device_path::TYPE_MEDIA && dpp.sub_type == 0x04 { 28 | let ptr = (dpp as *const _ as usize + offset_of!(FileDevicePathProtocol, filename)) 29 | as *const u16; 30 | let mut path = [0u8; 256]; 31 | crate::common::ucs2_to_ascii(ptr, &mut path); 32 | return DevicePath::File(path); 33 | } 34 | if dpp.r#type == r_efi::protocols::device_path::TYPE_HARDWARE 35 | && dpp.sub_type == r_efi::protocols::device_path::Hardware::SUBTYPE_MMAP 36 | { 37 | let memory_type_ptr = (dpp as *const _ as usize 38 | + offset_of!(MemoryDevicePathProtocol, memory_type)) 39 | as *const MemoryType; 40 | let start_ptr = (dpp as *const _ as usize 41 | + offset_of!(MemoryDevicePathProtocol, start)) 42 | as *const u64; 43 | let end_ptr = (dpp as *const _ as usize + offset_of!(MemoryDevicePathProtocol, end)) 44 | as *const u64; 45 | return DevicePath::Memory( 46 | unsafe { *memory_type_ptr }, 47 | unsafe { *start_ptr }, 48 | unsafe { *end_ptr }, 49 | ); 50 | } 51 | 52 | if dpp.r#type == r_efi::protocols::device_path::TYPE_END && dpp.sub_type == 0xff { 53 | error!("Unexpected end of device path"); 54 | return DevicePath::Unsupported; 55 | } 56 | let len = u16::from_ne_bytes(dpp.length); 57 | dpp = unsafe { &*((dpp as *const _ as u64 + len as u64) as *const _) }; 58 | } 59 | } 60 | 61 | pub fn generate(&self) -> *mut r_efi::protocols::device_path::Protocol { 62 | match self { 63 | Self::File(path) => file_device_path(crate::common::ascii_strip(path)), 64 | Self::Memory(memory_type, start, end) => memory_device_path(*memory_type, *start, *end), 65 | Self::Unsupported => panic!("Cannot generate from unsupported Device Path type"), 66 | } 67 | } 68 | } 69 | 70 | #[repr(C)] 71 | struct FileDevicePathProtocol { 72 | pub device_path: DevicePathProtocol, 73 | pub filename: [u16; 256], 74 | } 75 | 76 | type FileDevicePaths = [FileDevicePathProtocol; 2]; 77 | 78 | fn file_device_path(path: &str) -> *mut r_efi::protocols::device_path::Protocol { 79 | let mut file_paths = null_mut(); 80 | let status = crate::efi::boot_services::allocate_pool( 81 | efi::LOADER_DATA, 82 | size_of::(), 83 | &mut file_paths as *mut *mut c_void, 84 | ); 85 | assert!(status == Status::SUCCESS); 86 | let file_paths = unsafe { &mut *(file_paths as *mut FileDevicePaths) }; 87 | *file_paths = [ 88 | FileDevicePathProtocol { 89 | device_path: DevicePathProtocol { 90 | r#type: r_efi::protocols::device_path::TYPE_MEDIA, 91 | sub_type: 4, // Media Path type file 92 | length: (size_of::() as u16).to_le_bytes(), 93 | }, 94 | filename: [0; 256], 95 | }, 96 | FileDevicePathProtocol { 97 | device_path: DevicePathProtocol { 98 | r#type: r_efi::protocols::device_path::TYPE_END, 99 | sub_type: r_efi::protocols::device_path::End::SUBTYPE_ENTIRE, 100 | length: (size_of::() as u16).to_le_bytes(), 101 | }, 102 | filename: [0; 256], 103 | }, 104 | ]; 105 | 106 | crate::common::ascii_to_ucs2(path, &mut file_paths[0].filename); 107 | 108 | &mut file_paths[0].device_path // Pointer to first path entry 109 | } 110 | 111 | #[repr(C)] 112 | struct MemoryDevicePathProtocol { 113 | pub device_path: DevicePathProtocol, 114 | pub memory_type: u32, 115 | pub start: u64, 116 | pub end: u64, 117 | } 118 | 119 | type MemoryDevicePaths = [MemoryDevicePathProtocol; 2]; 120 | 121 | fn memory_device_path( 122 | memory_type: MemoryType, 123 | start: u64, 124 | end: u64, 125 | ) -> *mut r_efi::protocols::device_path::Protocol { 126 | let mut memory_paths = null_mut(); 127 | let status = crate::efi::boot_services::allocate_pool( 128 | efi::LOADER_DATA, 129 | size_of::(), 130 | &mut memory_paths as *mut *mut c_void, 131 | ); 132 | assert!(status == Status::SUCCESS); 133 | let memory_paths = unsafe { &mut *(memory_paths as *mut MemoryDevicePaths) }; 134 | *memory_paths = [ 135 | MemoryDevicePathProtocol { 136 | device_path: DevicePathProtocol { 137 | r#type: r_efi::protocols::device_path::TYPE_HARDWARE, 138 | sub_type: r_efi::protocols::device_path::Hardware::SUBTYPE_MMAP, 139 | length: (size_of::() as u16).to_le_bytes(), 140 | }, 141 | memory_type, 142 | start, 143 | end, 144 | }, 145 | MemoryDevicePathProtocol { 146 | device_path: DevicePathProtocol { 147 | r#type: r_efi::protocols::device_path::TYPE_END, 148 | sub_type: r_efi::protocols::device_path::End::SUBTYPE_ENTIRE, 149 | length: (size_of::() as u16).to_le_bytes(), 150 | }, 151 | memory_type: 0, 152 | start: 0, 153 | end: 0, 154 | }, 155 | ]; 156 | 157 | &mut memory_paths[0].device_path // Pointer to first path entry 158 | } 159 | -------------------------------------------------------------------------------- /src/efi/file.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use core::ffi::c_void; 5 | 6 | use log::error; 7 | use r_efi::{ 8 | efi::{self, Char16, Guid, Status}, 9 | protocols::{ 10 | file::Protocol as FileProtocol, simple_file_system::Protocol as SimpleFileSystemProtocol, 11 | }, 12 | }; 13 | 14 | use crate::block::SectorBuf; 15 | 16 | pub extern "efiapi" fn filesystem_open_volume( 17 | fs_proto: *mut SimpleFileSystemProtocol, 18 | file: *mut *mut FileProtocol, 19 | ) -> Status { 20 | let wrapper = container_of!(fs_proto, FileSystemWrapper, proto); 21 | let wrapper = unsafe { &*wrapper }; 22 | let root = wrapper.fs.root().unwrap(); 23 | 24 | if let Some(fw) = wrapper.create_file(root.into()) { 25 | unsafe { 26 | *file = &mut (*fw).proto; 27 | } 28 | Status::SUCCESS 29 | } else { 30 | Status::DEVICE_ERROR 31 | } 32 | } 33 | 34 | pub extern "efiapi" fn open( 35 | file_in: *mut FileProtocol, 36 | file_out: *mut *mut FileProtocol, 37 | path_in: *mut Char16, 38 | _: u64, 39 | _: u64, 40 | ) -> Status { 41 | let wrapper = container_of!(file_in, FileWrapper, proto); 42 | let wrapper = unsafe { &*wrapper }; 43 | 44 | let mut path = [0; 256]; 45 | crate::common::ucs2_to_ascii(path_in, &mut path[0..255]); 46 | let path = unsafe { core::str::from_utf8_unchecked(&path) }; 47 | 48 | let root = wrapper.fs.root().unwrap(); 49 | let dir = if crate::fat::is_absolute_path(path) { 50 | &root 51 | } else { 52 | match &wrapper.node { 53 | crate::fat::Node::Directory(d) => d, 54 | _ => { 55 | error!("Attempt to open from non-directory is unsupported"); 56 | return Status::UNSUPPORTED; 57 | } 58 | } 59 | }; 60 | 61 | match dir.open(path) { 62 | Ok(f) => { 63 | let fs_wrapper = unsafe { &(*wrapper.fs_wrapper) }; 64 | if let Some(file_out_wrapper) = fs_wrapper.create_file(f) { 65 | unsafe { 66 | *file_out = &mut (*file_out_wrapper).proto; 67 | } 68 | Status::SUCCESS 69 | } else { 70 | Status::DEVICE_ERROR 71 | } 72 | } 73 | Err(crate::fat::Error::NotFound) => Status::NOT_FOUND, 74 | Err(_) => Status::DEVICE_ERROR, 75 | } 76 | } 77 | 78 | pub extern "efiapi" fn close(proto: *mut FileProtocol) -> Status { 79 | let wrapper = container_of!(proto, FileWrapper, proto); 80 | super::ALLOCATOR 81 | .borrow_mut() 82 | .free_pages(&wrapper as *const _ as u64) 83 | } 84 | 85 | pub extern "efiapi" fn delete(_: *mut FileProtocol) -> Status { 86 | Status::UNSUPPORTED 87 | } 88 | 89 | pub extern "efiapi" fn read(file: *mut FileProtocol, size: *mut usize, buf: *mut c_void) -> Status { 90 | use crate::fat::Read; 91 | let wrapper = container_of_mut!(file, FileWrapper, proto); 92 | if let crate::fat::Node::Directory(d) = unsafe { &mut (*wrapper).node } { 93 | match d.has_next() { 94 | Ok(has_next) => { 95 | if has_next && unsafe { *size } < core::mem::size_of::() { 96 | unsafe { *size = core::mem::size_of::() }; 97 | return Status::BUFFER_TOO_SMALL; 98 | } 99 | } 100 | Err(_) => return Status::DEVICE_ERROR, 101 | }; 102 | 103 | let (node, name) = match d.next_node() { 104 | Ok(node) => node, 105 | Err(crate::fat::Error::EndOfFile) => { 106 | unsafe { *size = 0 }; 107 | return Status::SUCCESS; 108 | } 109 | Err(_) => return Status::DEVICE_ERROR, 110 | }; 111 | 112 | let attribute = match &node { 113 | crate::fat::Node::Directory(_) => r_efi::protocols::file::DIRECTORY, 114 | crate::fat::Node::File(_) => r_efi::protocols::file::ARCHIVE, 115 | }; 116 | 117 | let info = buf as *mut FileInfo; 118 | 119 | let name = crate::common::ascii_strip(&name); 120 | unsafe { 121 | (*info).size = core::mem::size_of::() as u64; 122 | (*info).file_size = node.get_size().into(); 123 | (*info).physical_size = node.get_size().into(); 124 | (*info).attribute = attribute; 125 | crate::common::ascii_to_ucs2(name, &mut (*info).file_name); 126 | } 127 | 128 | return Status::SUCCESS; 129 | } 130 | 131 | if unsafe { *size } < unsafe { (*wrapper).node.get_size() as usize } { 132 | unsafe { *size = (*wrapper).node.get_size() as usize }; 133 | return Status::BUFFER_TOO_SMALL; 134 | } 135 | 136 | let mut current_offset = 0; 137 | let mut bytes_remaining = unsafe { *size }; 138 | 139 | loop { 140 | let buf = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, *size) }; 141 | 142 | let mut data = SectorBuf::new(); 143 | unsafe { 144 | match (*wrapper).node.read(data.as_mut_bytes()) { 145 | Ok(bytes_read) => { 146 | buf[current_offset..current_offset + bytes_read as usize] 147 | .copy_from_slice(&data.as_bytes()[0..bytes_read as usize]); 148 | current_offset += bytes_read as usize; 149 | 150 | if bytes_remaining <= bytes_read as usize { 151 | *size = current_offset; 152 | return Status::SUCCESS; 153 | } 154 | bytes_remaining -= bytes_read as usize; 155 | } 156 | Err(_) => { 157 | return Status::DEVICE_ERROR; 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | pub extern "efiapi" fn write(_: *mut FileProtocol, _: *mut usize, _: *mut c_void) -> Status { 165 | Status::UNSUPPORTED 166 | } 167 | 168 | pub extern "efiapi" fn get_position(_: *mut FileProtocol, _: *mut u64) -> Status { 169 | Status::UNSUPPORTED 170 | } 171 | 172 | pub extern "efiapi" fn set_position(file: *mut FileProtocol, position: u64) -> Status { 173 | // Seeking to end of file is not supported 174 | if position == 0xFFFFFFFFFFFFFFFF { 175 | return Status::UNSUPPORTED; 176 | } 177 | use crate::fat::Read; 178 | let wrapper = container_of_mut!(file, FileWrapper, proto); 179 | match unsafe { (*wrapper).node.seek(position as u32) } { 180 | Err(crate::fat::Error::Unsupported) => Status::UNSUPPORTED, 181 | Err(_) => Status::DEVICE_ERROR, 182 | Ok(()) => Status::SUCCESS, 183 | } 184 | } 185 | 186 | #[repr(C)] 187 | struct FileInfo { 188 | size: u64, 189 | file_size: u64, 190 | physical_size: u64, 191 | _create_time: r_efi::system::Time, 192 | _last_access_time: r_efi::system::Time, 193 | _modification_time: r_efi::system::Time, 194 | attribute: u64, 195 | file_name: [Char16; 256], 196 | } 197 | 198 | pub extern "efiapi" fn get_info( 199 | file: *mut FileProtocol, 200 | guid: *mut Guid, 201 | info_size: *mut usize, 202 | info: *mut c_void, 203 | ) -> Status { 204 | if unsafe { *guid } == r_efi::protocols::file::INFO_ID { 205 | if unsafe { *info_size } < core::mem::size_of::() { 206 | unsafe { *info_size = core::mem::size_of::() }; 207 | Status::BUFFER_TOO_SMALL 208 | } else { 209 | let info = info as *mut FileInfo; 210 | 211 | let wrapper = container_of!(file, FileWrapper, proto); 212 | let attribute = match unsafe { &(*wrapper).node } { 213 | crate::fat::Node::Directory(_) => r_efi::protocols::file::DIRECTORY, 214 | crate::fat::Node::File(_) => r_efi::protocols::file::ARCHIVE, 215 | }; 216 | use crate::fat::Read; 217 | unsafe { 218 | (*info).size = core::mem::size_of::() as u64; 219 | (*info).file_size = (*wrapper).node.get_size().into(); 220 | (*info).physical_size = (*wrapper).node.get_size().into(); 221 | (*info).attribute = attribute; 222 | } 223 | 224 | Status::SUCCESS 225 | } 226 | } else { 227 | Status::UNSUPPORTED 228 | } 229 | } 230 | 231 | pub extern "efiapi" fn set_info( 232 | _: *mut FileProtocol, 233 | _: *mut Guid, 234 | _: usize, 235 | _: *mut c_void, 236 | ) -> Status { 237 | Status::UNSUPPORTED 238 | } 239 | 240 | pub extern "efiapi" fn flush(_: *mut FileProtocol) -> Status { 241 | Status::UNSUPPORTED 242 | } 243 | 244 | struct FileWrapper<'a> { 245 | fs: &'a crate::fat::Filesystem<'a>, 246 | proto: FileProtocol, 247 | node: crate::fat::Node<'a>, 248 | fs_wrapper: *const FileSystemWrapper<'a>, 249 | } 250 | 251 | #[repr(C)] 252 | pub struct FileSystemWrapper<'a> { 253 | hw: super::HandleWrapper, 254 | pub fs: &'a crate::fat::Filesystem<'a>, 255 | pub proto: SimpleFileSystemProtocol, 256 | pub block_part_id: Option, 257 | } 258 | 259 | impl<'a> FileSystemWrapper<'a> { 260 | fn create_file(&self, node: crate::fat::Node<'a>) -> Option<*mut FileWrapper> { 261 | let (status, new_address) = super::ALLOCATOR 262 | .borrow_mut() 263 | .allocate_pool(efi::LOADER_DATA, core::mem::size_of::()); 264 | assert!(status == Status::SUCCESS); 265 | 266 | if status == Status::SUCCESS { 267 | let fw = new_address as *mut FileWrapper; 268 | unsafe { 269 | (*fw).fs = self.fs; 270 | (*fw).fs_wrapper = self; 271 | (*fw).node = node; 272 | (*fw).proto.revision = r_efi::protocols::file::REVISION; 273 | (*fw).proto.open = open; 274 | (*fw).proto.close = close; 275 | (*fw).proto.delete = delete; 276 | (*fw).proto.read = read; 277 | (*fw).proto.write = write; 278 | (*fw).proto.get_position = get_position; 279 | (*fw).proto.set_position = set_position; 280 | (*fw).proto.get_info = get_info; 281 | (*fw).proto.set_info = set_info; 282 | (*fw).proto.flush = flush; 283 | } 284 | 285 | Some(fw) 286 | } else { 287 | None 288 | } 289 | } 290 | 291 | pub fn new( 292 | fs: &'a crate::fat::Filesystem, 293 | block_part_id: Option, 294 | ) -> FileSystemWrapper<'a> { 295 | FileSystemWrapper { 296 | hw: super::HandleWrapper { 297 | handle_type: super::HandleType::FileSystem, 298 | }, 299 | fs, 300 | proto: SimpleFileSystemProtocol { 301 | revision: r_efi::protocols::simple_file_system::REVISION, 302 | open_volume: filesystem_open_volume, 303 | }, 304 | block_part_id, 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/efi/mem_file.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Rivos Inc. 3 | 4 | use core::slice::from_raw_parts; 5 | 6 | use crate::{block::SectorBuf, fat}; 7 | 8 | pub struct MemoryFile { 9 | address: u64, 10 | size: u32, 11 | position: u32, 12 | } 13 | 14 | impl MemoryFile { 15 | pub fn new(address: u64, size: u32) -> Self { 16 | MemoryFile { 17 | address, 18 | size, 19 | position: 0, 20 | } 21 | } 22 | } 23 | 24 | impl fat::Read for MemoryFile { 25 | fn get_size(&self) -> u32 { 26 | self.size 27 | } 28 | 29 | fn read(&mut self, data: &mut [u8]) -> Result { 30 | let sector_size = SectorBuf::len() as u32; 31 | assert_eq!(data.len(), SectorBuf::len()); 32 | 33 | if (self.position + sector_size) > self.size { 34 | let bytes_read = self.size - self.position; 35 | let memory = unsafe { 36 | from_raw_parts( 37 | (self.address + self.position as u64) as *const u8, 38 | bytes_read as usize, 39 | ) 40 | }; 41 | data[0..bytes_read as usize].copy_from_slice(memory); 42 | self.position = self.size; 43 | Ok(bytes_read) 44 | } else { 45 | let memory = unsafe { 46 | from_raw_parts( 47 | (self.address + self.position as u64) as *const u8, 48 | sector_size as usize, 49 | ) 50 | }; 51 | data[0..sector_size as usize].copy_from_slice(memory); 52 | self.position += sector_size; 53 | Ok(sector_size) 54 | } 55 | } 56 | 57 | fn seek(&mut self, position: u32) -> Result<(), fat::Error> { 58 | let sector_size = SectorBuf::len() as u32; 59 | if position % sector_size != 0 { 60 | return Err(fat::Error::InvalidOffset); 61 | } 62 | 63 | if position >= self.size { 64 | return Err(fat::Error::EndOfFile); 65 | } 66 | 67 | self.position = position; 68 | 69 | Ok(()) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/efi/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use core::{cell::SyncUnsafeCell, ffi::c_void, mem::size_of, ptr::null_mut}; 5 | 6 | use atomic_refcell::AtomicRefCell; 7 | use r_efi::{ 8 | efi::{self, Guid, Handle, Status}, 9 | protocols::loaded_image::Protocol as LoadedImageProtocol, 10 | }; 11 | 12 | use crate::{bootinfo, layout}; 13 | 14 | mod alloc; 15 | mod block; 16 | mod boot_services; 17 | mod console; 18 | mod device_path; 19 | mod file; 20 | mod mem_file; 21 | mod runtime_services; 22 | mod var; 23 | 24 | use alloc::Allocator; 25 | use boot_services::{BS, CT}; 26 | use device_path::DevicePath; 27 | use runtime_services::RS; 28 | use var::VariableAllocator; 29 | 30 | #[cfg(target_arch = "aarch64")] 31 | pub const EFI_BOOT_PATH: &str = "\\EFI\\BOOT\\BOOTAA64.EFI"; 32 | #[cfg(target_arch = "x86_64")] 33 | pub const EFI_BOOT_PATH: &str = "\\EFI\\BOOT\\BOOTX64.EFI"; 34 | #[cfg(target_arch = "riscv64")] 35 | pub const EFI_BOOT_PATH: &str = "\\EFI\\BOOT\\BOOTRISCV64.EFI"; 36 | 37 | #[derive(Copy, Clone, PartialEq, Eq)] 38 | enum HandleType { 39 | None, 40 | Block, 41 | FileSystem, 42 | LoadedImage, 43 | } 44 | 45 | #[repr(C)] 46 | #[derive(Copy, Clone)] 47 | struct HandleWrapper { 48 | handle_type: HandleType, 49 | } 50 | 51 | pub static ALLOCATOR: AtomicRefCell = 52 | AtomicRefCell::new(Allocator::new(layout::MemoryDescriptor::PAGE_SIZE as u64)); 53 | 54 | pub static VARIABLES: AtomicRefCell = 55 | AtomicRefCell::new(VariableAllocator::new()); 56 | 57 | // RHF string in UCS-2 58 | const FIRMWARE_STRING: [u16; 4] = [0x0052, 0x0048, 0x0046, 0x0000]; 59 | 60 | static mut ST: SyncUnsafeCell = SyncUnsafeCell::new(efi::SystemTable { 61 | hdr: efi::TableHeader { 62 | signature: efi::SYSTEM_TABLE_SIGNATURE, 63 | revision: (2 << 16) | (80), 64 | header_size: size_of::() as u32, 65 | crc32: 0, // TODO 66 | reserved: 0, 67 | }, 68 | firmware_vendor: FIRMWARE_STRING.as_ptr() as *mut u16, 69 | firmware_revision: 0, 70 | console_in_handle: console::STDIN_HANDLE, 71 | con_in: null_mut(), 72 | console_out_handle: console::STDOUT_HANDLE, 73 | con_out: null_mut(), 74 | standard_error_handle: console::STDERR_HANDLE, 75 | std_err: null_mut(), 76 | runtime_services: null_mut(), 77 | boot_services: null_mut(), 78 | number_of_table_entries: 0, 79 | configuration_table: null_mut(), 80 | }); 81 | 82 | static mut BLOCK_WRAPPERS: SyncUnsafeCell = 83 | SyncUnsafeCell::new(block::BlockWrappers { 84 | wrappers: [null_mut(); 16], 85 | count: 0, 86 | }); 87 | 88 | // Populate allocator from E820, fixed ranges for the firmware and the loaded binary. 89 | fn populate_allocator(info: &dyn bootinfo::Info, image_address: u64, image_size: u64) { 90 | for i in 0..info.num_entries() { 91 | let entry = info.entry(i); 92 | match entry.entry_type { 93 | bootinfo::EntryType::Ram => { 94 | let page_count = ALLOCATOR.borrow().page_count(entry.size as usize); 95 | ALLOCATOR.borrow_mut().add_initial_allocation( 96 | efi::CONVENTIONAL_MEMORY, 97 | page_count, 98 | entry.addr, 99 | efi::MEMORY_WB, 100 | ); 101 | } 102 | _ => continue, 103 | } 104 | } 105 | 106 | for descriptor in info.memory_layout() { 107 | let memory_type = match descriptor.attribute { 108 | layout::MemoryAttribute::Code => efi::RUNTIME_SERVICES_CODE, 109 | layout::MemoryAttribute::Data => efi::RUNTIME_SERVICES_DATA, 110 | layout::MemoryAttribute::Unusable => efi::UNUSABLE_MEMORY, 111 | layout::MemoryAttribute::Mmio => efi::MEMORY_MAPPED_IO, 112 | }; 113 | ALLOCATOR.borrow_mut().allocate_pages( 114 | efi::ALLOCATE_ADDRESS, 115 | memory_type, 116 | descriptor.page_count() as u64, 117 | descriptor.range_start() as u64, 118 | ); 119 | } 120 | 121 | if let Some(fdt_entry) = info.fdt_reservation() { 122 | let page_count = ALLOCATOR.borrow().page_count(fdt_entry.size as usize); 123 | ALLOCATOR.borrow_mut().allocate_pages( 124 | efi::ALLOCATE_ADDRESS, 125 | efi::UNUSABLE_MEMORY, 126 | page_count, 127 | fdt_entry.addr, 128 | ); 129 | } 130 | 131 | // Add the loaded binary 132 | let page_count = ALLOCATOR.borrow().page_count(image_size as usize); 133 | ALLOCATOR.borrow_mut().allocate_pages( 134 | efi::ALLOCATE_ADDRESS, 135 | efi::LOADER_CODE, 136 | page_count, 137 | image_address, 138 | ); 139 | } 140 | 141 | #[repr(C)] 142 | struct LoadedImageWrapper { 143 | hw: HandleWrapper, 144 | proto: LoadedImageProtocol, 145 | entry_point: u64, 146 | } 147 | 148 | fn new_image_handle( 149 | file_path: *mut r_efi::protocols::device_path::Protocol, 150 | parent_handle: Handle, 151 | device_handle: Handle, 152 | load_addr: u64, 153 | load_size: u64, 154 | entry_addr: u64, 155 | ) -> *mut LoadedImageWrapper { 156 | let mut image = null_mut(); 157 | let status = boot_services::allocate_pool( 158 | efi::LOADER_DATA, 159 | size_of::(), 160 | &mut image as *mut *mut c_void, 161 | ); 162 | assert!(status == Status::SUCCESS); 163 | let image = unsafe { &mut *(image as *mut LoadedImageWrapper) }; 164 | *image = LoadedImageWrapper { 165 | hw: HandleWrapper { 166 | handle_type: HandleType::LoadedImage, 167 | }, 168 | proto: LoadedImageProtocol { 169 | revision: r_efi::protocols::loaded_image::REVISION, 170 | parent_handle, 171 | #[allow(static_mut_refs)] 172 | system_table: unsafe { ST.get_mut() }, 173 | device_handle, 174 | file_path, 175 | load_options_size: 0, 176 | load_options: null_mut(), 177 | image_base: load_addr as *mut _, 178 | image_size: load_size, 179 | image_code_type: efi::LOADER_CODE, 180 | image_data_type: efi::LOADER_DATA, 181 | unload: Some(boot_services::unload_image), 182 | reserved: null_mut(), 183 | }, 184 | entry_point: entry_addr, 185 | }; 186 | image 187 | } 188 | 189 | pub fn efi_exec( 190 | address: u64, 191 | loaded_address: u64, 192 | loaded_size: u64, 193 | info: &dyn bootinfo::Info, 194 | fs: &crate::fat::Filesystem, 195 | block: &crate::block::VirtioBlockDevice, 196 | ) { 197 | let vendor_data = 0u32; 198 | 199 | #[allow(static_mut_refs)] 200 | let ct = unsafe { CT.get_mut() }; 201 | let mut ct_index = 0; 202 | 203 | // Populate with FDT table if present 204 | // To ensure ACPI is used during boot do not include FDT table on aarch64 205 | // https://github.com/torvalds/linux/blob/d528014517f2b0531862c02865b9d4c908019dc4/arch/arm64/kernel/acpi.c#L203 206 | #[cfg(not(target_arch = "aarch64"))] 207 | if let Some(fdt_entry) = info.fdt_reservation() { 208 | ct[ct_index] = efi::ConfigurationTable { 209 | vendor_guid: Guid::from_fields( 210 | 0xb1b621d5, 211 | 0xf19c, 212 | 0x41a5, 213 | 0x83, 214 | 0x0b, 215 | &[0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0], 216 | ), 217 | vendor_table: fdt_entry.addr as *const u64 as *mut _, 218 | }; 219 | ct_index += 1; 220 | } 221 | 222 | // Populate with ACPI RSDP table if present 223 | if let Some(acpi_rsdp_ptr) = info.rsdp_addr() { 224 | ct[ct_index] = efi::ConfigurationTable { 225 | vendor_guid: Guid::from_fields( 226 | 0x8868_e871, 227 | 0xe4f1, 228 | 0x11d3, 229 | 0xbc, 230 | 0x22, 231 | &[0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81], 232 | ), 233 | vendor_table: acpi_rsdp_ptr as *mut _, 234 | }; 235 | ct_index += 1; 236 | } 237 | 238 | // Othwerwise fill with zero vendor data 239 | if ct_index == 0 { 240 | ct[ct_index] = efi::ConfigurationTable { 241 | vendor_guid: Guid::from_fields( 242 | 0x678a_9665, 243 | 0x9957, 244 | 0x4e7c, 245 | 0xa6, 246 | 0x27, 247 | &[0x34, 0xc9, 0x46, 0x3d, 0xd2, 0xac], 248 | ), 249 | vendor_table: &vendor_data as *const _ as *mut _, 250 | } 251 | }; 252 | 253 | let mut stdin = console::STDIN; 254 | let mut stdout = console::STDOUT; 255 | #[allow(static_mut_refs)] 256 | let st = unsafe { ST.get_mut() }; 257 | st.con_in = &mut stdin; 258 | st.con_out = &mut stdout; 259 | st.std_err = &mut stdout; 260 | st.runtime_services = unsafe { 261 | #[allow(static_mut_refs)] 262 | RS.get_mut() 263 | }; 264 | st.boot_services = unsafe { 265 | #[allow(static_mut_refs)] 266 | BS.get_mut() 267 | }; 268 | st.number_of_table_entries = 1; 269 | st.configuration_table = &mut ct[0]; 270 | 271 | populate_allocator(info, loaded_address, loaded_size); 272 | 273 | #[allow(static_mut_refs)] 274 | let efi_part_id = unsafe { block::populate_block_wrappers(BLOCK_WRAPPERS.get_mut(), block) }; 275 | 276 | let wrapped_fs = file::FileSystemWrapper::new(fs, efi_part_id); 277 | 278 | let mut path = [0u8; 256]; 279 | path[0..crate::efi::EFI_BOOT_PATH.len()].copy_from_slice(crate::efi::EFI_BOOT_PATH.as_bytes()); 280 | let device_path = DevicePath::File(path); 281 | let image = new_image_handle( 282 | device_path.generate(), 283 | 0 as Handle, 284 | &wrapped_fs as *const _ as Handle, 285 | loaded_address, 286 | loaded_size, 287 | address, 288 | ); 289 | 290 | let ptr = address as *const (); 291 | let code: extern "efiapi" fn(Handle, *mut efi::SystemTable) -> Status = 292 | unsafe { core::mem::transmute(ptr) }; 293 | (code)((image as *const _) as Handle, &mut *st); 294 | } 295 | -------------------------------------------------------------------------------- /src/efi/runtime_services.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use core::{ 5 | cell::SyncUnsafeCell, 6 | ffi::c_void, 7 | mem::{size_of, transmute}, 8 | }; 9 | 10 | use r_efi::{ 11 | efi::{ 12 | self, Boolean, CapsuleHeader, Char16, Guid, MemoryDescriptor, PhysicalAddress, ResetType, 13 | Status, Time, TimeCapabilities, 14 | }, 15 | system::{ConfigurationTable, RuntimeServices}, 16 | }; 17 | 18 | use crate::rtc; 19 | 20 | use super::{ALLOCATOR, ST, VARIABLES}; 21 | 22 | pub static mut RS: SyncUnsafeCell = 23 | SyncUnsafeCell::new(efi::RuntimeServices { 24 | hdr: efi::TableHeader { 25 | signature: efi::RUNTIME_SERVICES_SIGNATURE, 26 | revision: efi::RUNTIME_SERVICES_REVISION, 27 | header_size: size_of::() as u32, 28 | crc32: 0, // TODO 29 | reserved: 0, 30 | }, 31 | get_time, 32 | set_time, 33 | get_wakeup_time, 34 | set_wakeup_time, 35 | set_virtual_address_map, 36 | convert_pointer, 37 | get_variable, 38 | get_next_variable_name, 39 | set_variable, 40 | get_next_high_mono_count, 41 | reset_system, 42 | update_capsule, 43 | query_capsule_capabilities, 44 | query_variable_info, 45 | }); 46 | 47 | #[allow(clippy::missing_transmute_annotations)] 48 | unsafe fn fixup_at_virtual(descriptors: &[MemoryDescriptor]) { 49 | #[allow(static_mut_refs)] 50 | let st = ST.get_mut(); 51 | #[allow(static_mut_refs)] 52 | let rs = RS.get_mut(); 53 | 54 | let ptr = ALLOCATOR 55 | .borrow() 56 | .convert_internal_pointer(descriptors, (not_available as *const ()) as u64) 57 | .unwrap(); 58 | rs.get_time = transmute(ptr); 59 | rs.set_time = transmute(ptr); 60 | rs.get_wakeup_time = transmute(ptr); 61 | rs.set_wakeup_time = transmute(ptr); 62 | rs.get_variable = transmute(ptr); 63 | rs.set_variable = transmute(ptr); 64 | rs.get_next_variable_name = transmute(ptr); 65 | rs.reset_system = transmute(ptr); 66 | rs.update_capsule = transmute(ptr); 67 | rs.query_capsule_capabilities = transmute(ptr); 68 | rs.query_variable_info = transmute(ptr); 69 | 70 | let ct = st.configuration_table; 71 | let ptr = ALLOCATOR 72 | .borrow() 73 | .convert_internal_pointer(descriptors, (ct as *const _) as u64) 74 | .unwrap(); 75 | st.configuration_table = ptr as *mut ConfigurationTable; 76 | 77 | let rs = st.runtime_services; 78 | let ptr = ALLOCATOR 79 | .borrow() 80 | .convert_internal_pointer(descriptors, (rs as *const _) as u64) 81 | .unwrap(); 82 | st.runtime_services = ptr as *mut RuntimeServices; 83 | } 84 | 85 | pub extern "efiapi" fn not_available() -> Status { 86 | Status::UNSUPPORTED 87 | } 88 | 89 | pub extern "efiapi" fn get_time(time: *mut Time, _: *mut TimeCapabilities) -> Status { 90 | if time.is_null() { 91 | return Status::INVALID_PARAMETER; 92 | } 93 | 94 | let (year, month, day) = match rtc::read_date() { 95 | Ok((y, m, d)) => (y, m, d), 96 | Err(()) => return Status::DEVICE_ERROR, 97 | }; 98 | let (hour, minute, second) = match rtc::read_time() { 99 | Ok((h, m, s)) => (h, m, s), 100 | Err(()) => return Status::DEVICE_ERROR, 101 | }; 102 | 103 | unsafe { 104 | (*time).year = 2000 + year as u16; 105 | (*time).month = month; 106 | (*time).day = day; 107 | (*time).hour = hour; 108 | (*time).minute = minute; 109 | (*time).second = second; 110 | (*time).nanosecond = 0; 111 | (*time).timezone = 0; 112 | (*time).daylight = 0; 113 | } 114 | 115 | Status::SUCCESS 116 | } 117 | 118 | pub extern "efiapi" fn set_time(_: *mut Time) -> Status { 119 | Status::DEVICE_ERROR 120 | } 121 | 122 | pub extern "efiapi" fn get_wakeup_time(_: *mut Boolean, _: *mut Boolean, _: *mut Time) -> Status { 123 | Status::UNSUPPORTED 124 | } 125 | 126 | pub extern "efiapi" fn set_wakeup_time(_: Boolean, _: *mut Time) -> Status { 127 | Status::UNSUPPORTED 128 | } 129 | 130 | pub extern "efiapi" fn set_virtual_address_map( 131 | map_size: usize, 132 | descriptor_size: usize, 133 | version: u32, 134 | descriptors: *mut MemoryDescriptor, 135 | ) -> Status { 136 | let count = map_size / descriptor_size; 137 | 138 | if version != efi::MEMORY_DESCRIPTOR_VERSION { 139 | return Status::INVALID_PARAMETER; 140 | } 141 | 142 | let descriptors = unsafe { core::slice::from_raw_parts_mut(descriptors, count) }; 143 | 144 | unsafe { 145 | fixup_at_virtual(descriptors); 146 | } 147 | 148 | ALLOCATOR.borrow_mut().update_virtual_addresses(descriptors) 149 | } 150 | 151 | pub extern "efiapi" fn convert_pointer(_: usize, _: *mut *mut c_void) -> Status { 152 | Status::UNSUPPORTED 153 | } 154 | 155 | pub extern "efiapi" fn get_variable( 156 | variable_name: *mut Char16, 157 | vendor_guid: *mut Guid, 158 | attributes: *mut u32, 159 | data_size: *mut usize, 160 | data: *mut c_void, 161 | ) -> Status { 162 | if cfg!(feature = "efi-var") { 163 | VARIABLES 164 | .borrow_mut() 165 | .get(variable_name, vendor_guid, attributes, data_size, data) 166 | } else { 167 | Status::NOT_FOUND 168 | } 169 | } 170 | 171 | pub extern "efiapi" fn get_next_variable_name( 172 | _: *mut usize, 173 | _: *mut Char16, 174 | _: *mut Guid, 175 | ) -> Status { 176 | Status::NOT_FOUND 177 | } 178 | 179 | pub extern "efiapi" fn set_variable( 180 | variable_name: *mut Char16, 181 | vendor_guid: *mut Guid, 182 | attributes: u32, 183 | data_size: usize, 184 | data: *mut c_void, 185 | ) -> Status { 186 | if cfg!(feature = "efi-var") { 187 | VARIABLES 188 | .borrow_mut() 189 | .set(variable_name, vendor_guid, attributes, data_size, data) 190 | } else { 191 | Status::UNSUPPORTED 192 | } 193 | } 194 | 195 | pub extern "efiapi" fn get_next_high_mono_count(_: *mut u32) -> Status { 196 | Status::DEVICE_ERROR 197 | } 198 | 199 | pub extern "efiapi" fn reset_system(_: ResetType, _: Status, _: usize, _: *mut c_void) { 200 | // Don't do anything to force the kernel to use ACPI for shutdown and triple-fault for reset 201 | } 202 | 203 | pub extern "efiapi" fn update_capsule( 204 | _: *mut *mut CapsuleHeader, 205 | _: usize, 206 | _: PhysicalAddress, 207 | ) -> Status { 208 | Status::UNSUPPORTED 209 | } 210 | 211 | pub extern "efiapi" fn query_capsule_capabilities( 212 | _: *mut *mut CapsuleHeader, 213 | _: usize, 214 | _: *mut u64, 215 | _: *mut ResetType, 216 | ) -> Status { 217 | Status::UNSUPPORTED 218 | } 219 | 220 | pub extern "efiapi" fn query_variable_info( 221 | _: u32, 222 | max_storage: *mut u64, 223 | remaining_storage: *mut u64, 224 | max_size: *mut u64, 225 | ) -> Status { 226 | unsafe { 227 | *max_storage = 0; 228 | *remaining_storage = 0; 229 | *max_size = 0; 230 | } 231 | Status::SUCCESS 232 | } 233 | -------------------------------------------------------------------------------- /src/fdt.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use fdt::Fdt; 5 | 6 | use crate::{ 7 | bootinfo::{EntryType, Info, MemoryEntry}, 8 | layout::MemoryDescriptor, 9 | }; 10 | 11 | pub struct StartInfo<'a> { 12 | acpi_rsdp_addr: Option, 13 | fdt_entry: MemoryEntry, 14 | fdt: Fdt<'a>, 15 | kernel_load_addr: u64, 16 | memory_layout: &'static [MemoryDescriptor], 17 | pci_bar_memory: Option, 18 | } 19 | 20 | impl StartInfo<'_> { 21 | pub fn new( 22 | ptr: *const u8, 23 | acpi_rsdp_addr: Option, 24 | kernel_load_addr: u64, 25 | memory_layout: &'static [MemoryDescriptor], 26 | pci_bar_memory: Option, 27 | ) -> Self { 28 | let fdt = unsafe { 29 | match Fdt::from_ptr(ptr) { 30 | Ok(fdt) => fdt, 31 | Err(e) => panic!("Failed to create device tree object: {:?}", e), 32 | } 33 | }; 34 | 35 | let fdt_entry = MemoryEntry { 36 | addr: ptr as u64, 37 | size: fdt.total_size() as u64, 38 | entry_type: EntryType::Reserved, 39 | }; 40 | 41 | Self { 42 | fdt_entry, 43 | fdt, 44 | acpi_rsdp_addr, 45 | kernel_load_addr, 46 | memory_layout, 47 | pci_bar_memory, 48 | } 49 | } 50 | 51 | pub fn find_compatible_region(&self, with: &[&str]) -> Option<(*const u8, usize)> { 52 | let node = self.fdt.find_compatible(with)?; 53 | if let Some(region) = node.reg()?.next() { 54 | return Some((region.starting_address, region.size?)); 55 | } 56 | None 57 | } 58 | } 59 | 60 | impl Info for StartInfo<'_> { 61 | fn name(&self) -> &str { 62 | "FDT" 63 | } 64 | 65 | fn rsdp_addr(&self) -> Option { 66 | self.acpi_rsdp_addr 67 | } 68 | 69 | fn fdt_reservation(&self) -> Option { 70 | Some(self.fdt_entry) 71 | } 72 | 73 | fn cmdline(&self) -> &[u8] { 74 | match self.fdt.chosen().bootargs() { 75 | Some(s) => s.as_bytes(), 76 | None => b"", 77 | } 78 | } 79 | 80 | fn num_entries(&self) -> usize { 81 | let nodes = self.fdt.find_all_nodes("/memory"); 82 | let regions = nodes.flat_map(|n| n.reg().expect("should contain valid memory regions")); 83 | regions.count() 84 | } 85 | 86 | fn entry(&self, idx: usize) -> MemoryEntry { 87 | let nodes = self.fdt.find_all_nodes("/memory"); 88 | let regions = nodes.flat_map(|n| n.reg().expect("should contain valid memory regions")); 89 | for (i, region) in regions.enumerate() { 90 | if i == idx { 91 | return MemoryEntry { 92 | addr: region.starting_address as u64, 93 | size: region.size.expect("memory size is required") as u64, 94 | entry_type: EntryType::Ram, 95 | }; 96 | } 97 | } 98 | panic!("No valid memory entry found"); 99 | } 100 | 101 | fn kernel_load_addr(&self) -> u64 { 102 | self.kernel_load_addr 103 | } 104 | 105 | fn memory_layout(&self) -> &'static [MemoryDescriptor] { 106 | self.memory_layout 107 | } 108 | 109 | fn pci_bar_memory(&self) -> Option { 110 | self.pci_bar_memory 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use core::ops::Range; 5 | 6 | #[allow(dead_code)] 7 | #[derive(Clone, Copy)] 8 | pub enum MemoryAttribute { 9 | Code, 10 | Data, 11 | Unusable, 12 | Mmio, 13 | } 14 | 15 | #[derive(Clone, Copy)] 16 | pub struct MemoryDescriptor { 17 | #[allow(dead_code)] 18 | pub name: &'static str, 19 | pub range: fn() -> Range, 20 | pub attribute: MemoryAttribute, 21 | } 22 | 23 | impl MemoryDescriptor { 24 | pub const PAGE_SIZE: usize = 0x1000; 25 | 26 | pub fn range_start(&self) -> usize { 27 | let addr = (self.range)().start; 28 | assert!(addr % Self::PAGE_SIZE == 0); 29 | addr 30 | } 31 | 32 | pub fn range_end(&self) -> usize { 33 | let addr = (self.range)().end; 34 | assert!(addr % Self::PAGE_SIZE == 0); 35 | addr 36 | } 37 | 38 | pub fn page_count(&self) -> usize { 39 | (self.range_end() - self.range_start()) / Self::PAGE_SIZE 40 | } 41 | } 42 | 43 | pub type MemoryLayout = [MemoryDescriptor; NUM_MEM_DESCS]; 44 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2024 Akira Moroo 3 | 4 | pub struct Logger; 5 | 6 | impl log::Log for Logger { 7 | fn enabled(&self, metadata: &log::Metadata) -> bool { 8 | metadata.level() <= log::Level::Info 9 | } 10 | 11 | fn log(&self, record: &log::Record) { 12 | if self.enabled(record.metadata()) { 13 | log!("[{}] {}", record.level(), record.args()); 14 | } 15 | } 16 | 17 | fn flush(&self) {} 18 | } 19 | 20 | pub fn init() { 21 | log::set_logger(&Logger).expect("Failed to set logger"); 22 | log::set_max_level(log::LevelFilter::Info); 23 | } 24 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | #![feature(stmt_expr_attributes)] 5 | #![feature(sync_unsafe_cell)] 6 | #![cfg_attr(not(test), no_std)] 7 | #![cfg_attr(not(test), no_main)] 8 | #![cfg_attr( 9 | all(not(test), not(feature = "integration_tests")), 10 | feature(alloc_error_handler) 11 | )] 12 | #![cfg_attr(test, allow(unused_imports, dead_code))] 13 | #![cfg_attr(not(feature = "log-serial"), allow(unused_variables, unused_imports))] 14 | #![cfg_attr(target_arch = "riscv64", feature(riscv_ext_intrinsics))] 15 | 16 | #[cfg(all(not(test), not(feature = "integration_tests")))] 17 | use core::panic::PanicInfo; 18 | 19 | use log::{error, info, warn}; 20 | #[cfg(all( 21 | not(test), 22 | not(feature = "integration_tests"), 23 | target_arch = "x86_64", 24 | feature = "log-panic" 25 | ))] 26 | use x86_64::instructions::hlt; 27 | 28 | #[cfg(target_arch = "aarch64")] 29 | use crate::arch::aarch64::layout::code_range; 30 | 31 | #[macro_use] 32 | mod serial; 33 | 34 | #[macro_use] 35 | mod common; 36 | 37 | mod arch; 38 | mod block; 39 | mod boot; 40 | mod bootinfo; 41 | mod bzimage; 42 | #[cfg(target_arch = "x86_64")] 43 | mod cmos; 44 | #[cfg(target_arch = "x86_64")] 45 | mod coreboot; 46 | mod delay; 47 | mod efi; 48 | mod fat; 49 | #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))] 50 | mod fdt; 51 | #[cfg(all(test, feature = "integration_tests"))] 52 | mod integration; 53 | mod layout; 54 | mod loader; 55 | mod logger; 56 | mod mem; 57 | mod part; 58 | mod pci; 59 | mod pe; 60 | #[cfg(all(target_arch = "x86_64", not(feature = "coreboot")))] 61 | mod pvh; 62 | mod rtc; 63 | #[cfg(target_arch = "riscv64")] 64 | mod rtc_goldfish; 65 | #[cfg(target_arch = "aarch64")] 66 | mod rtc_pl031; 67 | #[cfg(target_arch = "riscv64")] 68 | mod uart_mmio; 69 | #[cfg(target_arch = "aarch64")] 70 | mod uart_pl011; 71 | mod virtio; 72 | 73 | #[cfg(all(not(test), not(feature = "integration_tests"), feature = "log-panic"))] 74 | #[panic_handler] 75 | fn panic(info: &PanicInfo) -> ! { 76 | log!("PANIC: {}", info); 77 | loop { 78 | #[cfg(target_arch = "x86_64")] 79 | hlt() 80 | } 81 | } 82 | 83 | #[cfg(all( 84 | not(test), 85 | not(feature = "integration_tests"), 86 | not(feature = "log-panic") 87 | ))] 88 | #[panic_handler] 89 | fn panic(_: &PanicInfo) -> ! { 90 | loop {} 91 | } 92 | 93 | const VIRTIO_PCI_VENDOR_ID: u16 = 0x1af4; 94 | const VIRTIO_PCI_BLOCK_DEVICE_ID: u16 = 0x1042; 95 | 96 | #[allow(dead_code)] 97 | #[derive(Debug)] 98 | enum Error { 99 | Virtio(virtio::Error), 100 | Partition(part::Error), 101 | Fat(fat::Error), 102 | Loader(loader::Error), 103 | Pe(pe::Error), 104 | ImageTooLarge, 105 | } 106 | 107 | fn boot_from_device( 108 | device: &mut block::VirtioBlockDevice, 109 | info: &dyn bootinfo::Info, 110 | ) -> Result<(), Error> { 111 | if let Err(err) = device.init() { 112 | error!("Error configuring block device: {err:?}"); 113 | return Err(Error::Virtio(err)); 114 | } 115 | info!( 116 | "Virtio block device configured. Capacity: {} sectors", 117 | device.get_capacity() 118 | ); 119 | 120 | let (start, end) = match part::find_efi_partition(device) { 121 | Ok(p) => p, 122 | Err(err) => { 123 | error!("Failed to find EFI partition: {err:?}"); 124 | return Err(Error::Partition(err)); 125 | } 126 | }; 127 | info!("Found EFI partition"); 128 | 129 | let mut f = fat::Filesystem::new(device, start, end); 130 | if let Err(err) = f.init() { 131 | error!("Failed to create filesystem: {err:?}"); 132 | return Err(Error::Fat(err)); 133 | } 134 | info!("Filesystem ready"); 135 | 136 | match loader::load_default_entry(&f, info) { 137 | Ok(mut kernel) => { 138 | info!("Jumping to kernel"); 139 | kernel.boot(); 140 | return Ok(()); 141 | } 142 | Err(err) => { 143 | warn!("Error loading default entry: {err:?}"); 144 | // Fall through to EFI boot 145 | } 146 | } 147 | 148 | info!("Using EFI boot."); 149 | 150 | let mut file = match f.open(efi::EFI_BOOT_PATH) { 151 | Ok(file) => file, 152 | Err(err) => { 153 | error!("Failed to load default EFI binary: {err:?}"); 154 | return Err(Error::Fat(err)); 155 | } 156 | }; 157 | info!("Found bootloader: {}", efi::EFI_BOOT_PATH); 158 | 159 | let mut l = pe::Loader::new(&mut file); 160 | 161 | let (entry_addr, load_addr, size) = match l.load(info.kernel_load_addr()) { 162 | Ok(load_info) => load_info, 163 | Err(err) => { 164 | error!("Error loading executable: {err:?}"); 165 | return Err(Error::Pe(err)); 166 | } 167 | }; 168 | 169 | #[cfg(target_arch = "aarch64")] 170 | if code_range().start < (info.kernel_load_addr() + size) as usize { 171 | error!("Error Boot Image is too large"); 172 | return Err(Error::ImageTooLarge); 173 | } 174 | 175 | info!("Executable loaded"); 176 | efi::efi_exec(entry_addr, load_addr, size, info, &f, device); 177 | Ok(()) 178 | } 179 | 180 | #[cfg(target_arch = "x86_64")] 181 | #[no_mangle] 182 | pub extern "C" fn rust64_start(#[cfg(not(feature = "coreboot"))] pvh_info: &pvh::StartInfo) -> ! { 183 | serial::PORT.borrow_mut().init(); 184 | logger::init(); 185 | 186 | arch::x86_64::sse::enable_sse(); 187 | arch::x86_64::paging::setup(); 188 | 189 | #[cfg(feature = "coreboot")] 190 | let info = &coreboot::StartInfo::default(); 191 | 192 | #[cfg(not(feature = "coreboot"))] 193 | let info = pvh_info; 194 | 195 | main(info) 196 | } 197 | 198 | #[cfg(target_arch = "aarch64")] 199 | #[no_mangle] 200 | pub extern "C" fn rust64_start(x0: *const u8) -> ! { 201 | arch::aarch64::simd::setup_simd(); 202 | arch::aarch64::paging::setup(); 203 | 204 | // Use atomic operation before MMU enabled may cause exception, see https://www.ipshop.xyz/5909.html 205 | serial::PORT.borrow_mut().init(); 206 | logger::init(); 207 | 208 | let info = fdt::StartInfo::new( 209 | x0, 210 | Some(arch::aarch64::layout::map::dram::ACPI_START as u64), 211 | arch::aarch64::layout::map::dram::KERNEL_START as u64, 212 | &crate::arch::aarch64::layout::MEM_LAYOUT[..], 213 | None, 214 | ); 215 | 216 | if let Some((base, length)) = info.find_compatible_region(&["pci-host-ecam-generic"]) { 217 | pci::init(base as u64, length as u64); 218 | } 219 | 220 | main(&info) 221 | } 222 | 223 | #[cfg(target_arch = "riscv64")] 224 | #[no_mangle] 225 | pub extern "C" fn rust64_start(a0: u64, a1: *const u8) -> ! { 226 | use crate::bootinfo::{EntryType, Info, MemoryEntry}; 227 | 228 | serial::PORT.borrow_mut().init(); 229 | logger::init(); 230 | 231 | info!("Starting on RV64 0x{:x} 0x{:x}", a0, a1 as u64,); 232 | 233 | let info = fdt::StartInfo::new( 234 | a1, 235 | None, 236 | 0x8040_0000, 237 | &crate::arch::riscv64::layout::MEM_LAYOUT[..], 238 | Some(MemoryEntry { 239 | addr: 0x4000_0000, 240 | size: 2 << 20, 241 | entry_type: EntryType::Reserved, 242 | }), 243 | ); 244 | 245 | for i in 0..info.num_entries() { 246 | let region = info.entry(i); 247 | info!( 248 | "Memory region {}MiB@0x{:x}", 249 | region.size / 1024 / 1024, 250 | region.addr 251 | ); 252 | } 253 | 254 | if let Some((base, length)) = info.find_compatible_region(&["pci-host-ecam-generic"]) { 255 | pci::init(base as u64, length as u64); 256 | } 257 | 258 | main(&info); 259 | } 260 | 261 | fn main(info: &dyn bootinfo::Info) -> ! { 262 | info!("Booting with {}", info.name()); 263 | 264 | pci::print_bus(); 265 | 266 | let mut next_address = info.pci_bar_memory().map(|m| m.addr); 267 | let max_address = info.pci_bar_memory().map(|m| m.addr + m.size); 268 | 269 | pci::with_devices( 270 | VIRTIO_PCI_VENDOR_ID, 271 | VIRTIO_PCI_BLOCK_DEVICE_ID, 272 | |mut pci_device| { 273 | pci_device.init(); 274 | 275 | next_address = pci_device.allocate_bars(next_address); 276 | if next_address > max_address { 277 | panic!("PCI BAR allocation space exceeded") 278 | } 279 | 280 | let mut pci_transport = pci::VirtioPciTransport::new(pci_device); 281 | let mut device = block::VirtioBlockDevice::new(&mut pci_transport); 282 | boot_from_device(&mut device, info).is_ok() 283 | }, 284 | ); 285 | 286 | panic!("Unable to boot from any virtio-blk device") 287 | } 288 | -------------------------------------------------------------------------------- /src/mem.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | #![allow(dead_code)] 5 | 6 | #[derive(Default)] 7 | /// Provides a checked way to access memory offsets from a range of raw memory 8 | pub struct MemoryRegion { 9 | base: u64, 10 | length: u64, 11 | } 12 | 13 | impl MemoryRegion { 14 | pub const fn new(base: u64, length: u64) -> MemoryRegion { 15 | MemoryRegion { base, length } 16 | } 17 | 18 | /// Take a slice and turn it into a region of memory 19 | pub fn from_bytes(data: &[u8]) -> MemoryRegion { 20 | MemoryRegion { 21 | base: data.as_ptr() as u64, 22 | length: data.len() as u64, 23 | } 24 | } 25 | 26 | // Expose the entire region as a byte slice 27 | pub fn as_bytes(&mut self) -> &mut [u8] { 28 | self.as_mut_slice(0, self.length) 29 | } 30 | 31 | /// Expose a section of the memory region as a slice 32 | pub fn as_mut_slice(&mut self, offset: u64, length: u64) -> &mut [T] { 33 | assert!((offset + (length * core::mem::size_of::() as u64)) <= self.length); 34 | unsafe { core::slice::from_raw_parts_mut((self.base + offset) as *mut T, length as usize) } 35 | } 36 | 37 | /// Read a value from a given offset 38 | fn read(&self, offset: u64) -> T 39 | where 40 | T: Copy + Sized, 41 | { 42 | assert!((offset + (core::mem::size_of::() - 1) as u64) < self.length); 43 | let ptr: *const T = core::ptr::with_exposed_provenance((self.base + offset) as usize); 44 | unsafe { ptr.read_unaligned() } 45 | } 46 | 47 | /// Read a single byte at a given offset 48 | pub fn read_u8(&self, offset: u64) -> u8 { 49 | self.read(offset) 50 | } 51 | 52 | /// Read a single word at a given offset 53 | pub fn read_u16(&self, offset: u64) -> u16 { 54 | self.read(offset) 55 | } 56 | 57 | /// Read a single dword at a given offset 58 | pub fn read_u32(&self, offset: u64) -> u32 { 59 | self.read(offset) 60 | } 61 | 62 | // Read a single qword at a given offset 63 | pub fn read_u64(&self, offset: u64) -> u64 { 64 | self.read(offset) 65 | } 66 | 67 | /// Write a value at the given offset 68 | pub fn write(&self, offset: u64, value: T) 69 | where 70 | T: Sized, 71 | { 72 | assert!((offset + (core::mem::size_of::() - 1) as u64) < self.length); 73 | let ptr: *mut T = core::ptr::with_exposed_provenance_mut((self.base + offset) as usize); 74 | unsafe { core::ptr::write_unaligned(ptr, value) } 75 | } 76 | 77 | /// Write a single byte at given offset 78 | pub fn write_u8(&self, offset: u64, value: u8) { 79 | self.write(offset, value) 80 | } 81 | 82 | /// Write a single word at given offset 83 | pub fn write_u16(&self, offset: u64, value: u16) { 84 | self.write(offset, value) 85 | } 86 | 87 | /// Write a single dword at given offset 88 | pub fn write_u32(&self, offset: u64, value: u32) { 89 | self.write(offset, value) 90 | } 91 | 92 | /// Write a single qword at given offset 93 | pub fn write_u64(&self, offset: u64, value: u64) { 94 | self.write(offset, value) 95 | } 96 | 97 | /// Read a value at given offset with a mechanism suitable for MMIO 98 | fn io_read(&self, offset: u64) -> T 99 | where 100 | T: Copy + Sized, 101 | { 102 | assert!((offset + (core::mem::size_of::() - 1) as u64) < self.length); 103 | let ptr: *const T = core::ptr::with_exposed_provenance((self.base + offset) as usize); 104 | unsafe { ptr.read_volatile() } 105 | } 106 | 107 | /// Read a single byte at given offset with a mechanism suitable for MMIO 108 | pub fn io_read_u8(&self, offset: u64) -> u8 { 109 | self.io_read(offset) 110 | } 111 | 112 | /// Read a single word at given offset with a mechanism suitable for MMIO 113 | pub fn io_read_u16(&self, offset: u64) -> u16 { 114 | self.io_read(offset) 115 | } 116 | 117 | /// Read a single dword at given offset with a mechanism suitable for MMIO 118 | pub fn io_read_u32(&self, offset: u64) -> u32 { 119 | self.io_read(offset) 120 | } 121 | 122 | /// Read a single qword at given offset with a mechanism suitable for MMIO 123 | pub fn io_read_u64(&self, offset: u64) -> u64 { 124 | self.io_read(offset) 125 | } 126 | 127 | /// Write a value at given offset using a mechanism suitable for MMIO 128 | pub fn io_write(&self, offset: u64, value: T) 129 | where 130 | T: Sized, 131 | { 132 | assert!((offset + (core::mem::size_of::() - 1) as u64) < self.length); 133 | let ptr: *mut T = core::ptr::with_exposed_provenance_mut((self.base + offset) as usize); 134 | unsafe { core::ptr::write_volatile(ptr, value) } 135 | } 136 | 137 | /// Write a single byte at given offset with a mechanism suitable for MMIO 138 | pub fn io_write_u8(&self, offset: u64, value: u8) { 139 | self.io_write(offset, value) 140 | } 141 | 142 | /// Write a single word at given offset with a mechanism suitable for MMIO 143 | pub fn io_write_u16(&self, offset: u64, value: u16) { 144 | self.io_write(offset, value) 145 | } 146 | 147 | /// Write a single dword at given offset with a mechanism suitable for MMIO 148 | pub fn io_write_u32(&self, offset: u64, value: u32) { 149 | self.io_write(offset, value) 150 | } 151 | 152 | /// Write a single qword at given offset with a mechanism suitable for MMIO 153 | pub fn io_write_u64(&self, offset: u64, value: u64) { 154 | self.io_write(offset, value) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/part.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use crate::block::{Error as BlockError, SectorBuf, SectorRead}; 5 | 6 | #[repr(C, packed)] 7 | /// GPT header 8 | struct Header { 9 | signature: u64, 10 | _revision: u32, 11 | _header_size: u32, 12 | _header_crc: u32, 13 | _reserved: u32, 14 | _current_lba: u64, 15 | _backup_lba: u64, 16 | first_usable_lba: u64, 17 | _last_usable_lba: u64, 18 | _disk_guid: [u8; 16], 19 | first_part_lba: u64, 20 | part_count: u32, 21 | _part_entry_size: u32, 22 | _part_crc: u32, 23 | } 24 | 25 | #[repr(C, packed)] 26 | #[derive(Clone, Copy, Default)] 27 | pub struct PartitionEntry { 28 | pub type_guid: [u8; 16], 29 | pub guid: [u8; 16], 30 | pub first_lba: u64, 31 | pub last_lba: u64, 32 | _flags: u64, 33 | _partition_name: [u32; 18], 34 | } 35 | 36 | impl PartitionEntry { 37 | pub fn is_efi_partition(&self) -> bool { 38 | // GUID is C12A7328-F81F-11D2-BA4B-00A0C93EC93B in mixed-endian 39 | // 0-3, 4-5, 6-7 are LE, 8-19, and 10-15 are BE 40 | self.type_guid 41 | == [ 42 | 0x28, 0x73, 0x2a, 0xc1, // LE C12A7328 43 | 0x1f, 0xf8, // LE F81F 44 | 0xd2, 0x11, // LE 11D2 45 | 0xba, 0x4b, // BE BA4B 46 | 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b, // BE 00A0C93EC93B 47 | ] 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | pub enum Error { 53 | #[allow(dead_code)] 54 | Block(BlockError), 55 | HeaderNotFound, 56 | ViolatesSpecification, 57 | ExceededPartitionCount, 58 | NoEFIPartition, 59 | } 60 | 61 | pub fn get_partitions(r: &dyn SectorRead, parts_out: &mut [PartitionEntry]) -> Result { 62 | let mut data = SectorBuf::new(); 63 | match r.read(1, data.as_mut_bytes()) { 64 | Ok(_) => {} 65 | Err(e) => return Err(Error::Block(e)), 66 | }; 67 | 68 | // Safe as sizeof header is less than 512 bytes (size of data) 69 | let h = unsafe { &*(data.as_bytes().as_ptr() as *const Header) }; 70 | 71 | // GPT magic constant 72 | if h.signature != 0x5452_4150_2049_4645u64 { 73 | return Err(Error::HeaderNotFound); 74 | } 75 | 76 | if h.first_usable_lba < 34 { 77 | return Err(Error::ViolatesSpecification); 78 | } 79 | 80 | let part_count = h.part_count; 81 | let mut checked_part_count = 0; 82 | 83 | let first_usable_lba = h.first_usable_lba; 84 | let first_part_lba = h.first_part_lba; 85 | 86 | let mut current_part = 0u32; 87 | 88 | for lba in first_part_lba..first_usable_lba { 89 | match r.read(lba, data.as_mut_bytes()) { 90 | Ok(_) => {} 91 | Err(e) => return Err(Error::Block(e)), 92 | } 93 | 94 | // Safe as size of partition struct * 4 is 512 bytes (size of data) 95 | let parts = unsafe { 96 | core::slice::from_raw_parts(data.as_bytes().as_ptr() as *const PartitionEntry, 4) 97 | }; 98 | 99 | for p in parts { 100 | if p.guid == [0; 16] { 101 | continue; 102 | } 103 | parts_out[current_part as usize] = *p; 104 | current_part += 1; 105 | } 106 | 107 | checked_part_count += 4; 108 | if checked_part_count >= part_count { 109 | break; 110 | } 111 | } 112 | 113 | Ok(current_part) 114 | } 115 | 116 | /// Find EFI partition 117 | pub fn find_efi_partition(r: &dyn SectorRead) -> Result<(u64, u64), Error> { 118 | // Assume no more than 16 partitions on the disk 119 | let mut parts = [PartitionEntry::default(); 16]; 120 | 121 | let part_count = get_partitions(r, &mut parts)? as usize; 122 | 123 | for (checked_part_count, p) in (parts[0..part_count]).iter().enumerate() { 124 | if p.is_efi_partition() { 125 | return Ok((p.first_lba, p.last_lba)); 126 | } 127 | if checked_part_count == part_count { 128 | return Err(Error::ExceededPartitionCount); 129 | } 130 | } 131 | 132 | Err(Error::NoEFIPartition) 133 | } 134 | 135 | #[cfg(test)] 136 | pub mod tests { 137 | use std::cell::RefCell; 138 | use std::env; 139 | use std::fs; 140 | use std::fs::File; 141 | use std::fs::Metadata; 142 | use std::io::Read; 143 | use std::io::Seek; 144 | use std::io::SeekFrom; 145 | use std::path::{Path, PathBuf}; 146 | 147 | use crate::block; 148 | use crate::block::{SectorBuf, SectorRead}; 149 | 150 | pub struct FakeDisk { 151 | file: RefCell, 152 | metadata: Metadata, 153 | } 154 | 155 | impl FakeDisk { 156 | pub fn new>(path: &P) -> FakeDisk { 157 | let file = File::open(path).expect("missing disk image"); 158 | let metadata = fs::metadata(path).expect("error getting file metadata"); 159 | FakeDisk { 160 | file: RefCell::new(file), 161 | metadata, 162 | } 163 | } 164 | 165 | pub fn len(&self) -> u64 { 166 | self.metadata.len() 167 | } 168 | } 169 | 170 | impl SectorRead for FakeDisk { 171 | fn read(&self, sector: u64, data: &mut [u8]) -> Result<(), block::Error> { 172 | let mut file = self.file.borrow_mut(); 173 | match file.seek(SeekFrom::Start(sector * SectorBuf::len() as u64)) { 174 | Ok(_) => {} 175 | Err(_) => return Err(block::Error::BlockIO), 176 | } 177 | match file.read(data) { 178 | Ok(_) => {} 179 | Err(_) => return Err(block::Error::BlockIO), 180 | } 181 | Ok(()) 182 | } 183 | } 184 | 185 | pub fn clear_disk_path() -> PathBuf { 186 | let mut disk_path = dirs::home_dir().unwrap(); 187 | disk_path.push("workloads"); 188 | disk_path.push("clear-28660-kvm.img"); 189 | 190 | disk_path 191 | } 192 | 193 | #[test] 194 | fn test_find_efi_partition() { 195 | let d = FakeDisk::new(&clear_disk_path()); 196 | 197 | match super::find_efi_partition(&d) { 198 | Ok((start, end)) => { 199 | assert_eq!(start, 2048); 200 | assert_eq!(end, 1_048_575); 201 | } 202 | Err(e) => panic!("{e:?}"), 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/pe.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | use crate::{block::SectorBuf, mem::MemoryRegion}; 5 | 6 | pub struct Loader<'a> { 7 | file: &'a mut dyn crate::fat::Read, 8 | num_sections: u16, 9 | image_base: u64, 10 | image_size: u32, 11 | } 12 | 13 | #[derive(Debug)] 14 | pub enum Error { 15 | FileError, 16 | InvalidExecutable, 17 | } 18 | 19 | #[repr(C, packed)] 20 | struct Section { 21 | _name: [u8; 8], 22 | virt_size: u32, 23 | virt_address: u32, 24 | raw_size: u32, 25 | raw_offset: u32, 26 | _unused: [u8; 16], 27 | } 28 | 29 | impl<'a> Loader<'a> { 30 | #[cfg(target_arch = "aarch64")] 31 | const MACHINE_TYPE: u16 = 0xaa64; 32 | #[cfg(target_arch = "x86_64")] 33 | const MACHINE_TYPE: u16 = 0x8664; 34 | 35 | #[cfg(target_arch = "riscv64")] 36 | const MACHINE_TYPE: u16 = 0x5064; 37 | 38 | #[cfg(any( 39 | target_arch = "aarch64", 40 | target_arch = "x86_64", 41 | target_arch = "riscv64" 42 | ))] 43 | const OPTIONAL_HEADER_MAGIC: u16 = 0x20b; // PE32+ 44 | 45 | pub fn new(file: &'a mut dyn crate::fat::Read) -> Loader<'a> { 46 | Loader { 47 | file, 48 | num_sections: 0, 49 | image_base: 0, 50 | image_size: 0, 51 | } 52 | } 53 | 54 | pub fn load(&mut self, load_addr: u64) -> Result<(u64, u64, u64), Error> { 55 | const HEADER_SIZE: usize = 1024; 56 | let mut data = [0_u8; HEADER_SIZE]; 57 | assert!(data.len() % 512 == 0); 58 | 59 | let mut buf = SectorBuf::new(); 60 | match self.file.read(buf.as_mut_bytes()) { 61 | Ok(_) => {} 62 | Err(_) => return Err(Error::FileError), 63 | } 64 | let sector_size = SectorBuf::len(); 65 | if data.len() <= sector_size { 66 | data.copy_from_slice(&buf.as_bytes()[..HEADER_SIZE]); 67 | } else if data.len() == sector_size * 2 { 68 | // The sector size is 512 69 | data[..sector_size].copy_from_slice(buf.as_bytes()); 70 | match self.file.read(buf.as_mut_bytes()) { 71 | Ok(_) => { 72 | data[sector_size..].copy_from_slice(buf.as_bytes()); 73 | } 74 | Err(_) => return Err(Error::FileError), 75 | } 76 | } else { 77 | // Unsupported sector size 78 | return Err(Error::FileError); 79 | } 80 | 81 | let dos_region = MemoryRegion::from_bytes(&data); 82 | 83 | // 'MZ' magic 84 | if dos_region.read_u16(0) != 0x5a4d { 85 | return Err(Error::InvalidExecutable); 86 | } 87 | 88 | // offset to COFF header 89 | let pe_header_offset = dos_region.read_u32(0x3c); 90 | 91 | if pe_header_offset >= sector_size as u32 { 92 | return Err(Error::InvalidExecutable); 93 | } 94 | 95 | let pe_region = MemoryRegion::from_bytes(&data[pe_header_offset as usize..]); 96 | 97 | // The Microsoft specification uses offsets relative to the COFF area 98 | // which is 4 after the signature (so all offsets are +4 relative to the spec) 99 | // 'PE' magic 100 | if pe_region.read_u32(0) != 0x0000_4550 { 101 | return Err(Error::InvalidExecutable); 102 | } 103 | 104 | // Check for supported machine 105 | if pe_region.read_u16(4) != Self::MACHINE_TYPE { 106 | return Err(Error::InvalidExecutable); 107 | } 108 | 109 | self.num_sections = pe_region.read_u16(6); 110 | 111 | let optional_header_size = pe_region.read_u16(20); 112 | let optional_region = MemoryRegion::from_bytes(&data[(24 + pe_header_offset) as usize..]); 113 | 114 | if optional_region.read_u16(0) != Self::OPTIONAL_HEADER_MAGIC { 115 | return Err(Error::InvalidExecutable); 116 | } 117 | 118 | let entry_point = optional_region.read_u32(16); 119 | 120 | self.image_base = optional_region.read_u64(24); 121 | let address = if self.image_base != 0 { 122 | // The image has desired load address 123 | self.image_base 124 | } else { 125 | load_addr 126 | }; 127 | self.image_size = optional_region.read_u32(56); 128 | let size_of_headers = optional_region.read_u32(60); 129 | 130 | let sections = &data[(24 + pe_header_offset + u32::from(optional_header_size)) as usize..]; 131 | let sections: &[Section] = unsafe { 132 | core::slice::from_raw_parts( 133 | sections.as_ptr() as *const Section, 134 | self.num_sections as usize, 135 | ) 136 | }; 137 | 138 | let image_info = ( 139 | address + u64::from(entry_point), 140 | address, 141 | u64::from(self.image_size), 142 | ); 143 | 144 | let mut loaded_region = MemoryRegion::new(address, u64::from(self.image_size)); 145 | 146 | // Copy the PE header into the start of the destination memory 147 | match self.file.seek(0) { 148 | Ok(_) => {} 149 | Err(_) => return Err(Error::FileError), 150 | } 151 | 152 | let mut header_offset = 0u64; 153 | while header_offset < u64::from(size_of_headers) { 154 | match self 155 | .file 156 | .read(loaded_region.as_mut_slice(header_offset, sector_size as u64)) 157 | { 158 | Ok(_) => {} 159 | Err(_) => { 160 | return Err(Error::FileError); 161 | } 162 | } 163 | header_offset += sector_size as u64; 164 | } 165 | 166 | for section in sections { 167 | for x in 0..section.virt_size { 168 | loaded_region.write_u8(u64::from(x) + u64::from(section.virt_address), 0); 169 | } 170 | 171 | // TODO: Handle strange offset sections. 172 | if section.raw_offset % sector_size as u32 != 0 { 173 | continue; 174 | } 175 | 176 | match self.file.seek(section.raw_offset) { 177 | Ok(_) => {} 178 | Err(_) => return Err(Error::FileError), 179 | } 180 | 181 | let mut section_data = SectorBuf::new(); 182 | 183 | let mut section_offset = 0; 184 | let section_size = core::cmp::min(section.raw_size, section.virt_size); 185 | while section_offset < section_size { 186 | let remaining_bytes = 187 | core::cmp::min(section_size - section_offset, sector_size as u32); 188 | match self.file.read(section_data.as_mut_bytes()) { 189 | Ok(_) => {} 190 | Err(_) => { 191 | return Err(Error::FileError); 192 | } 193 | } 194 | 195 | let l: &mut [u8] = loaded_region.as_mut_slice( 196 | u64::from(section.virt_address + section_offset), 197 | u64::from(remaining_bytes), 198 | ); 199 | l.copy_from_slice(§ion_data.as_bytes()[0..remaining_bytes as usize]); 200 | section_offset += remaining_bytes; 201 | } 202 | } 203 | 204 | let base_diff = address as i64 - self.image_base as i64; 205 | 206 | let num_data_dirs = optional_region.read_u32(108); 207 | if num_data_dirs < 5 { 208 | // No base relocation table entry 209 | return Ok(image_info); 210 | } 211 | let reloc_dir_virt_addr = optional_region.read_u32(152); 212 | let reloc_dir_size = optional_region.read_u32(156); 213 | if reloc_dir_virt_addr == 0 || reloc_dir_size == 0 { 214 | // No base relocation table available 215 | return Ok(image_info); 216 | } 217 | for section in sections { 218 | if section.virt_address == reloc_dir_virt_addr 219 | && section.raw_offset % sector_size as u32 != 0 220 | { 221 | // This section is not loaded 222 | return Ok(image_info); 223 | } 224 | } 225 | 226 | let section_size = reloc_dir_size; 227 | let l: &mut [u8] = 228 | loaded_region.as_mut_slice(u64::from(reloc_dir_virt_addr), u64::from(section_size)); 229 | 230 | let reloc_region = MemoryRegion::from_bytes(l); 231 | 232 | let mut section_bytes_remaining = section_size; 233 | let mut offset = 0; 234 | while section_bytes_remaining > 0 { 235 | // Read details for block 236 | let page_rva = reloc_region.read_u32(offset); 237 | let block_size = reloc_region.read_u32(offset + 4); 238 | let mut block_offset = 8; 239 | while block_offset < block_size { 240 | let entry = reloc_region.read_u16(offset + u64::from(block_offset)); 241 | 242 | let entry_type = entry >> 12; 243 | let entry_offset = entry & 0xfff; 244 | 245 | if entry_type == 10 { 246 | let location = u64::from(page_rva + u32::from(entry_offset)); 247 | let value = loaded_region.read_u64(location); 248 | loaded_region.write_u64(location, (value as i64 + base_diff) as u64); 249 | } 250 | 251 | block_offset += 2; 252 | } 253 | 254 | section_bytes_remaining -= block_size; 255 | offset += u64::from(block_size); 256 | } 257 | 258 | Ok(image_info) 259 | } 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use crate::part::tests::*; 265 | 266 | use std::alloc; 267 | 268 | // TODO: Add aarch64 specific loader test target 269 | #[cfg(target_arch = "x86_64")] 270 | #[test] 271 | fn test_loader() { 272 | let d = FakeDisk::new(&clear_disk_path()); 273 | let (start, end) = crate::part::find_efi_partition(&d).unwrap(); 274 | 275 | let mut f = crate::fat::Filesystem::new(&d, start, end); 276 | f.init().unwrap(); 277 | let mut file = f.open("/EFI/BOOT/BOOTX64 EFI").unwrap(); 278 | let mut l = super::Loader::new(&mut file); 279 | 280 | let fake_mem = unsafe { 281 | let layout = alloc::Layout::from_size_align(64 * 1024 * 1024, 1024 * 1024).unwrap(); 282 | alloc::alloc(layout) 283 | }; 284 | 285 | let (entry, addr, size) = l.load(fake_mem as u64).expect("expect loading success"); 286 | assert_eq!(entry, fake_mem as u64 + 0x4000); 287 | assert_eq!(addr, fake_mem as u64); 288 | assert_eq!(size, 110_592); 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/pvh.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright 2020 Google LLC 3 | 4 | use core::mem::size_of; 5 | 6 | use crate::{ 7 | bootinfo::{EntryType, Info, MemoryEntry}, 8 | common, 9 | layout::MemoryDescriptor, 10 | }; 11 | 12 | // Structures from xen/include/public/arch-x86/hvm/start_info.h 13 | #[derive(Debug)] 14 | #[repr(C)] 15 | pub struct StartInfo { 16 | magic: [u8; 4], 17 | version: u32, 18 | flags: u32, 19 | nr_modules: u32, 20 | modlist_paddr: u64, 21 | cmdline_paddr: u64, 22 | rsdp_paddr: u64, 23 | memmap_paddr: u64, 24 | memmap_entries: u32, 25 | _pad: u32, 26 | } 27 | 28 | #[derive(Clone, Copy, Debug)] 29 | #[repr(C)] 30 | struct MemMapEntry { 31 | addr: u64, 32 | size: u64, 33 | entry_type: u32, 34 | _pad: u32, 35 | } 36 | 37 | impl From for MemoryEntry { 38 | fn from(value: MemMapEntry) -> Self { 39 | Self { 40 | addr: value.addr, 41 | size: value.size, 42 | entry_type: EntryType::from(value.entry_type), 43 | } 44 | } 45 | } 46 | 47 | impl Info for StartInfo { 48 | fn name(&self) -> &str { 49 | "PVH Boot Protocol" 50 | } 51 | fn rsdp_addr(&self) -> Option { 52 | Some(self.rsdp_paddr) 53 | } 54 | fn cmdline(&self) -> &[u8] { 55 | unsafe { common::from_cstring(self.cmdline_paddr) } 56 | } 57 | fn num_entries(&self) -> usize { 58 | // memmap_paddr and memmap_entries only exist in version 1 or later 59 | if self.version < 1 || self.memmap_paddr == 0 { 60 | return 0; 61 | } 62 | self.memmap_entries as usize 63 | } 64 | fn entry(&self, idx: usize) -> MemoryEntry { 65 | assert!(idx < self.num_entries()); 66 | let ptr = self.memmap_paddr as *const MemMapEntry; 67 | let entry = unsafe { *ptr.add(idx) }; 68 | MemoryEntry::from(entry) 69 | } 70 | fn kernel_load_addr(&self) -> u64 { 71 | crate::arch::x86_64::layout::KERNEL_START 72 | } 73 | fn memory_layout(&self) -> &'static [MemoryDescriptor] { 74 | &crate::arch::x86_64::layout::MEM_LAYOUT[..] 75 | } 76 | } 77 | 78 | // The PVH Boot Protocol starts at the 32-bit entrypoint to our firmware. 79 | extern "C" { 80 | fn ram32_start(); 81 | } 82 | 83 | // The kind/name/desc of the PHV ELF Note are from xen/include/public/elfnote.h. 84 | // This is the "Physical entry point into the kernel". 85 | const XEN_ELFNOTE_PHYS32_ENTRY: u32 = 18; 86 | type Name = [u8; 4]; 87 | type Desc = unsafe extern "C" fn(); 88 | 89 | // We make sure our ELF Note has an alignment of 4 for maximum compatibility. 90 | // Some software (QEMU) calculates padding incorectly if alignment != 4. 91 | #[repr(C, packed(4))] 92 | struct Note { 93 | name_size: u32, 94 | desc_size: u32, 95 | kind: u32, 96 | name: Name, 97 | desc: Desc, 98 | } 99 | 100 | // This is: ELFNOTE(Xen, XEN_ELFNOTE_PHYS32_ENTRY, .quad ram32_start) 101 | #[cfg(not(test))] 102 | #[link_section = ".note"] 103 | #[used] 104 | static PVH_NOTE: Note = Note { 105 | name_size: size_of::() as u32, 106 | desc_size: size_of::() as u32, 107 | kind: XEN_ELFNOTE_PHYS32_ENTRY, 108 | name: *b"Xen\0", 109 | desc: ram32_start, 110 | }; 111 | -------------------------------------------------------------------------------- /src/rtc.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | #[cfg(target_arch = "aarch64")] 5 | pub use crate::rtc_pl031::{read_date, read_time}; 6 | 7 | #[cfg(target_arch = "x86_64")] 8 | pub use crate::cmos::{read_date, read_time}; 9 | 10 | #[cfg(target_arch = "riscv64")] 11 | pub use crate::rtc_goldfish::{read_date, read_time}; 12 | -------------------------------------------------------------------------------- /src/rtc_goldfish.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Rivos Inc. 3 | 4 | use crate::mem::MemoryRegion; 5 | use atomic_refcell::AtomicRefCell; 6 | use chrono::{DateTime, Datelike, Timelike}; 7 | 8 | // TODO: Fill from FDT 9 | const RTC_GOLDFISH_ADDRESS: u64 = 0x101000; 10 | static RTC_GOLDFISH: AtomicRefCell = 11 | AtomicRefCell::new(RtcGoldfish::new(RTC_GOLDFISH_ADDRESS)); 12 | 13 | pub struct RtcGoldfish { 14 | region: MemoryRegion, 15 | } 16 | 17 | impl RtcGoldfish { 18 | pub const fn new(base: u64) -> RtcGoldfish { 19 | RtcGoldfish { 20 | region: MemoryRegion::new(base, 8), 21 | } 22 | } 23 | 24 | fn read_ts(&self) -> u64 { 25 | const NSECS_PER_SEC: u64 = 1_000_000_000; 26 | 27 | let low = u64::from(self.region.io_read_u32(0x0)); 28 | let high = u64::from(self.region.io_read_u32(0x04)); 29 | 30 | let t = high << 32 | low; 31 | t / NSECS_PER_SEC 32 | } 33 | } 34 | 35 | pub fn read_date() -> Result<(u8, u8, u8), ()> { 36 | let ts = RTC_GOLDFISH.borrow_mut().read_ts(); 37 | 38 | let datetime = DateTime::from_timestamp(ts as i64, 0).ok_or(())?; 39 | let date = datetime.date_naive(); 40 | Ok(( 41 | (date.year() - 2000) as u8, 42 | date.month() as u8, 43 | date.day() as u8, 44 | )) 45 | } 46 | 47 | pub fn read_time() -> Result<(u8, u8, u8), ()> { 48 | let ts = RTC_GOLDFISH.borrow_mut().read_ts(); 49 | let datetime = DateTime::from_timestamp(ts as i64, 0).ok_or(())?; 50 | let time = datetime.time(); 51 | Ok((time.hour() as u8, time.minute() as u8, time.second() as u8)) 52 | } 53 | -------------------------------------------------------------------------------- /src/rtc_pl031.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use atomic_refcell::AtomicRefCell; 5 | use chrono::{DateTime, Datelike, Timelike}; 6 | 7 | use crate::{arch::aarch64::layout::map, mem}; 8 | 9 | static RTC: AtomicRefCell = AtomicRefCell::new(Pl031::new(map::mmio::PL031_START)); 10 | 11 | struct Pl031 { 12 | region: mem::MemoryRegion, 13 | } 14 | 15 | impl Pl031 { 16 | const RTCDR: u64 = 0x000; 17 | 18 | pub const fn new(base: usize) -> Self { 19 | Self { 20 | region: mem::MemoryRegion::new(base as u64, 0x1000), 21 | } 22 | } 23 | 24 | fn read_timestamp(&self) -> u32 { 25 | self.region.io_read_u32(Self::RTCDR) 26 | } 27 | 28 | pub fn read_date(&self) -> Result<(u8, u8, u8), ()> { 29 | let timestamp = self.read_timestamp(); 30 | let datetime = DateTime::from_timestamp(timestamp as i64, 0).ok_or(())?; 31 | let date = datetime.date_naive(); 32 | Ok(( 33 | (date.year() - 2000) as u8, 34 | date.month() as u8, 35 | date.day() as u8, 36 | )) 37 | } 38 | 39 | pub fn read_time(&self) -> Result<(u8, u8, u8), ()> { 40 | let timestamp = self.read_timestamp(); 41 | let datetime = DateTime::from_timestamp(timestamp as i64, 0).ok_or(())?; 42 | let time = datetime.time(); 43 | Ok((time.hour() as u8, time.minute() as u8, time.second() as u8)) 44 | } 45 | } 46 | 47 | pub fn read_date() -> Result<(u8, u8, u8), ()> { 48 | RTC.borrow_mut().read_date() 49 | } 50 | 51 | pub fn read_time() -> Result<(u8, u8, u8), ()> { 52 | RTC.borrow_mut().read_time() 53 | } 54 | -------------------------------------------------------------------------------- /src/serial.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | // Inspired by https://github.com/phil-opp/blog_os/blob/post-03/src/vga_buffer.rs 5 | // from Philipp Oppermann 6 | 7 | use core::fmt; 8 | 9 | use atomic_refcell::AtomicRefCell; 10 | 11 | #[cfg(target_arch = "aarch64")] 12 | use crate::{arch::aarch64::layout::map, uart_pl011::Pl011 as UartPl011}; 13 | 14 | #[cfg(target_arch = "x86_64")] 15 | use uart_16550::SerialPort as Uart16550; 16 | 17 | #[cfg(target_arch = "riscv64")] 18 | use crate::uart_mmio::UartMmio; 19 | 20 | // We use COM1 as it is the standard first serial port. 21 | #[cfg(target_arch = "x86_64")] 22 | pub static PORT: AtomicRefCell = AtomicRefCell::new(unsafe { Uart16550::new(0x3f8) }); 23 | 24 | #[cfg(target_arch = "aarch64")] 25 | pub static PORT: AtomicRefCell = 26 | AtomicRefCell::new(UartPl011::new(map::mmio::PL011_START)); 27 | 28 | // TODO: Fill from FDT? 29 | #[cfg(target_arch = "riscv64")] 30 | const SERIAL_PORT_ADDRESS: u64 = 0x1000_0000; 31 | #[cfg(target_arch = "riscv64")] 32 | pub static PORT: AtomicRefCell = AtomicRefCell::new(UartMmio::new(SERIAL_PORT_ADDRESS)); 33 | 34 | pub struct Serial; 35 | impl fmt::Write for Serial { 36 | fn write_str(&mut self, s: &str) -> fmt::Result { 37 | PORT.borrow_mut().write_str(s) 38 | } 39 | } 40 | 41 | #[macro_export] 42 | macro_rules! log { 43 | ($($arg:tt)*) => {{ 44 | use core::fmt::Write; 45 | #[cfg(all(feature = "log-serial", not(test)))] 46 | writeln!($crate::serial::Serial, $($arg)*).unwrap(); 47 | #[cfg(all(feature = "log-serial", test))] 48 | println!($($arg)*); 49 | }}; 50 | } 51 | -------------------------------------------------------------------------------- /src/uart_mmio.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2023 Rivos Inc. 3 | 4 | use crate::mem::MemoryRegion; 5 | use core::fmt; 6 | 7 | pub struct UartMmio { 8 | region: MemoryRegion, 9 | } 10 | 11 | impl UartMmio { 12 | pub const fn new(base: u64) -> UartMmio { 13 | UartMmio { 14 | region: MemoryRegion::new(base, 8), 15 | } 16 | } 17 | 18 | fn send(&mut self, byte: u8) { 19 | self.region.io_write_u8(0, byte) 20 | } 21 | 22 | pub fn init(&mut self) {} 23 | } 24 | 25 | impl fmt::Write for UartMmio { 26 | fn write_str(&mut self, s: &str) -> fmt::Result { 27 | for byte in s.bytes() { 28 | self.send(byte); 29 | } 30 | Ok(()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/uart_pl011.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright (C) 2022 Akira Moroo 3 | 4 | use core::fmt; 5 | 6 | pub struct Pl011 { 7 | base: usize, 8 | } 9 | 10 | impl Pl011 { 11 | pub const fn new(base: usize) -> Self { 12 | Self { base } 13 | } 14 | 15 | pub fn init(&mut self) { 16 | // Do nothing 17 | } 18 | 19 | pub fn send(&mut self, data: u8) { 20 | unsafe { 21 | core::ptr::write_volatile(self.base as *mut u8, data); 22 | } 23 | } 24 | } 25 | 26 | impl fmt::Write for Pl011 { 27 | fn write_str(&mut self, s: &str) -> fmt::Result { 28 | for byte in s.bytes() { 29 | // Unix-like OS treats LF as CRLF 30 | if byte == b'\n' { 31 | self.send(b'\r'); 32 | } 33 | self.send(byte); 34 | } 35 | Ok(()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/virtio.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache-2.0 2 | // Copyright © 2019 Intel Corporation 3 | 4 | /// Virtio related errors 5 | #[derive(Debug)] 6 | pub enum Error { 7 | UnsupportedDevice, 8 | LegacyOnly, 9 | FeatureNegotiationFailed, 10 | QueueTooSmall, 11 | } 12 | 13 | /// Trait to allow separation of transport from block driver 14 | pub trait VirtioTransport { 15 | fn init(&mut self, device_type: u32) -> Result<(), Error>; 16 | fn get_status(&self) -> u32; 17 | fn set_status(&self, status: u32); 18 | fn add_status(&self, status: u32); 19 | #[allow(dead_code)] 20 | fn reset(&self); 21 | fn get_features(&self) -> u64; 22 | fn set_features(&self, features: u64); 23 | fn set_queue(&self, queue: u16); 24 | fn get_queue_max_size(&self) -> u16; 25 | fn set_queue_size(&self, queue_size: u16); 26 | fn set_descriptors_address(&self, address: u64); 27 | fn set_avail_ring(&self, address: u64); 28 | fn set_used_ring(&self, address: u64); 29 | fn set_queue_enable(&self); 30 | fn notify_queue(&self, queue: u16); 31 | fn read_device_config(&self, offset: u64) -> u32; 32 | } 33 | -------------------------------------------------------------------------------- /x86_64-unknown-none.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", 4 | "arch": "x86_64", 5 | "target-endian": "little", 6 | "target-pointer-width": "64", 7 | "target-c-int-width": "32", 8 | "os": "none", 9 | "executables": true, 10 | "linker": "rust-lld", 11 | "linker-flavor": "gnu-lld", 12 | "panic-strategy": "abort", 13 | "disable-redzone": true, 14 | "features": "-mmx,-sse,+soft-float", 15 | "code-model": "small", 16 | "relocation-model": "pic", 17 | "rustc-abi": "x86-softfloat", 18 | "pre-link-args": { 19 | "gnu-lld": [ 20 | "--script=x86_64-unknown-none.ld" 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /x86_64-unknown-none.ld: -------------------------------------------------------------------------------- 1 | ENTRY(ram32_start) /* coreboot uses the ELF entrypoint */ 2 | 3 | PHDRS 4 | { 5 | ram PT_LOAD FILEHDR PHDRS ; 6 | note PT_NOTE ; 7 | } 8 | 9 | /* Loaders like to put stuff in low memory (< 1M), so we don't use it. */ 10 | ram_min = 1M; 11 | 12 | SECTIONS 13 | { 14 | /* Mapping the program headers and note into RAM makes the file smaller. */ 15 | . = ram_min; 16 | . += SIZEOF_HEADERS; 17 | .note : { *(.note) } :note :ram 18 | 19 | /* These sections are mapped into RAM from the file. Omitting :ram from 20 | later sections avoids emitting empty sections in the final binary. */ 21 | .rodata : { *(.rodata .rodata.*) } :ram 22 | . = ALIGN(4K); 23 | code_start = .; 24 | .text : { *(.text .text.*) } 25 | .text32 : { *(.text32) } 26 | . = ALIGN(4K); 27 | code_end = .; 28 | 29 | data_start = .; 30 | .data : { *(.data .data.*) } 31 | .got : { *(.got .got.*) } 32 | 33 | /* The BSS section isn't mapped from file data. It is just zeroed in RAM. */ 34 | .bss : { 35 | *(.bss .bss.*) 36 | } 37 | . = ALIGN(4K); 38 | data_end = .; 39 | 40 | /* Our stack grows down and is page-aligned. TODO: Add stack guard pages. */ 41 | stack_start = .; 42 | .stack (NOLOAD) : ALIGN(4K) { . += 128K; } 43 | /* ram32.s only maps the first 2 MiB, and that must include the stack. */ 44 | ASSERT((. <= 2M), "Stack overflows initial identity-mapped memory region") 45 | stack_end = .; 46 | 47 | /* Strip symbols from the output binary (comment out to get symbols) */ 48 | /DISCARD/ : { 49 | *(.symtab) 50 | *(.strtab) 51 | } 52 | } 53 | --------------------------------------------------------------------------------