diff --git a/nixos-modules/genode-core.nix b/nixos-modules/genode-core.nix index ab0fee3..16cebb1 100644 --- a/nixos-modules/genode-core.nix +++ b/nixos-modules/genode-core.nix @@ -1,9 +1,25 @@ { config, pkgs, lib, modulesPath, ... }: with lib; -let localPackages = pkgs.buildPackages; +let + localPackages = pkgs.buildPackages; + coreROMs = mkOption { + type = with types; listOf str; + default = [ ]; + description = '' + List of label suffixes that when matched against + ROM requests shall be forwared to the core. + ''; + example = [ "platform_info" ]; + }; + inputs = mkOption { + description = "List of packages to build a ROM store with."; + default = [ ]; + type = types.listOf types.package; + }; in { options.genode = { + core = { prefix = mkOption { @@ -18,6 +34,25 @@ in { basePackages = mkOption { type = types.listOf types.package; }; + children = mkOption { + type = with types; + attrsOf (submodule { + options = { + inherit coreROMs inputs; + configFile = mkOption { + type = types.path; + description = '' + Set of children at the lowest init level, these children must not + have any dependency on a Nix store. + Configuration format is a Dhall configuration of type + Genode.Init.Child.Type. + See https://git.sr.ht/~ehmry/dhall-genode/tree/master/Init/Child/Type + ''; + }; + }; + }); + }; + }; boot = { @@ -52,6 +87,44 @@ in { description = "Attr set of initial ROM modules"; }; + storeBackend = mkOption { + type = types.enum [ "tarball" "usb" ]; # "parent"? + default = "tarball"; + description = '' + Backend for the initial /nix/store file-system. + + + + + tarball + + + An in-memory tarball. + + + + + + usb + + + An EXT2 file-system backed by USB storage. + + + + + + ''; + }; + + storePaths = mkOption { + type = with types; listOf package; + example = literalExample "[ pkgs.genodePackages.vfs_lwp ]"; + description = '' + Derivations to be included in the Nix store in the generated boot image. + ''; + }; + }; }; @@ -108,18 +181,64 @@ in { message = "invalid Genode core for this system"; }]; + genode.core.children.store_fs.configFile = let + + storeVfsConfig = { + tarball = '' + VFS.vfs [ VFS.leafAttrs "tar" (toMap { name = "${config.system.build.tarball.fileName}.tar" }) ] + ''; + usb = '' + VFS.vfs [ VFS.leafAttrs "rump" (toMap { fs = "ext2fs", ram="12M" }) ] + ''; + }.${config.genode.boot.storeBackend}; + + storeResources = { + tarball = "Init.Resources.default"; + usb = "Init.Resources::{ caps = 256, ram = Genode.units.MiB 16 }"; + }.${config.genode.boot.storeBackend}; + + in builtins.toFile "store_fs.dhall" '' + let Genode = env:DHALL_GENODE + + let Init = Genode.Init + + let VFS = Genode.VFS + + in Init.Child.flat + Init.Child.Attributes::{ + , binary = "vfs" + , resources = ${storeResources} + , config = Init.Config::{ + , content = [ ${storeVfsConfig} ] + , policies = + [ Init.Config.Policy::{ + , service = "File_system" + , label = Init.LabelSelector.suffix "nix-store" + , attributes = toMap { root = "/nix/store" } + } + , Init.Config.Policy::{ + , service = "File_system" + , label = Init.LabelSelector.prefix "store_rom" + , attributes = toMap { root = "/" } + } + ] + } + , provides = [ "File_system" ] + } + ''; + genode.boot.configFile = let tarball = "${config.system.build.tarball}/tarball/${config.system.build.tarball.fileName}.tar"; - manifest = mergeManifests (map addManifest - (config.genode.core.basePackages ++ [ config.system.build.tarball ] - ++ (with pkgs.genodePackages; [ - init - cached_fs_rom - jitter_sponge - report_rom - vfs - ]))); + + storeBackendInputs = { + tarball = [ config.system.build.tarball ]; + usb = [ pkgs.genodePackages.rump ]; + }.${config.genode.boot.storeBackend}; + + manifest = mergeManifests (map addManifest (with pkgs.genodePackages; + config.genode.core.basePackages ++ storeBackendInputs + ++ [ init cached_fs_rom jitter_sponge report_rom vfs ])); storeRomPolicies = mapAttrsToList (name: value: '', { mapKey = "${name}", mapValue = "${value}" }'') @@ -138,19 +257,34 @@ in { } '') value.coreROMs) config.genode.init.children)); + extraCoreChildren = "[ ${ + toString (lib.mapAttrsToList (name: value: + '', { mapKey = "${name}", mapValue = ${value.configFile} }'') + config.genode.core.children) + } ]"; + in localPackages.runCommand "boot.dhall" { } '' cat > $out << EOF let Genode = env:DHALL_GENODE in + let VFS = Genode.VFS + let XML = Genode.Prelude.XML + in ${./store-wrapper.dhall} - (${config.genode.init.configFile}) - "${config.system.build.tarball.fileName}.tar" - $(stat --format '%s' ${tarball}) - ([${toString storeRomPolicies} ] : Genode.Prelude.Map.Type Text Text) - ([${extraRoutes} ] : List Genode.Init.ServiceRoute.Type) - ${manifest} + { extraCoreChildren = ${extraCoreChildren} + , subinit = ${config.genode.init.configFile} + , storeSize = $(stat --format '%s' ${tarball}) + , storeRomPolicies = [${ + toString storeRomPolicies + } ] : Genode.Prelude.Map.Type Text Text + , routes = [${extraRoutes} ] : List Genode.Init.ServiceRoute.Type + , bootManifest = ${manifest} + } EOF ''; + genode.boot.storePaths = [ config.genode.init.configFile ] + ++ (builtins.attrValues romDirectories); + # Create the tarball of the store to live in core ROM system.build.tarball = pkgs.callPackage "${modulesPath}/../lib/make-system-tarball.nix" { @@ -181,6 +315,21 @@ in { xmllint --noout $out ''; + system.build.bootDriveImage = let + storeFsImage = pkgs.callPackage ./lib/make-ext2-fs.nix { + inherit (config.genode.boot) storePaths; + inherit (config.system.build) qemu; + volumeLabel = "NIXOS_GENODE"; + }; + in storeFsImage; + + virtualisation.qemu.options = + lib.optionals (config.genode.boot.storeBackend == "usb") [ + "-usb" + "-drive id=usbdisk,file=${config.system.build.bootDriveImage},if=none,readonly" + "-device usb-storage,drive=usbdisk" + ]; + }; } diff --git a/nixos-modules/hardware.nix b/nixos-modules/hardware.nix index c2d7b24..9ad92c3 100644 --- a/nixos-modules/hardware.nix +++ b/nixos-modules/hardware.nix @@ -32,6 +32,9 @@ with lib; hardware.usb.genode.enable = lib.mkEnableOption "USB driver"; + hardware.usb.genode.storage.enable = + lib.mkEnableOption "USB mass storage driver"; + }; config = { @@ -49,13 +52,18 @@ with lib; in lib.mapAttrsToList addrCheck config.networking.interfaces ++ lib.mapAttrsToList routeCheck config.networking.interfaces; + hardware.usb.genode.storage.enable = config.genode.boot.storeBackend + == "usb"; + + hardware.usb.genode.enable = config.hardware.usb.genode.storage.enable; + hardware.genode.platform.policies = lib.lists.imap0 (i: name: builtins.toFile (name + ".platform-policy.dhall") '' let Genode = env:DHALL_GENODE in Genode.Init.Config.Policy::{ , service = "Platform" - , label = Genode.Init.LabelSelector.prefix "${name}.driver" + , label = Genode.Init.LabelSelector.prefix "nixos -> ${name}.driver" , content = [ Genode.Prelude.XML.leaf { name = "pci" @@ -66,12 +74,25 @@ with lib; } ] } - '') (builtins.attrNames config.networking.interfaces); + '') (builtins.attrNames config.networking.interfaces) + ++ lib.optional config.hardware.usb.genode.enable + (builtins.toFile ("usb.platform-policy.dhall") '' + let Genode = env:DHALL_GENODE - genode.core.basePackages = with pkgs.genodePackages; [ - acpi_drv - platform_drv - ]; + in Genode.Init.Config.Policy::{ + , service = "Platform" + , label = Genode.Init.LabelSelector.prefix "usb_drv" + , content = + [ Genode.Prelude.XML.leaf + { name = "pci", attributes = toMap { class = "USB" } } + ] + } + ''); + + genode.core.basePackages = with pkgs.genodePackages; + [ acpi_drv platform_drv ] + ++ lib.optional config.hardware.usb.genode.enable + pkgs.genodePackages.usb_drv; genode.init.children = let @@ -110,7 +131,10 @@ with lib; , caps = 128 , ram = Genode.units.MiB 4 } - , routes = [ Init.ServiceRoute.parent "IO_MEM" ] + , routes = [ + , Init.ServiceRoute.parent "IO_MEM" + , Init.ServiceRoute.parent "Platform" + ] , config = Init.Config::{ , attributes = toMap { verbose = "true" } , policies = ${policies} @@ -201,7 +225,9 @@ with lib; }; }) config.networking.interfaces; - in lib.filterAttrs (n: v: v != null) (nics // sockets // { + in lib.filterAttrs (n: v: v != null) (nics // sockets); + + genode.core.children = { acpi_drv = { coreROMs = [ "acpi_drv" ]; @@ -261,33 +287,52 @@ with lib; } ''; }; + } // (if config.hardware.usb.genode.enable then { - usb_drv = if config.hardware.usb.genode.enable then { - inputs = [ pkgs.genodePackages.usb_drv ]; - configFile = pkgs.writeText "${name'}.dhall" '' + usb_drv = { + coreROMs = [ "usb_drv" ]; + configFile = builtins.toFile "usb_drv.dhall" '' let Genode = env:DHALL_GENODE let XML = Genode.Prelude.XML let Init = Genode.Init + let storageEnable = ${ + if config.hardware.usb.genode.storage.enable then + "True" + else + "False" + } + in Init.Child.flat Init.Child.Attributes::{ , binary = "usb_drv" - , provides = [ "Usb" ] + , provides = [ "Block", "Usb" ] , resources = Init.Resources::{ caps = 256, ram = Genode.units.MiB 12 } , routes = [ Init.ServiceRoute.parent "IO_MEM" ] , config = Init.Config::{ , attributes = toMap { uhci = "yes", ehci = "yes", xhci = "yes" } , content = - [ XML.leaf { name = "raw", attributes = XML.emptyAttributes } ] + if storageEnable + then [ XML.leaf + { name = "storage", attributes = XML.emptyAttributes } + ] + else [] : List XML.Type + , policies = + if storageEnable + then [ Init.Config.Policy::{ + , service = "Block" + , label = Init.LabelSelector.prefix "store_fs" + } + ] + else [] : List Init.Config.Policy.Type } } ''; - } else - null; - - }); + }; + } else + { }); }; diff --git a/nixos-modules/lib/make-ext2-fs.nix b/nixos-modules/lib/make-ext2-fs.nix new file mode 100644 index 0000000..78b273c --- /dev/null +++ b/nixos-modules/lib/make-ext2-fs.nix @@ -0,0 +1,83 @@ +# Builds an ext2 image containing a populated /nix/store with the closure +# of store paths passed in the storePaths parameter, in addition to the +# contents of a directory that can be populated with commands. The +# generated image is sized to only fit its contents, with the expectation +# that a script resizes the filesystem at boot time. +{ pkgs +, lib +# List of derivations to be included +, storePaths +# Shell commands to populate the ./files directory. +# All files in that directory are copied to the root of the FS. +, populateImageCommands ? "" +, volumeLabel +, uuid ? "44444444-4444-4444-8888-888888888888" +, e2fsprogs +, libfaketime +, perl +, fakeroot +, qemu +}: + +let + sdClosureInfo = pkgs.buildPackages.closureInfo { rootPaths = storePaths; }; +in +pkgs.stdenv.mkDerivation { + name = "ext2-fs.qcow2"; + + nativeBuildInputs = [ e2fsprogs.bin libfaketime perl fakeroot qemu ]; + + buildCommand = + '' + img=temp.raw + ( + mkdir -p ./files + ${populateImageCommands} + ) + + echo "Preparing store paths for image..." + + # Create nix/store before copying path + mkdir -p ./rootImage/nix/store + + xargs -I % cp -a --reflink=auto % -t ./rootImage/nix/store/ < ${sdClosureInfo}/store-paths + ( + GLOBIGNORE=".:.." + shopt -u dotglob + + for f in ./files/*; do + cp -a --reflink=auto -t ./rootImage/ "$f" + done + ) + + # Also include a manifest of the closures in a format suitable for nix-store --load-db + cp ${sdClosureInfo}/registration ./rootImage/nix-path-registration + + # Make a crude approximation of the size of the target image. + # If the script starts failing, increase the fudge factors here. + numInodes=$(find ./rootImage | wc -l) + numDataBlocks=$(du -s -c -B 4096 --apparent-size ./rootImage | tail -1 | awk '{ print int($1 * 1.10) }') + bytes=$((2 * 4096 * $numInodes + 4096 * $numDataBlocks)) + echo "Creating an EXT2 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks)" + + truncate -s $bytes $img + + faketime -f "1970-01-01 00:00:01" fakeroot mkfs.ext2 -L ${volumeLabel} -U ${uuid} -d ./rootImage $img + + export EXT2FS_NO_MTAB_OK=yes + # I have ended up with corrupted images sometimes, I suspect that happens when the build machine's disk gets full during the build. + if ! fsck.ext2 -n -f $img; then + echo "--- Fsck failed for EXT2 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks) ---" + cat errorlog + return 1 + fi + + echo "Resizing to minimum allowed size" + resize2fs -M $img + + # And a final fsck, because of the previous truncating. + fsck.ext2 -n -f $img + + qemu-img convert $img $out + ''; +} diff --git a/nixos-modules/store-wrapper.dhall b/nixos-modules/store-wrapper.dhall index 15f089a..dbc0db0 100644 --- a/nixos-modules/store-wrapper.dhall +++ b/nixos-modules/store-wrapper.dhall @@ -2,195 +2,184 @@ let Genode = env:DHALL_GENODE let Prelude = Genode.Prelude +let XML = Prelude.XML + let Init = Genode.Init let Child = Init.Child let TextMapType = Prelude.Map.Type Text +let ChildMapType = TextMapType Child.Type + let Manifest/Type = TextMapType (TextMapType Text) -in λ(subinit : Init.Type) → - λ(storeName : Text) → - λ(storeSize : Natural) → - λ(storeRomPolicies : Prelude.Map.Type Text Text) → - λ(routes : List Init.ServiceRoute.Type) → - λ(bootManifest : Manifest/Type) → +in λ ( params + : { extraCoreChildren : ChildMapType + , subinit : Init.Type + , storeSize : Natural + , storeRomPolicies : Prelude.Map.Type Text Text + , routes : List Init.ServiceRoute.Type + , bootManifest : Manifest/Type + } + ) → Genode.Boot::{ , config = Init::{ - , routes + , routes = params.routes , children = let child = Prelude.Map.keyValue Child.Type - in [ child - "timer" - ( Child.flat - Child.Attributes::{ - , binary = "timer_drv" - , provides = [ "Timer" ] - } - ) - , child - "rtc" - ( Child.flat - Child.Attributes::{ - , binary = "rtc_drv" - , provides = [ "Rtc" ] - , routes = [ Init.ServiceRoute.parent "IO_PORT" ] - } - ) - , child - "jitter_sponge" - ( Child.flat - Child.Attributes::{ - , binary = "jitter_sponge" - , provides = [ "Terminal" ] - , config = Init.Config::{ - , policies = - [ Init.Config.Policy::{ - , service = "Terminal" - , label = Init.LabelSelector.suffix "entropy" - } - ] + in [ child + "timer" + ( Child.flat + Child.Attributes::{ + , binary = "timer_drv" + , provides = [ "Timer" ] + , config = Init.Config::{ + , policies = + [ Init.Config.Policy::{ + , service = "Timer" + , label = Init.LabelSelector.none + } + ] + } } - } - ) - , child - "store_fs" - ( Child.flat - Child.Attributes::{ - , binary = "vfs" - , config = Init.Config::{ - , content = - let VFS = Genode.VFS - - in [ VFS.vfs - [ VFS.leafAttrs - "tar" - (toMap { name = storeName }) - ] + ) + , child + "rtc" + ( Child.flat + Child.Attributes::{ + , binary = "rtc_drv" + , provides = [ "Rtc" ] + , routes = [ Init.ServiceRoute.parent "IO_PORT" ] + } + ) + , child + "jitter_sponge" + ( Child.flat + Child.Attributes::{ + , binary = "jitter_sponge" + , provides = [ "Terminal" ] + , config = Init.Config::{ + , policies = + [ Init.Config.Policy::{ + , service = "Terminal" + , label = Init.LabelSelector.suffix "entropy" + } + ] + } + } + ) + , child + "store_rom" + ( Child.flat + Child.Attributes::{ + , binary = "cached_fs_rom" + , provides = [ "ROM" ] + , resources = Init.Resources::{ + , ram = params.storeSize + Genode.units.MiB 1 + } + , config = Init.Config::{ + , policies = + [ Init.Config.Policy::{ + , service = "ROM" + , label = + Init.LabelSelector.prefix + "nixos -> /nix/store" + , diag = Some True + } ] - , policies = - [ Init.Config.Policy::{ - , service = "File_system" - , label = Init.LabelSelector.suffix "nix-store" - , attributes = toMap { root = "/nix/store" } - } - , Init.Config.Policy::{ - , service = "File_system" - , label = Init.LabelSelector.prefix "store_rom" - , attributes = toMap { root = "/" } - } - ] - } - , provides = [ "File_system" ] - } - ) - , child - "store_rom" - ( Child.flat - Child.Attributes::{ - , binary = "cached_fs_rom" - , provides = [ "ROM" ] - , resources = Init.Resources::{ - , ram = storeSize + Genode.units.MiB 1 - } - , config = Init.Config::{ - , policies = - [ Init.Config.Policy::{ - , service = "ROM" - , label = - Init.LabelSelector.prefix - "nixos -> /nix/store" - } - ] - # ( let Entry = Prelude.Map.Entry Text Text + # ( let Entry = Prelude.Map.Entry Text Text - in Prelude.List.concatMap - Entry - Init.Config.Policy.Type - ( λ(e : Entry) → - [ Init.Config.Policy::{ - , service = "ROM" - , label = - Init.LabelSelector.prefix - "nixos -> ${e.mapKey}" - , attributes = toMap - { directory = - "${e.mapValue}/bin" - } - } - , Init.Config.Policy::{ - , service = "ROM" - , label = - Init.LabelSelector.Type.Partial - { prefix = Some - "nixos -> ${e.mapKey}" - , suffix = Some ".lib.so" + in Prelude.List.concatMap + Entry + Init.Config.Policy.Type + ( λ(e : Entry) → + [ Init.Config.Policy::{ + , service = "ROM" + , diag = Some True + , label = + Init.LabelSelector.prefix + "nixos -> ${e.mapKey}" + , attributes = toMap + { directory = + "${e.mapValue}/bin" } - , attributes = toMap - { directory = - "${e.mapValue}/lib" - } - } - ] - ) - storeRomPolicies - ) - } - } - ) - , child - "nixos" - ( Init.toChild - subinit - Init.Attributes::{ - , exitPropagate = True - , resources = Init.Resources::{ - , ram = Genode.units.MiB 4 - } - , routes = - let parentROMs = - Prelude.List.concatMap - Text - Init.ServiceRoute.Type - ( λ(suffix : Text) → - Prelude.List.map - Text - Init.ServiceRoute.Type - ( λ(prefix : Text) → - { service = - { name = "ROM" + } + , Init.Config.Policy::{ + , service = "ROM" + , diag = Some True , label = Init.LabelSelector.Type.Partial - { prefix = Some prefix - , suffix = Some suffix + { prefix = Some + "nixos -> ${e.mapKey}" + , suffix = Some ".so" } + , attributes = toMap + { directory = + "${e.mapValue}/lib" + } } - , route = - Init.Route.parent - (Some suffix) - } + ] ) - ( Prelude.Map.keys - Text - Init.Child.Type - subinit.children - ) - ) + params.storeRomPolicies + ) + } + } + ) + ] + # params.extraCoreChildren + # [ child + "nixos" + ( Init.toChild + params.subinit + Init.Attributes::{ + , exitPropagate = True + , resources = Init.Resources::{ + , ram = Genode.units.MiB 4 + } + , routes = + let parentROMs = + Prelude.List.concatMap + Text + Init.ServiceRoute.Type + ( λ(suffix : Text) → + Prelude.List.map + Text + Init.ServiceRoute.Type + ( λ(prefix : Text) → + { service = + { name = "ROM" + , label = + Init.LabelSelector.Type.Partial + { prefix = Some prefix + , suffix = Some suffix + } + } + , route = + Init.Route.parent + (Some suffix) + } + ) + ( Prelude.Map.keys + Text + Init.Child.Type + params.subinit.children + ) + ) - in parentROMs - [ "ld.lib.so", "vfs.lib.so", "init" ] - # [ Init.ServiceRoute.parent "IO_MEM" - , Init.ServiceRoute.parent "IO_PORT" - , Init.ServiceRoute.parent "IRQ" - , Init.ServiceRoute.parent "VM" - , Init.ServiceRoute.child "Timer" "timer" - , Init.ServiceRoute.child "Rtc" "rtc" - ] - } - ) - ] + in parentROMs + [ "ld.lib.so", "vfs.lib.so", "init" ] + # [ Init.ServiceRoute.parent "IO_MEM" + , Init.ServiceRoute.parent "IO_PORT" + , Init.ServiceRoute.parent "IRQ" + , Init.ServiceRoute.parent "VM" + , Init.ServiceRoute.child "Timer" "timer" + , Init.ServiceRoute.child "Rtc" "rtc" + ] + } + ) + ] } , rom = Genode.BootModules.toRomPaths @@ -199,7 +188,7 @@ in λ(subinit : Init.Type) → ( Prelude.Map.values Text (Prelude.Map.Type Text Text) - bootManifest + params.bootManifest ) ) } diff --git a/tests/lib/build-vms.nix b/tests/lib/build-vms.nix index 1e3db3f..fcc32a6 100644 --- a/tests/lib/build-vms.nix +++ b/tests/lib/build-vms.nix @@ -29,6 +29,7 @@ rec { baseModules = (import "${modulesPath}/module-list.nix") ++ [ ../../nixos-modules/genode-core.nix ../../nixos-modules/genode-init.nix + ../../nixos-modules/hardware.nix ../../nixos-modules/qemu-vm.nix { key = "no-manual";