mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 10:10:17 +00:00
add efi shell so that chainloading multiple items can be tested
This commit is contained in:
@@ -8,6 +8,7 @@ WORKDIR /work
|
|||||||
COPY sprout.efi /work/${EFI_NAME}.EFI
|
COPY sprout.efi /work/${EFI_NAME}.EFI
|
||||||
COPY sprout.toml /work/SPROUT.TOML
|
COPY sprout.toml /work/SPROUT.TOML
|
||||||
COPY kernel.efi /work/KERNEL.EFI
|
COPY kernel.efi /work/KERNEL.EFI
|
||||||
|
COPY shell.efi /work/SHELL.EFI
|
||||||
RUN truncate -s256MiB sprout.img && \
|
RUN truncate -s256MiB sprout.img && \
|
||||||
parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \
|
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 mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \
|
||||||
@@ -17,6 +18,7 @@ RUN truncate -s256MiB sprout.img && \
|
|||||||
mmd -i sprout.img ::/EFI/BOOT && \
|
mmd -i sprout.img ::/EFI/BOOT && \
|
||||||
mcopy -i sprout.img ${EFI_NAME}.EFI ::/EFI/BOOT/ && \
|
mcopy -i sprout.img ${EFI_NAME}.EFI ::/EFI/BOOT/ && \
|
||||||
mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \
|
mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \
|
||||||
|
mcopy -i sprout.img SHELL.EFI ::/EFI/BOOT/ && \
|
||||||
mcopy -i sprout.img SPROUT.TOML ::/ && \
|
mcopy -i sprout.img SPROUT.TOML ::/ && \
|
||||||
mv sprout.img /sprout.img
|
mv sprout.img /sprout.img
|
||||||
|
|
||||||
|
|||||||
14
hack/boot.sh
14
hack/boot.sh
@@ -22,7 +22,19 @@ set -- "${@}" -smp 2 -m 4096
|
|||||||
if [ "${NO_GRAPHICAL_BOOT}" = "1" ]; then
|
if [ "${NO_GRAPHICAL_BOOT}" = "1" ]; then
|
||||||
set -- "${@}" -nographic
|
set -- "${@}" -nographic
|
||||||
else
|
else
|
||||||
set -- "${@}" -serial stdio -vga none -device "virtio-gpu,edid=on,xres=1024,yres=768"
|
set -- "${@}" \
|
||||||
|
-device virtio-serial-pci,id=vs0 \
|
||||||
|
-chardev stdio,id=stdio0 \
|
||||||
|
-device virtconsole,chardev=stdio0,id=console0
|
||||||
|
|
||||||
|
if [ "${TARGET_ARCH}" = "x86_64" ]; then
|
||||||
|
set -- "${@}" \
|
||||||
|
-vga std
|
||||||
|
else
|
||||||
|
set -- "${@}" \
|
||||||
|
-vga none \
|
||||||
|
-device "virtio-gpu,edid=on,xres=1024,yres=768"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -f "${FINAL_DIR}/ovmf-boot.fd"
|
rm -f "${FINAL_DIR}/ovmf-boot.fd"
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ if [ "${SKIP_VM_BUILD}" != "1" ]; then
|
|||||||
echo "[vm build] ${TARGET_ARCH} ${RUST_PROFILE}"
|
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 build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}:${DOCKER_TAG}" -f vm/Dockerfile.ovmf "${FINAL_DIR}"
|
||||||
copy_from_image "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}" "ovmf.fd" "${FINAL_DIR}/ovmf.fd"
|
copy_from_image "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}" "ovmf.fd" "${FINAL_DIR}/ovmf.fd"
|
||||||
|
copy_from_image "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}" "shell.efi" "${FINAL_DIR}/shell.efi"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then
|
if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then
|
||||||
@@ -84,6 +85,9 @@ if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then
|
|||||||
if [ -f "${FINAL_DIR}/kernel.efi" ]; then
|
if [ -f "${FINAL_DIR}/kernel.efi" ]; then
|
||||||
cp "${FINAL_DIR}/kernel.efi" "${FINAL_DIR}/efi/EFI/BOOT/KERNEL.EFI"
|
cp "${FINAL_DIR}/kernel.efi" "${FINAL_DIR}/efi/EFI/BOOT/KERNEL.EFI"
|
||||||
fi
|
fi
|
||||||
|
if [ -f "${FINAL_DIR}/shell.efi" ]; then
|
||||||
|
cp "${FINAL_DIR}/shell.efi" "${FINAL_DIR}/efi/EFI/BOOT/SHELL.EFI"
|
||||||
|
fi
|
||||||
cp "hack/configs/kernel.sprout.toml" "${FINAL_DIR}/efi/SPROUT.TOML"
|
cp "hack/configs/kernel.sprout.toml" "${FINAL_DIR}/efi/SPROUT.TOML"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
[[modules]]
|
[[modules]]
|
||||||
chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI" }
|
chainloader = { path = "\\EFI\\BOOT\\SHELL.EFI" }
|
||||||
|
|
||||||
|
[[modules]]
|
||||||
|
chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] }
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ pub struct ModuleConfiguration {
|
|||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct ChainloaderConfiguration {
|
pub struct ChainloaderConfiguration {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub options: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load() -> RootConfiguration {
|
pub fn load() -> RootConfiguration {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use crate::config::ChainloaderConfiguration;
|
use crate::config::ChainloaderConfiguration;
|
||||||
use crate::utils::text_to_device_path;
|
use crate::utils;
|
||||||
use log::info;
|
use log::info;
|
||||||
use uefi::proto::device_path::{
|
use uefi::CString16;
|
||||||
DevicePath, LoadedImageDevicePath,
|
use uefi::proto::device_path::LoadedImageDevicePath;
|
||||||
text::{AllowShortcuts, DisplayOnly},
|
|
||||||
};
|
|
||||||
use uefi::proto::loaded_image::LoadedImage;
|
use uefi::proto::loaded_image::LoadedImage;
|
||||||
|
|
||||||
pub fn chainloader(configuration: ChainloaderConfiguration) {
|
pub fn chainloader(configuration: ChainloaderConfiguration) {
|
||||||
@@ -13,28 +11,12 @@ pub fn chainloader(configuration: ChainloaderConfiguration) {
|
|||||||
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
|
uefi::boot::open_protocol_exclusive::<LoadedImageDevicePath>(sprout_image)
|
||||||
.expect("unable to open loaded image device path protocol");
|
.expect("unable to open loaded image device path protocol");
|
||||||
|
|
||||||
let image_device_path: &DevicePath = &image_device_path_protocol;
|
let mut full_path = utils::device_path_root(&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::<Vec<_>>()
|
|
||||||
.join("/");
|
|
||||||
full_path.push('/');
|
|
||||||
full_path.push_str(&configuration.path);
|
full_path.push_str(&configuration.path);
|
||||||
|
|
||||||
info!("path={}", full_path);
|
info!("path={}", full_path);
|
||||||
|
|
||||||
let device_path = text_to_device_path(&full_path);
|
let device_path = utils::text_to_device_path(&full_path);
|
||||||
|
|
||||||
let image = uefi::boot::load_image(
|
let image = uefi::boot::load_image(
|
||||||
sprout_image,
|
sprout_image,
|
||||||
@@ -45,10 +27,31 @@ pub fn chainloader(configuration: ChainloaderConfiguration) {
|
|||||||
)
|
)
|
||||||
.expect("failed to load image");
|
.expect("failed to load image");
|
||||||
|
|
||||||
let image_device_path_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
|
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
|
||||||
.expect("unable to open loaded image protocol");
|
.expect("unable to open loaded image protocol");
|
||||||
|
|
||||||
let (base, size) = image_device_path_protocol.info();
|
let options = configuration.options.join(" ");
|
||||||
|
if !options.is_empty() {
|
||||||
|
let options = Box::new(
|
||||||
|
CString16::try_from(&options[..])
|
||||||
|
.expect("unable to convert chainloader options to CString16"),
|
||||||
|
);
|
||||||
|
info!("options={}", options);
|
||||||
|
|
||||||
|
if options.num_bytes() > u32::MAX as usize {
|
||||||
|
panic!("chainloader options too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: options size is checked to validate it is safe to pass.
|
||||||
|
// Additionally, the pointer is allocated and retained on the heap which makes
|
||||||
|
// passing the options pointer safe to the next image.
|
||||||
|
unsafe {
|
||||||
|
loaded_image_protocol
|
||||||
|
.set_load_options(options.as_ptr() as *const u8, options.num_bytes() as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (base, size) = loaded_image_protocol.info();
|
||||||
info!("loaded image base={:#x} size={:#x}", base.addr(), size);
|
info!("loaded image base={:#x} size={:#x}", base.addr(), size);
|
||||||
uefi::boot::start_image(image).expect("failed to start image");
|
uefi::boot::start_image(image).expect("failed to start image");
|
||||||
}
|
}
|
||||||
|
|||||||
24
src/utils.rs
24
src/utils.rs
@@ -1,7 +1,7 @@
|
|||||||
use uefi::CString16;
|
use uefi::CString16;
|
||||||
use uefi::fs::{FileSystem, Path};
|
use uefi::fs::{FileSystem, Path};
|
||||||
use uefi::proto::device_path::PoolDevicePath;
|
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};
|
||||||
use uefi::proto::device_path::text::DevicePathFromText;
|
use uefi::proto::device_path::{DevicePath, PoolDevicePath};
|
||||||
use uefi::proto::media::fs::SimpleFileSystem;
|
use uefi::proto::media::fs::SimpleFileSystem;
|
||||||
|
|
||||||
pub fn text_to_device_path(path: &str) -> PoolDevicePath {
|
pub fn text_to_device_path(path: &str) -> PoolDevicePath {
|
||||||
@@ -17,6 +17,26 @@ pub fn text_to_device_path(path: &str) -> PoolDevicePath {
|
|||||||
.expect("unable to convert text to device path")
|
.expect("unable to convert text to device path")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn device_path_root(path: &DevicePath) -> String {
|
||||||
|
let mut path = 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::<Vec<_>>()
|
||||||
|
.join("/");
|
||||||
|
path.push('/');
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_file_contents(path: &str) -> Vec<u8> {
|
pub fn read_file_contents(path: &str) -> Vec<u8> {
|
||||||
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(
|
let fs = uefi::boot::open_protocol_exclusive::<SimpleFileSystem>(
|
||||||
uefi::boot::get_handle_for_protocol::<SimpleFileSystem>().expect("no filesystem protocol"),
|
uefi::boot::get_handle_for_protocol::<SimpleFileSystem>().expect("no filesystem protocol"),
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 AS build
|
FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 AS build
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ] || [ "${TARGETPLATFORM}" = "linux/x86_64" ]; then \
|
RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ] || [ "${TARGETPLATFORM}" = "linux/x86_64" ]; then \
|
||||||
apk --no-cache add ovmf; cp /usr/share/ovmf/bios.bin /ovmf.fd; fi
|
apk --no-cache add ovmf edk2-shell; cp /usr/share/ovmf/bios.bin /ovmf.fd; fi
|
||||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ] || [ "${TARGETPLATFORM}" = "linux/aarch64" ]; then \
|
RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ] || [ "${TARGETPLATFORM}" = "linux/aarch64" ]; then \
|
||||||
apk --no-cache add aavmf; cp /usr/share/AAVMF/QEMU_EFI.fd /ovmf.fd; fi
|
apk --no-cache add aavmf edk2-shell; cp /usr/share/AAVMF/QEMU_EFI.fd /ovmf.fd; fi
|
||||||
|
|
||||||
FROM scratch AS final
|
FROM scratch AS final
|
||||||
COPY --from=build /ovmf.fd /ovmf.fd
|
COPY --from=build /ovmf.fd /ovmf.fd
|
||||||
|
COPY --from=build /usr/share/edk2-shell/ShellFull.efi /shell.efi
|
||||||
|
|||||||
Reference in New Issue
Block a user