nixos/xen: Add v2 bootspec extension with multiboot support

This commit is contained in:
Rane
2025-08-27 15:50:12 +10:00
parent 45de297332
commit c0dcc49d24
2 changed files with 135 additions and 49 deletions

View File

@@ -3,14 +3,15 @@
export LC_ALL=C export LC_ALL=C
# Handle input argument and exit if the flag is invalid. See virtualisation.xen.efi.bootBuilderVerbosity below. # Handle input argument and exit if the flag is invalid. See virtualisation.xen.boot.builderVerbosity below.
[[ $# -ne 1 ]] && echo -e "\e[1;31merror:\e[0m xenBootBuilder must be called with exactly one verbosity argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option." && exit 1 [[ $# -ne 1 ]] && echo -e "\e[1;31merror:\e[0m xenBootBuilder must be called with exactly one verbosity argument. See the \e[1;34mvirtualisation.xen.boot.builderVerbosity\e[0m option." && exit 1
case "$1" in case "$1" in
"quiet") true ;; "quiet") true ;;
"default" | "info") echo -n "Installing Xen Project Hypervisor boot entries..." ;; "default" | "info") echo -n "Installing Xen Project Hypervisor boot entries..." ;;
"debug") echo -e "\e[1;34mxenBootBuilder:\e[0m called with the '$1' flag" ;; "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m called with the '$1' flag" ;;
*) *)
echo -e "\e[1;31merror:\e[0m xenBootBuilder was called with an invalid argument. See the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option." echo -e "\e[1;31merror:\e[0m xenBootBuilder was called with an invalid argument. See the \e[1;34mvirtualisation.xen.boot.builderVerbosity\e[0m option."
exit 2 exit 2
;; ;;
esac esac
@@ -46,8 +47,26 @@ for gen in "${gens[@]}"; do
# We do nothing if the Bootspec for the current $gen does not contain the Xen # We do nothing if the Bootspec for the current $gen does not contain the Xen
# extension, which is added as a configuration attribute below. # extension, which is added as a configuration attribute below.
# We determine this by checking for the v1 or v2 bootspec extension,
# and setting the appropriate attributes based on version
xenSpecVer=""
xenParamVar=""
xenEfiPath=""
if grep -sq '"org.xenproject.bootspec.v1"' "$bootspecFile"; then if grep -sq '"org.xenproject.bootspec.v1"' "$bootspecFile"; then
[ "$1" = "debug" ] && echo -e " \e[1;32msuccess:\e[0m found Xen entries in $gen." xenSpecVer="v1"
xenParamVar="xenParams"
xenEfiPath="xen"
fi
# We prefer the v2 extension, so if both are present somehow,
# we will use the v2 attributes
if grep -sq '"org.xenproject.bootspec.v2"' "$bootspecFile"; then
xenSpecVer="v2"
xenParamVar="params"
xenEfiPath="efiPath"
fi
# Check for a valid Xen spec being detected
if [ -n "$xenSpecVer" ]; then
[ "$1" = "debug" ] && echo -e " \e[1;32msuccess:\e[0m found $xenSpecVer Xen entries in $gen."
# TODO: Support DeviceTree booting. Xen has some special handling for DeviceTree # TODO: Support DeviceTree booting. Xen has some special handling for DeviceTree
# attributes, which will need to be translated in a boot script similar to this # attributes, which will need to be translated in a boot script similar to this
@@ -63,7 +82,7 @@ for gen in "${gens[@]}"; do
# the corresponding nixos generation, substituting `nixos` with `xen`: # the corresponding nixos generation, substituting `nixos` with `xen`:
# `xen-$profile-generation-$number-specialisation-$specialisation.{cfg,conf}` # `xen-$profile-generation-$number-specialisation-$specialisation.{cfg,conf}`
xenGen=$(echo "$gen" | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g') xenGen=$(echo "$gen" | sed 's_/loader/entries/nixos_/loader/entries/xen_g;s_^.*/xen_xen_g;s_.conf$__g')
bootParams=$(jq -re '."org.xenproject.bootspec.v1".xenParams | join(" ")' "$bootspecFile") bootParams=$(jq -re ".\"org.xenproject.bootspec.$xenSpecVer\".$xenParamVar | join(\" \")" "$bootspecFile")
kernel=$(jq -re '."org.nixos.bootspec.v1".kernel | sub("^/nix/store/"; "") | sub("/bzImage"; "-bzImage.efi")' "$bootspecFile") kernel=$(jq -re '."org.nixos.bootspec.v1".kernel | sub("^/nix/store/"; "") | sub("/bzImage"; "-bzImage.efi")' "$bootspecFile")
kernelParams=$(jq -re '."org.nixos.bootspec.v1".kernelParams | join(" ")' "$bootspecFile") kernelParams=$(jq -re '."org.nixos.bootspec.v1".kernelParams | join(" ")' "$bootspecFile")
initrd=$(jq -re '."org.nixos.bootspec.v1".initrd | sub("^/nix/store/"; "") | sub("/initrd"; "-initrd.efi")' "$bootspecFile") initrd=$(jq -re '."org.nixos.bootspec.v1".initrd | sub("^/nix/store/"; "") | sub("/initrd"; "-initrd.efi")' "$bootspecFile")
@@ -90,7 +109,7 @@ EOF
# Create Xen UKI for $generation. Most of this is lifted from # Create Xen UKI for $generation. Most of this is lifted from
# https://xenbits.xenproject.org/docs/unstable/misc/efi.html. # https://xenbits.xenproject.org/docs/unstable/misc/efi.html.
[ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m making Xen UKI..." [ "$1" = "debug" ] && echo -e "\e[1;34mxenBootBuilder:\e[0m making Xen UKI..."
xenEfi=$(jq -re '."org.xenproject.bootspec.v1".xen' "$bootspecFile") xenEfi=$(jq -re ".\"org.xenproject.bootspec.$xenSpecVer\".$xenEfiPath" "$bootspecFile")
finalSection=$(objdump --header --wide "$xenEfi" | tail -n +6 | sort --key="4,4" | tail -n 1 | grep -Eo '\.[a-z]*') finalSection=$(objdump --header --wide "$xenEfi" | tail -n +6 | sort --key="4,4" | tail -n 1 | grep -Eo '\.[a-z]*')
padding=$(objdump --header --section="$finalSection" "$xenEfi" | awk -v section="$finalSection" '$0 ~ section { printf("0x%016x\n", and(strtonum("0x"$3) + strtonum("0x"$4) + 0xfff, compl(0xfff)))};') padding=$(objdump --header --section="$finalSection" "$xenEfi" | awk -v section="$finalSection" '$0 ~ section { printf("0x%016x\n", and(strtonum("0x"$3) + strtonum("0x"$4) + 0xfff, compl(0xfff)))};')
[ "$1" = "debug" ] && echo " - padding: $padding" [ "$1" = "debug" ] && echo " - padding: $padding"
@@ -133,8 +152,8 @@ mapfile -t postGenerations < <(find "$efiMountPoint"/loader/entries -type f -nam
# any hypervisor boot entries. # any hypervisor boot entries.
if ((${#postGenerations[@]} == 0)); then if ((${#postGenerations[@]} == 0)); then
case "$1" in case "$1" in
"default" | "info") echo "none found." && echo -e "If you believe this is an error, set the \e[1;34mvirtualisation.xen.efi.bootBuilderVerbosity\e[0m option to \e[1;34m\"debug\"\e[0m and rebuild to print debug logs." ;; "default" | "info") echo "none found." && echo -e "If you believe this is an error, set the \e[1;34mvirtualisation.xen.boot.builderVerbosity\e[0m option to \e[1;34m\"debug\"\e[0m and rebuild to print debug logs." ;;
"debug") echo -e "\e[1;34mxenBootBuilder:\e[0m wrote \e[1;31mno generations\e[0m. Most likely, there were no generations with a valid \e[1;34morg.xenproject.bootspec.v1\e[0m entry." ;; "debug") echo -e "\e[1;34mxenBootBuilder:\e[0m wrote \e[1;31mno generations\e[0m. Most likely, there were no generations with a valid \e[1;34morg.xenproject.bootspec.v1\e[0m or \e[1;34morg.xenproject.bootspec.v2\e[0m entry." ;;
esac esac
# If the script is successful, change the default boot, say "done.", write a # If the script is successful, change the default boot, say "done.", write a

View File

@@ -51,7 +51,7 @@ let
gnused gnused
jq jq
]) ])
++ optionals (cfg.efi.bootBuilderVerbosity == "info") ( ++ optionals (cfg.boot.builderVerbosity == "info") (
with pkgs; with pkgs;
[ [
bat bat
@@ -146,6 +146,48 @@ in
"path" "path"
] ]
) )
(mkRenamedOptionModule
[
"virtualisation"
"xen"
"efi"
"bootBuilderVerbosity"
]
[
"virtualisation"
"xen"
"boot"
"builderVerbosity"
]
)
(mkRenamedOptionModule
[
"virtualisation"
"xen"
"bootParams"
]
[
"virtualisation"
"xen"
"boot"
"params"
]
)
(mkRenamedOptionModule
[
"virtualisation"
"xen"
"efi"
"path"
]
[
"virtualisation"
"xen"
"boot"
"efi"
"path"
]
)
]; ];
## Interface ## ## Interface ##
@@ -178,25 +220,24 @@ in
}; };
}; };
bootParams = mkOption { boot = {
default = [ ]; params = mkOption {
example = '' default = [ ];
[ example = ''
"iommu=force:true,qinval:true,debug:true" [
"noreboot=true" "iommu=force:true,qinval:true,debug:true"
"vga=ask" "noreboot=true"
] "vga=ask"
''; ]
type = listOf str; '';
description = '' type = listOf str;
Xen Command Line parameters passed to Domain 0 at boot time. description = ''
Note: these are different from `boot.kernelParams`. See Xen Command Line parameters passed to Domain 0 at boot time.
the [Xen documentation](https://xenbits.xenproject.org/docs/unstable/misc/xen-command-line.html) for more information. Note: these are different from `boot.kernelParams`. See
''; the [Xen documentation](https://xenbits.xenproject.org/docs/unstable/misc/xen-command-line.html) for more information.
}; '';
};
efi = { builderVerbosity = mkOption {
bootBuilderVerbosity = mkOption {
type = enum [ type = enum [
"default" "default"
"info" "info"
@@ -206,7 +247,7 @@ in
default = "default"; default = "default";
example = "info"; example = "info";
description = '' description = ''
The EFI boot entry builder script should be called with exactly one of the following arguments in order to specify its verbosity: The boot entry builder script should be called with exactly one of the following arguments in order to specify its verbosity:
- `quiet` supresses all messages. - `quiet` supresses all messages.
@@ -220,19 +261,33 @@ in
This option does not alter the actual functionality of the script, just the number of messages printed when rebuilding the system. This option does not alter the actual functionality of the script, just the number of messages printed when rebuilding the system.
''; '';
}; };
bios = {
path = mkOption { path = mkOption {
type = path; type = path;
default = "${cfg.package.boot}/${cfg.package.efi}"; default = "${cfg.package.boot}/${cfg.package.multiboot}";
defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}"; defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.multiboot}";
example = literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi"; example = literalExpression "\${config.virtualisation.xen.package}/boot/xen-\${config.virtualisation.xen.package.version}";
description = '' description = ''
Path to xen.efi. `pkgs.xen` is patched to install the xen.efi file Path to the Xen `multiboot` binary used for BIOS booting.
on `$boot/boot/xen.efi`, but an unpatched Xen build may install it Unless you're building your own Xen derivation, you should leave this
somewhere else, such as `$out/boot/efi/efi/nixos/xen.efi`. Unless option as the default value.
you're building your own Xen derivation, you should leave this '';
option as the default value. };
''; };
efi = {
path = mkOption {
type = path;
default = "${cfg.package.boot}/${cfg.package.efi}";
defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.efi}";
example = literalExpression "\${config.virtualisation.xen.package}/boot/efi/efi/nixos/xen-\${config.virtualisation.xen.package.version}.efi";
description = ''
Path to xen.efi. `pkgs.xen` is patched to install the xen.efi file
on `$boot/boot/xen.efi`, but an unpatched Xen build may install it
somewhere else, such as `$out/boot/efi/efi/nixos/xen.efi`. Unless
you're building your own Xen derivation, you should leave this
option as the default value.
'';
};
}; };
}; };
@@ -612,8 +667,9 @@ in
{ {
assertion = assertion =
config.boot.loader.systemd-boot.enable config.boot.loader.systemd-boot.enable
|| (config.boot ? lanzaboote) && config.boot.lanzaboote.enable; || (config.boot ? lanzaboote) && config.boot.lanzaboote.enable
message = "Xen only supports booting on systemd-boot or Lanzaboote."; || config.boot.loader.limine.enable;
message = "Xen only supports booting on systemd-boot, Lanzaboote or Limine.";
} }
{ {
assertion = config.boot.initrd.systemd.enable; assertion = config.boot.initrd.systemd.enable;
@@ -639,7 +695,7 @@ in
} }
]; ];
virtualisation.xen.bootParams = virtualisation.xen.boot.params =
optionals cfg.trace [ optionals cfg.trace [
"loglvl=all" "loglvl=all"
"guest_loglvl=all" "guest_loglvl=all"
@@ -692,17 +748,28 @@ in
''; '';
# Xen Bootspec extension. This extension allows NixOS bootloaders to # Xen Bootspec extension. This extension allows NixOS bootloaders to
# fetch the `xen.efi` path and access the `cfg.bootParams` option. # fetch the dom0 kernel paths and access the `cfg.boot.params` option.
bootspec.extensions = { bootspec.extensions = {
# Bootspec extension v1 is deprecated, and will be removed in 26.05
# It is present for backwards compatibility
"org.xenproject.bootspec.v1" = { "org.xenproject.bootspec.v1" = {
xen = cfg.efi.path; xen = cfg.boot.efi.path;
xenParams = cfg.bootParams; xenParams = cfg.boot.params;
};
# Bootspec extension v2 includes more detail,
# including supporting multiboot, and is the current supported
# bootspec extension
"org.xenproject.bootspec.v2" = {
efiPath = cfg.boot.efi.path;
multibootPath = cfg.boot.bios.path;
version = cfg.package.version;
params = cfg.boot.params;
}; };
}; };
# See the `xenBootBuilder` script in the main `let...in` statement of this file. # See the `xenBootBuilder` script in the main `let...in` statement of this file.
loader.systemd-boot.extraInstallCommands = '' loader.systemd-boot.extraInstallCommands = ''
${getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity} ${getExe xenBootBuilder} ${cfg.boot.builderVerbosity}
''; '';
}; };