nixos/xen: Add v2 bootspec extension with multiboot support

Rane c0dcc49d 45de2973

Changed files
+135 -49
nixos
modules
+27 -8
nixos/modules/virtualisation/xen-boot-builder.sh
···
export LC_ALL=C
-
# Handle input argument and exit if the flag is invalid. See virtualisation.xen.efi.bootBuilderVerbosity 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
+
# 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.boot.builderVerbosity\e[0m option." && exit 1
+
case "$1" in
"quiet") true ;;
"default" | "info") echo -n "Installing Xen Project Hypervisor boot entries..." ;;
"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
;;
esac
···
# We do nothing if the Bootspec for the current $gen does not contain the Xen
# 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
-
[ "$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
# attributes, which will need to be translated in a boot script similar to this
···
# the corresponding nixos generation, substituting `nixos` with `xen`:
# `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')
-
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")
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")
···
# Create Xen UKI for $generation. Most of this is lifted from
# https://xenbits.xenproject.org/docs/unstable/misc/efi.html.
[ "$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]*')
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"
···
# any hypervisor boot entries.
if ((${#postGenerations[@]} == 0)); then
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." ;;
-
"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." ;;
+
"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 or \e[1;34morg.xenproject.bootspec.v2\e[0m entry." ;;
esac
# If the script is successful, change the default boot, say "done.", write a
+108 -41
nixos/modules/virtualisation/xen-dom0.nix
···
gnused
jq
])
-
++ optionals (cfg.efi.bootBuilderVerbosity == "info") (
+
++ optionals (cfg.boot.builderVerbosity == "info") (
with pkgs;
[
bat
···
"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 ##
···
};
};
-
bootParams = mkOption {
-
default = [ ];
-
example = ''
-
[
-
"iommu=force:true,qinval:true,debug:true"
-
"noreboot=true"
-
"vga=ask"
-
]
-
'';
-
type = listOf str;
-
description = ''
-
Xen Command Line parameters passed to Domain 0 at boot time.
-
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 = {
-
bootBuilderVerbosity = mkOption {
+
boot = {
+
params = mkOption {
+
default = [ ];
+
example = ''
+
[
+
"iommu=force:true,qinval:true,debug:true"
+
"noreboot=true"
+
"vga=ask"
+
]
+
'';
+
type = listOf str;
+
description = ''
+
Xen Command Line parameters passed to Domain 0 at boot time.
+
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.
+
'';
+
};
+
builderVerbosity = mkOption {
type = enum [
"default"
"info"
···
default = "default";
example = "info";
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.
···
This option does not alter the actual functionality of the script, just the number of messages printed when rebuilding the system.
'';
};
-
-
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.
-
'';
+
bios = {
+
path = mkOption {
+
type = path;
+
default = "${cfg.package.boot}/${cfg.package.multiboot}";
+
defaultText = literalExpression "\${config.virtualisation.xen.package.boot}/\${config.virtualisation.xen.package.multiboot}";
+
example = literalExpression "\${config.virtualisation.xen.package}/boot/xen-\${config.virtualisation.xen.package.version}";
+
description = ''
+
Path to the Xen `multiboot` binary used for BIOS booting.
+
Unless 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.
+
'';
+
};
};
};
···
{
assertion =
config.boot.loader.systemd-boot.enable
-
|| (config.boot ? lanzaboote) && config.boot.lanzaboote.enable;
-
message = "Xen only supports booting on systemd-boot or Lanzaboote.";
+
|| (config.boot ? lanzaboote) && config.boot.lanzaboote.enable
+
|| config.boot.loader.limine.enable;
+
message = "Xen only supports booting on systemd-boot, Lanzaboote or Limine.";
}
{
assertion = config.boot.initrd.systemd.enable;
···
}
];
-
virtualisation.xen.bootParams =
+
virtualisation.xen.boot.params =
optionals cfg.trace [
"loglvl=all"
"guest_loglvl=all"
···
'';
# 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 extension v1 is deprecated, and will be removed in 26.05
+
# It is present for backwards compatibility
"org.xenproject.bootspec.v1" = {
-
xen = cfg.efi.path;
-
xenParams = cfg.bootParams;
+
xen = cfg.boot.efi.path;
+
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.
loader.systemd-boot.extraInstallCommands = ''
-
${getExe xenBootBuilder} ${cfg.efi.bootBuilderVerbosity}
+
${getExe xenBootBuilder} ${cfg.boot.builderVerbosity}
'';
};