commit 17ca11f239928b74e7c94f62bc6afe2c5b8930ab Author: Alex Zenla Date: Wed Oct 1 16:45:04 2025 -0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f69aab --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +/target + +# Infrastructure +.env +.terraform +terraform.tfstate +terraform.tfstate.backup +aws-packer-config.json +override.install.sh + +# Ignore the google cloud auth creds created in Actions workflows +gha-creds-*.json +crane +tok + +# IDE +/.idea +/.vscode + +# Benchmarks +/results + +# cli markdown output +cli.md + +# precommit hooks +.editorconfig +.markdownlint-cli2.jsonc +.pre-commit-config.yaml +.prettierrc +.shellcheckrc + +# example outputs +/image-cache + +/out diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d5b8dd9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,141 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sprout" +version = "0.1.0" +dependencies = [ + "uefi", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "ucs2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79298e11f316400c57ec268f3c2c29ac3c4d4777687955cd3d4f3a35ce7eba" +dependencies = [ + "bit_field", +] + +[[package]] +name = "uefi" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7569ceafb898907ff764629bac90ac24ba4203c38c33ef79ee88c74aa35b11" +dependencies = [ + "bitflags", + "cfg-if", + "log", + "ptr_meta", + "ucs2", + "uefi-macros", + "uefi-raw", + "uguid", +] + +[[package]] +name = "uefi-macros" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3dad47b3af8f99116c0f6d4d669c439487d9aaf1c8d9480d686cda6f3a8aa23" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "uefi-raw" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cad96b8baaf1615d3fdd0f03d04a0b487d857c1b51b19dcbfe05e2e3c447b78" +dependencies = [ + "bitflags", + "uguid", +] + +[[package]] +name = "uguid" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab14ea9660d240e7865ce9d54ecdbd1cd9fa5802ae6f4512f093c7907e921533" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6ea09b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sprout" +version = "0.1.0" +edition = "2024" + +[dependencies.uefi] +version = "0.35.0" +features = ["alloc"] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d4f3ab0 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# syntax=docker/dockerfile:1.7-labs +ARG RUST_PROFILE=release +ARG RUST_TARGET_SUBDIR=release + +FROM --platform=$BUILDPLATFORM rustlang/rust:nightly-alpine@sha256:b8107fa66d3e5ad7f729d3347c7feedbd3f4b60b01006edce39eb6b994ff00bd AS build +RUN apk --no-cache add musl-dev busybox-static +ARG RUST_PROFILE +RUN adduser -S -s /bin/sh build +COPY \ + --exclude=rust-toolchain.toml \ + --chown=build:build \ + . /build +WORKDIR /build +ARG TARGETPLATFORM +ARG RUST_TARGET_SUBDIR +RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ] || [ "${TARGETPLATFORM}" = "linux/x86_64" ]; then \ + rustup target add x86_64-unknown-uefi; cargo build --bin sprout --profile "${RUST_PROFILE}" --target x86_64-unknown-uefi && \ + cp "target/x86_64-unknown-uefi/${RUST_TARGET_SUBDIR}/sprout.efi" /sprout.efi; fi +RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ] || [ "${TARGETPLATFORM}" = "linux/aarch64" ]; then \ + rustup target add aarch64-unknown-uefi; cargo build --bin sprout --profile "${RUST_PROFILE}" --target aarch64-unknown-uefi && \ + cp "target/aarch64-unknown-uefi/${RUST_TARGET_SUBDIR}/sprout.efi" /sprout.efi; fi + +FROM scratch AS final +COPY --from=build /sprout.efi /sprout.efi diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3dc7e5b --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +This repository is proprietary and owned by Edera, Inc. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e403557 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# sprout + +The Edera bootloader. diff --git a/boot/Dockerfile b/boot/Dockerfile new file mode 100644 index 0000000..6a62684 --- /dev/null +++ b/boot/Dockerfile @@ -0,0 +1,22 @@ +FROM --platform=$BUILDPLATFORM debian:trixie@sha256:fd8f5a1df07b5195613e4b9a0b6a947d3772a151b81975db27d47f093f60c6e6 AS build +ARG BUILDPLATFORM +ARG EFI_NAME +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y \ + parted dosfstools mtools && \ + rm -rf /var/lib/apt/lists/* +WORKDIR /work +COPY sprout.efi /work/${EFI_NAME}.EFI +COPY kernel.efi /work/KERNEL.EFI +RUN truncate -s256MiB sprout.img && \ + parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \ + parted --script sprout.img mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \ + parted --script sprout.img set 1 esp on > /dev/null 2>&1 && \ + mkfs.vfat -F32 -n EFI sprout.img && \ + mmd -i sprout.img ::/EFI && \ + mmd -i sprout.img ::/EFI/BOOT && \ + mcopy -i sprout.img ${EFI_NAME}.EFI ::/EFI/BOOT/ && \ + mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \ + mv sprout.img /sprout.img + +FROM scratch AS final +COPY --from=build /sprout.img /sprout.img diff --git a/hack/autofix.sh b/hack/autofix.sh new file mode 100755 index 0000000..649d1d3 --- /dev/null +++ b/hack/autofix.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +cd "$(dirname "${0}")/.." || exit 1 + +NATIVE_ARCH="$(uname -m)" +[ "${NATIVE_ARCH}" = "arm64" ] && NATIVE_ARCH="aarch64" +[ "${NATIVE_ARCH}" = "amd64" ] && NATIVE_ARCH="x86_64" + +if [ "$(uname)" != "Linux" ]; then + cargo clippy --workspace --fix --allow-dirty --allow-staged \ + --target "${NATIVE_ARCH}-unknown-uefi" +else + cargo clippy --workspace --fix --allow-dirty --allow-staged +fi + +./hack/format.sh diff --git a/hack/boot.sh b/hack/boot.sh new file mode 100755 index 0000000..aa9d6c6 --- /dev/null +++ b/hack/boot.sh @@ -0,0 +1,48 @@ +#!/bin/sh +set -e + +cd "$(dirname "${0}")/.." || exit 1 + +. "hack/common.sh" + +./hack/build.sh "${TARGET_ARCH}" "${RUST_PROFILE}" + +clear + +set -- + +if [ "${TARGET_ARCH}" = "x86_64" ]; then + set -- "${@}" qemu-system-x86_64 -M q35 +elif [ "${TARGET_ARCH}" = "aarch64" ]; then + set -- "${@}" qemu-system-aarch64 -M virt -cpu cortex-a57 +fi + +set -- "${@}" -smp 2 -m 4096 + +if [ "${NO_GRAPHICAL_BOOT}" = "1" ]; then + set -- "${@}" -nographic +else + set -- "${@}" -serial stdio -vga none -device "virtio-gpu,edid=on,xres=1024,yres=768" +fi + +rm -f "${FINAL_DIR}/ovmf-boot.fd" +cp "${FINAL_DIR}/ovmf.fd" "${FINAL_DIR}/ovmf-boot.fd" +if [ "${TARGET_ARCH}" = "aarch64" ]; then + dd if=/dev/zero of="${FINAL_DIR}/ovmf-boot.fd" bs=1 count=1 seek=67108863 >/dev/null 2>&1 +fi +# shellcheck disable=SC2086 +set -- "${@}" \ + -drive "if=pflash,file=${FINAL_DIR}/ovmf-boot.fd,format=raw,readonly=on" \ + -device nvme,drive=disk1,serial=cafebabe + +if [ "${DISK_BOOT}" = "1" ]; then + set -- "${@}" \ + -drive "if=none,file=${FINAL_DIR}/sprout.img,format=raw,id=disk1,readonly=on" +else + set -- "${@}" \ + -drive "if=none,file=fat:rw:${FINAL_DIR}/efi,format=raw,id=disk1" +fi + +set -- "${@}" -name "sprout ${TARGET_ARCH}" + +exec "${@}" diff --git a/hack/build.sh b/hack/build.sh new file mode 100755 index 0000000..81e1068 --- /dev/null +++ b/hack/build.sh @@ -0,0 +1,72 @@ +#!/bin/sh +set -e + +cd "$(dirname "${0}")/.." || exit 1 + +. "hack/common.sh" + +EFI_NAME="BOOTX64" +if [ "${TARGET_ARCH}" = "aarch64" ]; then + EFI_NAME="BOOTAA64" +fi + +echo "[build] ${TARGET_ARCH} ${RUST_PROFILE}" + +if ! command -v docker >/dev/null 2>&1; then + echo "ERROR: docker is required to build sprout." >/dev/stderr + exit 1 +fi + +export DOCKER_CLI_HINTS="0" + +if [ "${SKIP_CLEANUP}" != 1 ]; then + rm -rf "${FINAL_DIR}" +fi +mkdir -p "${FINAL_DIR}" + +if [ "${SKIP_KERNEL_BUILD}" != "1" ] || [ "${SKIP_VM_BUILD}" != "1" ] || [ "${SKIP_SPROUT_BUILD}" != "1" ]; then + docker build -t "${DOCKER_PREFIX}/sprout-utils-copy:${DOCKER_TAG}" -f hack/utils/Dockerfile.copy hack +fi + +if [ "${SKIP_KERNEL_BUILD}" != "1" ]; then + echo "[kernel build] ${TARGET_ARCH} ${RUST_PROFILE}" + docker build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-kernel-${TARGET_ARCH}:${DOCKER_TAG}" -f kernel/Dockerfile kernel + + if [ "${KERNEL_BUILD_TAG}" = "1" ]; then + docker build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-kernel-build-${TARGET_ARCH}:${DOCKER_TAG}" -f kernel/Dockerfile --target + build kernel + fi + + docker run --rm -i \ + --mount="type=image,source=${DOCKER_PREFIX}/sprout-kernel-${TARGET_ARCH}:${DOCKER_TAG},target=/image" \ + "${DOCKER_PREFIX}/sprout-utils-copy:${DOCKER_TAG}" cat /image/kernel.efi >"${FINAL_DIR}/kernel.efi" +fi + +if [ "${SKIP_VM_BUILD}" != "1" ]; then + echo "[vm build] ${TARGET_ARCH} ${RUST_PROFILE}" + docker build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}:${DOCKER_TAG}" -f vm/Dockerfile.ovmf "${FINAL_DIR}" + docker run --rm -i \ + --mount="type=image,source=${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}:${DOCKER_TAG},target=/image" \ + "${DOCKER_PREFIX}/sprout-utils-copy:${DOCKER_TAG}" cat /image/ovmf.fd >"${FINAL_DIR}/ovmf.fd" +fi + +if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then + echo "[sprout build] ${TARGET_ARCH} ${RUST_PROFILE}" + docker build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-${TARGET_ARCH}:${DOCKER_TAG}" --build-arg="RUST_TARGET_SUBDIR=${RUST_TARGET_SUBDIR}" -f Dockerfile . + docker run --rm -i \ + --mount="type=image,source=${DOCKER_PREFIX}/sprout-${TARGET_ARCH}:${DOCKER_TAG},target=/image" \ + "${DOCKER_PREFIX}/sprout-utils-copy:${DOCKER_TAG}" cat /image/sprout.efi >"${FINAL_DIR}/sprout.efi" + mkdir -p "${FINAL_DIR}/efi/EFI/BOOT" + cp "${FINAL_DIR}/sprout.efi" "${FINAL_DIR}/efi/EFI/BOOT/${EFI_NAME}.EFI" + if [ -f "${FINAL_DIR}/kernel.efi" ]; then + cp "${FINAL_DIR}/kernel.efi" "${FINAL_DIR}/efi/EFI/BOOT/KERNEL.EFI" + fi +fi + +if [ "${SKIP_BOOT_BUILD}" != "1" ]; then + echo "[boot build] ${TARGET_ARCH} ${RUST_PROFILE}" + docker build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-boot-${TARGET_ARCH}:${DOCKER_TAG}" --build-arg "EFI_NAME=${EFI_NAME}" -f boot/Dockerfile "${FINAL_DIR}" + docker run --rm -i \ + --mount="type=image,source=${DOCKER_PREFIX}/sprout-boot-${TARGET_ARCH}:${DOCKER_TAG},target=/image" \ + "${DOCKER_PREFIX}/sprout-utils-copy:${DOCKER_TAG}" cat /image/sprout.img >"${FINAL_DIR}/sprout.img" +fi diff --git a/hack/clean.sh b/hack/clean.sh new file mode 100755 index 0000000..ae9a858 --- /dev/null +++ b/hack/clean.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +cd "$(dirname "${0}")/.." || exit 1 + +. "hack/common.sh" + +delete_image() { + IMAGE="${1}" + docker image ls -q --no-trunc --filter "reference=${DOCKER_PREFIX}/${IMAGE}" | xargs -rn1 docker image rm +} + +cargo clean || true + +if command -v docker >/dev/null 2>&1; then + delete_image sprout-utils-copy || true + delete_image sprout-ovmf || true + delete_image sprout-x86_64 || true + delete_image sprout-aarch64 || true +fi diff --git a/hack/common.sh b/hack/common.sh new file mode 100644 index 0000000..a966ce2 --- /dev/null +++ b/hack/common.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# shellcheck disable=SC2034 +set -e + +DOCKER_PREFIX="ghcr.io/edera-dev/sprout" +DEFAULT_RUST_PROFILE="release" +DEFAULT_DOCKER_TAG="latest" + +[ -z "${TARGET_ARCH}" ] && TARGET_ARCH="${1}" +{ [ -z "${TARGET_ARCH}" ] || [ "${TARGET_ARCH}" = "native" ]; } && TARGET_ARCH="$(uname -m)" +[ -z "${RUST_PROFILE}" ] && RUST_PROFILE="${2}" +[ -z "${RUST_PROFILE}" ] && RUST_PROFILE="${DEFAULT_RUST_PROFILE}" + +[ "${TARGET_ARCH}" = "arm64" ] && TARGET_ARCH="aarch64" +[ "${TARGET_ARCH}" = "amd64" ] && TARGET_ARCH="x86_64" + +if [ "${TARGET_ARCH}" != "x86_64" ] && [ "${TARGET_ARCH}" != "aarch64" ]; then + echo "Unsupported Architecture: ${TARGET_ARCH}" >/dev/stderr + exit 1 +fi + +[ "${RUST_PROFILE}" = "debug" ] && RUST_PROFILE="dev" + +RUST_TARGET_SUBDIR="${RUST_PROFILE}" +[ "${RUST_PROFILE}" = "dev" ] && RUST_TARGET_SUBDIR="debug" + +RUST_TARGET="${TARGET_ARCH}-unknown-uefi" + +[ -z "${DOCKER_TAG}" ] && DOCKER_TAG="${DEFAULT_DOCKER_TAG}" +DOCKER_TARGET="linux/${TARGET_ARCH}" +FINAL_DIR="target/final/${TARGET_ARCH}" diff --git a/hack/format.sh b/hack/format.sh new file mode 100755 index 0000000..49f5251 --- /dev/null +++ b/hack/format.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -e + +cd "$(dirname "${0}")/.." || exit 1 + +cargo fmt --all +shfmt -w hack/*.sh diff --git a/hack/utils/Dockerfile.copy b/hack/utils/Dockerfile.copy new file mode 100644 index 0000000..6b2bdbe --- /dev/null +++ b/hack/utils/Dockerfile.copy @@ -0,0 +1 @@ +FROM --platform=$BUILDPLATFORM debian:trixie@sha256:fd8f5a1df07b5195613e4b9a0b6a947d3772a151b81975db27d47f093f60c6e6 diff --git a/kernel/Dockerfile b/kernel/Dockerfile new file mode 100644 index 0000000..746243e --- /dev/null +++ b/kernel/Dockerfile @@ -0,0 +1,37 @@ +ARG KERNEL_SOURCE_URL=https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.16.9.tar.xz +ARG KERNEL_CHECKSUM=sha256:7ac8c8a3cf05476375deaaa85dfcee095a826ffe557b437f43774fc3b64ce58d + +FROM --platform=$BUILDPLATFORM debian:trixie@sha256:fd8f5a1df07b5195613e4b9a0b6a947d3772a151b81975db27d47f093f60c6e6 AS buildenv +RUN export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y \ + build-essential squashfs-tools python3-yaml \ + patch diffutils sed mawk findutils zstd \ + python3 python3-packaging curl rsync cpio \ + flex bison pahole libssl-dev libelf-dev bc kmod && \ + rm -rf /var/lib/apt/lists/* +ARG BUILDPLATFORM +RUN if [ "${BUILDPLATFORM}" = "linux/amd64" ] || [ "${BUILDPLATFORM}" = "linux/x86_64" ]; then \ + apt-get update && apt-get install -y linux-headers-amd64 gcc-aarch64-linux-gnu && rm -rf /var/lib/apt/lists/*; fi +RUN if [ "${BUILDPLATFORM}" = "linux/arm64" ] || [ "${BUILDPLATFORM}" = "linux/aarch64" ]; then \ + apt-get update && apt-get install -y linux-headers-arm64 gcc-x86-64-linux-gnu && rm -rf /var/lib/apt/lists/*; fi +RUN useradd -ms /bin/sh build +COPY --chown=build:build docker-build.sh /build/docker-build.sh +USER build +WORKDIR /build + +FROM scratch AS source +ARG KERNEL_SOURCE_URL +ARG KERNEL_CHECKSUM +ADD --checksum=${KERNEL_CHECKSUM} ${KERNEL_SOURCE_URL} /src.tar.xz + +FROM --platform=$BUILDPLATFORM buildenv AS build +COPY --from=source --chown=build:build /src.tar.xz /build/src.tar.xz +RUN mkdir /build/src && tar -C /build/src --strip-components=1 -xf /build/src.tar.xz && rm /build/src.tar.xz +ARG BUILDPLATFORM +ARG TARGETPLATFORM +ENV BUILDPLATFORM=${BUILDPLATFORM} +ENV TARGETPLATFORM=${TARGETPLATFORM} +WORKDIR /build/src +RUN /build/docker-build.sh + +FROM scratch AS final +COPY --from=build /build/src/kernel.image /kernel.efi diff --git a/kernel/docker-build.sh b/kernel/docker-build.sh new file mode 100755 index 0000000..3d36142 --- /dev/null +++ b/kernel/docker-build.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -e + +TARGET_KARCH="" +TARGET_SARCH="" + +MAYBE_CROSS_COMPILE="" + +CURRENT_SARCH="$(uname -m)" + +[ "${CURRENT_SARCH}" = "amd64" ] && CURRENT_SARCH="x86_64" +[ "${CURRENT_SARCH}" = "arm64" ] && CURRENT_SARCH="aarch64" + +if [ "${TARGETPLATFORM}" = "linux/aarch64" ] || [ "${TARGETPLATFORM}" = "linux/arm64" ]; then + TARGET_KARCH="arm64" + TARGET_SARCH="aarch64" + if [ "${CURRENT_SARCH}" != "${TARGET_SARCH}" ]; then + MAYBE_CROSS_COMPILE="aarch64-linux-gnu-" + fi +elif [ "${TARGETPLATFORM}" = "linux/x86_64" ] || [ "${TARGETPLATFORM}" = "linux/amd64" ]; then + TARGET_KARCH="x86_64" + TARGET_SARCH="x86_64" + if [ "${CURRENT_SARCH}" != "${TARGET_SARCH}" ]; then + MAYBE_CROSS_COMPILE="x86_64-linux-gnu-" + fi +else + echo "Unknown platform: ${TARGETPLATFORM}" >/dev/stderr + exit 1 +fi + +make CROSS_COMPILE="${MAYBE_CROSS_COMPILE}" ARCH="${TARGET_KARCH}" defconfig +make CROSS_COMPILE="${MAYBE_CROSS_COMPILE}" ARCH="${TARGET_KARCH}" mod2yesconfig + +./scripts/config -e DRM_VIRTIO_GPU +./scripts/config -e FRAMEBUFFER_CONSOLE +./scripts/config -e FRAMEBUFFER_CONSOLE_DETECT_PRIMARY + +make "-j$(nproc)" CROSS_COMPILE="${MAYBE_CROSS_COMPILE}" ARCH="${TARGET_KARCH}" + +[ -f "arch/x86/boot/bzImage" ] && cp "arch/x86/boot/bzImage" kernel.image +[ -f "arch/arm64/boot/Image.gz" ] && gzip -d < "arch/arm64/boot/Image.gz" > kernel.image +exit 0 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..fd9858c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "rust-std", "clippy"] +targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] diff --git a/src/chainload.rs b/src/chainload.rs new file mode 100644 index 0000000..f9452b2 --- /dev/null +++ b/src/chainload.rs @@ -0,0 +1,61 @@ +use uefi::{ + CString16, + proto::device_path::{ + DevicePath, LoadedImageDevicePath, PoolDevicePath, + text::{AllowShortcuts, DevicePathFromText, DisplayOnly}, + }, +}; + +fn text_to_device_path(path: &str) -> PoolDevicePath { + let path = CString16::try_from(path).expect("unable to convert path to CString16"); + let device_path_from_text = uefi::boot::open_protocol_exclusive::( + uefi::boot::get_handle_for_protocol::() + .expect("no device path from text protocol"), + ) + .expect("unable to open device path from text protocol"); + + device_path_from_text + .convert_text_to_device_path(&path) + .expect("unable to convert text to device path") +} + +pub fn chainload(path: &str) { + let sprout_image = uefi::boot::image_handle(); + let image_device_path_protocol = + uefi::boot::open_protocol_exclusive::(sprout_image) + .expect("unable to open loaded image protocol"); + + let image_device_path: &DevicePath = &image_device_path_protocol; + let mut full_path = image_device_path + .node_iter() + .filter_map(|item| { + let item = item + .to_string(DisplayOnly(false), AllowShortcuts(false)) + .expect("unable to convert device path to string"); + if item.to_string().contains("(") { + Some(item) + } else { + None + } + }) + .map(|item| item.to_string()) + .collect::>() + .join("/"); + full_path.push('/'); + full_path.push_str(path); + + println!("chainload: {}", full_path); + + let device_path = text_to_device_path(&full_path); + + let image = uefi::boot::load_image( + sprout_image, + uefi::boot::LoadImageSource::FromDevicePath { + device_path: &device_path, + boot_policy: uefi::proto::BootPolicy::ExactMatch, + }, + ) + .expect("failed to load image"); + uefi::boot::start_image(image).expect("failed to start image"); + panic!("chainloaded image exited"); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..74527a0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,10 @@ +#![feature(uefi_std)] +pub mod chainload; +pub mod setup; + +const CHAINLOADER_TARGET: &str = "\\EFI\\BOOT\\KERNEL.efi"; + +fn main() { + setup::init(); + chainload::chainload(CHAINLOADER_TARGET); +} diff --git a/src/setup.rs b/src/setup.rs new file mode 100644 index 0000000..c63275d --- /dev/null +++ b/src/setup.rs @@ -0,0 +1,16 @@ +use std::os::uefi as uefi_std; + +pub fn init() { + let system_table = uefi_std::env::system_table(); + let image_handle = uefi_std::env::image_handle(); + + // SAFETY: The uefi variables above come from the Rust std. + // These variables are nonnull and calling the uefi crates with these values is validated + // to be corrected by hand. + unsafe { + uefi::table::set_system_table(system_table.as_ptr().cast()); + let handle = uefi::Handle::from_ptr(image_handle.as_ptr().cast()) + .expect("unable to resolve image handle"); + uefi::boot::set_image_handle(handle); + } +} diff --git a/vm/Dockerfile.ovmf b/vm/Dockerfile.ovmf new file mode 100644 index 0000000..647b420 --- /dev/null +++ b/vm/Dockerfile.ovmf @@ -0,0 +1,9 @@ +FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 AS build +ARG TARGETPLATFORM +RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ] || [ "${TARGETPLATFORM}" = "linux/x86_64" ]; then \ + apk --no-cache add ovmf; cp /usr/share/ovmf/bios.bin /ovmf.fd; fi +RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ] || [ "${TARGETPLATFORM}" = "linux/aarch64" ]; then \ + apk --no-cache add aavmf; cp /usr/share/AAVMF/QEMU_EFI.fd /ovmf.fd; fi + +FROM scratch AS final +COPY --from=build /ovmf.fd /ovmf.fd