From b99986c6cf7310e12a5f2275a8ac3be6bbeca9be Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Mon, 11 May 2020 15:42:13 +0530 Subject: [PATCH] WiP! nixos-module --- flake.nix | 3 + nixos-modules/buildBootDescription.nix | 58 +++ nixos-modules/default.nix | 5 + nixos-modules/partition-type | 1 + nixos-modules/vbox-host.dhall | 509 +++++++++++++++++++++++++ nixos-modules/vbox.nix | 76 ++++ nixos-modules/workman.map.dhall | 245 ++++++++++++ 7 files changed, 897 insertions(+) create mode 100644 nixos-modules/buildBootDescription.nix create mode 100644 nixos-modules/default.nix create mode 100644 nixos-modules/partition-type create mode 100644 nixos-modules/vbox-host.dhall create mode 100644 nixos-modules/vbox.nix create mode 100644 nixos-modules/workman.map.dhall diff --git a/flake.nix b/flake.nix index 220ad15..a9116b0 100644 --- a/flake.nix +++ b/flake.nix @@ -129,5 +129,8 @@ legacyPackages = self.legacyPackages.${system}; }); in { x86_64-linux = checks'.x86_64-linux-x86_64-genode; }; + + nixosModules = import ./nixos-modules { inherit self; }; + }; } diff --git a/nixos-modules/buildBootDescription.nix b/nixos-modules/buildBootDescription.nix new file mode 100644 index 0000000..fc2323a --- /dev/null +++ b/nixos-modules/buildBootDescription.nix @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: CC0-1.0 + +# Generate a total boot description by matching the binaries referred to by an init +# configuration with a list of input packages. + +{ lib, writeText, dhall-json }: + +{ name, initConfig, imageInputs, extraBinaries ? [ ], extraRoms ? { } }: + +with builtins; +let + extractDrv = lib.runDhallCommand "binaries.json" { + nativeBuildInputs = [ dhall-json ]; + } '' + dhall-to-json << TRUE_DEATH > $out + let Genode = env:DHALL_GENODE + let init = ${initConfig} + in Genode.Init.Child.binaries (Genode.Init.toChild init Genode.Init.Attributes::{=}) + TRUE_DEATH + ''; + binariesJSON = readFile (toString extractDrv); + binaries = lib.unique (fromJSON binariesJSON ++ extraBinaries); + + matches = let + f = binary: { + name = binary; + value = let maybeNull = map (drv: toPath "${drv}/${binary}") imageInputs; + in filter pathExists maybeNull; + }; + in map f binaries; + + binaryPaths = let + f = { name, value }: + let l = length value; + in if l == 1 then { + inherit name; + value = elemAt value 0; + } else if l == 0 then + throw "${name} not found in imageInputs" + else + throw "${name} found in multiple imageInputs, ${toString value}"; + in map f matches; + + extraList = + lib.mapAttrsToList (name: value: { inherit name value; }) extraRoms; + +in writeText "${name}.boot.dhall" '' + let Genode = env:DHALL_GENODE + in { config = ${initConfig} + , rom = Genode.BootModules.toRomPaths ([ + ${ + toString (map ({ name, value }: '' + , { mapKey = "${name}", mapValue = "${value}" } + '') (binaryPaths ++ extraList)) + } + ]) + } +'' diff --git a/nixos-modules/default.nix b/nixos-modules/default.nix new file mode 100644 index 0000000..5899f5c --- /dev/null +++ b/nixos-modules/default.nix @@ -0,0 +1,5 @@ +{ self }: + +{ + vbox = import ./vbox.nix { inherit self; }; +} diff --git a/nixos-modules/partition-type b/nixos-modules/partition-type new file mode 100644 index 0000000..2cca25d --- /dev/null +++ b/nixos-modules/partition-type @@ -0,0 +1 @@ +"24b69406-18a1-428d-908e-d21a1437122c" diff --git a/nixos-modules/vbox-host.dhall b/nixos-modules/vbox-host.dhall new file mode 100644 index 0000000..de23307 --- /dev/null +++ b/nixos-modules/vbox-host.dhall @@ -0,0 +1,509 @@ +-- SPDX-License-Identifier: CC0-1.0 + +let Genode = env:DHALL_GENODE + +let Prelude = Genode.Prelude + +let XML = Prelude.XML + +let Init = Genode.Init + +let Child = Init.Child + +let Resources = Init.Resources + +let ServiceRoute = Init.ServiceRoute + +let label = + λ(label : Text) + → { local = label, route = label } : Child.Attributes.Label + +let inlineConfig = + λ(name : Text) + → λ(config : Init.Config.Type) + → XML.element + { name = "inline" + , attributes = toMap { name = name } + , content = + Prelude.Optional.toList XML.Type (Init.Config.toXML config) + } + +let Vfs/inline = + λ(name : Text) + → λ(body : Text) + → XML.element + { name = "inline" + , attributes = XML.emptyAttributes + , content = XML.Text body + } + +let rootInit = + λ ( params + : { vdiFilename : Text + , vdiUuid : Text + , memorySize : Natural + , vmName : Text + } + ) + → let vboxConfig = + '' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '' + + in Init::{ + , routes = [ ServiceRoute.child "Timer" "timer" ] + , children = toMap + { timer = + Child.flat + Child.Attributes::{ + , binary = "timer_drv" + , provides = [ "Timer" ] + } + , rtc = + Child.flat + Child.Attributes::{ + , binary = "rtc_drv" + , provides = [ "Rtc" ] + , routes = + [ ServiceRoute.parent "IO_PORT" + , ServiceRoute.parent "IO_MEM" + ] + } + , acpi_drv = + Child.flat + Child.Attributes::{ + , binary = "acpi_drv" + , priority = 1 + , resources = Resources::{ + , caps = 350 + , ram = Genode.units.MiB 4 + } + , romReports = [ label "acpi", label "smbios_table" ] + , routes = [ ServiceRoute.parent "IO_MEM" ] + } + , platform_drv = + Child.flat + Child.Attributes::{ + , binary = "platform_drv" + , resources = Resources::{ + , caps = 400 + , ram = Genode.units.MiB 4 + , constrainPhys = True + } + , reportRoms = [ label "acpi" ] + , romReports = [ label "pci" ] + , provides = [ "Acpi", "Platform" ] + , routes = + [ ServiceRoute.parent "IRQ" + , ServiceRoute.parent "IO_MEM" + , ServiceRoute.parent "IO_PORT" + , ServiceRoute.parentLabel + "ROM" + (Some "system") + (Some "system") + ] + , config = Init.Config::{ + , attributes = toMap { system = "yes" } + , content = + [ XML.text + '' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + '' + ] + } + } + , framebuffer = + Child.flat + Child.Attributes::{ + , binary = "vesa_fb_drv" + , provides = [ "Framebuffer" ] + , resources = Init.Resources::{ + , caps = 256 + , ram = Genode.units.MiB 16 + } + , routes = + [ ServiceRoute.parent "IO_MEM" + , ServiceRoute.parent "IO_PORT" + , ServiceRoute.childLabel + "Platform" + "platform_drv" + (None Text) + (Some "vesa_fb_drv") + ] + } + , input_filter = + Child.flat + Child.Attributes::{ + , binary = "input_filter" + , config = Init.Config::{ + , content = + [ XML.leaf + { name = "input" + , attributes = toMap { label = "ps2" } + } + , XML.element + { name = "output" + , attributes = XML.emptyAttributes + , content = + [ XML.element + { name = "chargen" + , attributes = XML.emptyAttributes + , content = + [ XML.element + { name = "remap" + , attributes = XML.emptyAttributes + , content = + [ remap + "KEY_LEFTMETA" + "KEY_SCREEN" + , XML.leaf + { name = "input" + , attributes = toMap + { name = "ps2" } + } + ] + } + , XML.element + { name = "mod1" + , attributes = XML.emptyAttributes + , content = + [ key "KEY_LEFTSHIFT" + , key "KEY_RIGHTSHIFT" + ] + } + , XML.element + { name = "mod2" + , attributes = XML.emptyAttributes + , content = + [ key "KEY_LEFTCTRL" + , key "KEY_RIGHTCTRL" + ] + } + , XML.element + { name = "mod3" + , attributes = XML.emptyAttributes + , content = [ key "KEY_RIGHTALT" ] + } + ] + # ./workman.map.dhall + } + ] + } + ] + } + , provides = [ "Input" ] + , routes = + [ ServiceRoute.parentLabel + "ROM" + (Some "config") + (Some "config -> input_filter.config") + , ServiceRoute.childLabel + "Input" + "ps2_drv" + (Some "ps2") + (None Text) + ] + } + , ps2_drv = + Child.flat + Child.Attributes::{ + , binary = "ps2_drv" + , provides = [ "Input" ] + , routes = + [ ServiceRoute.childLabel + "Platform" + "platform_drv" + (None Text) + (Some "ps2_drv") + ] + } + , nitpicker = + Child.flat + Child.Attributes::{ + , binary = "nitpicker" + , config = Init.Config::{ + , content = + [ XML.text + '' + + + + + '' + ] + } + , provides = [ "Nitpicker" ] + , routes = + [ ServiceRoute.child "Framebuffer" "framebuffer" + , ServiceRoute.child "Input" "input_filter" + ] + } + , pointer = + Child.flat + Child.Attributes::{ + , binary = "pointer" + , routes = [ ServiceRoute.child "Nitpicker" "nitpicker" ] + } + , log_console = + Init.toChild + ./log-console.dhall + Init.Attributes::{ + , routes = [ ServiceRoute.child "Nitpicker" "nitpicker" ] + } + , block = + Child.flat + Child.Attributes::{ + , binary = "ahci_drv" + , config = Init.Config::{ + , content = + [ Genode.Prelude.XML.leaf + { name = "default-policy" + , attributes = toMap + { device = "0", writeable = "yes" } + } + ] + } + , provides = [ "Block" ] + , resources = Init.Resources::{ + , caps = 256 + , ram = Genode.units.MiB 10 + } + , routes = + [ ServiceRoute.child + "Platform" + "platform_drv" + (None Text) + (Some "ahci_drv") + ] + } + , block_partitions = + Child.flat + Child.Attributes::{ + , binary = "part_block" + , config = Init.Config::{ + , content = + Prelude.List.map + Natural + XML.Type + ( λ(i : Natural) + → XML.leaf + { name = "policy" + , attributes = + let partition = + Prelude.Natural.show (i + 1) + + in toMap + { label_suffix = " ${partition}" + , partition = partition + , writeable = "yes" + } + } + ) + (Prelude.Natural.enumerate 128) + # [ XML.leaf + { name = "report" + , attributes = toMap { partitions = "yes" } + } + ] + } + , resources = Resources::{ + , caps = 256 + , ram = Genode.units.MiB 8 + } + , provides = [ "Block" ] + , routes = + [ ServiceRoute.child "Block" "block" + , ServiceRoute.child "Report" "block_router" + ] + } + , block_router = + Child.flat + Child.Attributes::{ + , binary = "block_router" + , config = Init.Config::{ + , attributes = toMap { verbose = "yes" } + , content = + [ XML.element + { name = "default-policy" + , attributes = XML.emptyAttributes + , content = + [ XML.leaf + { name = "partition" + , attributes = toMap + { type = ./partition-type + , writeable = "yes" + } + } + ] + } + ] + } + , resources = Resources::{ + , caps = 256 + , ram = Genode.units.MiB 8 + } + , provides = [ "Block", "Report" ] + , routes = + [ ServiceRoute.child "Block" "block_partitions" ] + } + , file_system = + Child.flat + Child.Attributes::{ + , binary = "vfs" + , config = Init.Config::{ + , content = + [ XML.element + { name = "vfs" + , attributes = XML.emptyAttributes + , content = + [ XML.leaf + { name = "rump" + , attributes = toMap + { fs = "ext2fs" + , writeable = "yes" + , ram = "8M" + } + } + ] + } + , XML.leaf + { name = "default-policy" + , attributes = toMap { root = "/" } + } + ] + } + , provides = [ "File_system" ] + , resources = Init.Resources::{ + , caps = 256 + , ram = Genode.units.MiB 12 + } + , routes = [ ServiceRoute.child "Block" "block_router" ] + } + , vbox = + Child.flat + Child.Attributes::{ + , binary = "virtualbox5" + , config = Init.Config::{ + , attributes = toMap + { vbox_file = "nixos.vbox", vm_name = "nixos" } + , content = + [ XML.leaf + { name = "libc" + , attributes = toMap + { stdout = "/dev/log" + , stderr = "/dev/log" + , rtc = "/dev/rtc" + } + } + , XML.element + { name = "vfs" + , attributes = XML.emptyAttributes + , content = + let tag = + λ(name : Text) + → XML.leaf + { name = name + , attributes = XML.emptyAttributes + } + + in [ XML.element + { name = "dev" + , attributes = XML.emptyAttributes + , content = [ tag log, tag rtc ] + } + , Vfs/inline "nixos.vbox" vboxConfig + , tag "file_system" + ] + } + ] + } + , resources = Resources::{ + , caps = 1024 + , ram = Genode.units.MiB 256 + } + , routes = + [ ServiceRoute.parent "VM" + , ServiceRoute.child "Nitpicker" "nitpicker" + , ServiceRoute.child "File_system" "file_system" + , ServiceRoute.child "Rtc" "rtc" + ] + } + } + } + +in rootInit diff --git a/nixos-modules/vbox.nix b/nixos-modules/vbox.nix new file mode 100644 index 0000000..fb7b268 --- /dev/null +++ b/nixos-modules/vbox.nix @@ -0,0 +1,76 @@ +{ self }: +let lib' = self.lib.x86_64-linux-x86_64-genode; +in { config, lib, pkgs, ... }: + +let + buildBootDescription = + self.legacyPackages.x86_64-linux.callPackage ./buildBootDescription.nix { + lib = lib'; + }; + + genodeParams = lib'.runDhallCommand "params.dhall" { } '' + vdiUuid=$(${pkgs.virtualbox}/bin/VBoxManage showmediuminfo ${config.system.build.virtualBoxVDI}/nixos.vdi | awk '/^UUID:/ {print $2}') + dhall > $out << EOD + { vdiFilename = "nixos.vdi" + , vidUuid = "$vdiUuid" + , memorySize = ${toString config.virtualbox.memorySize} + , vmName = "${config.virtualbox.vmName}" + } + EOD + ''; + + bootDescription = buildBootDescription { + name = "boot-description"; + initConfig = "./vbox-host.dhall ${genodeParams}"; + imageInputs = (map pkgs.genodeSources.depot [ + "acpi_drv" + "ahci_drv" + "init" + "input_filter" + "libc" + "nitpicker" + "part_block" + "platform_drv" + "ps2_drv" + "report_rom" + "rtc_drv" + "vesa_drv" + "vfs" + ]) ++ (map pkgs.genodeSources.make [ "drivers/input/dummy" ]) + ++ [ pkgs.base-nova pkgs.block_router ]; + extraBinaries = [ + "ld.lib.so" + "libc.lib.so" + "libm.lib.so" + "rump.lib.so" + "rump_fs.lib.so" + "vfs_rump.lib.so" + ]; + }; + + novaImage = lib'.novaImage "stage0" { } bootDescription; + + # add system.build.virtualBoxVDI/nixos.vdi to disk image + + genodeDiskImage = with pkgs; + runCommand "genode.disk" { + nativeBuildInputs = [ e2fsprogs gptfdisk lzip vmTools.qemu ]; + } '' + qemu-img create -f raw fs.raw 16M + qemu-img create -f raw zero.raw 1M + + mke2fs fs.raw + + cat zero.raw fs.raw zero.raw > gpt.raw + + sgdisk gpt.raw --new=partnum 1:34:-34 \ + --typecode=1:${import ./partition-type} \ + --change-name=1:genode + + mkdir -p $out + lzip -c gpt.raw > "$out/genode-nixos-${config.system.nixos.label}.disk" + ''; + +in { + system.build.genodeDiskImage = novaImage; +} diff --git a/nixos-modules/workman.map.dhall b/nixos-modules/workman.map.dhall new file mode 100644 index 0000000..90885a1 --- /dev/null +++ b/nixos-modules/workman.map.dhall @@ -0,0 +1,245 @@ +let Genode = ./Genode.dhall + +let Prelude = Genode.Prelude + +let XML = Prelude.XML + +let Key = < Ascii : Natural | Char : Text | Code : Natural > : Type + +let Map = + { Type = + { keys : Prelude.Map.Type Text Key + , mod1 : Bool + , mod2 : Bool + , mod3 : Bool + , mod4 : Bool + } + , default = { mod1 = False, mod2 = False, mod3 = False, mod4 = False } + } + +let boolToAttr = λ(_ : Bool) → if _ then "yes" else "no" + +let keyToXML = + λ(x : Prelude.Map.Entry Text Key) + → XML.leaf + { name = "key" + , attributes = + [ merge + { Ascii = + λ(_ : Natural) + → { mapKey = "ascii", mapValue = Prelude.Natural.show _ } + , Char = λ(_ : Text) → { mapKey = "char", mapValue = _ } + , Code = + λ(_ : Natural) + → { mapKey = "code", mapValue = Prelude.Natural.show _ } + } + x.mapValue + , { mapKey = "name", mapValue = x.mapKey } + ] + } + +let mapToXML = + λ(map : Map.Type) + → XML.element + { name = "map" + , attributes = toMap + { mod1 = boolToAttr map.mod1 + , mod2 = boolToAttr map.mod2 + , mod3 = boolToAttr map.mod3 + , mod4 = boolToAttr map.mod4 + } + , content = + Prelude.List.map + (Prelude.Map.Entry Text Key) + XML.Type + keyToXML + map.keys + } + +let workman = + [ Map::{ + , keys = toMap + { KEY_ESC = Key.Ascii 27 + , KEY_1 = Key.Char "1" + , KEY_2 = Key.Char "2" + , KEY_3 = Key.Char "3" + , KEY_4 = Key.Char "4" + , KEY_5 = Key.Char "5" + , KEY_6 = Key.Char "6" + , KEY_7 = Key.Char "7" + , KEY_8 = Key.Char "8" + , KEY_9 = Key.Char "9" + , KEY_0 = Key.Char "0" + , KEY_MINUS = Key.Char "-" + , KEY_EQUAL = Key.Char "=" + , KEY_BACKSPACE = Key.Ascii 8 + , KEY_TAB = Key.Ascii 9 + , KEY_Q = Key.Char "q" + , KEY_W = Key.Char "d" + , KEY_E = Key.Char "r" + , KEY_R = Key.Char "w" + , KEY_T = Key.Char "b" + , KEY_Y = Key.Char "j" + , KEY_U = Key.Char "f" + , KEY_I = Key.Char "u" + , KEY_O = Key.Char "p" + , KEY_P = Key.Char ";" + , KEY_LEFTBRACE = Key.Char "[" + , KEY_RIGHTBRACE = Key.Char "]" + , KEY_ENTER = Key.Ascii 10 + , KEY_A = Key.Char "a" + , KEY_S = Key.Char "s" + , KEY_D = Key.Char "h" + , KEY_F = Key.Char "t" + , KEY_G = Key.Char "g" + , KEY_H = Key.Char "y" + , KEY_J = Key.Char "n" + , KEY_K = Key.Char "e" + , KEY_L = Key.Char "o" + , KEY_SEMICOLON = Key.Char "i" + , KEY_APOSTROPHE = Key.Char "'" + , KEY_GRAVE = Key.Char "`" + , KEY_BACKSLASH = Key.Ascii 92 + , KEY_Z = Key.Char "z" + , KEY_X = Key.Char "x" + , KEY_C = Key.Char "m" + , KEY_V = Key.Char "c" + , KEY_B = Key.Char "v" + , KEY_N = Key.Char "k" + , KEY_M = Key.Char "l" + , KEY_COMMA = Key.Char "," + , KEY_DOT = Key.Char "." + , KEY_SLASH = Key.Char "/" + , KEY_SPACE = Key.Char " " + , KEY_KP7 = Key.Char "7" + , KEY_KP8 = Key.Char "8" + , KEY_KP9 = Key.Char "9" + , KEY_KPMINUS = Key.Char "-" + , KEY_KP4 = Key.Char "4" + , KEY_KP5 = Key.Char "5" + , KEY_KP6 = Key.Char "6" + , KEY_KPPLUS = Key.Char "+" + , KEY_KP1 = Key.Char "1" + , KEY_KP2 = Key.Char "2" + , KEY_KP3 = Key.Char "3" + , KEY_KP0 = Key.Char "0" + , KEY_KPDOT = Key.Char "." + , KEY_KPENTER = Key.Ascii 10 + , KEY_KPSLASH = Key.Char "/" + } + } + , Map::{ + , mod1 = True + , keys = toMap + { KEY_1 = Key.Char "!" + , KEY_2 = Key.Char "@" + , KEY_3 = Key.Char "#" + , KEY_4 = Key.Char "\$" + , KEY_5 = Key.Char "%" + , KEY_6 = Key.Char "^" + , KEY_7 = Key.Ascii 38 + , KEY_8 = Key.Char "*" + , KEY_9 = Key.Char "(" + , KEY_0 = Key.Char ")" + , KEY_MINUS = Key.Char "_" + , KEY_EQUAL = Key.Char "+" + , KEY_Q = Key.Char "Q" + , KEY_W = Key.Char "D" + , KEY_E = Key.Char "R" + , KEY_R = Key.Char "W" + , KEY_T = Key.Char "B" + , KEY_Y = Key.Char "J" + , KEY_U = Key.Char "F" + , KEY_I = Key.Char "U" + , KEY_O = Key.Char "P" + , KEY_P = Key.Char ":" + , KEY_LEFTBRACE = Key.Char "{" + , KEY_RIGHTBRACE = Key.Char "}" + , KEY_A = Key.Char "A" + , KEY_S = Key.Char "S" + , KEY_D = Key.Char "H" + , KEY_F = Key.Char "T" + , KEY_G = Key.Char "G" + , KEY_H = Key.Char "Y" + , KEY_J = Key.Char "N" + , KEY_K = Key.Char "E" + , KEY_L = Key.Char "O" + , KEY_SEMICOLON = Key.Char "I" + , KEY_APOSTROPHE = Key.Ascii 34 + , KEY_GRAVE = Key.Char "~" + , KEY_BACKSLASH = Key.Char "|" + , KEY_Z = Key.Char "Z" + , KEY_X = Key.Char "X" + , KEY_C = Key.Char "M" + , KEY_V = Key.Char "C" + , KEY_B = Key.Char "V" + , KEY_N = Key.Char "K" + , KEY_M = Key.Char "L" + , KEY_COMMA = Key.Ascii 60 + , KEY_DOT = Key.Ascii 62 + , KEY_SLASH = Key.Char "?" + } + } + , Map::{ + , mod2 = True + , keys = toMap + { KEY_A = Key.Ascii 1 + , KEY_B = Key.Ascii 22 + , KEY_C = Key.Ascii 13 + , KEY_D = Key.Ascii 8 + , KEY_E = Key.Ascii 18 + , KEY_F = Key.Ascii 20 + , KEY_G = Key.Ascii 7 + , KEY_H = Key.Ascii 25 + , KEY_I = Key.Ascii 21 + , KEY_J = Key.Ascii 14 + , KEY_K = Key.Ascii 5 + , KEY_L = Key.Ascii 15 + , KEY_M = Key.Ascii 12 + , KEY_N = Key.Ascii 11 + , KEY_O = Key.Ascii 16 + , KEY_P = Key.Ascii 9 + , KEY_Q = Key.Ascii 17 + , KEY_R = Key.Ascii 23 + , KEY_S = Key.Ascii 19 + , KEY_T = Key.Ascii 2 + , KEY_U = Key.Ascii 6 + , KEY_V = Key.Ascii 3 + , KEY_W = Key.Ascii 4 + , KEY_X = Key.Ascii 24 + , KEY_Y = Key.Ascii 10 + , KEY_Z = Key.Ascii 26 + } + } + , Map::{ + , mod3 = True + , keys = toMap + { KEY_4 = Key.Code 8364 + , KEY_A = Key.Code 228 + , KEY_S = Key.Code 223 + , KEY_I = Key.Code 252 + , KEY_DOT = Key.Code 8230 + , KEY_K = Key.Code 235 + , KEY_C = Key.Code 181 + , KEY_L = Key.Code 246 + } + } + , Map::{ + , mod1 = True + , mod3 = True + , keys = toMap + { KEY_0 = Key.Code 8320 + , KEY_1 = Key.Code 8321 + , KEY_2 = Key.Code 8322 + , KEY_3 = Key.Code 8323 + , KEY_4 = Key.Code 8324 + , KEY_5 = Key.Code 8325 + , KEY_6 = Key.Code 8326 + , KEY_7 = Key.Code 8327 + , KEY_8 = Key.Code 8328 + , KEY_9 = Key.Code 8329 + } + } + ] + +in Prelude.List.map Map.Type XML.Type mapToXML workman