diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec3a8e20..6162625f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,6 +9,14 @@ jobs: if: github.event_name == 'pull_request' name: Build runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-none.json + tests: true + - target: aarch64-unknown-none.json + tests: false steps: - name: Code checkout uses: actions/checkout@v2 @@ -16,23 +24,20 @@ jobs: fetch-depth: 0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - - name: Install rust-src - run: rustup component add rust-src - - name: Install clippy - run: rustup component add clippy - - name: Install rustfmt - run: rustup component add rustfmt + - name: Install Rust components + run: rustup component add rust-src clippy rustfmt - name: Build (debug) - run: cargo build --target x86_64-unknown-none.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem + run: cargo build --target ${{ matrix.target }} -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem - name: Build (release) - run: cargo build --release --target x86_64-unknown-none.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem + run: cargo build --release --target ${{ matrix.target }} -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem - name: Clippy (default) - run: cargo clippy --target x86_64-unknown-none.json -Zbuild-std=core,alloc + run: cargo clippy --target ${{ matrix.target }} -Zbuild-std=core,alloc - name: Clippy (all targets, all features) run: cargo clippy --all-targets --all-features - name: Formatting run: cargo fmt --all -- --check - - name: Unit tests + - if: ${{ matrix.tests }} + name: Unit tests run: | sudo apt-get install -y mtools bash scripts/run_unit_tests.sh diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 13dfaa70..b7c209f1 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -18,24 +18,32 @@ jobs: - name: Code checkout uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Get active Rust toolchain id: get-toolchain run: echo "toolchain=`rustup show active-toolchain | cut -d ' ' -f1`" >> $GITHUB_ENV - name: Login to DockerHub if: ${{ github.repository == 'cloud-hypervisor/rust-hypervisor-firmware' && github.event_name == 'push' }} - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: file: ./resources/Dockerfile build-args: | RUST_TOOLCHAIN=${{ env.toolchain }} - platforms: linux/amd64 + platforms: | + linux/arm64 + linux/amd64 push: ${{ github.repository == 'cloud-hypervisor/rust-hypervisor-firmware' && github.event_name == 'push' }} tags: rusthypervisorfirmware/dev:latest diff --git a/Cargo.lock b/Cargo.lock index 04330ad7..05cf9228 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,25 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "cortex-a" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cd4524931a4e0ec50ae91f0d55f571f31ffe11dd9ce2f9905b8343c018c25bb" +dependencies = [ + "tock-registers", +] + [[package]] name = "dirs" version = "4.0.0" @@ -67,6 +86,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fdt" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964f5becd44d069dca0beea2b4bc05639ae7bf3b3f5369c295aff360bb57cca2" + [[package]] name = "getrandom" version = "0.2.2" @@ -84,12 +109,16 @@ version = "0.4.2" dependencies = [ "atomic_refcell", "bitflags", + "chrono", + "cortex-a", "dirs", + "fdt", "linked_list_allocator", "r-efi", "rand", "ssh2", "tempfile", + "tock-registers", "uart_16550", "x86_64", ] @@ -153,6 +182,25 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "openssl-sys" version = "0.9.61" @@ -320,6 +368,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "tock-registers" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c" + [[package]] name = "uart_16550" version = "0.2.18" diff --git a/Cargo.toml b/Cargo.toml index 66b4fe87..23bfe559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,12 @@ atomic_refcell = "0.1.9" r-efi = { version = "4.1.0", features = ["efiapi"] } linked_list_allocator = "0.10.4" +[target.'cfg(target_arch = "aarch64")'.dependencies] +tock-registers = "0.8.1" +cortex-a = "8.0.0" +fdt = "0.1.4" +chrono = { version = "0.4", default-features = false } + [target.'cfg(target_arch = "x86_64")'.dependencies] uart_16550 = "0.2.18" x86_64 = "0.14.10" diff --git a/Jenkinsfile b/Jenkinsfile index 08fbfd98..9e96d611 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -62,6 +62,21 @@ pipeline { } } } + stage ('AArch64 Unit Tests') { + agent { node { label 'focal-arm64' } } + stages { + stage ('Checkout') { + steps { + checkout scm + } + } + stage('Run unit tests') { + steps { + sh "scripts/dev_cli.sh --local tests --unit" + } + } + } + } } } } diff --git a/aarch64-unknown-none.json b/aarch64-unknown-none.json new file mode 100644 index 00000000..076e72fa --- /dev/null +++ b/aarch64-unknown-none.json @@ -0,0 +1,20 @@ +{ + "llvm-target": "aarch64-unknown-none", + "abi": "softfloat", + "arch": "aarch64", + "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", + "disable-redzone": true, + "features": "+strict-align,-neon,-fp-armv8", + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "os": "none", + "executables": true, + "max-atomic-width": 128, + "panic-strategy": "abort", + "code-model": "small", + "relocation-model": "pic", + "target-pointer-width": "64", + "pre-link-args": { + "ld.lld": ["--script=aarch64-unknown-none.ld", "--oformat=binary"] + } +} diff --git a/aarch64-unknown-none.ld b/aarch64-unknown-none.ld new file mode 100644 index 00000000..5042bae8 --- /dev/null +++ b/aarch64-unknown-none.ld @@ -0,0 +1,44 @@ +ENTRY(ram64_start) + +/* Cloud Hypervisor Memory layout: + DRAM: [0x4000_0000-0xfc00_0000] + FDT: [0x4000_0000-0x401f_ffff) + ACPI: [0x4020_0000-0x403f_ffff) + kernel: [0x4048_0000-] + The stack start is at the end of the DRAM region. */ +ram_min = 0x40480000; + +SECTIONS +{ + /* Mapping the program headers and note into RAM makes the file smaller. */ + . = ram_min; + + /* These sections are mapped into RAM from the file. Omitting :ram from + later sections avoids emitting empty sections in the final binary. */ + code_start = .; + .text.boot : { *(.text.boot) } + .text : { *(.text .text.*) } + . = ALIGN(4K); + code_end = .; + + data_start = .; + .data : { *(.data .data.*) } + .rodata : { *(.rodata .rodata.*) } + + /* The BSS section isn't mapped from file data. It is just zeroed in RAM. */ + .bss : { + *(.bss .bss.*) + } + . = ALIGN(4K); + data_end = .; + + stack_start = .; + .stack (NOLOAD) : ALIGN(4K) { . += 128K; } + stack_end = .; + + /* Strip symbols from the output binary (comment out to get symbols) */ + /DISCARD/ : { + *(.symtab) + *(.strtab) + } +} diff --git a/build.rs b/build.rs index 92c2bbe1..738fcdd7 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,6 @@ fn main() { + println!("cargo:rerun-if-changed=aarch64-unknown-none.json"); + println!("cargo:rerun-if-changed=aarch64-unknown-none.ld"); println!("cargo:rerun-if-changed=x86_64-unknown-none.json"); println!("cargo:rerun-if-changed=x86_64-unknown-none.ld"); } diff --git a/resources/Dockerfile b/resources/Dockerfile index 1dbca29f..3361263b 100644 --- a/resources/Dockerfile +++ b/resources/Dockerfile @@ -14,7 +14,8 @@ ENV PATH="$PATH:$CARGO_HOME/bin" ENV COREBOOT_DIR=/opt/coreboot/src # Install all CI dependencies -RUN apt-get update \ +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + apt-get update \ && apt-get -yq upgrade \ && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ build-essential \ @@ -49,7 +50,29 @@ RUN apt-get update \ zlib1g-dev \ gnat-9 \ && apt-get clean \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* \ + ; fi + +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + apt-get update \ + && apt-get -yq upgrade \ + && DEBIAN_FRONTEND=noninteractive apt-get install -yq \ + build-essential \ + docker.io \ + curl \ + wget \ + sudo \ + mtools \ + libssl-dev \ + pkg-config \ + qemu-utils \ + libseccomp-dev \ + libcap-ng-dev \ + libcap2-bin \ + dosfstools \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + ; fi # Fix the libssl-dev install RUN export ARCH="$(uname -m)" \ @@ -62,22 +85,26 @@ ENV OPENSSL_INCLUDE_DIR=/usr/include/ # Install the rust toolchain RUN export ARCH="$(uname -m)" \ - && nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \ - && rustup component add rustfmt \ - && rustup component add clippy \ - && rustup component add rust-src \ - && rm -rf "$CARGO_HOME/registry" \ - && ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \ - && rm -rf "$CARGO_HOME/git" \ - && ln -s "$CARGO_GIT_REGISTRY_DIR" "$CARGO_HOME/git" + && nohup curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN" \ + && rustup component add rustfmt \ + && rustup component add clippy \ + && rustup component add rust-src \ + && rustup target add aarch64-unknown-linux-gnu \ + && rustup target add x86_64-unknown-linux-gnu \ + && rm -rf "$CARGO_HOME/registry" \ + && ln -s "$CARGO_REGISTRY_DIR" "$CARGO_HOME/registry" \ + && rm -rf "$CARGO_HOME/git" \ + && ln -s "$CARGO_GIT_REGISTRY_DIR" "$CARGO_HOME/git" # Set the rust environment RUN echo 'source $CARGO_HOME/env' >> $HOME/.bashrc \ - && mkdir $HOME/.cargo \ - && ln -s $CARGO_HOME/env $HOME/.cargo/env + && mkdir $HOME/.cargo \ + && ln -s $CARGO_HOME/env $HOME/.cargo/env # Checkout coreboot repository and setup cross toolchains -RUN git clone --quiet --branch "$COREBOOT_VERSION" --depth 1 https://github.com/coreboot/coreboot.git "$COREBOOT_DIR" \ - && cd "$COREBOOT_DIR" \ - && git submodule update --init --checkout \ - && make crossgcc-i386 CPUS=`nproc` +RUN if [ "$TARGETARCH" = "amd64" ]; then \ + git clone --quiet --branch "$COREBOOT_VERSION" --depth 1 https://github.com/coreboot/coreboot.git "$COREBOOT_DIR" \ + && cd "$COREBOOT_DIR" \ + && git submodule update --init --checkout \ + && make crossgcc-i386 CPUS=`nproc`; \ + fi diff --git a/rust-toolchain.toml b/rust-toolchain.toml index be86d058..e4893771 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] channel = "nightly-2022-11-12" components = ["rust-src", "clippy", "rustfmt"] -targets = ["x86_64-unknown-linux-gnu"] +targets = ["aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"] profile = "default" diff --git a/scripts/dev_cli.sh b/scripts/dev_cli.sh index 8f030067..faf8e259 100755 --- a/scripts/dev_cli.sh +++ b/scripts/dev_cli.sh @@ -157,9 +157,44 @@ ensure_build_dir() { # Make sure we're using the latest dev container, by just pulling it. ensure_latest_ctr() { - $DOCKER_RUNTIME pull "$CTR_IMAGE" + if [ "$CTR_IMAGE_VERSION" = "local" ]; then + build_container + else + arch="$(uname -m)" + [ "$arch" = "aarch64" ] && PLATFORM="linux/arm64" + [ "$arch" = "x86_64" ] && PLATFORM="linux/amd64" - ok_or_die "Error pulling container image. Aborting." + $DOCKER_RUNTIME pull --platform "$PLATFORM" "$CTR_IMAGE" + + if [ $? -ne 0 ]; then + build_container + fi + + ok_or_die "Error pulling container image. Aborting." + fi +} + +build_container() { + arch="$(uname -m)" + + ensure_build_dir + + BUILD_DIR="$RHF_CTR_BUILD_DIR" + + mkdir -p $BUILD_DIR + cp $RHF_DOCKERFILE $BUILD_DIR + + [ "$arch" = "aarch64" ] && TARGETARCH="arm64" + [ "$arch" = "x86_64" ] && TARGETARCH="amd64" + + RUST_TOOLCHAIN="$(rustup show active-toolchain | cut -d ' ' -f1)" + + $DOCKER_RUNTIME build \ + -t $CTR_IMAGE \ + -f $BUILD_DIR/Dockerfile \ + --build-arg TARGETARCH=$TARGETARCH \ + --build-arg RUST_TOOLCHAIN=$RUST_TOOLCHAIN \ + $BUILD_DIR } cmd_help() { @@ -202,6 +237,7 @@ cmd_help() { } cmd_build() { + arch="$(uname -m)" build="debug" features_build="" exported_device="dev/kvm" @@ -227,20 +263,22 @@ cmd_build() { process_volumes_args + [ "$arch" = "aarch64" ] && target="aarch64-unknown-none.json" + [ "$arch" = "x86_64" ] && target="x86_64-unknown-none.json" + cargo_args=("$@") [ $build = "release" ] && cargo_args+=("--release") rustflags="" $DOCKER_RUNTIME run \ - --user "$(id -u):$(id -g)" \ --workdir "$CTR_RHF_ROOT_DIR" \ --rm \ --volume $exported_device \ --volume "$RHF_ROOT_DIR:$CTR_RHF_ROOT_DIR" $exported_volumes \ --env RUSTFLAGS="$rustflags" \ "$CTR_IMAGE" \ - cargo build --target "x86_64-unknown-none.json" \ + cargo build --target "$target" \ -Zbuild-std=core,alloc \ -Zbuild-std-features=compiler-builtins-mem \ --target-dir "$CTR_RHF_CARGO_TARGET" \ @@ -254,7 +292,6 @@ cmd_clean() { ensure_latest_ctr $DOCKER_RUNTIME run \ - --user "$(id -u):$(id -g)" \ --workdir "$CTR_RHF_ROOT_DIR" \ --rm \ --volume "$RHF_ROOT_DIR:$CTR_RHF_ROOT_DIR" $exported_volumes \ @@ -265,6 +302,7 @@ cmd_clean() { } cmd_tests() { + arch="$(uname -m)" unit=false cargo=false integration=false @@ -284,7 +322,7 @@ cmd_tests() { shift arg_vols="$1" ;; - "--all") { cargo=true; unit=true; integration=true; } ;; + "--all") { cargo=true; unit=true; [ "$arch" = "x86_64" ] && integration=true; } ;; "--") { shift; break; } ;; *) die "Unknown tests argument: $1. Please use --help for help." @@ -293,6 +331,18 @@ cmd_tests() { shift done + if [ "$(uname -m)" = "aarch64" ] ; then + if [ "$integration" = true ] ; then + die "Integration test is not supported for aarch64." + fi + if [ "$integration_coreboot" = true ] ; then + die "coreboot integration test is not supported for aarch64." + fi + if [ "$integration_windows" = true ] ; then + die "Windows integration test is not supported for aarch64." + fi + fi + ensure_build_dir ensure_latest_ctr @@ -378,12 +428,11 @@ cmd_tests() { cmd_build-container() { - container_type="dev" + arch="$(uname -m)" while [ $# -gt 0 ]; do case "$1" in "-h"|"--help") { cmd_help; exit 1; } ;; - "--dev") { container_type="dev"; } ;; "--") { shift; break; } ;; *) die "Unknown build-container argument: $1. Please use --help for help." @@ -399,7 +448,8 @@ cmd_build-container() { mkdir -p $BUILD_DIR cp $RHF_DOCKERFILE $BUILD_DIR - [ $(uname -m) = "x86_64" ] && TARGETARCH="amd64" + [ "$arch" = "aarch64" ] && TARGETARCH="arm64" + [ "$arch" = "x86_64" ] && TARGETARCH="amd64" RUST_TOOLCHAIN="$(rustup show active-toolchain | cut -d ' ' -f1)" $DOCKER_RUNTIME build \ @@ -455,6 +505,10 @@ cmd_shell() { while [ $# -gt 0 ]; do case "$1" in -h|--help) { cmd_help; exit 1; } ;; + -l|--local) { + CTR_IMAGE_VERSION="local" + CTR_IMAGE="${CTR_IMAGE_TAG}:${CTR_IMAGE_VERSION}" + } ;; -y|--unattended) { OPT_UNATTENDED=true; } ;; -*) die "Unknown arg: $1. Please use \`$0 help\` for help." diff --git a/scripts/run_cargo_tests.sh b/scripts/run_cargo_tests.sh index 711ef710..85c80205 100755 --- a/scripts/run_cargo_tests.sh +++ b/scripts/run_cargo_tests.sh @@ -6,17 +6,37 @@ source "${CARGO_HOME:-$HOME/.cargo}/env" export RUSTFLAGS="-D warnings" +arch="$(uname -m)" + +do_cargo_tests() { + local cargo_args=("-Zbuild-std=core,alloc" "-Zbuild-std-features=compiler-builtins-mem") + local cmd="$1" + local target="$2" + local features="$3" + [ -n "$features" ] && cargo_args+=("--features" "$features") + time cargo "$cmd" --target "$target" "${cargo_args[@]}" + time cargo "$cmd" --target "$target" --release "${cargo_args[@]}" +} + +cargo_tests() { + local features="$1" + + [ "$arch" = "aarch64" ] && target="aarch64-unknown-none.json" + [ "$arch" = "x86_64" ] && target="x86_64-unknown-none.json" + + do_cargo_tests "build" "$target" "$features" + do_cargo_tests "clippy" "$target" "$features" +} + # Install cargo components time rustup component add clippy time rustup component add rustfmt time rustup component add rust-src # Run cargo builds and checks -time cargo build --target x86_64-unknown-none.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem -time cargo build --release --target x86_64-unknown-none.json -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem -time cargo build --target x86_64-unknown-none.json --features "coreboot" -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem -time cargo build --release --target x86_64-unknown-none.json --features "coreboot" -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem -time cargo clippy --target x86_64-unknown-none.json -Zbuild-std=core,alloc -time cargo clippy --target x86_64-unknown-none.json -Zbuild-std=core,alloc --features "coreboot" +cargo_tests "" +if [ "$arch" = "x86_64" ] ; then + cargo_tests "coreboot" +fi time cargo clippy --all-targets --all-features time cargo fmt --all -- --check diff --git a/src/arch/aarch64/asm.rs b/src/arch/aarch64/asm.rs new file mode 100644 index 00000000..d7baac8e --- /dev/null +++ b/src/arch/aarch64/asm.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo + +use super::layout::map; +use core::arch::global_asm; + +global_asm!(include_str!("ram64.s"), + FDT_START = const map::dram::FDT_START); diff --git a/src/arch/aarch64/layout.rs b/src/arch/aarch64/layout.rs new file mode 100644 index 00000000..54382fc1 --- /dev/null +++ b/src/arch/aarch64/layout.rs @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo +// Copyright (c) 2021-2022 Andre Richter + +use core::{ + cell::UnsafeCell, + ops::{Range, RangeInclusive}, +}; + +use crate::layout::{MemoryAttribute, MemoryDescriptor, MemoryLayout}; + +use super::paging::*; + +extern "Rust" { + static code_start: UnsafeCell<()>; + static code_end: UnsafeCell<()>; + static data_start: UnsafeCell<()>; + static data_end: UnsafeCell<()>; + static stack_start: UnsafeCell<()>; + static stack_end: UnsafeCell<()>; +} + +pub mod map { + pub const END: usize = 0x1_0000_0000; + + pub mod fw { + pub const START: usize = 0x0000_0000; + pub const END: usize = 0x0040_0000; + } + pub mod mmio { + pub const START: usize = super::fw::END; + pub const PL011_START: usize = 0x0900_0000; + pub const PL031_START: usize = 0x0901_0000; + pub const END: usize = 0x4000_0000; + } + + pub mod dram { + const FDT_SIZE: usize = 0x0020_0000; + const ACPI_SIZE: usize = 0x0020_0000; + + pub const START: usize = super::mmio::END; + pub const FDT_START: usize = START; + pub const ACPI_START: usize = FDT_START + FDT_SIZE; + pub const KERNEL_START: usize = ACPI_START + ACPI_SIZE; + pub const END: usize = super::END; + } +} + +pub type KernelAddrSpace = AddressSpace<{ map::END }>; + +const NUM_MEM_RANGES: usize = 3; + +pub static LAYOUT: KernelVirtualLayout = KernelVirtualLayout::new( + map::END - 1, + [ + TranslationDescriptor { + name: "Firmware", + virtual_range: RangeInclusive::new(map::fw::START, map::fw::END - 1), + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: false, + }, + }, + TranslationDescriptor { + name: "Device MMIO", + virtual_range: RangeInclusive::new(map::mmio::START, map::mmio::END - 1), + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::Device, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + }, + }, + TranslationDescriptor { + name: "System Memory", + virtual_range: RangeInclusive::new(map::dram::START, map::dram::END - 1), + physical_range_translation: Translation::Identity, + attribute_fields: AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, // FIXME + execute_never: false, + }, + }, + ], +); + +pub fn virt_mem_layout() -> &'static KernelVirtualLayout { + &LAYOUT +} + +pub fn reserved_range() -> Range { + map::dram::START..map::dram::KERNEL_START +} + +pub fn code_range() -> Range { + unsafe { (code_start.get() as _)..(code_end.get() as _) } +} + +pub fn data_range() -> Range { + unsafe { (data_start.get() as _)..(data_end.get() as _) } +} + +pub fn stack_range() -> Range { + unsafe { (stack_start.get() as _)..(stack_end.get() as _) } +} + +const NUM_MEM_DESCS: usize = 4; + +pub static MEM_LAYOUT: MemoryLayout = [ + MemoryDescriptor { + name: "Reserved", + range: reserved_range, + attribute: MemoryAttribute::Unusable, + }, + MemoryDescriptor { + name: "Code", + range: code_range, + attribute: MemoryAttribute::Code, + }, + MemoryDescriptor { + name: "Data", + range: data_range, + attribute: MemoryAttribute::Data, + }, + MemoryDescriptor { + name: "Stack", + range: stack_range, + attribute: MemoryAttribute::Data, + }, +]; diff --git a/src/arch/aarch64/mod.rs b/src/arch/aarch64/mod.rs new file mode 100644 index 00000000..1ad69e8b --- /dev/null +++ b/src/arch/aarch64/mod.rs @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo + +#[cfg(not(test))] +pub mod asm; +pub mod layout; +pub mod paging; +mod translation; diff --git a/src/arch/aarch64/paging.rs b/src/arch/aarch64/paging.rs new file mode 100644 index 00000000..cc456517 --- /dev/null +++ b/src/arch/aarch64/paging.rs @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo +// Copyright (c) 2021-2022 Andre Richter + +use core::ops::RangeInclusive; + +use cortex_a::{asm::barrier, registers::*}; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; + +use self::interface::Mmu; + +use super::{layout::KernelAddrSpace, translation::TranslationTable}; + +/// MMU enable errors variants. +#[derive(Debug)] +pub enum MmuEnableError { + AlreadyEnabled, + Other(&'static str), +} + +/// Memory Management interfaces. +pub mod interface { + use super::*; + + /// MMU functions. + pub trait Mmu { + unsafe fn enable_mmu_and_caching(&self) -> Result<(), MmuEnableError>; + + fn is_enabled(&self) -> bool; + } +} + +/// Describes the characteristics of a translation granule. +pub struct TranslationGranule; + +/// Describes properties of an address space. +pub struct AddressSpace; + +/// Architecture agnostic translation types. +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum Translation { + Identity, + Offset(usize), +} + +/// Architecture agnostic memory attributes. +#[derive(Copy, Clone)] +pub enum MemAttributes { + CacheableDRAM, + Device, +} + +/// Architecture agnostic access permissions. +#[derive(Copy, Clone)] +pub enum AccessPermissions { + ReadOnly, + ReadWrite, +} + +/// Collection of memory attributes. +#[derive(Copy, Clone)] +pub struct AttributeFields { + pub mem_attributes: MemAttributes, + pub acc_perms: AccessPermissions, + pub execute_never: bool, +} + +impl Default for AttributeFields { + fn default() -> AttributeFields { + AttributeFields { + mem_attributes: MemAttributes::CacheableDRAM, + acc_perms: AccessPermissions::ReadWrite, + execute_never: true, + } + } +} + +/// Architecture agnostic descriptor for a memory range. +pub struct TranslationDescriptor { + pub name: &'static str, + pub virtual_range: RangeInclusive, + pub physical_range_translation: Translation, + pub attribute_fields: AttributeFields, +} + +/// Type for expressing the kernel's virtual memory layout. +pub struct KernelVirtualLayout { + /// The last (inclusive) address of the address space. + max_virt_addr_inclusive: usize, + + /// Array of descriptors for non-standard (normal cacheable DRAM) memory regions. + inner: [TranslationDescriptor; NUM_SPECIAL_RANGES], +} + +impl TranslationGranule { + /// The granule's size. + pub const SIZE: usize = Self::size_checked(); + + /// The granule's shift, aka log2(size). + pub const SHIFT: usize = Self::SIZE.trailing_zeros() as usize; + + const fn size_checked() -> usize { + assert!(GRANULE_SIZE.is_power_of_two()); + + GRANULE_SIZE + } +} + +impl AddressSpace { + /// The address space size. + pub const SIZE: usize = Self::size_checked(); + + /// The address space shift, aka log2(size). + pub const SIZE_SHIFT: usize = Self::SIZE.trailing_zeros() as usize; + + const fn size_checked() -> usize { + assert!(AS_SIZE.is_power_of_two()); + + // Check for architectural restrictions as well. + Self::arch_address_space_size_sanity_checks(); + + AS_SIZE + } +} + +impl KernelVirtualLayout<{ NUM_SPECIAL_RANGES }> { + /// Create a new instance. + pub const fn new(max: usize, layout: [TranslationDescriptor; NUM_SPECIAL_RANGES]) -> Self { + Self { + max_virt_addr_inclusive: max, + inner: layout, + } + } + + /// For a virtual address, find and return the physical output address and corresponding + /// attributes. + /// + /// If the address is not found in `inner`, return an identity mapped default with normal + /// cacheable DRAM attributes. + pub fn virt_addr_properties( + &self, + virt_addr: usize, + ) -> Result<(usize, AttributeFields), &'static str> { + if virt_addr > self.max_virt_addr_inclusive { + return Err("Address out of range"); + } + + for i in self.inner.iter() { + if i.virtual_range.contains(&virt_addr) { + let output_addr = match i.physical_range_translation { + Translation::Identity => virt_addr, + Translation::Offset(a) => a + (virt_addr - (i.virtual_range).start()), + }; + + return Ok((output_addr, i.attribute_fields)); + } + } + + Ok((virt_addr, AttributeFields::default())) + } +} + +/// Memory Management Unit type. +struct MemoryManagementUnit; + +pub type Granule512MiB = TranslationGranule<{ 512 * 1024 * 1024 }>; +pub type Granule64KiB = TranslationGranule<{ 64 * 1024 }>; + +/// Constants for indexing the MAIR_EL1. +pub mod mair { + pub const DEVICE: u64 = 0; + pub const NORMAL: u64 = 1; +} + +/// The kernel translation tables. +/// +/// # Safety +/// +/// - Supposed to land in `.bss`. Therefore, ensure that all initial member values boil down to "0". +static mut KERNEL_TABLES: TranslationTable = TranslationTable::new(); + +static MMU: MemoryManagementUnit = MemoryManagementUnit; + +impl AddressSpace { + /// Checks for architectural restrictions. + pub const fn arch_address_space_size_sanity_checks() { + // Size must be at least one full 512 MiB table. + assert!((AS_SIZE % Granule512MiB::SIZE) == 0); + + // Check for 48 bit virtual address size as maximum, which is supported by any ARMv8 + // version. + assert!(AS_SIZE <= (1 << 48)); + } +} + +impl MemoryManagementUnit { + /// Setup function for the MAIR_EL1 register. + fn setup_mair(&self) { + // Define the memory types being mapped. + MAIR_EL1.write( + // Attribute 1 - Cacheable normal DRAM. + MAIR_EL1::Attr1_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc + + MAIR_EL1::Attr1_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc + // Attribute 0 - Device. + + MAIR_EL1::Attr0_Device::nonGathering_nonReordering_EarlyWriteAck, + ); + } + + /// Configure various settings of stage 1 of the EL1 translation regime. + fn configure_translation_control(&self) { + let t0sz = (64 - KernelAddrSpace::SIZE_SHIFT) as u64; + + TCR_EL1.write( + TCR_EL1::TBI0::Used + + TCR_EL1::IPS::Bits_40 + + TCR_EL1::TG0::KiB_64 + + TCR_EL1::SH0::Inner + + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::EPD0::EnableTTBR0Walks + + TCR_EL1::A1::TTBR0 + + TCR_EL1::T0SZ.val(t0sz) + + TCR_EL1::EPD1::DisableTTBR1Walks, + ); + } +} + +/// Return a reference to the MMU instance. +fn mmu() -> &'static impl interface::Mmu { + &MMU +} + +impl interface::Mmu for MemoryManagementUnit { + unsafe fn enable_mmu_and_caching(&self) -> Result<(), MmuEnableError> { + if self.is_enabled() { + return Err(MmuEnableError::AlreadyEnabled); + } + + // Fail early if translation granule is not supported. + if !ID_AA64MMFR0_EL1.matches_all(ID_AA64MMFR0_EL1::TGran64::Supported) { + return Err(MmuEnableError::Other( + "Translation granule not supported in HW", + )); + } + + // Prepare the memory attribute indirection register. + self.setup_mair(); + + // Populate translation tables. + KERNEL_TABLES + .populate_tt_entries() + .map_err(MmuEnableError::Other)?; + + // Set the "Translation Table Base Register". + TTBR0_EL1.set_baddr(KERNEL_TABLES.phys_base_address()); + + self.configure_translation_control(); + + // Switch the MMU on. + // + // First, force all previous changes to be seen before the MMU is enabled. + barrier::isb(barrier::SY); + + // Enable the MMU and turn on data and instruction caching. + SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable); + + // Force MMU init to complete before next instruction. + barrier::isb(barrier::SY); + + Ok(()) + } + + #[inline(always)] + fn is_enabled(&self) -> bool { + SCTLR_EL1.matches_all(SCTLR_EL1::M::Enable) + } +} + +pub fn setup() { + unsafe { + if let Err(e) = mmu().enable_mmu_and_caching() { + panic!("Failed to setup paging: {:?}", e); + } + } +} diff --git a/src/arch/aarch64/ram64.s b/src/arch/aarch64/ram64.s new file mode 100644 index 00000000..93e28e63 --- /dev/null +++ b/src/arch/aarch64/ram64.s @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +/* Copyright (C) 2022 Akira Moroo */ + +.section .text.boot, "ax" +.global ram64_start + +ram64_start: + /* + * This header follows the AArch64 Linux kernel image header [1] to load + * as a PE binary by the hypervisor. + * + * [1] https://docs.kernel.org/arm64/booting.html#call-the-kernel-image + */ + add x13, x18, #0x16 /* code0: UEFI "MZ" signature magic instruction */ + b jump_to_rust /* code1 */ + + .quad 0 /* text_offset */ + .quad 0 /* image_size */ + .quad 0 /* flags */ + .quad 0 /* res2 */ + .quad 0 /* res3 */ + .quad 0 /* res4 */ + + .long 0x644d5241 /* "ARM\x64" magic number */ + .long 0 /* res5 */ + .align 3 + +jump_to_rust: + /* x0 typically points to device tree at entry */ + ldr x0, ={FDT_START} + + /* setup stack */ + ldr x30, =stack_end + mov sp, x30 + + /* x0: pointer to device tree */ + b rust64_start \ No newline at end of file diff --git a/src/arch/aarch64/translation.rs b/src/arch/aarch64/translation.rs new file mode 100644 index 00000000..903712e7 --- /dev/null +++ b/src/arch/aarch64/translation.rs @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo +// Copyright (c) 2021-2022 Andre Richter + +//! Architectural translation table. +//! +//! Only 64 KiB granule is supported. + +use core::convert; + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_bitfields, + registers::InMemoryRegister, +}; + +use super::{layout, paging::*}; + +// A table descriptor, as per ARMv8-A Architecture Reference Manual Figure D5-15. +register_bitfields! {u64, + STAGE1_TABLE_DESCRIPTOR [ + /// Physical address of the next descriptor. + NEXT_LEVEL_TABLE_ADDR_64KiB OFFSET(16) NUMBITS(32) [], // [47:16] + + TYPE OFFSET(1) NUMBITS(1) [ + Block = 0, + Table = 1 + ], + + VALID OFFSET(0) NUMBITS(1) [ + False = 0, + True = 1 + ] + ] +} + +// A level 3 page descriptor, as per ARMv8-A Architecture Reference Manual Figure D5-17. +register_bitfields! {u64, + STAGE1_PAGE_DESCRIPTOR [ + /// Unprivileged execute-never. + UXN OFFSET(54) NUMBITS(1) [ + False = 0, + True = 1 + ], + + /// Privileged execute-never. + PXN OFFSET(53) NUMBITS(1) [ + False = 0, + True = 1 + ], + + /// Physical address of the next table descriptor (lvl2) or the page descriptor (lvl3). + OUTPUT_ADDR_64KiB OFFSET(16) NUMBITS(32) [], + + /// Access flag. + AF OFFSET(10) NUMBITS(1) [ + False = 0, + True = 1 + ], + + /// Shareability field. + SH OFFSET(8) NUMBITS(2) [ + OuterShareable = 0b10, + InnerShareable = 0b11 + ], + + /// Access Permissions. + AP OFFSET(6) NUMBITS(2) [ + RW_EL1 = 0b00, + RW_EL1_EL0 = 0b01, + RO_EL1 = 0b10, + RO_EL1_EL0 = 0b11 + ], + + /// Memory attributes index into the MAIR_EL1 register. + AttrIndx OFFSET(2) NUMBITS(3) [], + + TYPE OFFSET(1) NUMBITS(1) [ + Reserved_Invalid = 0, + Page = 1 + ], + + VALID OFFSET(0) NUMBITS(1) [ + False = 0, + True = 1 + ] + ] +} + +/// A table descriptor for 64 KiB aperture. +/// +/// The output points to the next table. +#[derive(Copy, Clone)] +#[repr(C)] +struct TableDescriptor { + value: u64, +} + +/// A page descriptor with 64 KiB aperture. +/// +/// The output points to physical memory. +#[derive(Copy, Clone)] +#[repr(C)] +struct PageDescriptor { + value: u64, +} + +trait StartAddr { + fn phys_start_addr_u64(&self) -> u64; + fn phys_start_addr_usize(&self) -> usize; +} + +const NUM_LVL2_TABLES: usize = layout::KernelAddrSpace::SIZE >> Granule512MiB::SHIFT; + +/// Big monolithic struct for storing the translation tables. Individual levels must be 64 KiB +/// aligned, so the lvl3 is put first. +#[repr(C)] +#[repr(align(65536))] +pub struct FixedSizeTranslationTable { + /// Page descriptors, covering 64 KiB windows per entry. + lvl3: [[PageDescriptor; 8192]; NUM_TABLES], + + /// Table descriptors, covering 512 MiB windows. + lvl2: [TableDescriptor; NUM_TABLES], +} + +/// A translation table type for the kernel space. +pub type TranslationTable = FixedSizeTranslationTable; + +// The binary is still identity mapped, so we don't need to convert here. +impl StartAddr for [T; N] { + fn phys_start_addr_u64(&self) -> u64 { + self as *const T as u64 + } + + fn phys_start_addr_usize(&self) -> usize { + self as *const _ as usize + } +} + +impl TableDescriptor { + /// Create an instance. + /// + /// Descriptor is invalid by default. + pub const fn new_zeroed() -> Self { + Self { value: 0 } + } + + /// Create an instance pointing to the supplied address. + pub fn from_next_lvl_table_addr(phys_next_lvl_table_addr: usize) -> Self { + let val = InMemoryRegister::::new(0); + + let shifted = phys_next_lvl_table_addr >> Granule64KiB::SHIFT; + val.write( + STAGE1_TABLE_DESCRIPTOR::NEXT_LEVEL_TABLE_ADDR_64KiB.val(shifted as u64) + + STAGE1_TABLE_DESCRIPTOR::TYPE::Table + + STAGE1_TABLE_DESCRIPTOR::VALID::True, + ); + + Self { value: val.get() } + } +} + +/// Convert the kernel's generic memory attributes to HW-specific attributes of the MMU. +impl convert::From + for tock_registers::fields::FieldValue +{ + fn from(attribute_fields: AttributeFields) -> Self { + // Memory attributes. + let mut desc = match attribute_fields.mem_attributes { + MemAttributes::CacheableDRAM => { + STAGE1_PAGE_DESCRIPTOR::SH::InnerShareable + + STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(mair::NORMAL) + } + MemAttributes::Device => { + STAGE1_PAGE_DESCRIPTOR::SH::OuterShareable + + STAGE1_PAGE_DESCRIPTOR::AttrIndx.val(mair::DEVICE) + } + }; + + // Access Permissions. + desc += match attribute_fields.acc_perms { + AccessPermissions::ReadOnly => STAGE1_PAGE_DESCRIPTOR::AP::RO_EL1, + AccessPermissions::ReadWrite => STAGE1_PAGE_DESCRIPTOR::AP::RW_EL1, + }; + + // The execute-never attribute is mapped to PXN in AArch64. + desc += if attribute_fields.execute_never { + STAGE1_PAGE_DESCRIPTOR::PXN::True + } else { + STAGE1_PAGE_DESCRIPTOR::PXN::False + }; + + // Always set unprivileged exectue-never as long as userspace is not implemented yet. + desc += STAGE1_PAGE_DESCRIPTOR::UXN::True; + + desc + } +} + +impl PageDescriptor { + /// Create an instance. + /// + /// Descriptor is invalid by default. + pub const fn new_zeroed() -> Self { + Self { value: 0 } + } + + /// Create an instance. + pub fn from_output_addr(phys_output_addr: usize, attribute_fields: &AttributeFields) -> Self { + let val = InMemoryRegister::::new(0); + + let shifted = phys_output_addr as u64 >> Granule64KiB::SHIFT; + val.write( + STAGE1_PAGE_DESCRIPTOR::OUTPUT_ADDR_64KiB.val(shifted) + + STAGE1_PAGE_DESCRIPTOR::AF::True + + STAGE1_PAGE_DESCRIPTOR::TYPE::Page + + STAGE1_PAGE_DESCRIPTOR::VALID::True + + (*attribute_fields).into(), + ); + + Self { value: val.get() } + } +} + +impl FixedSizeTranslationTable { + /// Create an instance. + pub const fn new() -> Self { + // Can't have a zero-sized address space. + assert!(NUM_TABLES > 0); + + Self { + lvl3: [[PageDescriptor::new_zeroed(); 8192]; NUM_TABLES], + lvl2: [TableDescriptor::new_zeroed(); NUM_TABLES], + } + } + + /// Iterates over all static translation table entries and fills them at once. + /// + /// # Safety + /// + /// - Modifies a `static mut`. Ensure it only happens from here. + pub unsafe fn populate_tt_entries(&mut self) -> Result<(), &'static str> { + for (l2_nr, l2_entry) in self.lvl2.iter_mut().enumerate() { + *l2_entry = + TableDescriptor::from_next_lvl_table_addr(self.lvl3[l2_nr].phys_start_addr_usize()); + + for (l3_nr, l3_entry) in self.lvl3[l2_nr].iter_mut().enumerate() { + let virt_addr = (l2_nr << Granule512MiB::SHIFT) + (l3_nr << Granule64KiB::SHIFT); + + let (phys_output_addr, attribute_fields) = + layout::virt_mem_layout().virt_addr_properties(virt_addr)?; + + *l3_entry = PageDescriptor::from_output_addr(phys_output_addr, &attribute_fields); + } + } + + Ok(()) + } + + /// The translation table's base address to be used for programming the MMU. + pub fn phys_base_address(&self) -> u64 { + self.lvl2.phys_start_addr_u64() + } +} diff --git a/src/arch/mod.rs b/src/arch/mod.rs index 080a70d8..f98be70b 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2022 Akira Moroo +#[cfg(target_arch = "aarch64")] +pub mod aarch64; + #[cfg(target_arch = "x86_64")] pub mod x86_64; diff --git a/src/delay.rs b/src/delay.rs index acb2daec..086c9b38 100644 --- a/src/delay.rs +++ b/src/delay.rs @@ -10,12 +10,26 @@ const NSECS_PER_SEC: u64 = 1000000000; const CPU_KHZ_DEFAULT: u64 = 200; const PAUSE_THRESHOLD_TICKS: u64 = 150; +#[cfg(target_arch = "aarch64")] +#[inline] +unsafe fn rdtsc() -> u64 { + let value: u64; + asm!("mrs {}, cntvct_el0", out(reg) value); + value +} + #[cfg(target_arch = "x86_64")] #[inline] unsafe fn rdtsc() -> u64 { _rdtsc() } +#[cfg(target_arch = "aarch64")] +#[inline] +unsafe fn pause() { + asm!("yield"); +} + #[cfg(target_arch = "x86_64")] #[inline] unsafe fn pause() { diff --git a/src/efi/mod.rs b/src/efi/mod.rs index 53f6ab19..7a6b7e59 100644 --- a/src/efi/mod.rs +++ b/src/efi/mod.rs @@ -932,6 +932,9 @@ fn populate_allocator(info: &dyn bootinfo::Info, image_address: u64, image_size: } } + #[cfg(target_arch = "aarch64")] + use crate::arch::aarch64::layout::MEM_LAYOUT; + #[cfg(target_arch = "x86_64")] use crate::arch::x86_64::layout::MEM_LAYOUT; @@ -1114,6 +1117,9 @@ pub fn efi_exec( let wrapped_fs = file::FileSystemWrapper::new(fs, efi_part_id); let image = new_image_handle( + #[cfg(target_arch = "aarch64")] + "\\EFI\\BOOT\\BOOTAA64.EFI", + #[cfg(target_arch = "x86_64")] "\\EFI\\BOOT\\BOOTX64.EFI", 0 as Handle, &wrapped_fs as *const _ as Handle, diff --git a/src/fdt.rs b/src/fdt.rs new file mode 100644 index 00000000..6daa94a3 --- /dev/null +++ b/src/fdt.rs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo + +use fdt::Fdt; + +use crate::bootinfo::{EntryType, Info, MemoryEntry}; + +pub struct StartInfo<'a> { + fdt: Fdt<'a>, +} + +impl StartInfo<'_> { + pub fn new(ptr: *const u8) -> Self { + let fdt = unsafe { + match Fdt::from_ptr(ptr) { + Ok(fdt) => fdt, + Err(e) => panic!("Failed to create device tree object: {:?}", e), + } + }; + + Self { fdt } + } + + pub fn find_compatible_region(&self, with: &[&str]) -> Option<(*const u8, usize)> { + let node = self.fdt.find_compatible(with)?; + if let Some(region) = node.reg()?.next() { + return Some((region.starting_address, region.size?)); + } + None + } +} + +impl Info for StartInfo<'_> { + fn name(&self) -> &str { + "FDT" + } + + fn rsdp_addr(&self) -> u64 { + // TODO: Remove reference to a platform specific value. + crate::arch::aarch64::layout::map::dram::ACPI_START as u64 + } + + fn cmdline(&self) -> &[u8] { + match self.fdt.chosen().bootargs() { + Some(s) => s.as_bytes(), + None => b"", + } + } + + fn num_entries(&self) -> usize { + self.fdt.memory().regions().count() + } + + fn entry(&self, idx: usize) -> MemoryEntry { + for (i, region) in self.fdt.memory().regions().enumerate() { + if i == idx { + return MemoryEntry { + addr: region.starting_address as u64, + size: region.size.expect("memory size is required") as u64, + entry_type: EntryType::Ram, + }; + } + } + panic!("No valid memory entry found"); + } +} diff --git a/src/main.rs b/src/main.rs index 0a84aeab..ea1d485b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,7 @@ // limitations under the License. #![feature(abi_efiapi)] +#![feature(asm_const)] #![feature(alloc_error_handler)] #![feature(stmt_expr_attributes)] #![feature(slice_take)] @@ -43,18 +44,23 @@ mod coreboot; mod delay; mod efi; mod fat; +#[cfg(target_arch = "aarch64")] +mod fdt; #[cfg(all(test, feature = "integration_tests"))] mod integration; mod layout; mod loader; mod mem; mod part; -#[cfg(target_arch = "x86_64")] mod pci; mod pe; #[cfg(target_arch = "x86_64")] mod pvh; mod rtc; +#[cfg(target_arch = "aarch64")] +mod rtc_pl031; +#[cfg(target_arch = "aarch64")] +mod uart_pl011; mod virtio; #[cfg(all(not(test), feature = "log-panic"))] @@ -112,16 +118,23 @@ fn boot_from_device(device: &mut block::VirtioBlockDevice, info: &dyn bootinfo:: } log!("Using EFI boot."); - let mut file = match f.open("/EFI/BOOT/BOOTX64 EFI") { + #[cfg(target_arch = "aarch64")] + let efi_boot_path = "/EFI/BOOT/BOOTAA64.EFI"; + #[cfg(target_arch = "x86_64")] + let efi_boot_path = "/EFI/BOOT/BOOTX64 EFI"; + let mut file = match f.open(efi_boot_path) { Ok(file) => file, Err(err) => { log!("Failed to load default EFI binary: {:?}", err); return false; } }; - log!("Found bootloader (BOOTX64.EFI)"); + log!("Found bootloader: {}", efi_boot_path); let mut l = pe::Loader::new(&mut file); + #[cfg(target_arch = "aarch64")] + let load_addr = arch::aarch64::layout::map::dram::KERNEL_START as u64; + #[cfg(target_arch = "x86_64")] let load_addr = 0x20_0000; let (entry_addr, load_addr, size) = match l.load(load_addr) { Ok(load_info) => load_info, @@ -153,7 +166,22 @@ pub extern "C" fn rust64_start(#[cfg(not(feature = "coreboot"))] pvh_info: &pvh: main(info) } -#[cfg(target_arch = "x86_64")] +#[cfg(target_arch = "aarch64")] +#[no_mangle] +pub extern "C" fn rust64_start(x0: *const u8) -> ! { + serial::PORT.borrow_mut().init(); + + arch::aarch64::paging::setup(); + + let info = fdt::StartInfo::new(x0); + + if let Some((base, length)) = info.find_compatible_region(&["pci-host-ecam-generic"]) { + pci::init(base as u64, length as u64); + } + + main(&info) +} + fn main(info: &dyn bootinfo::Info) -> ! { log!("\nBooting with {}", info.name()); diff --git a/src/pci.rs b/src/pci.rs index 9b9d1292..59d0ddf4 100644 --- a/src/pci.rs +++ b/src/pci.rs @@ -30,6 +30,11 @@ const INVALID_VENDOR_ID: u16 = 0xffff; static PCI_CONFIG: AtomicRefCell = AtomicRefCell::new(PciConfig::new()); +#[cfg(target_arch = "aarch64")] +struct PciConfig { + region: Option, +} + #[cfg(target_arch = "x86_64")] struct PciConfig { address_port: PortWriteOnly, @@ -37,6 +42,12 @@ struct PciConfig { } impl PciConfig { + #[cfg(target_arch = "aarch64")] + const fn new() -> Self { + // We use Enhanced Configuration Access Mechanism (ECAM). + Self { region: None } + } + #[cfg(target_arch = "x86_64")] const fn new() -> Self { // We use the legacy, port-based Configuration Access Mechanism (CAM). @@ -46,6 +57,19 @@ impl PciConfig { } } + #[cfg(target_arch = "aarch64")] + fn init(&mut self, base: u64, length: u64) { + self.region = Some(mem::MemoryRegion::new(base, length)); + } + + #[cfg(target_arch = "aarch64")] + fn read_at(&mut self, addr: u32) -> u32 { + self.region + .as_ref() + .expect("PCI config space is not initialized") + .io_read_u32(addr as u64) + } + #[cfg(target_arch = "x86_64")] fn read_at(&mut self, addr: u32) -> u32 { let addr = addr | 1u32 << 31; // enable bit 31 @@ -74,6 +98,11 @@ impl PciConfig { } } +#[cfg(target_arch = "aarch64")] +pub fn init(base: u64, length: u64) { + PCI_CONFIG.borrow_mut().init(base, length); +} + fn get_device_details(bus: u8, device: u8, func: u8) -> (u16, u16) { let data = PCI_CONFIG.borrow_mut().read(bus, device, func, 0); ((data & 0xffff) as u16, (data >> 16) as u16) diff --git a/src/pe.rs b/src/pe.rs index ac79c5bb..699bcc44 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -38,10 +38,12 @@ struct Section { } impl<'a> Loader<'a> { + #[cfg(target_arch = "aarch64")] + const MACHINE_TYPE: u16 = 0xaa64; #[cfg(target_arch = "x86_64")] const MACHINE_TYPE: u16 = 0x8664; - #[cfg(target_arch = "x86_64")] + #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))] const OPTIONAL_HEADER_MAGIC: u16 = 0x20b; // PE32+ pub fn new(file: &'a mut dyn crate::fat::Read) -> Loader { @@ -268,6 +270,8 @@ mod tests { use std::alloc; + // TODO: Add aarch64 specific loader test target + #[cfg(target_arch = "x86_64")] #[test] fn test_loader() { let d = FakeDisk::new(&clear_disk_path()); diff --git a/src/rtc.rs b/src/rtc.rs index 5074c2ae..3bb63967 100644 --- a/src/rtc.rs +++ b/src/rtc.rs @@ -1,5 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright (C) 2022 Akira Moroo +#[cfg(target_arch = "aarch64")] +pub use crate::rtc_pl031::{read_date, read_time}; + #[cfg(target_arch = "x86_64")] pub use crate::cmos::{read_date, read_time}; diff --git a/src/rtc_pl031.rs b/src/rtc_pl031.rs new file mode 100644 index 00000000..c7dc05dd --- /dev/null +++ b/src/rtc_pl031.rs @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo + +use atomic_refcell::AtomicRefCell; +use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc}; + +use crate::{arch::aarch64::layout::map, mem}; + +static RTC: AtomicRefCell = AtomicRefCell::new(Pl031::new(map::mmio::PL031_START)); + +struct Pl031 { + region: mem::MemoryRegion, +} + +impl Pl031 { + const RTCDR: u64 = 0x000; + + pub const fn new(base: usize) -> Self { + Self { + region: mem::MemoryRegion::new(base as u64, 0x1000), + } + } + + fn read_timestamp(&self) -> u32 { + self.region.io_read_u32(Self::RTCDR) + } + + pub fn read_date(&self) -> Result<(u8, u8, u8), ()> { + let timestamp = self.read_timestamp(); + let naive = NaiveDateTime::from_timestamp(timestamp as i64, 0); + let datetime: DateTime = DateTime::from_utc(naive, Utc); + Ok(( + (datetime.year() - 2000) as u8, + datetime.month() as u8, + datetime.day() as u8, + )) + } + + pub fn read_time(&self) -> Result<(u8, u8, u8), ()> { + let timestamp = self.read_timestamp(); + let naive = NaiveDateTime::from_timestamp(timestamp as i64, 0); + let datetime: DateTime = DateTime::from_utc(naive, Utc); + Ok(( + datetime.hour() as u8, + datetime.minute() as u8, + datetime.second() as u8, + )) + } +} + +pub fn read_date() -> Result<(u8, u8, u8), ()> { + RTC.borrow_mut().read_date() +} + +pub fn read_time() -> Result<(u8, u8, u8), ()> { + RTC.borrow_mut().read_time() +} diff --git a/src/serial.rs b/src/serial.rs index 2491765e..38f875c2 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -19,16 +19,22 @@ use core::fmt; use atomic_refcell::AtomicRefCell; +#[cfg(target_arch = "aarch64")] +use crate::{arch::aarch64::layout::map, uart_pl011::Pl011 as UartPl011}; + #[cfg(target_arch = "x86_64")] -use uart_16550::SerialPort; +use uart_16550::SerialPort as Uart16550; // We use COM1 as it is the standard first serial port. #[cfg(target_arch = "x86_64")] -pub static PORT: AtomicRefCell = AtomicRefCell::new(unsafe { SerialPort::new(0x3f8) }); +pub static PORT: AtomicRefCell = AtomicRefCell::new(unsafe { Uart16550::new(0x3f8) }); + +#[cfg(target_arch = "aarch64")] +pub static PORT: AtomicRefCell = + AtomicRefCell::new(UartPl011::new(map::mmio::PL011_START)); pub struct Serial; impl fmt::Write for Serial { - #[cfg(target_arch = "x86_64")] fn write_str(&mut self, s: &str) -> fmt::Result { PORT.borrow_mut().write_str(s) } diff --git a/src/uart_pl011.rs b/src/uart_pl011.rs new file mode 100644 index 00000000..a2e5173b --- /dev/null +++ b/src/uart_pl011.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2022 Akira Moroo + +use core::fmt; + +pub struct Pl011 { + base: usize, +} + +impl Pl011 { + pub const fn new(base: usize) -> Self { + Self { base } + } + + pub fn init(&mut self) { + // Do nothing + } + + pub fn send(&mut self, data: u8) { + unsafe { + core::ptr::write_volatile(self.base as *mut u8, data); + } + } +} + +impl fmt::Write for Pl011 { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + self.send(byte); + } + Ok(()) + } +}